diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2017-11-21 19:02:35 +0100 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2017-11-21 19:02:35 +0100 |
| commit | ad0af9fff5f7ffbd9597e3ffb956fae3f4292b89 (patch) | |
| tree | 79599e37c513c10cfe6c9f630a26e767f29a5e3d | |
| parent | d4d14b030efd569a3562e5cddd751ab56afda107 (diff) | |
vm: return Report from MonitorExecution
This allows callers to get access to Report.Corrupted.
Better than adding 6-th return value and will allow
to pipe other report properties if necessary.
| -rw-r--r-- | pkg/repro/repro.go | 10 | ||||
| -rw-r--r-- | syz-ci/jobs.go | 15 | ||||
| -rw-r--r-- | syz-manager/manager.go | 14 | ||||
| -rw-r--r-- | tools/syz-crush/crush.go | 10 | ||||
| -rw-r--r-- | vm/vm.go | 47 |
5 files changed, 56 insertions, 40 deletions
diff --git a/pkg/repro/repro.go b/pkg/repro/repro.go index 11f0cccaf..dd2bf769d 100644 --- a/pkg/repro/repro.go +++ b/pkg/repro/repro.go @@ -595,15 +595,15 @@ func (ctx *context) testImpl(inst *vm.Instance, command string, duration time.Du if err != nil { return false, fmt.Errorf("failed to run command in VM: %v", err) } - title, report, output, crashed, _ := vm.MonitorExecution(outc, errc, ctx.reporter) - if !crashed { + rep, output := vm.MonitorExecution(outc, errc, ctx.reporter, true) + if rep == nil { ctx.reproLog(2, "program did not crash") return false, nil } - ctx.title = title + ctx.title = rep.Title ctx.log = output - ctx.report = report - ctx.reproLog(2, "program crashed: %v", title) + ctx.report = rep.Report + ctx.reproLog(2, "program crashed: %v", rep.Title) return true, nil } diff --git a/syz-ci/jobs.go b/syz-ci/jobs.go index cccdc8241..84b9998d6 100644 --- a/syz-ci/jobs.go +++ b/syz-ci/jobs.go @@ -339,11 +339,12 @@ func (job *Job) testProgram(inst *vm.Instance, command string, reporter report.R if err != nil { return false, fmt.Errorf("failed to run binary in VM: %v", err) } - title, report, output, crashed, _ := vm.MonitorExecution(outc, errc, reporter) - if crashed { - job.resp.CrashTitle = title - job.resp.CrashReport = report - job.resp.CrashLog = output - } - return crashed, nil + rep, output := vm.MonitorExecution(outc, errc, reporter, true) + if rep == nil { + return false, nil + } + job.resp.CrashTitle = rep.Title + job.resp.CrashReport = rep.Report + job.resp.CrashLog = output + return true, nil } diff --git a/syz-manager/manager.go b/syz-manager/manager.go index 470be5281..5f79b9a65 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -544,21 +544,17 @@ func (mgr *Manager) runInstance(index int) (*Crash, error) { return nil, fmt.Errorf("failed to run fuzzer: %v", err) } - title, report, output, crashed, timedout := vm.MonitorExecution(outc, errc, mgr.getReporter()) - if timedout { + rep, output := vm.MonitorExecution(outc, errc, mgr.getReporter(), false) + if rep == nil { // This is the only "OK" outcome. - Logf(0, "vm-%v: running for %v, restarting (%v)", index, time.Since(start), title) + Logf(0, "vm-%v: running for %v, restarting", index, time.Since(start)) return nil, nil } - if !crashed { - // syz-fuzzer exited, but it should not. - title = "lost connection to test machine" - } cash := &Crash{ vmIndex: index, hub: false, - title: title, - report: report, + title: rep.Title, + report: rep.Report, log: output, } return cash, nil diff --git a/tools/syz-crush/crush.go b/tools/syz-crush/crush.go index b349e50bc..1d858790b 100644 --- a/tools/syz-crush/crush.go +++ b/tools/syz-crush/crush.go @@ -109,22 +109,18 @@ func runInstance(cfg *mgrconfig.Config, reporter report.Reporter, vmPool *vm.Poo } log.Logf(0, "vm-%v: crushing...", index) - title, _, output, crashed, timedout := vm.MonitorExecution(outc, errc, reporter) - if timedout { + rep, output := vm.MonitorExecution(outc, errc, reporter, false) + if rep == nil { // This is the only "OK" outcome. log.Logf(0, "vm-%v: running long enough, restarting", index) } else { - if !crashed { - // syz-execprog exited, but it should not. - title = "lost connection to test machine" - } f, err := ioutil.TempFile(".", "syz-crush") if err != nil { log.Logf(0, "failed to create temp file: %v", err) return } defer f.Close() - log.Logf(0, "vm-%v: crashed: %v, saving to %v", index, title, f.Name()) + log.Logf(0, "vm-%v: crashed: %v, saving to %v", index, rep.Title, f.Name()) f.Write(output) } return @@ -96,8 +96,14 @@ func (inst *Instance) Close() { os.RemoveAll(inst.workdir) } -func MonitorExecution(outc <-chan []byte, errc <-chan error, reporter report.Reporter) ( - title string, report, output []byte, crashed, timedout bool) { +// MonitorExecution monitors execution of a program running inside of a VM. +// It detects kernel oopses in output, lost connections, hangs, etc. +// outc/errc is what vm.Instance.Run returns, reporter parses kernel output for oopses. +// If canExit is false and the program exits, it is treated as an error. +// Returns crash report and raw output around the crash, or nil if no error happens. +func MonitorExecution(outc <-chan []byte, errc <-chan error, reporter report.Reporter, canExit bool) ( + rep *report.Report, output []byte) { + //title string, report, output []byte, crashed, timedout bool) { waitForOutput := func() { timer := time.NewTimer(10 * time.Second).C for { @@ -118,14 +124,23 @@ func MonitorExecution(outc <-chan []byte, errc <-chan error, reporter report.Rep beforeContext = 1024 << 10 afterContext = 128 << 10 ) - extractError := func(defaultError string) (string, []byte, []byte, bool, bool) { + extractError := func(defaultError string) (*report.Report, []byte) { // Give it some time to finish writing the error message. waitForOutput() if bytes.Contains(output, []byte("SYZ-FUZZER: PREEMPTED")) { - return "preempted", nil, nil, false, true + return nil, nil } if !reporter.ContainsCrash(output[matchPos:]) { - return defaultError, nil, output, defaultError != "", false + if defaultError == "" { + if canExit { + return nil, nil + } + defaultError = "lost connection to test machine" + } + rep := &report.Report{ + Title: defaultError, + } + return rep, output } rep := reporter.Parse(output[matchPos:]) if rep == nil { @@ -139,7 +154,7 @@ func MonitorExecution(outc <-chan []byte, errc <-chan error, reporter report.Rep if end > len(output) { end = len(output) } - return rep.Title, rep.Report, output[start:end], true, false + return rep, output[start:end] } lastExecuteTime := time.Now() @@ -159,7 +174,7 @@ func MonitorExecution(outc <-chan []byte, errc <-chan error, reporter report.Rep // but wait for kernel output in case there is some delayed oops. return extractError("") case TimeoutErr: - return err.Error(), nil, nil, false, true + return nil, nil default: // Note: connection lost can race with a kernel oops message. // In such case we want to return the kernel oops. @@ -167,10 +182,12 @@ func MonitorExecution(outc <-chan []byte, errc <-chan error, reporter report.Rep } case out := <-outc: output = append(output, out...) - if bytes.Index(output[matchPos:], []byte("executing program")) != -1 { // syz-fuzzer output + // syz-fuzzer output + if bytes.Index(output[matchPos:], []byte("executing program")) != -1 { lastExecuteTime = time.Now() } - if bytes.Index(output[matchPos:], []byte("executed programs:")) != -1 { // syz-execprog output + // syz-execprog output + if bytes.Index(output[matchPos:], []byte("executed programs:")) != -1 { lastExecuteTime = time.Now() } if reporter.ContainsCrash(output[matchPos:]) { @@ -189,13 +206,19 @@ func MonitorExecution(outc <-chan []byte, errc <-chan error, reporter report.Rep // We intentionally produce the same title as no output at all, // because frequently it's the same condition. if time.Since(lastExecuteTime) > 3*time.Minute { - return "no output from test machine", nil, output, true, false + rep := &report.Report{ + Title: "no output from test machine", + } + return rep, output } case <-ticker.C: tickerFired = true - return "no output from test machine", nil, output, true, false + rep := &report.Report{ + Title: "no output from test machine", + } + return rep, output case <-Shutdown: - return "", nil, nil, false, false + return nil, nil } } } |
