aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-11-21 19:02:35 +0100
committerDmitry Vyukov <dvyukov@google.com>2017-11-21 19:02:35 +0100
commitad0af9fff5f7ffbd9597e3ffb956fae3f4292b89 (patch)
tree79599e37c513c10cfe6c9f630a26e767f29a5e3d
parentd4d14b030efd569a3562e5cddd751ab56afda107 (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.go10
-rw-r--r--syz-ci/jobs.go15
-rw-r--r--syz-manager/manager.go14
-rw-r--r--tools/syz-crush/crush.go10
-rw-r--r--vm/vm.go47
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
diff --git a/vm/vm.go b/vm/vm.go
index b4933576f..24a5d55ae 100644
--- a/vm/vm.go
+++ b/vm/vm.go
@@ -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
}
}
}