From 409ee912f2c4f07e3064b4e6f4a83e1f812531d8 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Fri, 15 Mar 2024 19:53:15 +0100 Subject: all: move fuzzer to the host Instead of doing fuzzing in parallel in running VM, make all decisions in the host syz-manager process. Instantiate and keep a fuzzer.Fuzzer object in syz-manager and update the RPC between syz-manager and syz-fuzzer to exchange exact programs to execute and their resulting signal and coverage. To optimize the networking traffic, exchange mostly only the difference between the known max signal and the detected signal. --- pkg/fuzzer/fuzzer.go | 77 ++++++++++++++++++---------------------------------- 1 file changed, 27 insertions(+), 50 deletions(-) (limited to 'pkg/fuzzer/fuzzer.go') diff --git a/pkg/fuzzer/fuzzer.go b/pkg/fuzzer/fuzzer.go index 196e59506..14c3f5902 100644 --- a/pkg/fuzzer/fuzzer.go +++ b/pkg/fuzzer/fuzzer.go @@ -13,15 +13,15 @@ import ( "time" "github.com/google/syzkaller/pkg/corpus" - "github.com/google/syzkaller/pkg/hash" "github.com/google/syzkaller/pkg/ipc" + "github.com/google/syzkaller/pkg/rpctype" + "github.com/google/syzkaller/pkg/signal" "github.com/google/syzkaller/prog" ) type Fuzzer struct { - Config *Config - Cover *Cover - NeedCandidates chan struct{} + Config *Config + Cover *Cover ctx context.Context mu sync.Mutex @@ -39,18 +39,16 @@ type Fuzzer struct { runningJobs atomic.Int64 queuedCandidates atomic.Int64 - // If the source of candidates runs out of them, we risk - // generating too many needCandidate requests (one for - // each Config.MinCandidates). We prevent this with candidatesRequested. - candidatesRequested atomic.Bool + + outOfQueue atomic.Bool + outOfQueueNext atomic.Int64 } func NewFuzzer(ctx context.Context, cfg *Config, rnd *rand.Rand, target *prog.Target) *Fuzzer { f := &Fuzzer{ - Config: cfg, - Cover: &Cover{}, - NeedCandidates: make(chan struct{}, 1), + Config: cfg, + Cover: &Cover{}, ctx: ctx, stats: map[string]uint64{}, @@ -82,18 +80,16 @@ type Config struct { EnabledCalls map[*prog.Syscall]bool NoMutateCalls map[int]bool FetchRawCover bool - // If the number of queued candidates is less than MinCandidates, - // NeedCandidates is triggered. - MinCandidates uint - NewInputs chan corpus.NewInput + NewInputFilter func(input *corpus.NewInput) bool } type Request struct { Prog *prog.Prog NeedCover bool NeedRawCover bool - NeedSignal bool + NeedSignal rpctype.SignalType NeedHints bool + SignalFilter signal.Signal // If specified, the resulting signal MAY be a subset of it. // Fields that are only relevant within pkg/fuzzer. flags ProgTypes stat string @@ -110,7 +106,7 @@ func (fuzzer *Fuzzer) Done(req *Request, res *Result) { // Triage individual calls. // We do it before unblocking the waiting threads because // it may result it concurrent modification of req.Prog. - if req.NeedSignal && res.Info != nil { + if req.NeedSignal != rpctype.NoSignal && res.Info != nil { for call, info := range res.Info.Calls { fuzzer.triageProgCall(req.Prog, &info, call, req.flags) } @@ -166,7 +162,6 @@ func signalPrio(p *prog.Prog, info *ipc.CallInfo, call int) (prio uint8) { type Candidate struct { Prog *prog.Prog - Hash hash.Sig Smashed bool Minimized bool } @@ -178,20 +173,19 @@ func (fuzzer *Fuzzer) NextInput() *Request { panic("queuedCandidates is out of sync") } } - if fuzzer.NeedCandidatesNow() && - !fuzzer.candidatesRequested.CompareAndSwap(false, true) { - select { - case fuzzer.NeedCandidates <- struct{}{}: - default: - } - } return req } func (fuzzer *Fuzzer) nextInput() *Request { - nextExec := fuzzer.nextExec.tryPop() - if nextExec != nil { - return nextExec.value + // The fuzzer may get biased to one specific part of the kernel. + // Periodically generate random programs to ensure that the coverage + // is more uniform. + if !fuzzer.outOfQueue.Load() || + fuzzer.outOfQueueNext.Add(1)%400 > 0 { + nextExec := fuzzer.nextExec.tryPop() + if nextExec != nil { + return nextExec.value + } } // Either generate a new input or mutate an existing one. mutateRate := 0.95 @@ -227,16 +221,15 @@ func (fuzzer *Fuzzer) Logf(level int, msg string, args ...interface{}) { fuzzer.Config.Logf(level, msg, args...) } -func (fuzzer *Fuzzer) NeedCandidatesNow() bool { - return fuzzer.queuedCandidates.Load() < int64(fuzzer.Config.MinCandidates) -} - func (fuzzer *Fuzzer) AddCandidates(candidates []Candidate) { fuzzer.queuedCandidates.Add(int64(len(candidates))) for _, candidate := range candidates { fuzzer.pushExec(candidateRequest(candidate), priority{candidatePrio}) } - fuzzer.candidatesRequested.Store(false) +} + +func (fuzzer *Fuzzer) EnableOutOfQueue() { + fuzzer.outOfQueue.Store(true) } func (fuzzer *Fuzzer) rand() *rand.Rand { @@ -250,7 +243,7 @@ func (fuzzer *Fuzzer) pushExec(req *Request, prio priority) { if req.stat == "" { panic("Request.Stat field must be set") } - if req.NeedHints && (req.NeedCover || req.NeedSignal) { + if req.NeedHints && (req.NeedCover || req.NeedSignal != rpctype.NoSignal) { panic("Request.NeedHints is mutually exclusive with other fields") } fuzzer.nextExec.push(&priorityQueueItem[*Request]{ @@ -330,19 +323,3 @@ func (fuzzer *Fuzzer) logCurrentStats() { fuzzer.Logf(0, "%s", str) } } - -type Stats struct { - CoverStats - corpus.Stats - Candidates int - RunningJobs int -} - -func (fuzzer *Fuzzer) Stats() Stats { - return Stats{ - CoverStats: fuzzer.Cover.Stats(), - Stats: fuzzer.Config.Corpus.Stats(), - Candidates: int(fuzzer.queuedCandidates.Load()), - RunningJobs: int(fuzzer.runningJobs.Load()), - } -} -- cgit mrf-deployment