From 701ad974ccc0ba0a61d2a2bd58db569f794bd037 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Tue, 28 May 2024 16:17:13 +0200 Subject: pkg/fuzzer: refactor progTypes The next commit will add another Candidate flag. Candidate flags duplicate progTypes enum, so to avoid conversions of one to another use progTypes in Candidate struct directly. Rename progTypes to progFlags since multiple can be set, so this is effectively flags rather than a single type. --- pkg/fuzzer/fuzzer.go | 34 ++++++++++++++++++++++++---------- pkg/fuzzer/job.go | 35 +++++------------------------------ pkg/fuzzer/job_test.go | 4 ++-- syz-manager/hub.go | 8 +++++--- syz-manager/manager.go | 26 ++++++++++++-------------- 5 files changed, 48 insertions(+), 59 deletions(-) diff --git a/pkg/fuzzer/fuzzer.go b/pkg/fuzzer/fuzzer.go index 8be912139..b0ceaf9c0 100644 --- a/pkg/fuzzer/fuzzer.go +++ b/pkg/fuzzer/fuzzer.go @@ -100,26 +100,26 @@ func (fuzzer *Fuzzer) execute(executor queue.Executor, req *queue.Request) *queu return fuzzer.executeWithFlags(executor, req, 0) } -func (fuzzer *Fuzzer) executeWithFlags(executor queue.Executor, req *queue.Request, flags ProgTypes) *queue.Result { +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 } -func (fuzzer *Fuzzer) prepare(req *queue.Request, flags ProgTypes) { +func (fuzzer *Fuzzer) prepare(req *queue.Request, flags ProgFlags) { req.OnDone(func(req *queue.Request, res *queue.Result) bool { fuzzer.processResult(req, res, flags) return true }) } -func (fuzzer *Fuzzer) enqueue(executor queue.Executor, req *queue.Request, flags ProgTypes) { +func (fuzzer *Fuzzer) enqueue(executor queue.Executor, req *queue.Request, flags ProgFlags) { fuzzer.prepare(req, flags) executor.Submit(req) } -func (fuzzer *Fuzzer) processResult(req *queue.Request, res *queue.Result, flags ProgTypes) { +func (fuzzer *Fuzzer) processResult(req *queue.Request, res *queue.Result, flags ProgFlags) { inTriage := flags&progInTriage > 0 // Triage individual calls. // We do it before unblocking the waiting threads because @@ -154,7 +154,7 @@ type Config struct { NewInputFilter func(call string) bool } -func (fuzzer *Fuzzer) triageProgCall(p *prog.Prog, info *flatrpc.CallInfo, call int, flags ProgTypes) { +func (fuzzer *Fuzzer) triageProgCall(p *prog.Prog, info *flatrpc.CallInfo, call int, flags ProgFlags) { if info == nil { return } @@ -243,17 +243,31 @@ func (fuzzer *Fuzzer) Logf(level int, msg string, args ...interface{}) { fuzzer.Config.Logf(level, msg, args...) } +type ProgFlags int + +const ( + ProgMinimized ProgFlags = 1 << iota + ProgSmashed + + progCandidate + progInTriage +) + type Candidate struct { - Prog *prog.Prog - Smashed bool - Minimized bool + Prog *prog.Prog + Flags ProgFlags } func (fuzzer *Fuzzer) AddCandidates(candidates []Candidate) { fuzzer.statCandidates.Add(len(candidates)) for _, candidate := range candidates { - req, flags := candidateRequest(fuzzer, candidate) - fuzzer.enqueue(fuzzer.candidateQueue, req, flags) + req := &queue.Request{ + Prog: candidate.Prog, + ExecOpts: setFlags(flatrpc.ExecFlagCollectSignal), + Stat: fuzzer.statExecCandidate, + Important: true, + } + fuzzer.enqueue(fuzzer.candidateQueue, req, candidate.Flags|progCandidate) } } diff --git a/pkg/fuzzer/job.go b/pkg/fuzzer/job.go index 00c7c034b..9d51ec2d8 100644 --- a/pkg/fuzzer/job.go +++ b/pkg/fuzzer/job.go @@ -20,15 +20,6 @@ type job interface { run(fuzzer *Fuzzer) } -type ProgTypes int - -const ( - progCandidate ProgTypes = 1 << iota - progMinimized - progSmashed - progInTriage -) - func genProgRequest(fuzzer *Fuzzer, rnd *rand.Rand) *queue.Request { p := fuzzer.target.Generate(rnd, prog.RecommendedCalls, @@ -59,22 +50,6 @@ func mutateProgRequest(fuzzer *Fuzzer, rnd *rand.Rand) *queue.Request { } } -func candidateRequest(fuzzer *Fuzzer, input Candidate) (*queue.Request, ProgTypes) { - flags := progCandidate - if input.Minimized { - flags |= progMinimized - } - if input.Smashed { - flags |= progSmashed - } - return &queue.Request{ - Prog: input.Prog, - ExecOpts: setFlags(flatrpc.ExecFlagCollectSignal), - Stat: fuzzer.statExecCandidate, - Important: true, - }, flags -} - // triageJob are programs for which we noticed potential new coverage during // first execution. But we are not sure yet if the coverage is real or not. // During triage we understand if these programs in fact give new coverage, @@ -84,12 +59,12 @@ type triageJob struct { call int info *flatrpc.CallInfo newSignal signal.Signal - flags ProgTypes + flags ProgFlags fuzzer *Fuzzer queue queue.Executor } -func (job *triageJob) execute(req *queue.Request, flags ProgTypes) *queue.Result { +func (job *triageJob) execute(req *queue.Request, flags ProgFlags) *queue.Result { req.Important = true // All triage executions are important. return job.fuzzer.executeWithFlags(job.queue, req, flags) } @@ -106,7 +81,7 @@ func (job *triageJob) run(fuzzer *Fuzzer) { if stop || info.newStableSignal.Empty() { return } - if job.flags&progMinimized == 0 { + if job.flags&ProgMinimized == 0 { stop = job.minimize(info.newStableSignal) if stop { return @@ -116,7 +91,7 @@ func (job *triageJob) run(fuzzer *Fuzzer) { return } fuzzer.Logf(2, "added new input for %v to the corpus: %s", callName, job.p) - if job.flags&progSmashed == 0 { + if job.flags&ProgSmashed == 0 { fuzzer.startJob(fuzzer.statJobsSmash, &smashJob{ p: job.p.Clone(), call: job.call, @@ -139,7 +114,7 @@ type deflakedCover struct { rawCover []uint64 } -func (job *triageJob) deflake(exec func(*queue.Request, ProgTypes) *queue.Result, stat *stats.Val, +func (job *triageJob) deflake(exec func(*queue.Request, ProgFlags) *queue.Result, stat *stats.Val, rawCover bool) (info deflakedCover, stop bool) { // As demonstrated in #4639, programs reproduce with a very high, but not 100% probability. // The triage algorithm must tolerate this, so let's pick the signal that is common diff --git a/pkg/fuzzer/job_test.go b/pkg/fuzzer/job_test.go index cbb99daec..c975d0128 100644 --- a/pkg/fuzzer/job_test.go +++ b/pkg/fuzzer/job_test.go @@ -29,7 +29,7 @@ func TestDeflakeFail(t *testing.T) { } run := 0 - ret, stop := testJob.deflake(func(_ *queue.Request, _ ProgTypes) *queue.Result { + ret, stop := testJob.deflake(func(_ *queue.Request, _ ProgFlags) *queue.Result { run++ // For first, we return 0 and 1. For second, 1 and 2. And so on. return fakeResult(0, []uint64{uint64(run), uint64(run + 1)}, []uint64{10, 20}) @@ -54,7 +54,7 @@ func TestDeflakeSuccess(t *testing.T) { newSignal: signal.FromRaw([]uint64{0, 1, 2}, 0), } run := 0 - ret, stop := testJob.deflake(func(_ *queue.Request, _ ProgTypes) *queue.Result { + ret, stop := testJob.deflake(func(_ *queue.Request, _ ProgFlags) *queue.Result { run++ switch run { case 1: diff --git a/syz-manager/hub.go b/syz-manager/hub.go index fc9662bb0..5ed0570c3 100644 --- a/syz-manager/hub.go +++ b/syz-manager/hub.go @@ -245,16 +245,18 @@ func (hc *HubConnector) processProgs(inputs []rpctype.HubInput) (minimized, smas continue } min, smash := matchDomains(hc.domain, inp.Domain) + var flags fuzzer.ProgFlags if min { minimized++ + flags |= fuzzer.ProgMinimized } if smash { smashed++ + flags |= fuzzer.ProgSmashed } candidates = append(candidates, fuzzer.Candidate{ - Prog: p, - Minimized: min, - Smashed: smash, + Prog: p, + Flags: flags, }) } hc.mgr.addNewCandidates(candidates) diff --git a/syz-manager/manager.go b/syz-manager/manager.go index 8d68b4be2..0837e0251 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -646,30 +646,30 @@ func (mgr *Manager) loadCorpus() { // By default we don't re-minimize/re-smash programs from corpus, // it takes lots of time on start and is unnecessary. // However, on version bumps we can selectively re-minimize/re-smash. - minimized, smashed := true, true + flags := fuzzer.ProgMinimized | fuzzer.ProgSmashed switch mgr.corpusDB.Version { case 0: // Version 0 had broken minimization, so we need to re-minimize. - minimized = false + flags &= ^fuzzer.ProgMinimized fallthrough case 1: // Version 1->2: memory is preallocated so lots of mmaps become unnecessary. - minimized = false + flags &= ^fuzzer.ProgMinimized fallthrough case 2: // Version 2->3: big-endian hints. - smashed = false + flags &= ^fuzzer.ProgSmashed fallthrough case 3: // Version 3->4: to shake things up. - minimized = false + flags &= ^fuzzer.ProgMinimized fallthrough case currentDBVersion: } var candidates []fuzzer.Candidate broken := 0 for key, rec := range mgr.corpusDB.Records { - drop, item := mgr.loadProg(rec.Val, minimized, smashed) + drop, item := mgr.loadProg(rec.Val, flags) if drop { mgr.corpusDB.Delete(key) broken++ @@ -681,7 +681,7 @@ func (mgr *Manager) loadCorpus() { mgr.fresh = len(mgr.corpusDB.Records) == 0 seeds := 0 for _, seed := range mgr.seeds { - _, item := mgr.loadProg(seed, true, false) + _, item := mgr.loadProg(seed, fuzzer.ProgMinimized) if item != nil { candidates = append(candidates, *item) seeds++ @@ -708,7 +708,7 @@ func (mgr *Manager) loadCorpus() { } // Returns (delete item from the corpus, a fuzzer.Candidate object). -func (mgr *Manager) loadProg(data []byte, minimized, smashed bool) (drop bool, candidate *fuzzer.Candidate) { +func (mgr *Manager) loadProg(data []byte, flags fuzzer.ProgFlags) (drop bool, candidate *fuzzer.Candidate) { p, disabled, bad := parseProgram(mgr.target, mgr.targetEnabledSyscalls, data) if bad != nil { return true, nil @@ -726,18 +726,16 @@ func (mgr *Manager) loadProg(data []byte, minimized, smashed bool) (drop bool, c leftover := programLeftover(mgr.target, mgr.targetEnabledSyscalls, data) if leftover != nil { candidate = &fuzzer.Candidate{ - Prog: leftover, - Minimized: false, - Smashed: smashed, + Prog: leftover, + Flags: flags & ^fuzzer.ProgMinimized, } } } return false, candidate } return false, &fuzzer.Candidate{ - Prog: p, - Minimized: minimized, - Smashed: smashed, + Prog: p, + Flags: flags, } } -- cgit mrf-deployment