diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2024-06-04 12:55:41 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2024-06-24 09:57:34 +0000 |
| commit | e16e2c9a4cb6937323e861b646792a6c4c978a3c (patch) | |
| tree | 6c513e98e5f465b44a98546d8984485d2c128582 /pkg/runtest/run_test.go | |
| parent | 90d67044dab68568e8f35bc14b68055dbd166eff (diff) | |
executor: add runner mode
Move all syz-fuzzer logic into syz-executor and remove syz-fuzzer.
Also restore syz-runtest functionality in the manager.
Update #4917 (sets most signal handlers to SIG_IGN)
Diffstat (limited to 'pkg/runtest/run_test.go')
| -rw-r--r-- | pkg/runtest/run_test.go | 175 |
1 files changed, 66 insertions, 109 deletions
diff --git a/pkg/runtest/run_test.go b/pkg/runtest/run_test.go index f04ad4b0f..92b6c2d77 100644 --- a/pkg/runtest/run_test.go +++ b/pkg/runtest/run_test.go @@ -8,41 +8,41 @@ import ( "context" "encoding/binary" "encoding/hex" - "errors" "flag" "fmt" - "os" "path/filepath" "runtime" "testing" - "time" "github.com/google/syzkaller/pkg/csource" "github.com/google/syzkaller/pkg/flatrpc" "github.com/google/syzkaller/pkg/fuzzer/queue" - "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/osutil" + "github.com/google/syzkaller/pkg/rpcserver" "github.com/google/syzkaller/pkg/testutil" + "github.com/google/syzkaller/pkg/vminfo" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/sys/targets" _ "github.com/google/syzkaller/sys/test/gen" // pull in the test target "github.com/stretchr/testify/assert" ) -// Can be used as: -// go test -v -run=Test/64_fork ./pkg/runtest -filter=nonfailing -// to select a subset of tests to run. -var flagFilter = flag.String("filter", "", "prefix to match test file names") - -var flagDebug = flag.Bool("debug", false, "include debug output from the executor") +var ( + // Can be used as: + // go test -v -run=Test/64_fork ./pkg/runtest -filter=nonfailing + // to select a subset of tests to run. + flagFilter = flag.String("filter", "", "prefix to match test file names") + flagDebug = flag.Bool("debug", false, "include debug output from the executor") + flagGDB = flag.Bool("gdb", false, "run executor under gdb") +) -func Test(t *testing.T) { +func TestUnit(t *testing.T) { switch runtime.GOOS { case targets.OpenBSD: t.Skipf("broken on %v", runtime.GOOS) } // Test only one target in short mode (each takes 5+ seconds to run). - shortTarget := targets.Get(targets.TestOS, targets.TestArch64) + shortTarget := targets.Get(targets.TestOS, targets.TestArch64Fork) for _, sysTarget := range targets.List[targets.TestOS] { if testing.Short() && sysTarget != shortTarget { continue @@ -83,27 +83,7 @@ func test(t *testing.T, sysTarget *targets.Target) { Verbose: true, Debug: *flagDebug, } - - executorCtx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - go func() { - for { - select { - case <-time.After(time.Millisecond): - case <-executorCtx.Done(): - return - } - req := ctx.Next() - if req == nil { - continue - } - if req.BinaryFile != "" { - req.Done(runTestC(req)) - } else { - req.Done(runTest(req, executor)) - } - } - }() + startRpcserver(t, target, executor, ctx) if err := ctx.Run(); err != nil { t.Fatal(err) } @@ -114,7 +94,7 @@ func TestCover(t *testing.T) { // We inject given blobs into KCOV buffer using syz_inject_cover, // and then test what we get back. t.Parallel() - for _, arch := range []string{targets.TestArch32, targets.TestArch64} { + for _, arch := range []string{targets.TestArch32, targets.TestArch64, targets.TestArch64Fork} { sysTarget := targets.Get(targets.TestOS, arch) t.Run(arch, func(t *testing.T) { if sysTarget.BrokenCompiler != "" { @@ -202,15 +182,15 @@ func testCover(t *testing.T, target *prog.Target) { Input: makeCover64(0xc0dec0dec0000033, 0xc0dec0dec0000022, 0xc0dec0dec0000011, 0xc0dec0dec0000011, 0xc0dec0dec0000022, 0xc0dec0dec0000033, 0xc0dec0dec0000011), Flags: flatrpc.ExecFlagCollectCover, - Cover: []uint64{0xc0dec0dec0000033, 0xc0dec0dec0000022, 0xc0dec0dec0000011, - 0xc0dec0dec0000011, 0xc0dec0dec0000022, 0xc0dec0dec0000033, 0xc0dec0dec0000011}, + Cover: []uint64{0xc0dec0dec0000011, 0xc0dec0dec0000033, 0xc0dec0dec0000022, + 0xc0dec0dec0000011, 0xc0dec0dec0000011, 0xc0dec0dec0000022, 0xc0dec0dec0000033}, }, { Is64Bit: 1, Input: makeCover64(0xc0dec0dec0000033, 0xc0dec0dec0000022, 0xc0dec0dec0000011, 0xc0dec0dec0000011, 0xc0dec0dec0000022, 0xc0dec0dec0000033, 0xc0dec0dec0000011), Flags: flatrpc.ExecFlagCollectCover | flatrpc.ExecFlagDedupCover, - Cover: []uint64{0xc0dec0dec0000011, 0xc0dec0dec0000022, 0xc0dec0dec0000033}, + Cover: []uint64{0xc0dec0dec0000033, 0xc0dec0dec0000022, 0xc0dec0dec0000011}, }, // Signal hashing. { @@ -218,8 +198,8 @@ func testCover(t *testing.T, target *prog.Target) { Input: makeCover64(0xc0dec0dec0011001, 0xc0dec0dec0022002, 0xc0dec0dec00330f0, 0xc0dec0dec0044b00, 0xc0dec0dec0011001, 0xc0dec0dec0022002), Flags: flatrpc.ExecFlagCollectSignal, - Signal: []uint64{0xc0dec0dec0011001, 0xc0dec0dec0022003, 0xc0dec0dec00330f2, - 0xc0dec0dec0044bf0, 0xc0dec0dec0011b01}, + Signal: []uint64{0xc0dec0dec0011b01, 0xc0dec0dec0044bf0, 0xc0dec0dec00330f2, + 0xc0dec0dec0022003, 0xc0dec0dec0011001}, }, // Invalid non-kernel PCs must fail test execution. { @@ -296,38 +276,49 @@ func testCover(t *testing.T, target *prog.Target) { // TODO: test max signal filtering and cover filter when syz-executor handles them. } executor := csource.BuildExecutor(t, target, "../../") + source := queue.Plain() + startRpcserver(t, target, executor, source) for i, test := range tests { test := test t.Run(fmt.Sprint(i), func(t *testing.T) { t.Parallel() - testCover1(t, target, executor, test) + testCover1(t, target, test, source) }) } } -func testCover1(t *testing.T, target *prog.Target, executor string, test CoverTest) { +func testCover1(t *testing.T, target *prog.Target, test CoverTest, source *queue.PlainQueue) { text := fmt.Sprintf(`syz_inject_cover(0x%v, &AUTO="%s", AUTO)`, test.Is64Bit, hex.EncodeToString(test.Input)) p, err := target.Deserialize([]byte(text), prog.Strict) if err != nil { t.Fatal(err) } req := &queue.Request{ - Prog: p, - Repeat: 1, + Prog: p, ExecOpts: flatrpc.ExecOpts{ - EnvFlags: flatrpc.ExecEnvSignal, + EnvFlags: flatrpc.ExecEnvSignal | flatrpc.ExecEnvSandboxNone, ExecFlags: test.Flags, }, } - res := runTest(req, executor) + if test.Flags&flatrpc.ExecFlagCollectSignal != 0 { + req.ReturnAllSignal = []int{0} + } + source.Submit(req) + res := req.Wait(context.Background()) if res.Err != nil || res.Info == nil || len(res.Info.Calls) != 1 || res.Info.Calls[0] == nil { - t.Fatalf("program execution failed: %v\n%s", res.Err, res.Output) + t.Fatalf("program execution failed: status=%v err=%v\n%s", res.Status, res.Err, res.Output) } call := res.Info.Calls[0] var comps [][2]uint64 for _, cmp := range call.Comps { comps = append(comps, [2]uint64{cmp.Op1, cmp.Op2}) } + if test.Cover == nil { + test.Cover = []uint64{} + } + if test.Signal == nil { + test.Signal = []uint64{} + } assert.Equal(t, test.Cover, call.Cover) assert.Equal(t, test.Signal, call.Signal) // Comparisons are reordered and order does not matter, so compare without order. @@ -361,72 +352,38 @@ func makeComps(comps ...Comparison) []byte { return w.Bytes() } -func runTest(req *queue.Request, executor string) *queue.Result { - cfg := new(ipc.Config) - sysTarget := targets.Get(req.Prog.Target.OS, req.Prog.Target.Arch) - cfg.UseForkServer = sysTarget.ExecutorUsesForkServer - cfg.Timeouts = sysTarget.Timeouts(1) - cfg.Executor = executor - env, err := ipc.MakeEnv(cfg, 0) - if err != nil { - return &queue.Result{ - Status: queue.ExecFailure, - Err: fmt.Errorf("failed to create ipc env: %w", err), - } +func startRpcserver(t *testing.T, target *prog.Target, executor string, source queue.Source) { + ctx, done := context.WithCancel(context.Background()) + cfg := &rpcserver.LocalConfig{ + Config: rpcserver.Config{ + Config: vminfo.Config{ + Target: target, + Debug: *flagDebug, + Features: flatrpc.FeatureSandboxNone, + Sandbox: flatrpc.ExecEnvSandboxNone, + }, + Procs: runtime.GOMAXPROCS(0), + Slowdown: 10, // to deflake slower tests + }, + Executor: executor, + Dir: t.TempDir(), + Context: ctx, + GDB: *flagGDB, } - defer env.Close() - ret := &queue.Result{Status: queue.Success} - for run := 0; run < req.Repeat; run++ { - if run%2 == 0 { - // Recreate Env every few iterations, this allows to cover more paths. - env.ForceRestart() - } - output, info, hanged, err := env.Exec(&req.ExecOpts, req.Prog) - ret.Output = append(ret.Output, output...) - if err != nil { - return &queue.Result{ - Status: queue.ExecFailure, - Err: fmt.Errorf("run %v: failed to run: %w", run, err), - } - } - if hanged { - return &queue.Result{ - Status: queue.ExecFailure, - Err: fmt.Errorf("run %v: hanged", run), - } - } - if run == 0 { - ret.Info = info - } else { - ret.Info.Calls = append(ret.Info.Calls, info.Calls...) - } + cfg.MachineChecked = func(features flatrpc.Feature, syscalls map[*prog.Syscall]bool) queue.Source { + cfg.Cover = true + return source } - return ret -} - -func runTestC(req *queue.Request) *queue.Result { - tmpDir, err := os.MkdirTemp("", "syz-runtest") - if err != nil { - return &queue.Result{ - Status: queue.ExecFailure, - Err: fmt.Errorf("failed to create temp dir: %w", err), + errc := make(chan error) + go func() { + errc <- rpcserver.RunLocal(cfg) + }() + t.Cleanup(func() { + done() + if err := <-errc; err != nil { + t.Fatal(err) } - } - defer os.RemoveAll(tmpDir) - cmd := osutil.Command(req.BinaryFile) - cmd.Dir = tmpDir - // Tell ASAN to not mess with our NONFAILING. - cmd.Env = append(append([]string{}, os.Environ()...), "ASAN_OPTIONS=handle_segv=0 allow_user_segv_handler=1") - res := &queue.Result{} - res.Output, res.Err = osutil.Run(20*time.Second, cmd) - var verr *osutil.VerboseError - if errors.As(res.Err, &verr) { - // The process can legitimately do something like exit_group(1). - // So we ignore the error and rely on the rest of the checks (e.g. syscall return values). - res.Err = nil - res.Output = verr.Output - } - return res + }) } func TestParsing(t *testing.T) { |
