aboutsummaryrefslogtreecommitdiffstats
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
parente072baa2563542356ae03ad6f2057ad143979f18 (diff)
pkg/fuzzer: manipulate ipc.ExecOpts
There's no need in duplicating the signal, coverage, hints flags.
-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
-rw-r--r--syz-manager/manager.go5
-rw-r--r--syz-manager/rpc.go31
-rw-r--r--tools/syz-execprog/execprog.go2
-rw-r--r--tools/syz-runtest/runtest.go2
13 files changed, 99 insertions, 108 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,
diff --git a/syz-manager/manager.go b/syz-manager/manager.go
index 648020482..7106d7a20 100644
--- a/syz-manager/manager.go
+++ b/syz-manager/manager.go
@@ -30,6 +30,7 @@ import (
"github.com/google/syzkaller/pkg/gce"
"github.com/google/syzkaller/pkg/hash"
"github.com/google/syzkaller/pkg/instance"
+ "github.com/google/syzkaller/pkg/ipc"
"github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/mgrconfig"
"github.com/google/syzkaller/pkg/osutil"
@@ -1340,7 +1341,8 @@ func (mgr *Manager) currentBugFrames() BugFrames {
return frames
}
-func (mgr *Manager) machineChecked(features flatrpc.Feature, enabledSyscalls map[*prog.Syscall]bool) {
+func (mgr *Manager) machineChecked(features flatrpc.Feature, enabledSyscalls map[*prog.Syscall]bool,
+ opts ipc.ExecOpts) {
mgr.mu.Lock()
defer mgr.mu.Unlock()
if mgr.checkDone {
@@ -1356,6 +1358,7 @@ func (mgr *Manager) machineChecked(features flatrpc.Feature, enabledSyscalls map
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
fuzzerObj := fuzzer.NewFuzzer(context.Background(), &fuzzer.Config{
Corpus: mgr.corpus,
+ BaseOpts: opts,
Coverage: mgr.cfg.Cover,
FaultInjection: features&flatrpc.FeatureFault != 0,
Comparisons: features&flatrpc.FeatureComparisons != 0,
diff --git a/syz-manager/rpc.go b/syz-manager/rpc.go
index 70dbcdbd2..4787c9d8e 100644
--- a/syz-manager/rpc.go
+++ b/syz-manager/rpc.go
@@ -93,7 +93,7 @@ type BugFrames struct {
// RPCManagerView restricts interface between RPCServer and Manager.
type RPCManagerView interface {
currentBugFrames() BugFrames
- machineChecked(features flatrpc.Feature, enabledSyscalls map[*prog.Syscall]bool)
+ machineChecked(features flatrpc.Feature, enabledSyscalls map[*prog.Syscall]bool, opts ipc.ExecOpts)
}
func startRPCServer(mgr *Manager, source queue.Source) (*RPCServer, error) {
@@ -305,7 +305,7 @@ func (serv *RPCServer) finishCheck(checkFilesInfo []flatrpc.FileInfo, checkFeatu
}
serv.enabledFeatures = features.Enabled()
serv.setupFeatures = features.NeedSetup()
- serv.mgr.machineChecked(serv.enabledFeatures, enabledCalls)
+ serv.mgr.machineChecked(serv.enabledFeatures, enabledCalls, serv.execOpts())
return nil
}
@@ -351,6 +351,9 @@ func (serv *RPCServer) ExchangeInfo(a *rpctype.ExchangeInfoRequest, r *rpctype.E
// It's unlikely that subsequent Next() calls will yield something.
break
}
+ if err := inp.Validate(); err != nil {
+ panic(fmt.Sprintf("invalid request: %v, req: %#v", err, inp))
+ }
if req, ok := serv.newRequest(runner, inp); ok {
r.Requests = append(r.Requests, req)
if inp.Risky() {
@@ -551,18 +554,11 @@ func (serv *RPCServer) newRequest(runner *Runner, req *queue.Request) (rpctype.E
}
}
runner.mu.Unlock()
-
- var execOpts ipc.ExecOpts
- if req.ExecOpts != nil {
- execOpts = *req.ExecOpts
- } else {
- execOpts = serv.createExecOpts(req)
- }
return rpctype.ExecutionRequest{
ID: id,
ProgData: progData,
- ExecOpts: execOpts,
- NewSignal: req.NeedSignal == queue.NewSignal,
+ ExecOpts: req.ExecOpts,
+ NewSignal: !req.ReturnAllSignal,
SignalFilter: signalFilter,
SignalFilterCall: req.SignalFilterCall,
ResetState: serv.cfg.Experimental.ResetAccState,
@@ -571,7 +567,7 @@ func (serv *RPCServer) newRequest(runner *Runner, req *queue.Request) (rpctype.E
}, true
}
-func (serv *RPCServer) createExecOpts(req *queue.Request) ipc.ExecOpts {
+func (serv *RPCServer) execOpts() ipc.ExecOpts {
env := ipc.FeaturesToFlags(serv.enabledFeatures, nil)
if *flagDebug {
env |= ipc.FlagDebug
@@ -592,17 +588,6 @@ func (serv *RPCServer) createExecOpts(req *queue.Request) ipc.ExecOpts {
if serv.cfg.HasCovFilter() {
exec |= ipc.FlagEnableCoverageFilter
}
- if serv.cfg.Cover {
- if req.NeedSignal != queue.NoSignal {
- exec |= ipc.FlagCollectSignal
- }
- if req.NeedCover {
- exec |= ipc.FlagCollectCover
- }
- if req.NeedHints {
- exec |= ipc.FlagCollectComps
- }
- }
return ipc.ExecOpts{
EnvFlags: env,
ExecFlags: exec,
diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go
index ad75909eb..5479637ea 100644
--- a/tools/syz-execprog/execprog.go
+++ b/tools/syz-execprog/execprog.go
@@ -479,7 +479,7 @@ func checkerExecutor(ctx context.Context, source queue.Source, config *ipc.Confi
if err != nil {
log.Fatalf("failed to serialize %s: %v", req.Prog.Serialize(), err)
}
- output, info, hanged, err := env.ExecProg(req.ExecOpts, progData)
+ output, info, hanged, err := env.ExecProg(&req.ExecOpts, progData)
res := &queue.Result{
Status: queue.Success,
Info: info,
diff --git a/tools/syz-runtest/runtest.go b/tools/syz-runtest/runtest.go
index 5605c3616..878ccad3a 100644
--- a/tools/syz-runtest/runtest.go
+++ b/tools/syz-runtest/runtest.go
@@ -301,7 +301,7 @@ func (mgr *Manager) ExchangeInfo(a *rpctype.ExchangeInfoRequest, r *rpctype.Exch
r.Requests = append(r.Requests, rpctype.ExecutionRequest{
ID: mgr.reqSeq,
ProgData: progData,
- ExecOpts: *req.ExecOpts,
+ ExecOpts: req.ExecOpts,
IsBinary: req.BinaryFile != "",
ResetState: req.BinaryFile == "",
ReturnOutput: true,