diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2024-06-04 12:55:41 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2024-06-24 09:57:34 +0000 |
| commit | e16e2c9a4cb6937323e861b646792a6c4c978a3c (patch) | |
| tree | 6c513e98e5f465b44a98546d8984485d2c128582 /pkg/fuzzer | |
| parent | 90d67044dab68568e8f35bc14b68055dbd166eff (diff) | |
executor: add runner mode
Move all syz-fuzzer logic into syz-executor and remove syz-fuzzer.
Also restore syz-runtest functionality in the manager.
Update #4917 (sets most signal handlers to SIG_IGN)
Diffstat (limited to 'pkg/fuzzer')
| -rw-r--r-- | pkg/fuzzer/fuzzer.go | 2 | ||||
| -rw-r--r-- | pkg/fuzzer/fuzzer_test.go | 176 | ||||
| -rw-r--r-- | pkg/fuzzer/job.go | 23 | ||||
| -rw-r--r-- | pkg/fuzzer/queue/queue.go | 27 |
4 files changed, 113 insertions, 115 deletions
diff --git a/pkg/fuzzer/fuzzer.go b/pkg/fuzzer/fuzzer.go index 92d8b0f8d..5b95f4eec 100644 --- a/pkg/fuzzer/fuzzer.go +++ b/pkg/fuzzer/fuzzer.go @@ -166,7 +166,6 @@ func (fuzzer *Fuzzer) processResult(req *queue.Request, res *queue.Result, flags type Config struct { Debug bool Corpus *corpus.Corpus - BaseOpts flatrpc.ExecOpts // Fuzzer will use BaseOpts as a base for all requests. Logf func(level int, msg string, args ...interface{}) Coverage bool FaultInjection bool @@ -251,7 +250,6 @@ func (fuzzer *Fuzzer) Next() *queue.Request { // 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 } diff --git a/pkg/fuzzer/fuzzer_test.go b/pkg/fuzzer/fuzzer_test.go index 206469fda..55ec09666 100644 --- a/pkg/fuzzer/fuzzer_test.go +++ b/pkg/fuzzer/fuzzer_test.go @@ -13,6 +13,7 @@ import ( "runtime" "strings" "sync" + "sync/atomic" "testing" "time" @@ -20,14 +21,13 @@ import ( "github.com/google/syzkaller/pkg/csource" "github.com/google/syzkaller/pkg/flatrpc" "github.com/google/syzkaller/pkg/fuzzer/queue" - "github.com/google/syzkaller/pkg/ipc" - "github.com/google/syzkaller/pkg/ipc/ipcconfig" + "github.com/google/syzkaller/pkg/rpcserver" "github.com/google/syzkaller/pkg/signal" "github.com/google/syzkaller/pkg/testutil" + "github.com/google/syzkaller/pkg/vminfo" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/sys/targets" "github.com/stretchr/testify/assert" - "golang.org/x/sync/errgroup" ) func TestFuzz(t *testing.T) { @@ -42,15 +42,14 @@ func TestFuzz(t *testing.T) { t.Skipf("skipping, broken cross-compiler: %v", sysTarget.BrokenCompiler) } executor := csource.BuildExecutor(t, target, "../..", "-fsanitize-coverage=trace-pc", "-g") + ctx, cancel := context.WithCancel(context.Background()) defer cancel() - _, opts, _ := ipcconfig.Default(target) corpusUpdates := make(chan corpus.NewItemEvent) fuzzer := NewFuzzer(ctx, &Config{ - Debug: true, - BaseOpts: *opts, - Corpus: corpus.NewMonitoredCorpus(ctx, corpusUpdates), + Debug: true, + Corpus: corpus.NewMonitoredCorpus(ctx, corpusUpdates), Logf: func(level int, msg string, args ...interface{}) { if level > 1 { return @@ -74,24 +73,24 @@ func TestFuzz(t *testing.T) { } }() - tf := newTestFuzzer(t, fuzzer, map[string]bool{ - "first bug": true, - "second bug": true, - }, 10000) - - for i := 0; i < 2; i++ { - tf.registerExecutor(newProc(t, target, executor)) + tf := &testFuzzer{ + t: t, + target: target, + fuzzer: fuzzer, + executor: executor, + iterLimit: 10000, + expectedCrashes: map[string]bool{ + "first bug": true, + "second bug": true, + }, } - tf.wait() + tf.run() t.Logf("resulting corpus:") for _, p := range fuzzer.Config.Corpus.Programs() { t.Logf("-----") t.Logf("%s", p.Serialize()) } - - assert.Equal(t, len(tf.expectedCrashes), len(tf.crashes), - "not all expected crashes were found") } func BenchmarkFuzzer(b *testing.B) { @@ -204,114 +203,87 @@ func emulateExec(req *queue.Request) (*queue.Result, string, error) { type testFuzzer struct { t testing.TB - eg errgroup.Group + target *prog.Target fuzzer *Fuzzer + executor string mu sync.Mutex crashes map[string]int expectedCrashes map[string]bool iter int iterLimit int + done func() + finished atomic.Bool +} + +func (f *testFuzzer) run() { + f.crashes = make(map[string]int) + ctx, done := context.WithCancel(context.Background()) + f.done = done + cfg := &rpcserver.LocalConfig{ + Config: rpcserver.Config{ + Config: vminfo.Config{ + Target: f.target, + Features: flatrpc.FeatureSandboxNone, + Sandbox: flatrpc.ExecEnvSandboxNone, + }, + Procs: 4, + Slowdown: 1, + }, + Executor: f.executor, + Dir: f.t.TempDir(), + Context: ctx, + } + cfg.MachineChecked = func(features flatrpc.Feature, syscalls map[*prog.Syscall]bool) queue.Source { + cfg.Cover = true + return f + } + if err := rpcserver.RunLocal(cfg); err != nil { + f.t.Fatal(err) + } + assert.Equal(f.t, len(f.expectedCrashes), len(f.crashes), "not all expected crashes were found") } -func newTestFuzzer(t testing.TB, fuzzer *Fuzzer, expectedCrashes map[string]bool, iterLimit int) *testFuzzer { - return &testFuzzer{ - t: t, - fuzzer: fuzzer, - expectedCrashes: expectedCrashes, - crashes: map[string]int{}, - iterLimit: iterLimit, +func (f *testFuzzer) Next() *queue.Request { + if f.finished.Load() { + return nil } + req := f.fuzzer.Next() + req.ExecOpts.EnvFlags |= flatrpc.ExecEnvSignal | flatrpc.ExecEnvSandboxNone + req.ReturnOutput = true + req.ReturnError = true + req.OnDone(f.OnDone) + return req } -func (f *testFuzzer) oneMore() bool { +func (f *testFuzzer) OnDone(req *queue.Request, res *queue.Result) bool { + // TODO: support hints emulation. + match := crashRe.FindSubmatch(res.Output) f.mu.Lock() defer f.mu.Unlock() + if match != nil { + crash := string(match[1]) + f.t.Logf("CRASH: %s", crash) + res.Status = queue.Crashed + if !f.expectedCrashes[crash] { + f.t.Errorf("unexpected crash: %q", crash) + } + f.crashes[crash]++ + } f.iter++ if f.iter%100 == 0 { f.t.Logf("<iter %d>: corpus %d, signal %d, max signal %d, crash types %d, running jobs %d", f.iter, f.fuzzer.Config.Corpus.StatProgs.Val(), f.fuzzer.Config.Corpus.StatSignal.Val(), len(f.fuzzer.Cover.maxSignal), len(f.crashes), f.fuzzer.statJobs.Val()) } - return f.iter < f.iterLimit && - (f.expectedCrashes == nil || len(f.crashes) != len(f.expectedCrashes)) -} - -func (f *testFuzzer) registerExecutor(proc *executorProc) { - f.eg.Go(func() error { - for f.oneMore() { - req := f.fuzzer.Next() - res, crash, err := proc.execute(req) - if err != nil { - return err - } - if crash != "" { - res = &queue.Result{Status: queue.Crashed} - if !f.expectedCrashes[crash] { - return fmt.Errorf("unexpected crash: %q", crash) - } - f.mu.Lock() - f.t.Logf("CRASH: %s", crash) - f.crashes[crash]++ - f.mu.Unlock() - } - req.Done(res) - } - return nil - }) -} - -func (f *testFuzzer) wait() { - t := f.t - err := f.eg.Wait() - if err != nil { - t.Fatal(err) - } - t.Logf("crashes:") - for title, cnt := range f.crashes { - t.Logf("%s: %d", title, cnt) - } -} - -// TODO: it's already implemented in syz-fuzzer/proc.go, -// pkg/runtest and tools/syz-execprog. -// Looks like it's time to factor out this functionality. -type executorProc struct { - env *ipc.Env - execOpts flatrpc.ExecOpts -} - -func newProc(t *testing.T, target *prog.Target, executor string) *executorProc { - config, execOpts, err := ipcconfig.Default(target) - if err != nil { - t.Fatal(err) - } - config.Executor = executor - execOpts.EnvFlags |= flatrpc.ExecEnvSignal - env, err := ipc.MakeEnv(config, 0) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { env.Close() }) - return &executorProc{ - env: env, - execOpts: *execOpts, + if !f.finished.Load() && (f.iter > f.iterLimit || len(f.crashes) == len(f.expectedCrashes)) { + f.done() + f.finished.Store(true) } + return true } var crashRe = regexp.MustCompile(`{{CRASH: (.*?)}}`) -func (proc *executorProc) execute(req *queue.Request) (*queue.Result, string, error) { - // 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 - } else if err != nil { - return nil, "", err - } - return &queue.Result{Info: info}, "", nil -} - func checkGoroutineLeaks() { // Inspired by src/net/http/main_test.go. buf := make([]byte, 2<<20) diff --git a/pkg/fuzzer/job.go b/pkg/fuzzer/job.go index 0f6e0309c..0268172a9 100644 --- a/pkg/fuzzer/job.go +++ b/pkg/fuzzer/job.go @@ -121,6 +121,7 @@ func (job *triageJob) handleCall(call int, info *triageCall) { } if job.flags&ProgSmashed == 0 { job.fuzzer.startJob(job.fuzzer.statJobsSmash, &smashJob{ + exec: job.fuzzer.smashQueue, p: p.Clone(), call: call, }) @@ -240,11 +241,10 @@ func (job *triageJob) minimize(call int, info *triageCall) (*prog.Prog, int) { } for i := 0; i < minimizeAttempts; i++ { result := job.execute(&queue.Request{ - Prog: p1, - ExecOpts: setFlags(flatrpc.ExecFlagCollectSignal), - SignalFilter: info.newStableSignal, - SignalFilterCall: call1, - Stat: job.fuzzer.statExecMinimize, + Prog: p1, + ExecOpts: setFlags(flatrpc.ExecFlagCollectSignal), + ReturnAllSignal: []int{call1}, + Stat: job.fuzzer.statExecMinimize, }, 0) if result.Stop() { stop = true @@ -294,6 +294,7 @@ func getSignalAndCover(p *prog.Prog, info *flatrpc.ProgInfo, call int) signal.Si } type smashJob struct { + exec queue.Executor p *prog.Prog call int } @@ -302,6 +303,7 @@ func (job *smashJob) run(fuzzer *Fuzzer) { fuzzer.Logf(2, "smashing the program %s (call=%d):", job.p, job.call) if fuzzer.Config.Comparisons && job.call >= 0 { fuzzer.startJob(fuzzer.statJobsHints, &hintsJob{ + exec: fuzzer.smashQueue, p: job.p.Clone(), call: job.call, }) @@ -315,7 +317,7 @@ func (job *smashJob) run(fuzzer *Fuzzer) { fuzzer.ChoiceTable(), fuzzer.Config.NoMutateCalls, fuzzer.Config.Corpus.Programs()) - result := fuzzer.execute(fuzzer.smashQueue, &queue.Request{ + result := fuzzer.execute(job.exec, &queue.Request{ Prog: p, ExecOpts: setFlags(flatrpc.ExecFlagCollectSignal), Stat: fuzzer.statExecSmash, @@ -324,7 +326,7 @@ func (job *smashJob) run(fuzzer *Fuzzer) { return } if fuzzer.Config.Collide { - result := fuzzer.execute(fuzzer.smashQueue, &queue.Request{ + result := fuzzer.execute(job.exec, &queue.Request{ Prog: randomCollide(p, rnd), Stat: fuzzer.statExecCollide, }) @@ -366,7 +368,7 @@ func (job *smashJob) faultInjection(fuzzer *Fuzzer) { job.call, nth) newProg := job.p.Clone() newProg.Calls[job.call].Props.FailNth = nth - result := fuzzer.execute(fuzzer.smashQueue, &queue.Request{ + result := fuzzer.execute(job.exec, &queue.Request{ Prog: newProg, Stat: fuzzer.statExecFaultInject, }) @@ -382,6 +384,7 @@ func (job *smashJob) faultInjection(fuzzer *Fuzzer) { } type hintsJob struct { + exec queue.Executor p *prog.Prog call int } @@ -393,7 +396,7 @@ func (job *hintsJob) run(fuzzer *Fuzzer) { var comps prog.CompMap for i := 0; i < 2; i++ { - result := fuzzer.execute(fuzzer.smashQueue, &queue.Request{ + result := fuzzer.execute(job.exec, &queue.Request{ Prog: p, ExecOpts: setFlags(flatrpc.ExecFlagCollectComps), Stat: fuzzer.statExecSeed, @@ -420,7 +423,7 @@ func (job *hintsJob) run(fuzzer *Fuzzer) { // Execute each of such mutants to check if it gives new coverage. p.MutateWithHints(job.call, comps, func(p *prog.Prog) bool { - result := fuzzer.execute(fuzzer.smashQueue, &queue.Request{ + result := fuzzer.execute(job.exec, &queue.Request{ Prog: p, ExecOpts: setFlags(flatrpc.ExecFlagCollectSignal), Stat: fuzzer.statExecHint, diff --git a/pkg/fuzzer/queue/queue.go b/pkg/fuzzer/queue/queue.go index 46df9d234..051f7205e 100644 --- a/pkg/fuzzer/queue/queue.go +++ b/pkg/fuzzer/queue/queue.go @@ -37,7 +37,6 @@ type Request struct { // Options needed by runtest. BinaryFile string // If set, it's executed instead of Prog. - Repeat int // Repeats in addition to the first run. // Important requests will be retried even from crashed VMs. Important bool @@ -113,6 +112,11 @@ func (r *Request) Validate() error { if (collectComps) && (collectSignal || collectCover) { return fmt.Errorf("hint collection is mutually exclusive with signal/coverage") } + sandboxes := flatrpc.ExecEnvSandboxNone | flatrpc.ExecEnvSandboxSetuid | + flatrpc.ExecEnvSandboxNamespace | flatrpc.ExecEnvSandboxAndroid + if r.BinaryFile == "" && r.ExecOpts.EnvFlags&sandboxes == 0 { + return fmt.Errorf("no sandboxes set") + } return nil } @@ -415,3 +419,24 @@ func (d *Deduplicator) onDone(req *Request, res *Result) bool { } return true } + +// DefaultOpts applies opts to all requests in source. +func DefaultOpts(source Source, opts flatrpc.ExecOpts) Source { + return &defaultOpts{source, opts} +} + +type defaultOpts struct { + source Source + opts flatrpc.ExecOpts +} + +func (do *defaultOpts) Next() *Request { + req := do.source.Next() + if req == nil { + return nil + } + req.ExecOpts.ExecFlags |= do.opts.ExecFlags + req.ExecOpts.EnvFlags |= do.opts.EnvFlags + req.ExecOpts.SandboxArg = do.opts.SandboxArg + return req +} |
