blob: 69d0d69a614cbfab77dacc02237d27ed84e50ec0 [file] [log] [blame]
// Copyright 2018 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package main
import (
"net"
"sync"
"github.com/google/syzkaller/pkg/cover"
"github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/rpctype"
"github.com/google/syzkaller/pkg/signal"
"github.com/google/syzkaller/prog"
"github.com/google/syzkaller/sys"
)
type RPCServer struct {
mgr RPCManagerView
target *prog.Target
enabledSyscalls []int
stats *Stats
batchSize int
mu sync.Mutex
fuzzers map[string]*Fuzzer
checkResult *rpctype.CheckArgs
maxSignal signal.Signal
corpusSignal signal.Signal
corpusCover cover.Cover
}
type Fuzzer struct {
name string
inputs []rpctype.RPCInput
newMaxSignal signal.Signal
}
type BugFrames struct {
memoryLeaks []string
dataRaces []string
}
// RPCManagerView restricts interface between RPCServer and Manager.
type RPCManagerView interface {
fuzzerConnect() ([]rpctype.RPCInput, BugFrames)
machineChecked(result *rpctype.CheckArgs)
newInput(inp rpctype.RPCInput, sign signal.Signal)
candidateBatch(size int) []rpctype.RPCCandidate
}
func startRPCServer(mgr *Manager) (int, error) {
serv := &RPCServer{
mgr: mgr,
target: mgr.target,
enabledSyscalls: mgr.enabledSyscalls,
stats: mgr.stats,
fuzzers: make(map[string]*Fuzzer),
}
serv.batchSize = 5
if serv.batchSize < mgr.cfg.Procs {
serv.batchSize = mgr.cfg.Procs
}
s, err := rpctype.NewRPCServer(mgr.cfg.RPC, "Manager", serv)
if err != nil {
return 0, err
}
log.Logf(0, "serving rpc on tcp://%v", s.Addr())
port := s.Addr().(*net.TCPAddr).Port
go s.Serve()
return port, nil
}
func (serv *RPCServer) Connect(a *rpctype.ConnectArgs, r *rpctype.ConnectRes) error {
log.Logf(1, "fuzzer %v connected", a.Name)
serv.stats.vmRestarts.inc()
corpus, bugFrames := serv.mgr.fuzzerConnect()
serv.mu.Lock()
defer serv.mu.Unlock()
serv.fuzzers[a.Name] = &Fuzzer{
name: a.Name,
inputs: corpus,
newMaxSignal: serv.maxSignal.Copy(),
}
r.MemoryLeakFrames = bugFrames.memoryLeaks
r.DataRaceFrames = bugFrames.dataRaces
r.EnabledCalls = serv.enabledSyscalls
r.CheckResult = serv.checkResult
r.GitRevision = sys.GitRevision
r.TargetRevision = serv.target.Revision
return nil
}
func (serv *RPCServer) Check(a *rpctype.CheckArgs, r *int) error {
serv.mu.Lock()
defer serv.mu.Unlock()
if serv.checkResult != nil {
return nil
}
serv.mgr.machineChecked(a)
a.DisabledCalls = nil
serv.checkResult = a
return nil
}
func (serv *RPCServer) NewInput(a *rpctype.NewInputArgs, r *int) error {
inputSignal := a.Signal.Deserialize()
log.Logf(4, "new input from %v for syscall %v (signal=%v, cover=%v)",
a.Name, a.Call, inputSignal.Len(), len(a.Cover))
if _, err := serv.target.Deserialize(a.RPCInput.Prog, prog.NonStrict); err != nil {
// This should not happen, but we see such cases episodically, reason unknown.
log.Logf(0, "failed to deserialize program from fuzzer: %v\n%s", err, a.RPCInput.Prog)
return nil
}
serv.mu.Lock()
defer serv.mu.Unlock()
if serv.corpusSignal.Diff(inputSignal).Empty() {
return nil
}
serv.mgr.newInput(a.RPCInput, inputSignal)
serv.stats.newInputs.inc()
serv.corpusSignal.Merge(inputSignal)
serv.stats.corpusSignal.set(serv.corpusSignal.Len())
serv.corpusCover.Merge(a.Cover)
serv.stats.corpusCover.set(len(serv.corpusCover))
a.RPCInput.Cover = nil // Don't send coverage back to all fuzzers.
for _, f := range serv.fuzzers {
if f.name == a.Name {
continue
}
f.inputs = append(f.inputs, a.RPCInput)
}
return nil
}
func (serv *RPCServer) Poll(a *rpctype.PollArgs, r *rpctype.PollRes) error {
serv.stats.mergeNamed(a.Stats)
serv.mu.Lock()
defer serv.mu.Unlock()
f := serv.fuzzers[a.Name]
if f == nil {
log.Fatalf("fuzzer %v is not connected", a.Name)
}
newMaxSignal := serv.maxSignal.Diff(a.MaxSignal.Deserialize())
if !newMaxSignal.Empty() {
serv.maxSignal.Merge(newMaxSignal)
for _, f1 := range serv.fuzzers {
if f1 == f {
continue
}
f1.newMaxSignal.Merge(newMaxSignal)
}
}
r.MaxSignal = f.newMaxSignal.Split(500).Serialize()
if a.NeedCandidates {
r.Candidates = serv.mgr.candidateBatch(serv.batchSize)
}
if len(r.Candidates) == 0 {
batchSize := serv.batchSize
// When the fuzzer starts, it pumps the whole corpus.
// If we do it using the final batchSize, it can be very slow
// (batch of size 6 can take more than 10 mins for 50K corpus and slow kernel).
// So use a larger batch initially (we use no stats as approximation of initial pump).
const initialBatch = 30
if len(a.Stats) == 0 && batchSize < initialBatch {
batchSize = initialBatch
}
for i := 0; i < batchSize && len(f.inputs) > 0; i++ {
last := len(f.inputs) - 1
r.NewInputs = append(r.NewInputs, f.inputs[last])
f.inputs[last] = rpctype.RPCInput{}
f.inputs = f.inputs[:last]
}
if len(f.inputs) == 0 {
f.inputs = nil
}
}
log.Logf(4, "poll from %v: candidates=%v inputs=%v maxsignal=%v",
a.Name, len(r.Candidates), len(r.NewInputs), len(r.MaxSignal.Elems))
return nil
}