From 33d1aba90b07c4319e1617be24f6f6dfd1b71d5e Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Tue, 19 Apr 2022 10:16:51 +0000 Subject: vm: support variable output buffer size Also update syz-crush to save RawOutput instead of output from the Report. --- pkg/instance/execprog.go | 9 ++++-- tools/syz-crush/crush.go | 11 +++---- vm/vm.go | 78 +++++++++++++++++++++++++++++++----------------- vm/vm_test.go | 2 +- 4 files changed, 63 insertions(+), 37 deletions(-) diff --git a/pkg/instance/execprog.go b/pkg/instance/execprog.go index 7610298a6..1d775c22c 100644 --- a/pkg/instance/execprog.go +++ b/pkg/instance/execprog.go @@ -22,6 +22,7 @@ type OptionalConfig struct { ExitCondition vm.ExitCondition Logf ExecutorLogger OldFlagsCompatMode bool + BeforeContextLen int } type ExecProgInstance struct { @@ -34,7 +35,7 @@ type ExecProgInstance struct { } type RunResult struct { - Report *report.Report + vm.ExecutionResult } func SetupExecProg(vmInst *vm.Instance, mgrCfg *mgrconfig.Config, reporter *report.Reporter, @@ -90,8 +91,10 @@ func (inst *ExecProgInstance) runCommand(command string, duration time.Duration) if err != nil { return nil, fmt.Errorf("failed to run command in VM: %v", err) } - result := &RunResult{} - result.Report = inst.VMInstance.MonitorExecution(outc, errc, inst.reporter, inst.ExitCondition) + result := &RunResult{ + ExecutionResult: *inst.VMInstance.MonitorExecutionRaw(outc, errc, + inst.reporter, inst.ExitCondition, inst.BeforeContextLen), + } if result.Report == nil { inst.Logf(2, "program did not crash") } else { diff --git a/tools/syz-crush/crush.go b/tools/syz-crush/crush.go index ac077befd..36424c61a 100644 --- a/tools/syz-crush/crush.go +++ b/tools/syz-crush/crush.go @@ -83,7 +83,7 @@ func main() { log.Printf("reproducing from log file: %v", reproduceMe) } log.Printf("booting %v test machines...", vmPool.Count()) - runDone := make(chan *report.Report) + runDone := make(chan *instance.RunResult) var shutdown, stoppedWorkers uint32 for i := 0; i < vmPool.Count(); i++ { @@ -124,7 +124,8 @@ func main() { log.Printf("all done. reproduced %v crashes. reproduce rate %.2f%%", crashes, float64(crashes)/float64(count)*100.0) } -func storeCrash(cfg *mgrconfig.Config, rep *report.Report) { +func storeCrash(cfg *mgrconfig.Config, res *instance.RunResult) { + rep := res.Report id := hash.String([]byte(rep.Title)) dir := filepath.Join(filepath.Dir(flag.Args()[0]), "crashes", id) osutil.MkdirAll(dir) @@ -137,7 +138,7 @@ func storeCrash(cfg *mgrconfig.Config, rep *report.Report) { if err := osutil.WriteFile(filepath.Join(dir, "description"), []byte(rep.Title+"\n")); err != nil { log.Printf("failed to write crash description: %v", err) } - if err := osutil.WriteFile(filepath.Join(dir, fmt.Sprintf("log%v", index)), rep.Output); err != nil { + if err := osutil.WriteFile(filepath.Join(dir, fmt.Sprintf("log%v", index)), res.RawOutput); err != nil { log.Printf("failed to write crash log: %v", err) } if err := osutil.WriteFile(filepath.Join(dir, fmt.Sprintf("tag%v", index)), []byte(cfg.Tag)); err != nil { @@ -154,7 +155,7 @@ func storeCrash(cfg *mgrconfig.Config, rep *report.Report) { } func runInstance(cfg *mgrconfig.Config, reporter *report.Reporter, - vmPool *vm.Pool, index int, timeout time.Duration, runType FileType) *report.Report { + vmPool *vm.Pool, index int, timeout time.Duration, runType FileType) *instance.RunResult { log.Printf("vm-%v: starting", index) optArgs := &instance.OptionalConfig{ ExitCondition: vm.ExitTimeout, @@ -186,7 +187,7 @@ func runInstance(cfg *mgrconfig.Config, reporter *report.Reporter, } if res.Report != nil { log.Printf("vm-%v: crash: %v", index, res.Report.Title) - return res.Report + return res } log.Printf("vm-%v: running long enough, stopping", index) return nil diff --git a/vm/vm.go b/vm/vm.go index 518319521..61020b6ed 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -181,19 +181,51 @@ const ( // Returns a non-symbolized crash report, or nil if no error happens. func (inst *Instance) MonitorExecution(outc <-chan []byte, errc <-chan error, reporter *report.Reporter, exit ExitCondition) (rep *report.Report) { + return inst.MonitorExecutionRaw(outc, errc, reporter, exit, 0).Report +} + +type ExecutionResult struct { + Report *report.Report + RawOutput []byte +} + +func (inst *Instance) MonitorExecutionRaw(outc <-chan []byte, errc <-chan error, + reporter *report.Reporter, exit ExitCondition, beforeContextSize int) (res *ExecutionResult) { + if beforeContextSize == 0 { + beforeContextSize = beforeContextDefault + } mon := &monitor{ - inst: inst, - outc: outc, - errc: errc, - reporter: reporter, - exit: exit, + inst: inst, + outc: outc, + errc: errc, + reporter: reporter, + beforeContext: beforeContextSize, + exit: exit, } - lastExecuteTime := time.Now() - ticker := time.NewTicker(tickerPeriod * inst.timeouts.Scale) + return &ExecutionResult{ + Report: mon.monitorExecution(), + RawOutput: mon.output, + } +} + +type monitor struct { + inst *Instance + outc <-chan []byte + errc <-chan error + reporter *report.Reporter + exit ExitCondition + output []byte + beforeContext int + matchPos int +} + +func (mon *monitor) monitorExecution() *report.Report { + ticker := time.NewTicker(tickerPeriod * mon.inst.timeouts.Scale) defer ticker.Stop() + lastExecuteTime := time.Now() for { select { - case err := <-errc: + case err := <-mon.errc: switch err { case nil: // The program has exited without errors, @@ -217,9 +249,9 @@ func (inst *Instance) MonitorExecution(outc <-chan []byte, errc <-chan error, } return mon.extractError(crash) } - case out, ok := <-outc: + case out, ok := <-mon.outc: if !ok { - outc = nil + mon.outc = nil continue } lastPos := len(mon.output) @@ -228,12 +260,12 @@ func (inst *Instance) MonitorExecution(outc <-chan []byte, errc <-chan error, bytes.Contains(mon.output[lastPos:], executingProgram2) { lastExecuteTime = time.Now() } - if reporter.ContainsCrash(mon.output[mon.matchPos:]) { + if mon.reporter.ContainsCrash(mon.output[mon.matchPos:]) { return mon.extractError("unknown error") } - if len(mon.output) > 2*beforeContext { - copy(mon.output, mon.output[len(mon.output)-beforeContext:]) - mon.output = mon.output[:beforeContext] + if len(mon.output) > 2*mon.beforeContext { + copy(mon.output, mon.output[len(mon.output)-mon.beforeContext:]) + mon.output = mon.output[:mon.beforeContext] } // Find the starting position for crash matching on the next iteration. // We step back from the end of output by maxErrorLength to handle the case @@ -254,7 +286,7 @@ func (inst *Instance) MonitorExecution(outc <-chan []byte, errc <-chan error, case <-ticker.C: // Detect both "no output whatsoever" and "kernel episodically prints // something to console, but fuzzer is not actually executing programs". - if time.Since(lastExecuteTime) > inst.timeouts.NoOutput { + if time.Since(lastExecuteTime) > mon.inst.timeouts.NoOutput { return mon.extractError(noOutputCrash) } case <-Shutdown: @@ -263,16 +295,6 @@ func (inst *Instance) MonitorExecution(outc <-chan []byte, errc <-chan error, } } -type monitor struct { - inst *Instance - outc <-chan []byte - errc <-chan error - reporter *report.Reporter - exit ExitCondition - output []byte - matchPos int -} - func (mon *monitor) extractError(defaultError string) *report.Report { diagOutput, diagWait := []byte{}, false if defaultError != "" { @@ -316,7 +338,7 @@ func (mon *monitor) createReport(defaultError string) *report.Report { Suppressed: report.IsSuppressed(mon.reporter, mon.output), } } - start := rep.StartPos - beforeContext + start := rep.StartPos - mon.beforeContext if start < 0 { start = 0 } @@ -362,8 +384,8 @@ var ( executingProgram1 = []byte("executing program") // syz-fuzzer, syz-runner output executingProgram2 = []byte("executed programs:") // syz-execprog output - beforeContext = 1024 << 10 - afterContext = 128 << 10 + beforeContextDefault = 1024 << 10 + afterContext = 128 << 10 tickerPeriod = 10 * time.Second waitForOutputTimeout = 10 * time.Second diff --git a/vm/vm_test.go b/vm/vm_test.go index 83c3f51eb..2bcbbc1db 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -69,7 +69,7 @@ func (inst *testInstance) Close() { } func init() { - beforeContext = maxErrorLength + 100 + beforeContextDefault = maxErrorLength + 100 tickerPeriod = 1 * time.Second waitForOutputTimeout = 3 * time.Second -- cgit mrf-deployment