diff options
| -rw-r--r-- | pkg/fuzzer/fuzzer.go | 16 | ||||
| -rw-r--r-- | pkg/fuzzer/queue/queue.go | 11 | ||||
| -rw-r--r-- | pkg/fuzzer/queue/queue_test.go | 12 | ||||
| -rw-r--r-- | pkg/fuzzer/stats.go | 43 | ||||
| -rw-r--r-- | syz-manager/manager.go | 63 | ||||
| -rw-r--r-- | tools/syz-testbed/instance.go | 25 | ||||
| -rw-r--r-- | tools/syz-testbed/targets.go | 2 | ||||
| -rw-r--r-- | tools/syz-testbed/testbed.go | 2 |
8 files changed, 101 insertions, 73 deletions
diff --git a/pkg/fuzzer/fuzzer.go b/pkg/fuzzer/fuzzer.go index 9ad218433..8be912139 100644 --- a/pkg/fuzzer/fuzzer.go +++ b/pkg/fuzzer/fuzzer.go @@ -76,7 +76,7 @@ type execQueues struct { func newExecQueues(fuzzer *Fuzzer) execQueues { ret := execQueues{ triageCandidateQueue: queue.DynamicOrder(), - candidateQueue: queue.PlainWithStat(fuzzer.StatCandidates), + candidateQueue: queue.Plain(), triageQueue: queue.DynamicOrder(), smashQueue: queue.Plain(), } @@ -92,6 +92,10 @@ func newExecQueues(fuzzer *Fuzzer) execQueues { return ret } +func (fuzzer *Fuzzer) CandidateTriageFinished() bool { + return fuzzer.statCandidates.Val()+fuzzer.statJobsTriageCandidate.Val() == 0 +} + func (fuzzer *Fuzzer) execute(executor queue.Executor, req *queue.Request) *queue.Result { return fuzzer.executeWithFlags(executor, req, 0) } @@ -130,6 +134,9 @@ func (fuzzer *Fuzzer) processResult(req *queue.Request, res *queue.Result, flags if res.Info != nil { fuzzer.statExecTime.Add(int(res.Info.Elapsed / 1e6)) } + if flags&progCandidate != 0 { + fuzzer.statCandidates.Add(-1) + } } type Config struct { @@ -161,11 +168,11 @@ func (fuzzer *Fuzzer) triageProgCall(p *prog.Prog, info *flatrpc.CallInfo, call } fuzzer.Logf(2, "found new signal in call %d in %s", call, p) - queue := fuzzer.triageQueue + queue, stat := fuzzer.triageQueue, fuzzer.statJobsTriage if flags&progCandidate > 0 { - queue = fuzzer.triageCandidateQueue + queue, stat = fuzzer.triageCandidateQueue, fuzzer.statJobsTriageCandidate } - fuzzer.startJob(fuzzer.statJobsTriage, &triageJob{ + fuzzer.startJob(stat, &triageJob{ p: p.Clone(), call: call, info: info, @@ -243,6 +250,7 @@ type Candidate struct { } 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) diff --git a/pkg/fuzzer/queue/queue.go b/pkg/fuzzer/queue/queue.go index a9411c1bd..cb70f5a9b 100644 --- a/pkg/fuzzer/queue/queue.go +++ b/pkg/fuzzer/queue/queue.go @@ -173,7 +173,6 @@ type Source interface { // PlainQueue is a straighforward thread-safe Request queue implementation. type PlainQueue struct { - stat *stats.Val mu sync.Mutex queue []*Request pos int @@ -183,10 +182,6 @@ func Plain() *PlainQueue { return &PlainQueue{} } -func PlainWithStat(val *stats.Val) *PlainQueue { - return &PlainQueue{stat: val} -} - func (pq *PlainQueue) Len() int { pq.mu.Lock() defer pq.mu.Unlock() @@ -194,9 +189,6 @@ func (pq *PlainQueue) Len() int { } func (pq *PlainQueue) Submit(req *Request) { - if pq.stat != nil { - pq.stat.Add(1) - } pq.mu.Lock() defer pq.mu.Unlock() @@ -235,9 +227,6 @@ func (pq *PlainQueue) nextLocked() *Request { ret := pq.queue[pq.pos] pq.queue[pq.pos] = nil pq.pos++ - if pq.stat != nil { - pq.stat.Add(-1) - } return ret } diff --git a/pkg/fuzzer/queue/queue_test.go b/pkg/fuzzer/queue/queue_test.go index a89ec0d3d..5b6a03ed0 100644 --- a/pkg/fuzzer/queue/queue_test.go +++ b/pkg/fuzzer/queue/queue_test.go @@ -6,29 +6,19 @@ package queue import ( "testing" - "github.com/google/syzkaller/pkg/stats" "github.com/stretchr/testify/assert" ) func TestPlainQueue(t *testing.T) { - val := stats.Create("v0", "desc0") - pq := PlainWithStat(val) + pq := Plain() req1, req2, req3 := &Request{}, &Request{}, &Request{} pq.Submit(req1) - assert.Equal(t, 1, val.Val()) pq.Submit(req2) - assert.Equal(t, 2, val.Val()) - assert.Equal(t, req1, pq.Next()) - assert.Equal(t, 1, val.Val()) - assert.Equal(t, req2, pq.Next()) - assert.Equal(t, 0, val.Val()) - pq.Submit(req3) - assert.Equal(t, 1, val.Val()) assert.Equal(t, req3, pq.Next()) assert.Nil(t, pq.Next()) } diff --git a/pkg/fuzzer/stats.go b/pkg/fuzzer/stats.go index c860b8bb9..2129c048a 100644 --- a/pkg/fuzzer/stats.go +++ b/pkg/fuzzer/stats.go @@ -6,35 +6,38 @@ package fuzzer import "github.com/google/syzkaller/pkg/stats" type Stats struct { - StatCandidates *stats.Val - statNewInputs *stats.Val - statJobs *stats.Val - statJobsTriage *stats.Val - statJobsSmash *stats.Val - statJobsHints *stats.Val - statExecTime *stats.Val - statExecGenerate *stats.Val - statExecFuzz *stats.Val - statExecCandidate *stats.Val - statExecTriage *stats.Val - statExecMinimize *stats.Val - statExecSmash *stats.Val - statExecHint *stats.Val - statExecSeed *stats.Val - statExecCollide *stats.Val + statCandidates *stats.Val + statNewInputs *stats.Val + statJobs *stats.Val + statJobsTriage *stats.Val + statJobsTriageCandidate *stats.Val + statJobsSmash *stats.Val + statJobsHints *stats.Val + statExecTime *stats.Val + statExecGenerate *stats.Val + statExecFuzz *stats.Val + statExecCandidate *stats.Val + statExecTriage *stats.Val + statExecMinimize *stats.Val + statExecSmash *stats.Val + statExecHint *stats.Val + statExecSeed *stats.Val + statExecCollide *stats.Val } func newStats() Stats { return Stats{ - StatCandidates: stats.Create("candidates", "Number of candidate programs in triage queue", + statCandidates: stats.Create("candidates", "Number of candidate programs in triage queue", stats.Console, stats.Graph("corpus")), statNewInputs: stats.Create("new inputs", "Potential untriaged corpus candidates", stats.Graph("corpus")), statJobs: stats.Create("fuzzer jobs", "Total running fuzzer jobs", stats.NoGraph), statJobsTriage: stats.Create("triage jobs", "Running triage jobs", stats.StackedGraph("jobs")), - statJobsSmash: stats.Create("smash jobs", "Running smash jobs", stats.StackedGraph("jobs")), - statJobsHints: stats.Create("hints jobs", "Running hints jobs", stats.StackedGraph("jobs")), - statExecTime: stats.Create("prog exec time", "Test program execution time (ms)", stats.Distribution{}), + statJobsTriageCandidate: stats.Create("candidate triage jobs", "Running candidate triage jobs", + stats.StackedGraph("jobs")), + statJobsSmash: stats.Create("smash jobs", "Running smash jobs", stats.StackedGraph("jobs")), + statJobsHints: stats.Create("hints jobs", "Running hints jobs", stats.StackedGraph("jobs")), + statExecTime: stats.Create("prog exec time", "Test program execution time (ms)", stats.Distribution{}), statExecGenerate: stats.Create("exec gen", "Executions of generated programs", stats.Rate{}, stats.StackedGraph("exec")), statExecFuzz: stats.Create("exec fuzz", "Executions of mutated programs", diff --git a/syz-manager/manager.go b/syz-manager/manager.go index 44df6d9fd..8d68b4be2 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -55,7 +55,9 @@ var ( " The test consists of booting VMs and running some simple test programs\n"+ " to ensure that fuzzing can proceed in general. After completing the test\n"+ " the process exits and the exit status indicates success/failure.\n"+ - " If the kernel oopses during testing, the report is saved to workdir/report.json.\n") + " If the kernel oopses during testing, the report is saved to workdir/report.json.\n"+ + " - corpus-triage: triage corpus and exit\n"+ + " This is useful mostly for benchmarking with testbed.\n") ) type Manager struct { @@ -103,6 +105,9 @@ type Manager struct { // Maps file name to modification time. usedFiles map[string]time.Time + benchMu sync.Mutex + benchFile *os.File + assetStorage *asset.Storage bootTime stats.AverageValue[time.Duration] @@ -116,6 +121,7 @@ type Mode int const ( ModeFuzzing Mode = iota ModeSmokeTest + ModeCorpusTriage ) const ( @@ -168,6 +174,10 @@ func RunManager(cfg *mgrconfig.Config) { mode = ModeSmokeTest cfg.DashboardClient = "" cfg.HubClient = "" + case "corpus-triage": + mode = ModeCorpusTriage + cfg.DashboardClient = "" + cfg.HubClient = "" default: flag.PrintDefaults() log.Fatalf("unknown mode: %v", *flagMode) @@ -258,6 +268,15 @@ func RunManager(cfg *mgrconfig.Config) { mgr.vmLoop() } +// Exit successfully in special operation modes. +func (mgr *Manager) exit(reason string) { + log.Logf(0, "%v finished, shutting down...", reason) + mgr.writeBench() + close(vm.Shutdown) + time.Sleep(10 * time.Second) + os.Exit(0) +} + func (mgr *Manager) heartbeatLoop() { lastTime := time.Now() for now := range time.NewTicker(10 * time.Second).C { @@ -280,23 +299,33 @@ func (mgr *Manager) initBench() { if err != nil { log.Fatalf("failed to open bench file: %v", err) } + mgr.benchFile = f go func() { for range time.NewTicker(time.Minute).C { - vals := make(map[string]int) - for _, stat := range stats.Collect(stats.All) { - vals[stat.Name] = stat.V - } - data, err := json.MarshalIndent(vals, "", " ") - if err != nil { - log.Fatalf("failed to serialize bench data") - } - if _, err := f.Write(append(data, '\n')); err != nil { - log.Fatalf("failed to write bench data") - } + mgr.writeBench() } }() } +func (mgr *Manager) writeBench() { + if mgr.benchFile == nil { + return + } + mgr.benchMu.Lock() + defer mgr.benchMu.Unlock() + vals := make(map[string]int) + for _, stat := range stats.Collect(stats.All) { + vals[stat.Name] = stat.V + } + data, err := json.MarshalIndent(vals, "", " ") + if err != nil { + log.Fatalf("failed to serialize bench data") + } + if _, err := mgr.benchFile.Write(append(data, '\n')); err != nil { + log.Fatalf("failed to write bench data") + } +} + type RunResult struct { idx int crash *Crash @@ -1409,10 +1438,7 @@ func (mgr *Manager) currentBugFrames() BugFrames { func (mgr *Manager) machineChecked(features flatrpc.Feature, enabledSyscalls map[*prog.Syscall]bool, opts flatrpc.ExecOpts) queue.Source { if mgr.mode == ModeSmokeTest { - log.Logf(0, "smoke test succeeded, shutting down...") - close(vm.Shutdown) - time.Sleep(10 * time.Second) - os.Exit(0) + mgr.exit("smoke test") } mgr.mu.Lock() @@ -1517,7 +1543,10 @@ func (mgr *Manager) fuzzerLoop(fuzzer *fuzzer.Fuzzer) { } // Update the state machine. - if fuzzer.StatCandidates.Val() == 0 { + if fuzzer.CandidateTriageFinished() { + if mgr.mode == ModeCorpusTriage { + mgr.exit("corpus triage") + } mgr.mu.Lock() if mgr.phase == phaseLoadedCorpus { if mgr.enabledFeatures&flatrpc.FeatureLeak != 0 { diff --git a/tools/syz-testbed/instance.go b/tools/syz-testbed/instance.go index 2ea3161e3..5ec278944 100644 --- a/tools/syz-testbed/instance.go +++ b/tools/syz-testbed/instance.go @@ -118,9 +118,10 @@ func (inst *SyzManagerInstance) Run() error { select { case err := <-ret: - // Syz-managers are not supposed to stop themselves under normal circumstances. - // If one of them did stop, there must have been a very good reason to do so. - return fmt.Errorf("[%s] stopped: %w", inst.Name, err) + if err != nil { + return fmt.Errorf("[%s] stopped: %w", inst.Name, err) + } + return nil case <-time.After(inst.RunTime): inst.Stop() <-ret @@ -131,6 +132,7 @@ func (inst *SyzManagerInstance) Run() error { type SyzkallerInfo struct { Workdir string CfgFile string + Mode string BenchFile string } @@ -162,7 +164,8 @@ func SetupSyzkallerInstance(mgrName, folder string, checkout *Checkout) (*Syzkal }, nil } -func (t *SyzManagerTarget) newSyzManagerInstance(slotName, uniqName string, checkout *Checkout) (Instance, error) { +func (t *SyzManagerTarget) newSyzManagerInstance(slotName, uniqName, mode string, checkout *Checkout) ( + Instance, error) { folder := filepath.Join(checkout.Path, fmt.Sprintf("run-%s", uniqName)) common, err := SetupSyzkallerInstance(slotName, folder, checkout) if err != nil { @@ -178,11 +181,15 @@ func (t *SyzManagerTarget) newSyzManagerInstance(slotName, uniqName string, chec } return &SyzManagerInstance{ InstanceCommon: InstanceCommon{ - Name: uniqName, - LogFile: filepath.Join(folder, "log.txt"), - ExecCommand: filepath.Join(checkout.Path, "bin", "syz-manager"), - ExecCommandArgs: []string{"-config", common.CfgFile, "-bench", common.BenchFile}, - stopChannel: make(chan bool, 1), + Name: uniqName, + LogFile: filepath.Join(folder, "log.txt"), + ExecCommand: filepath.Join(checkout.Path, "bin", "syz-manager"), + ExecCommandArgs: []string{ + "-config", common.CfgFile, + "-mode", mode, + "-bench", common.BenchFile, + }, + stopChannel: make(chan bool, 1), }, SyzkallerInfo: *common, RunTime: t.config.RunTime.Duration, diff --git a/tools/syz-testbed/targets.go b/tools/syz-testbed/targets.go index ef0b189a4..6ba08e504 100644 --- a/tools/syz-testbed/targets.go +++ b/tools/syz-testbed/targets.go @@ -113,7 +113,7 @@ func (t *SyzManagerTarget) NewJob(slotName string, checkouts []*Checkout) (*Chec t.nextInstanceID++ t.mu.Unlock() uniqName := fmt.Sprintf("%s-%d", checkout.Name, instanceID) - instance, err := t.newSyzManagerInstance(slotName, uniqName, checkout) + instance, err := t.newSyzManagerInstance(slotName, uniqName, t.config.ManagerMode, checkout) if err != nil { return nil, nil, err } diff --git a/tools/syz-testbed/testbed.go b/tools/syz-testbed/testbed.go index c07ac3bc0..52e63b4be 100644 --- a/tools/syz-testbed/testbed.go +++ b/tools/syz-testbed/testbed.go @@ -40,6 +40,7 @@ type TestbedConfig struct { Workdir string `json:"workdir"` // instances will be checked out there ReproConfig ReproTestConfig `json:"repro_config"` // syz-repro benchmarking config ManagerConfig json.RawMessage `json:"manager_config"` // base manager config + ManagerMode string `json:"manager_mode"` // manager mode flag Checkouts []CheckoutConfig `json:"checkouts"` } @@ -81,6 +82,7 @@ func main() { ReproConfig: ReproTestConfig{ CrashesPerBug: 1, }, + ManagerMode: "fuzzing", } err := config.LoadFile(*flagConfig, &cfg) if err != nil { |
