From 05abdec478c0e0186e19d301ba5bd67e553a0737 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Tue, 28 May 2024 16:43:16 +0200 Subject: pkg/fuzzer: smarter retry for corpus candidates Currently we always retry corpus candidates 2 times. Retry up to 3 times if we did not get new signal. This should both reduce total number of runs (if we get new signal on the first run) and reduce signal loss after restart (since we try up to 3 times instead of 2). --- pkg/fuzzer/fuzzer.go | 55 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 19 deletions(-) (limited to 'pkg') diff --git a/pkg/fuzzer/fuzzer.go b/pkg/fuzzer/fuzzer.go index b0ceaf9c0..44aad6010 100644 --- a/pkg/fuzzer/fuzzer.go +++ b/pkg/fuzzer/fuzzer.go @@ -101,42 +101,56 @@ func (fuzzer *Fuzzer) execute(executor queue.Executor, req *queue.Request) *queu } func (fuzzer *Fuzzer) executeWithFlags(executor queue.Executor, req *queue.Request, flags ProgFlags) *queue.Result { - executor.Submit(req) - res := req.Wait(fuzzer.ctx) - fuzzer.processResult(req, res, flags) - return res + fuzzer.enqueue(executor, req, flags, 0) + return req.Wait(fuzzer.ctx) } -func (fuzzer *Fuzzer) prepare(req *queue.Request, flags ProgFlags) { +func (fuzzer *Fuzzer) prepare(req *queue.Request, flags ProgFlags, attempt int) { req.OnDone(func(req *queue.Request, res *queue.Result) bool { - fuzzer.processResult(req, res, flags) - return true + return fuzzer.processResult(req, res, flags, attempt) }) } -func (fuzzer *Fuzzer) enqueue(executor queue.Executor, req *queue.Request, flags ProgFlags) { - fuzzer.prepare(req, flags) +func (fuzzer *Fuzzer) enqueue(executor queue.Executor, req *queue.Request, flags ProgFlags, attempt int) { + fuzzer.prepare(req, flags, attempt) executor.Submit(req) } -func (fuzzer *Fuzzer) processResult(req *queue.Request, res *queue.Result, flags ProgFlags) { +func (fuzzer *Fuzzer) processResult(req *queue.Request, res *queue.Result, flags ProgFlags, attempt int) bool { inTriage := flags&progInTriage > 0 // Triage individual calls. // We do it before unblocking the waiting threads because // it may result it concurrent modification of req.Prog. // If we are already triaging this exact prog, this is flaky coverage. + triaging := false if req.ExecOpts.ExecFlags&flatrpc.ExecFlagCollectSignal > 0 && res.Info != nil && !inTriage { for call, info := range res.Info.Calls { - fuzzer.triageProgCall(req.Prog, info, call, flags) + if fuzzer.triageProgCall(req.Prog, info, call, flags) { + triaging = true + } + } + if fuzzer.triageProgCall(req.Prog, res.Info.Extra, -1, flags) { + triaging = true } - fuzzer.triageProgCall(req.Prog, res.Info.Extra, -1, flags) } + if res.Info != nil { fuzzer.statExecTime.Add(int(res.Info.Elapsed / 1e6)) } + + // Corpus candidates may have flaky coverage, so we give them a second chance. + maxCandidateAttempts := 3 + if req.Risky() { + maxCandidateAttempts = 2 + } + if !triaging && flags&ProgFromCorpus != 0 && attempt < maxCandidateAttempts { + fuzzer.enqueue(fuzzer.candidateQueue, req, flags, attempt+1) + return false + } if flags&progCandidate != 0 { fuzzer.statCandidates.Add(-1) } + return true } type Config struct { @@ -154,17 +168,17 @@ type Config struct { NewInputFilter func(call string) bool } -func (fuzzer *Fuzzer) triageProgCall(p *prog.Prog, info *flatrpc.CallInfo, call int, flags ProgFlags) { +func (fuzzer *Fuzzer) triageProgCall(p *prog.Prog, info *flatrpc.CallInfo, call int, flags ProgFlags) bool { if info == nil { - return + return false } prio := signalPrio(p, info, call) newMaxSignal := fuzzer.Cover.addRawMaxSignal(info.Signal, prio) if newMaxSignal.Empty() { - return + return false } if !fuzzer.Config.NewInputFilter(p.CallName(call)) { - return + return false } fuzzer.Logf(2, "found new signal in call %d in %s", call, p) @@ -180,6 +194,7 @@ func (fuzzer *Fuzzer) triageProgCall(p *prog.Prog, info *flatrpc.CallInfo, call flags: flags, queue: queue.Append(), }) + return true } func signalPrio(p *prog.Prog, info *flatrpc.CallInfo, call int) (prio uint8) { @@ -211,7 +226,7 @@ func (fuzzer *Fuzzer) genFuzz() *queue.Request { if req == nil { req = genProgRequest(fuzzer, rnd) } - fuzzer.prepare(req, 0) + fuzzer.prepare(req, 0, 0) return req } @@ -246,7 +261,9 @@ func (fuzzer *Fuzzer) Logf(level int, msg string, args ...interface{}) { type ProgFlags int const ( - ProgMinimized ProgFlags = 1 << iota + // The candidate was loaded from our local corpus rather than come from hub. + ProgFromCorpus ProgFlags = 1 << iota + ProgMinimized ProgSmashed progCandidate @@ -267,7 +284,7 @@ func (fuzzer *Fuzzer) AddCandidates(candidates []Candidate) { Stat: fuzzer.statExecCandidate, Important: true, } - fuzzer.enqueue(fuzzer.candidateQueue, req, candidate.Flags|progCandidate) + fuzzer.enqueue(fuzzer.candidateQueue, req, candidate.Flags|progCandidate, 0) } } -- cgit mrf-deployment