From 19e202fa8a3722f5c90ed5847edb5aeabdd5f38f Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Thu, 16 May 2024 12:56:43 +0200 Subject: pkg/fuzzer: manipulate ipc.ExecOpts There's no need in duplicating the signal, coverage, hints flags. --- pkg/fuzzer/fuzzer.go | 28 +++++++++++++------------ pkg/fuzzer/fuzzer_test.go | 22 ++++++++------------ pkg/fuzzer/job.go | 48 +++++++++++++++++++++---------------------- pkg/fuzzer/queue/queue.go | 52 +++++++++++++++++++++++------------------------ 4 files changed, 73 insertions(+), 77 deletions(-) (limited to 'pkg/fuzzer') diff --git a/pkg/fuzzer/fuzzer.go b/pkg/fuzzer/fuzzer.go index 9d8957922..10056b528 100644 --- a/pkg/fuzzer/fuzzer.go +++ b/pkg/fuzzer/fuzzer.go @@ -96,17 +96,7 @@ type execOpt any type dontTriage struct{} type progFlags ProgTypes -func (fuzzer *Fuzzer) validateRequest(req *queue.Request) { - if req.NeedHints && (req.NeedCover || req.NeedSignal != queue.NoSignal) { - panic("Request.NeedHints is mutually exclusive with other fields") - } - if req.SignalFilter != nil && req.NeedSignal != queue.NewSignal { - panic("SignalFilter must be used with NewSignal") - } -} - func (fuzzer *Fuzzer) execute(executor queue.Executor, req *queue.Request, opts ...execOpt) *queue.Result { - fuzzer.validateRequest(req) executor.Submit(req) res := req.Wait(fuzzer.ctx) fuzzer.processResult(req, res, opts...) @@ -114,7 +104,6 @@ func (fuzzer *Fuzzer) execute(executor queue.Executor, req *queue.Request, opts } func (fuzzer *Fuzzer) prepare(req *queue.Request, opts ...execOpt) { - fuzzer.validateRequest(req) req.OnDone(func(req *queue.Request, res *queue.Result) bool { fuzzer.processResult(req, res, opts...) return true @@ -141,7 +130,7 @@ func (fuzzer *Fuzzer) processResult(req *queue.Request, res *queue.Result, opts // 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. - if req.NeedSignal != queue.NoSignal && res.Info != nil && !noTriage { + if req.ExecOpts.ExecFlags&ipc.FlagCollectSignal > 0 && res.Info != nil && !noTriage { for call, info := range res.Info.Calls { fuzzer.triageProgCall(req.Prog, &info, call, flags) } @@ -155,6 +144,7 @@ func (fuzzer *Fuzzer) processResult(req *queue.Request, res *queue.Result, opts type Config struct { Debug bool Corpus *corpus.Corpus + BaseOpts ipc.ExecOpts // Fuzzer will use BaseOpts as a base for all requests. Logf func(level int, msg string, args ...interface{}) Coverage bool FaultInjection bool @@ -236,7 +226,13 @@ func (fuzzer *Fuzzer) startJob(stat *stats.Val, newJob job) { } func (fuzzer *Fuzzer) Next() *queue.Request { - return fuzzer.source.Next() + req := fuzzer.source.Next() + if req == nil { + // The fuzzer is not supposed to issue nil requests. + panic("nil request from the fuzzer") + } + req.ExecOpts = fuzzer.Config.BaseOpts.MergeFlags(req.ExecOpts) + return req } func (fuzzer *Fuzzer) Logf(level int, msg string, args ...interface{}) { @@ -337,3 +333,9 @@ func (fuzzer *Fuzzer) RotateMaxSignal(items int) { delta := pureMaxSignal.RandomSubset(fuzzer.rand(), items) fuzzer.Cover.subtract(delta) } + +func setFlags(execFlags ipc.ExecFlags) ipc.ExecOpts { + return ipc.ExecOpts{ + ExecFlags: execFlags, + } +} diff --git a/pkg/fuzzer/fuzzer_test.go b/pkg/fuzzer/fuzzer_test.go index 5a903dedb..f358aacab 100644 --- a/pkg/fuzzer/fuzzer_test.go +++ b/pkg/fuzzer/fuzzer_test.go @@ -44,10 +44,12 @@ func TestFuzz(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + _, opts, _ := ipcconfig.Default(target) corpusUpdates := make(chan corpus.NewItemEvent) fuzzer := NewFuzzer(ctx, &Config{ - Debug: true, - Corpus: corpus.NewMonitoredCorpus(ctx, corpusUpdates), + Debug: true, + BaseOpts: *opts, + Corpus: corpus.NewMonitoredCorpus(ctx, corpusUpdates), Logf: func(level int, msg string, args ...interface{}) { if level > 1 { return @@ -188,10 +190,10 @@ func emulateExec(req *queue.Request) (*queue.Result, string, error) { cover := uint32(call.Meta.ID*1024) + crc32.Checksum(serializedLines[i], crc32q)%4 callInfo := ipc.CallInfo{} - if req.NeedCover { + if req.ExecOpts.ExecFlags&ipc.FlagCollectCover > 0 { callInfo.Cover = []uint32{cover} } - if req.NeedSignal != queue.NoSignal { + if req.ExecOpts.ExecFlags&ipc.FlagCollectSignal > 0 { callInfo.Signal = []uint32{cover} } info.Calls = append(info.Calls, callInfo) @@ -298,16 +300,8 @@ func newProc(t *testing.T, target *prog.Target, executor string) *executorProc { var crashRe = regexp.MustCompile(`{{CRASH: (.*?)}}`) func (proc *executorProc) execute(req *queue.Request) (*queue.Result, string, error) { - execOpts := proc.execOpts - // TODO: it's duplicated from fuzzer.go. - if req.NeedSignal != queue.NoSignal { - execOpts.ExecFlags |= ipc.FlagCollectSignal - } - if req.NeedCover { - execOpts.ExecFlags |= ipc.FlagCollectCover - } - // TODO: support req.NeedHints. - output, info, _, err := proc.env.Exec(&execOpts, req.Prog) + // TODO: support hints emulation. + output, info, _, err := proc.env.Exec(&req.ExecOpts, req.Prog) ret := crashRe.FindStringSubmatch(string(output)) if ret != nil { return nil, ret[1], nil diff --git a/pkg/fuzzer/job.go b/pkg/fuzzer/job.go index d54e6bd48..2ec415bdb 100644 --- a/pkg/fuzzer/job.go +++ b/pkg/fuzzer/job.go @@ -33,9 +33,9 @@ func genProgRequest(fuzzer *Fuzzer, rnd *rand.Rand) *queue.Request { prog.RecommendedCalls, fuzzer.ChoiceTable()) return &queue.Request{ - Prog: p, - NeedSignal: queue.NewSignal, - Stat: fuzzer.statExecGenerate, + Prog: p, + ExecOpts: setFlags(ipc.FlagCollectSignal), + Stat: fuzzer.statExecGenerate, } } @@ -52,9 +52,9 @@ func mutateProgRequest(fuzzer *Fuzzer, rnd *rand.Rand) *queue.Request { fuzzer.Config.Corpus.Programs(), ) return &queue.Request{ - Prog: newP, - NeedSignal: queue.NewSignal, - Stat: fuzzer.statExecFuzz, + Prog: newP, + ExecOpts: setFlags(ipc.FlagCollectSignal), + Stat: fuzzer.statExecFuzz, } } @@ -67,10 +67,10 @@ func candidateRequest(fuzzer *Fuzzer, input Candidate) (*queue.Request, ProgType flags |= progSmashed } return &queue.Request{ - Prog: input.Prog, - NeedSignal: queue.NewSignal, - Stat: fuzzer.statExecCandidate, - Important: true, + Prog: input.Prog, + ExecOpts: setFlags(ipc.FlagCollectSignal), + Stat: fuzzer.statExecCandidate, + Important: true, }, flags } @@ -161,10 +161,10 @@ func (job *triageJob) deflake(exec func(*queue.Request, ...execOpt) *queue.Resul break } result := exec(&queue.Request{ - Prog: job.p, - NeedSignal: queue.AllSignal, - NeedCover: true, - Stat: stat, + Prog: job.p, + ExecOpts: setFlags(ipc.FlagCollectCover | ipc.FlagCollectSignal), + ReturnAllSignal: true, + Stat: stat, }, &dontTriage{}) if result.Stop() { stop = true @@ -201,7 +201,7 @@ func (job *triageJob) minimize(newSignal signal.Signal) (stop bool) { for i := 0; i < minimizeAttempts; i++ { result := job.execute(&queue.Request{ Prog: p1, - NeedSignal: queue.NewSignal, + ExecOpts: setFlags(ipc.FlagCollectSignal), SignalFilter: newSignal, SignalFilterCall: call1, Stat: job.fuzzer.statExecMinimize, @@ -271,9 +271,9 @@ func (job *smashJob) run(fuzzer *Fuzzer) { fuzzer.Config.NoMutateCalls, fuzzer.Config.Corpus.Programs()) result := fuzzer.execute(fuzzer.smashQueue, &queue.Request{ - Prog: p, - NeedSignal: queue.NewSignal, - Stat: fuzzer.statExecSmash, + Prog: p, + ExecOpts: setFlags(ipc.FlagCollectSignal), + Stat: fuzzer.statExecSmash, }) if result.Stop() { return @@ -349,9 +349,9 @@ func (job *hintsJob) run(fuzzer *Fuzzer) { var comps prog.CompMap for i := 0; i < 2; i++ { result := fuzzer.execute(fuzzer.smashQueue, &queue.Request{ - Prog: p, - NeedHints: true, - Stat: fuzzer.statExecSeed, + Prog: p, + ExecOpts: setFlags(ipc.FlagCollectComps), + Stat: fuzzer.statExecSeed, }) if result.Stop() || result.Info == nil { return @@ -372,9 +372,9 @@ func (job *hintsJob) run(fuzzer *Fuzzer) { p.MutateWithHints(job.call, comps, func(p *prog.Prog) bool { result := fuzzer.execute(fuzzer.smashQueue, &queue.Request{ - Prog: p, - NeedSignal: queue.NewSignal, - Stat: fuzzer.statExecHint, + Prog: p, + ExecOpts: setFlags(ipc.FlagCollectSignal), + Stat: fuzzer.statExecHint, }) return !result.Stop() }) diff --git a/pkg/fuzzer/queue/queue.go b/pkg/fuzzer/queue/queue.go index 48b912846..68552083c 100644 --- a/pkg/fuzzer/queue/queue.go +++ b/pkg/fuzzer/queue/queue.go @@ -19,19 +19,19 @@ import ( ) type Request struct { - Prog *prog.Prog - NeedSignal SignalType - NeedCover bool - NeedHints bool - ExecOpts *ipc.ExecOpts + Prog *prog.Prog + ExecOpts ipc.ExecOpts // If specified, the resulting signal for call SignalFilterCall // will include subset of it even if it's not new. SignalFilter signal.Signal SignalFilterCall int - ReturnError bool - ReturnOutput bool + // By default, only the newly seen signal is returned. + // ReturnAllSignal tells the executor to return everything. + ReturnAllSignal bool + ReturnError bool + ReturnOutput bool // This stat will be incremented on request completion. Stat *stats.Val @@ -101,20 +101,28 @@ func (r *Request) Risky() bool { return r.onceCrashed } +func (r *Request) Validate() error { + collectSignal := r.ExecOpts.ExecFlags&ipc.FlagCollectSignal > 0 + if r.ReturnAllSignal && !collectSignal { + return fmt.Errorf("ReturnAllSignal is set, but FlagCollectSignal is not") + } + if r.SignalFilter != nil && !collectSignal { + return fmt.Errorf("SignalFilter must be used with FlagCollectSignal") + } + collectComps := r.ExecOpts.ExecFlags&ipc.FlagCollectComps > 0 + collectCover := r.ExecOpts.ExecFlags&ipc.FlagCollectCover > 0 + if (collectComps) && (collectSignal || collectCover) { + return fmt.Errorf("hint collection is mutually exclusive with signal/coverage") + } + return nil +} + func (r *Request) hash() hash.Sig { buf := new(bytes.Buffer) - if r.ExecOpts != nil { - if err := gob.NewEncoder(buf).Encode(r.ExecOpts); err != nil { - panic(err) - } + if err := gob.NewEncoder(buf).Encode(r.ExecOpts); err != nil { + panic(err) } - return hash.Hash( - []byte(fmt.Sprint(r.NeedSignal)), - []byte(fmt.Sprint(r.NeedCover)), - []byte(fmt.Sprint(r.NeedHints)), - r.Prog.Serialize(), - buf.Bytes(), - ) + return hash.Hash(r.Prog.Serialize(), buf.Bytes()) } func (r *Request) initChannel() { @@ -125,14 +133,6 @@ func (r *Request) initChannel() { r.mu.Unlock() } -type SignalType int - -const ( - NoSignal SignalType = iota // we don't need any signal - NewSignal // we need the newly seen signal - AllSignal // we need all signal -) - type Result struct { Info *ipc.ProgInfo Output []byte -- cgit mrf-deployment