From cbd0445ec3b0b184db66966d8a47e6b37d13692e Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Wed, 25 Nov 2020 09:17:50 +0100 Subject: all: make timeouts configurable Add sys/targets.Timeouts struct that parametrizes timeouts throughout the system. The struct allows to control syscall/program/no output timeouts for OS/arch/VM/etc. See comment on the struct for more details. --- pkg/instance/instance.go | 97 ++++++++++++++++++++++++++++--------------- pkg/instance/instance_test.go | 11 +++-- 2 files changed, 71 insertions(+), 37 deletions(-) (limited to 'pkg/instance') diff --git a/pkg/instance/instance.go b/pkg/instance/instance.go index 861d51e72..52881b353 100644 --- a/pkg/instance/instance.go +++ b/pkg/instance/instance.go @@ -21,6 +21,7 @@ import ( "github.com/google/syzkaller/pkg/mgrconfig" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/pkg/report" + "github.com/google/syzkaller/pkg/tool" "github.com/google/syzkaller/pkg/vcs" "github.com/google/syzkaller/sys/targets" "github.com/google/syzkaller/vm" @@ -33,7 +34,8 @@ type Env interface { } type env struct { - cfg *mgrconfig.Config + cfg *mgrconfig.Config + optionalFlags bool } func NewEnv(cfg *mgrconfig.Config) (Env, error) { @@ -53,20 +55,30 @@ func NewEnv(cfg *mgrconfig.Config) (Env, error) { return nil, fmt.Errorf("failed to create tmp dir: %v", err) } env := &env{ - cfg: cfg, + cfg: cfg, + optionalFlags: true, } return env, nil } -func (env *env) BuildSyzkaller(repo, commit string) error { +func (env *env) BuildSyzkaller(repoURL, commit string) error { cfg := env.cfg srcIndex := strings.LastIndex(cfg.Syzkaller, "/src/") if srcIndex == -1 { return fmt.Errorf("syzkaller path %q is not in GOPATH", cfg.Syzkaller) } - if _, err := vcs.NewSyzkallerRepo(cfg.Syzkaller).CheckoutCommit(repo, commit); err != nil { + repo := vcs.NewSyzkallerRepo(cfg.Syzkaller) + if _, err := repo.CheckoutCommit(repoURL, commit); err != nil { return fmt.Errorf("failed to checkout syzkaller repo: %v", err) } + // The following commit ("syz-fuzzer: support optional flags") adds support for optional flags + // in syz-fuzzer and syz-execprog. This is required to invoke older binaries with newer flags + // without failing due to unknown flags. + optionalFlags, err := repo.Contains("64435345f0891706a7e0c7885f5f7487581e6005") + if err != nil { + return fmt.Errorf("optional flags check failed: %v", err) + } + env.optionalFlags = optionalFlags cmd := osutil.Command(MakeBin, "target") cmd.Dir = cfg.Syzkaller cmd.Env = append([]string{}, os.Environ()...) @@ -210,13 +222,14 @@ func (env *env) Test(numVMs int, reproSyz, reproOpts, reproC []byte) ([]error, e res := make(chan error, numVMs) for i := 0; i < numVMs; i++ { inst := &inst{ - cfg: env.cfg, - reporter: reporter, - vmPool: vmPool, - vmIndex: i, - reproSyz: reproSyz, - reproOpts: reproOpts, - reproC: reproC, + cfg: env.cfg, + optionalFlags: env.optionalFlags, + reporter: reporter, + vmPool: vmPool, + vmIndex: i, + reproSyz: reproSyz, + reproOpts: reproOpts, + reproC: reproC, } go func() { res <- inst.test() }() } @@ -228,14 +241,15 @@ func (env *env) Test(numVMs int, reproSyz, reproOpts, reproC []byte) ([]error, e } type inst struct { - cfg *mgrconfig.Config - reporter report.Reporter - vmPool *vm.Pool - vm *vm.Instance - vmIndex int - reproSyz []byte - reproOpts []byte - reproC []byte + cfg *mgrconfig.Config + optionalFlags bool + reporter report.Reporter + vmPool *vm.Pool + vm *vm.Instance + vmIndex int + reproSyz []byte + reproOpts []byte + reproC []byte } func (inst *inst) test() error { @@ -318,8 +332,8 @@ func (inst *inst) testInstance() error { } cmd := OldFuzzerCmd(fuzzerBin, executorBin, targets.TestOS, inst.cfg.TargetOS, inst.cfg.TargetArch, fwdAddr, - inst.cfg.Sandbox, 0, inst.cfg.Cover, true) - outc, errc, err := inst.vm.Run(10*time.Minute, nil, cmd) + inst.cfg.Sandbox, 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) if err != nil { return fmt.Errorf("failed to run binary in VM: %v", err) } @@ -381,8 +395,9 @@ func (inst *inst) testRepro() error { opts.FaultCall = -1 } cmdSyz := ExecprogCmd(execprogBin, executorBin, cfg.TargetOS, cfg.TargetArch, opts.Sandbox, - true, true, true, cfg.Procs, opts.FaultCall, opts.FaultNth, vmProgFile) - if err := inst.testProgram(cmdSyz, 7*time.Minute); err != nil { + true, true, true, cfg.Procs, opts.FaultCall, opts.FaultNth, inst.optionalFlags, + cfg.Timeouts.Slowdown, vmProgFile) + if err := inst.testProgram(cmdSyz, cfg.Timeouts.NoOutputRunningTime); err != nil { return err } } @@ -398,9 +413,9 @@ func (inst *inst) testRepro() error { if err != nil { return &TestError{Title: fmt.Sprintf("failed to copy test binary to VM: %v", err)} } - // We should test for longer (e.g. 5 mins), but the problem is that - // reproducer does not print anything, so after 3 mins we detect "no output". - return inst.testProgram(vmBin, time.Minute) + // We should test for more than full "no output" timeout, but the problem is that C reproducers + // don't print anything, so we will get a false "no output" crash. + return inst.testProgram(vmBin, cfg.Timeouts.NoOutput/2) } func (inst *inst) testProgram(command string, testTime time.Duration) error { @@ -420,7 +435,7 @@ func (inst *inst) testProgram(command string, testTime time.Duration) error { } func FuzzerCmd(fuzzer, executor, name, OS, arch, fwdAddr, sandbox string, procs, verbosity int, - cover, debug, test, runtest bool) string { + cover, debug, test, runtest, optionalFlags bool, slowdown int) string { osArg := "" if targets.Get(OS, arch).HostFuzzer { // Only these OSes need the flag, because the rest assume host OS. @@ -436,18 +451,26 @@ func FuzzerCmd(fuzzer, executor, name, OS, arch, fwdAddr, sandbox string, procs, if verbosity != 0 { verbosityArg = fmt.Sprintf(" -vv=%v", verbosity) } + optionalArg := "" + if optionalFlags { + optionalArg = " " + tool.OptionalFlags([]tool.Flag{ + {Name: "slowdown", Value: fmt.Sprint(slowdown)}, + }) + } return fmt.Sprintf("%v -executor=%v -name=%v -arch=%v%v -manager=%v -sandbox=%v"+ - " -procs=%v -cover=%v -debug=%v -test=%v%v%v", + " -procs=%v -cover=%v -debug=%v -test=%v%v%v%v", fuzzer, executor, name, arch, osArg, fwdAddr, sandbox, - procs, cover, debug, test, runtestArg, verbosityArg) + procs, cover, debug, test, runtestArg, verbosityArg, optionalArg) } -func OldFuzzerCmd(fuzzer, executor, name, OS, arch, fwdAddr, sandbox string, procs int, cover, test bool) string { - return FuzzerCmd(fuzzer, executor, name, OS, arch, fwdAddr, sandbox, procs, 0, cover, false, test, false) +func OldFuzzerCmd(fuzzer, executor, name, OS, arch, fwdAddr, sandbox string, procs int, + cover, test, optionalFlags bool, slowdown int) string { + return FuzzerCmd(fuzzer, executor, name, OS, arch, fwdAddr, sandbox, procs, 0, cover, false, test, false, + optionalFlags, slowdown) } func ExecprogCmd(execprog, executor, OS, arch, sandbox string, repeat, threaded, collide bool, - procs, faultCall, faultNth int, progFile string) string { + procs, faultCall, faultNth int, optionalFlags bool, slowdown int, progFile string) string { repeatCount := 1 if repeat { repeatCount = 0 @@ -456,12 +479,18 @@ func ExecprogCmd(execprog, executor, OS, arch, sandbox string, repeat, threaded, if targets.Get(OS, arch).HostFuzzer { osArg = " -os=" + OS } + optionalArg := "" + if optionalFlags { + optionalArg = " " + tool.OptionalFlags([]tool.Flag{ + {Name: "slowdown", Value: fmt.Sprint(slowdown)}, + }) + } return fmt.Sprintf("%v -executor=%v -arch=%v%v -sandbox=%v"+ " -procs=%v -repeat=%v -threaded=%v -collide=%v -cover=0"+ - " -fault_call=%v -fault_nth=%v %v", + " -fault_call=%v -fault_nth=%v%v %v", execprog, executor, arch, osArg, sandbox, procs, repeatCount, threaded, collide, - faultCall, faultNth, progFile) + faultCall, faultNth, optionalArg, progFile) } var MakeBin = func() string { diff --git a/pkg/instance/instance_test.go b/pkg/instance/instance_test.go index e19f91faf..d95b6623e 100644 --- a/pkg/instance/instance_test.go +++ b/pkg/instance/instance_test.go @@ -10,6 +10,7 @@ import ( "strings" "testing" + "github.com/google/syzkaller/pkg/tool" "github.com/google/syzkaller/sys/targets" ) @@ -32,7 +33,7 @@ func TestFuzzerCmd(t *testing.T) { flagDebug := flags.Bool("debug", false, "debug output from executor") flagV := flags.Int("v", 0, "verbosity") cmdLine := OldFuzzerCmd(os.Args[0], "/myexecutor", "myname", targets.Linux, targets.I386, "localhost:1234", - "namespace", 3, true, true) + "namespace", 3, true, true, false, 0) args := strings.Split(cmdLine, " ")[1:] if err := flags.Parse(args); err != nil { t.Fatal(err) @@ -93,10 +94,11 @@ func TestExecprogCmd(t *testing.T) { flagCollide := flags.Bool("collide", true, "collide syscalls to provoke data races") flagSignal := flags.Bool("cover", false, "collect feedback signals (coverage)") flagSandbox := flags.String("sandbox", "none", "sandbox for fuzzing (none/setuid/namespace)") + flagSlowdown := flags.Int("slowdown", 1, "") cmdLine := ExecprogCmd(os.Args[0], "/myexecutor", targets.FreeBSD, targets.I386, - "namespace", true, false, false, 7, 2, 3, "myprog") + "namespace", true, false, false, 7, 2, 3, true, 10, "myprog") args := strings.Split(cmdLine, " ")[1:] - if err := flags.Parse(args); err != nil { + if err := tool.ParseFlags(flags, args); err != nil { t.Fatal(err) } if len(flags.Args()) != 1 || flags.Arg(0) != "myprog" { @@ -135,4 +137,7 @@ func TestExecprogCmd(t *testing.T) { if *flagCollide { t.Errorf("bad collide: %v, want: %v", *flagCollide, false) } + if *flagSlowdown != 10 { + t.Errorf("bad slowdown: %v, want: %v", *flagSlowdown, 10) + } } -- cgit mrf-deployment