diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2024-04-11 15:06:11 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2024-04-11 14:27:17 +0000 |
| commit | 3f7932d24f9b230ac0e3592093a15a5a8c0a3770 (patch) | |
| tree | 904de8641bddcba7d52263dfb361cdba5faeaa03 | |
| parent | da2de8407550b81caeab72cf2fe645d1705f409e (diff) | |
vm: combine Run and MonitorExecution
All callers of Run always call MonitorExecution right after it.
Combine these 2 methods. This allows to hide some implementation
details and simplify users of vm package.
| -rw-r--r-- | pkg/build/netbsd.go | 3 | ||||
| -rw-r--r-- | pkg/instance/execprog.go | 23 | ||||
| -rw-r--r-- | pkg/instance/instance.go | 8 | ||||
| -rw-r--r-- | pkg/repro/strace.go | 2 | ||||
| -rw-r--r-- | syz-manager/manager.go | 16 | ||||
| -rw-r--r-- | syz-verifier/verifier.go | 5 | ||||
| -rw-r--r-- | tools/syz-crush/crush.go | 2 | ||||
| -rw-r--r-- | tools/syz-runtest/runtest.go | 3 | ||||
| -rw-r--r-- | vm/vm.go | 100 | ||||
| -rw-r--r-- | vm/vm_test.go | 9 |
10 files changed, 82 insertions, 89 deletions
diff --git a/pkg/build/netbsd.go b/pkg/build/netbsd.go index e4390872a..962b6e82c 100644 --- a/pkg/build/netbsd.go +++ b/pkg/build/netbsd.go @@ -155,12 +155,11 @@ func (ctx netbsd) copyKernelToDisk(targetArch, vmType, outputDir, kernel string) } commands = append(commands, "mknod /dev/vhci c 355 0") commands = append(commands, "sync") // Run sync so that the copied image is stored properly. - outc, errc, err := inst.Run(time.Minute, nil, strings.Join(commands, ";")) + _, rep, err := inst.Run(time.Minute, reporter, strings.Join(commands, ";")) if err != nil { return fmt.Errorf("error syncing the instance %w", err) } // Make sure that the command has executed properly. - rep := inst.MonitorExecution(outc, errc, reporter, vm.ExitNormal) if rep != nil { return fmt.Errorf("error executing sync: %v", rep.Title) } diff --git a/pkg/instance/execprog.go b/pkg/instance/execprog.go index 9e97dcd0c..acade5e8e 100644 --- a/pkg/instance/execprog.go +++ b/pkg/instance/execprog.go @@ -37,7 +37,8 @@ type ExecProgInstance struct { } type RunResult struct { - vm.ExecutionResult + Output []byte + Report *report.Report } func SetupExecProg(vmInst *vm.Instance, mgrCfg *mgrconfig.Config, reporter *report.Reporter, @@ -107,25 +108,23 @@ func (inst *ExecProgInstance) runCommand(command string, duration time.Duration) command = inst.StraceBin + filterCalls + ` -s 100 -x -f ` + command prefixOutput = []byte(fmt.Sprintf("%s\n\n<...>\n", command)) } - outc, errc, err := inst.VMInstance.Run(duration, nil, command) + opts := []any{inst.ExitCondition} + if inst.BeforeContextLen != 0 { + opts = append(opts, vm.OutputSize(inst.BeforeContextLen)) + } + output, rep, err := inst.VMInstance.Run(duration, inst.reporter, command, opts...) if err != nil { return nil, fmt.Errorf("failed to run command in VM: %w", err) } - result := &RunResult{ - ExecutionResult: *inst.VMInstance.MonitorExecutionRaw(outc, errc, - inst.reporter, inst.ExitCondition, inst.BeforeContextLen), - } - if len(prefixOutput) > 0 { - result.RawOutput = append(prefixOutput, result.RawOutput...) - } - if result.Report == nil { + if rep == nil { inst.Logf(2, "program did not crash") } else { - if err := inst.reporter.Symbolize(result.Report); err != nil { + if err := inst.reporter.Symbolize(rep); err != nil { inst.Logf(0, "failed to symbolize report: %v", err) } - inst.Logf(2, "program crashed: %v", result.Report.Title) + inst.Logf(2, "program crashed: %v", rep.Title) } + result := &RunResult{append(prefixOutput, output...), rep} return result, nil } diff --git a/pkg/instance/instance.go b/pkg/instance/instance.go index 1e4787c94..4c61b1d24 100644 --- a/pkg/instance/instance.go +++ b/pkg/instance/instance.go @@ -388,11 +388,11 @@ func (inst *inst) testInstance() error { cmd := OldFuzzerCmd(fuzzerBin, executorBin, targets.TestOS, inst.cfg.TargetOS, inst.cfg.TargetArch, fwdAddr, inst.cfg.Sandbox, inst.cfg.SandboxArg, 0, inst.cfg.Cover, true, inst.optionalFlags, inst.cfg.Timeouts.Slowdown) - outc, errc, err := inst.vm.Run(10*time.Minute*inst.cfg.Timeouts.Scale, nil, cmd) + timeout := 10 * time.Minute * inst.cfg.Timeouts.Scale + _, rep, err := inst.vm.Run(timeout, inst.reporter, cmd) if err != nil { return fmt.Errorf("failed to run binary in VM: %w", err) } - rep := inst.vm.MonitorExecution(outc, errc, inst.reporter, vm.ExitNormal) if rep != nil { if err := inst.reporter.Symbolize(rep); err != nil { // TODO(dvyukov): send such errors to dashboard. @@ -424,9 +424,9 @@ func (inst *inst) testRepro() ([]byte, error) { return nil, err } if res != nil && res.Report != nil { - return res.RawOutput, &CrashError{Report: res.Report} + return res.Output, &CrashError{Report: res.Report} } - return res.RawOutput, nil + return res.Output, nil } out := []byte{} if len(inst.reproSyz) > 0 { diff --git a/pkg/repro/strace.go b/pkg/repro/strace.go index 87d3cf64e..ef4f7c934 100644 --- a/pkg/repro/strace.go +++ b/pkg/repro/strace.go @@ -51,7 +51,7 @@ func RunStrace(result *Result, cfg *mgrconfig.Config, reporter *report.Reporter, } return &StraceResult{ Report: runRes.Report, - Output: runRes.RawOutput, + Output: runRes.Output, } } diff --git a/syz-manager/manager.go b/syz-manager/manager.go index 58744d35a..e3c7e4fb2 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -827,23 +827,19 @@ func (mgr *Manager) runInstanceInner(index int, instanceName string) (*report.Re }, } cmd := instance.FuzzerCmd(args) - outc, errc, err := inst.Run(mgr.cfg.Timeouts.VMRunningTime, mgr.vmStop, cmd) + _, rep, err := inst.Run(mgr.cfg.Timeouts.VMRunningTime, mgr.reporter, cmd, vm.ExitTimeout, vm.StopChan(mgr.vmStop)) if err != nil { return nil, nil, fmt.Errorf("failed to run fuzzer: %w", err) } - - var vmInfo []byte - rep := inst.MonitorExecution(outc, errc, mgr.reporter, vm.ExitTimeout) if rep == nil { // This is the only "OK" outcome. log.Logf(0, "%s: running for %v, restarting", instanceName, time.Since(start)) - } else { - vmInfo, err = inst.Info() - if err != nil { - vmInfo = []byte(fmt.Sprintf("error getting VM info: %v\n", err)) - } + return nil, nil, nil + } + vmInfo, err := inst.Info() + if err != nil { + vmInfo = []byte(fmt.Sprintf("error getting VM info: %v\n", err)) } - return rep, vmInfo, nil } diff --git a/syz-verifier/verifier.go b/syz-verifier/verifier.go index d173b81bd..295a9da93 100644 --- a/syz-verifier/verifier.go +++ b/syz-verifier/verifier.go @@ -266,13 +266,10 @@ func (vrf *Verifier) createAndManageInstance(pi *poolInfo, poolID, vmID int) { } cmd := instance.RunnerCmd(runnerBin, fwdAddr, vrf.target.OS, vrf.target.Arch, poolID, 0, false, vrf.newEnv) - outc, errc, err := inst.Run(pi.cfg.Timeouts.VMRunningTime, vrf.vmStop, cmd) + _, _, err = inst.Run(pi.cfg.Timeouts.VMRunningTime, pi.Reporter, cmd, vm.ExitTimeout, vm.StopChan(vrf.vmStop)) if err != nil { log.Fatalf("failed to start runner: %v", err) } - - inst.MonitorExecution(outc, errc, pi.Reporter, vm.ExitTimeout) - log.Logf(0, "reboot the VM in pool %d", poolID) } diff --git a/tools/syz-crush/crush.go b/tools/syz-crush/crush.go index 0ee550e79..f4e778a00 100644 --- a/tools/syz-crush/crush.go +++ b/tools/syz-crush/crush.go @@ -143,7 +143,7 @@ func storeCrash(cfg *mgrconfig.Config, res *instance.RunResult) { 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)), res.RawOutput); err != nil { + if err := osutil.WriteFile(filepath.Join(dir, fmt.Sprintf("log%v", index)), res.Output); 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 { diff --git a/tools/syz-runtest/runtest.go b/tools/syz-runtest/runtest.go index e90c5d70b..b61dfdad8 100644 --- a/tools/syz-runtest/runtest.go +++ b/tools/syz-runtest/runtest.go @@ -194,11 +194,10 @@ func (mgr *Manager) boot(name string, index int) (*report.Report, error) { }, } cmd := instance.FuzzerCmd(args) - outc, errc, err := inst.Run(time.Hour, mgr.vmStop, cmd) + _, rep, err := inst.Run(time.Hour, mgr.reporter, cmd, vm.StopChan(mgr.vmStop)) if err != nil { return nil, fmt.Errorf("failed to run fuzzer: %w", err) } - rep := inst.MonitorExecution(outc, errc, mgr.reporter, vm.ExitNormal) return rep, nil } @@ -185,9 +185,58 @@ func (inst *Instance) Forward(port int) (string, error) { return inst.impl.Forward(port) } -func (inst *Instance) Run(timeout time.Duration, stop <-chan bool, command string) ( - outc <-chan []byte, errc <-chan error, err error) { - return inst.impl.Run(timeout, stop, command) +type ExitCondition int + +const ( + // The program is allowed to exit after timeout. + ExitTimeout = ExitCondition(1 << iota) + // The program is allowed to exit with no errors. + ExitNormal + // The program is allowed to exit with errors. + ExitError +) + +type StopChan <-chan bool +type OutputSize int + +// Run runs cmd inside of the VM (think of ssh cmd) and monitors command execution +// and the kernel console output. It detects kernel oopses in output, lost connections, hangs, etc. +// Returns command+kernel output and a non-symbolized crash report (nil if no error happens). +// Accepted options: +// - StopChan: stop channel can be used to prematurely stop the command +// - ExitCondition: says which exit modes should be considered as errors/OK +// - OutputSize: how much output to keep/return +func (inst *Instance) Run(timeout time.Duration, reporter *report.Reporter, command string, opts ...any) ( + []byte, *report.Report, error) { + exit := ExitNormal + var stop <-chan bool + outputSize := beforeContextDefault + for _, o := range opts { + switch opt := o.(type) { + case ExitCondition: + exit = opt + case StopChan: + stop = opt + case OutputSize: + outputSize = int(opt) + default: + panic(fmt.Sprintf("unknown option %#v", opt)) + } + } + outc, errc, err := inst.impl.Run(timeout, stop, command) + if err != nil { + return nil, nil, err + } + mon := &monitor{ + inst: inst, + outc: outc, + errc: errc, + reporter: reporter, + beforeContext: outputSize, + exit: exit, + } + rep := mon.monitorExecution() + return mon.output, rep, nil } func (inst *Instance) Info() ([]byte, error) { @@ -222,51 +271,6 @@ func (inst *Instance) Close() { inst.onClose() } -type ExitCondition int - -const ( - // The program is allowed to exit after timeout. - ExitTimeout = ExitCondition(1 << iota) - // The program is allowed to exit with no errors. - ExitNormal - // The program is allowed to exit with errors. - ExitError -) - -// 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. -// Exit says which exit modes should be considered as errors/OK. -// 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, - beforeContext: beforeContextSize, - exit: exit, - } - return &ExecutionResult{ - Report: mon.monitorExecution(), - RawOutput: mon.output, - } -} - type monitor struct { inst *Instance outc <-chan []byte diff --git a/vm/vm_test.go b/vm/vm_test.go index 62b93f4c1..a83adbe26 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -366,10 +366,6 @@ func testMonitorExecution(t *testing.T, test *Test) { t.Fatal(err) } defer inst.Close() - outc, errc, err := inst.Run(time.Second, nil, "") - if err != nil { - t.Fatal(err) - } testInst := inst.impl.(*testInstance) testInst.diagnoseBug = test.DiagnoseBug testInst.diagnoseNoWait = test.DiagnoseNoWait @@ -378,7 +374,10 @@ func testMonitorExecution(t *testing.T, test *Test) { test.Body(testInst.outc, testInst.errc) done <- true }() - rep := inst.MonitorExecution(outc, errc, reporter, test.Exit) + _, rep, err := inst.Run(time.Second, reporter, "", test.Exit) + if err != nil { + t.Fatal(err) + } <-done if test.Report != nil && rep == nil { t.Fatalf("got no report") |
