aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2024-05-16 12:56:43 +0200
committerDmitry Vyukov <dvyukov@google.com>2024-05-16 15:38:27 +0000
commit19e202fa8a3722f5c90ed5847edb5aeabdd5f38f (patch)
treea528875b3c3c531b43d5a184f83ec1a04c213914 /pkg
parente072baa2563542356ae03ad6f2057ad143979f18 (diff)
pkg/fuzzer: manipulate ipc.ExecOpts
There's no need in duplicating the signal, coverage, hints flags.
Diffstat (limited to 'pkg')
-rw-r--r--pkg/fuzzer/fuzzer.go28
-rw-r--r--pkg/fuzzer/fuzzer_test.go22
-rw-r--r--pkg/fuzzer/job.go48
-rw-r--r--pkg/fuzzer/queue/queue.go52
-rw-r--r--pkg/ipc/ipc.go7
-rw-r--r--pkg/runtest/run.go4
-rw-r--r--pkg/runtest/run_test.go2
-rw-r--r--pkg/vminfo/features.go2
-rw-r--r--pkg/vminfo/syscalls.go2
9 files changed, 85 insertions, 82 deletions
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
diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go
index d87cb8b0e..d6bae8af1 100644
--- a/pkg/ipc/ipc.go
+++ b/pkg/ipc/ipc.go
@@ -69,6 +69,13 @@ type ExecOpts struct {
SandboxArg int
}
+func (eo ExecOpts) MergeFlags(diff ExecOpts) ExecOpts {
+ ret := eo
+ ret.ExecFlags |= diff.ExecFlags
+ ret.EnvFlags |= diff.EnvFlags
+ return ret
+}
+
// Config is the configuration for Env.
type Config struct {
// Path to executor binary.
diff --git a/pkg/runtest/run.go b/pkg/runtest/run.go
index a85fc3209..a3df53f5a 100644
--- a/pkg/runtest/run.go
+++ b/pkg/runtest/run.go
@@ -438,7 +438,7 @@ func (ctx *Context) createSyzTest(p *prog.Prog, sandbox string, threaded, cov bo
req := &runRequest{
Request: &queue.Request{
Prog: p,
- ExecOpts: &opts,
+ ExecOpts: opts,
Repeat: times,
},
}
@@ -492,7 +492,7 @@ func (ctx *Context) createCTest(p *prog.Prog, sandbox string, threaded bool, tim
Request: &queue.Request{
Prog: p,
BinaryFile: bin,
- ExecOpts: &ipc.ExecOpts{
+ ExecOpts: ipc.ExecOpts{
ExecFlags: ipcFlags,
},
Repeat: times,
diff --git a/pkg/runtest/run_test.go b/pkg/runtest/run_test.go
index 09b714e1e..fb3f8b8e9 100644
--- a/pkg/runtest/run_test.go
+++ b/pkg/runtest/run_test.go
@@ -125,7 +125,7 @@ func runTest(req *queue.Request, executor string) *queue.Result {
// Recreate Env every few iterations, this allows to cover more paths.
env.ForceRestart()
}
- output, info, hanged, err := env.Exec(req.ExecOpts, req.Prog)
+ output, info, hanged, err := env.Exec(&req.ExecOpts, req.Prog)
ret.Output = append(ret.Output, output...)
if err != nil {
return &queue.Result{
diff --git a/pkg/vminfo/features.go b/pkg/vminfo/features.go
index fb443774c..4a499c1e0 100644
--- a/pkg/vminfo/features.go
+++ b/pkg/vminfo/features.go
@@ -56,7 +56,7 @@ func (ctx *checkContext) startFeaturesCheck() {
Prog: testProg,
ReturnOutput: true,
ReturnError: true,
- ExecOpts: &ipc.ExecOpts{
+ ExecOpts: ipc.ExecOpts{
EnvFlags: envFlags,
ExecFlags: execFlags,
SandboxArg: ctx.cfg.SandboxArg,
diff --git a/pkg/vminfo/syscalls.go b/pkg/vminfo/syscalls.go
index 54678291f..48fae4d89 100644
--- a/pkg/vminfo/syscalls.go
+++ b/pkg/vminfo/syscalls.go
@@ -254,7 +254,7 @@ func (ctx *checkContext) execRaw(calls []string, mode prog.DeserializeMode, root
}
req := &queue.Request{
Prog: p,
- ExecOpts: &ipc.ExecOpts{
+ ExecOpts: ipc.ExecOpts{
EnvFlags: sandbox,
ExecFlags: 0,
SandboxArg: ctx.cfg.SandboxArg,