github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/runtest/run_test.go (about) 1 // Copyright 2018 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package runtest 5 6 import ( 7 "context" 8 "errors" 9 "flag" 10 "fmt" 11 "os" 12 "path/filepath" 13 "runtime" 14 "testing" 15 "time" 16 17 "github.com/google/syzkaller/pkg/csource" 18 "github.com/google/syzkaller/pkg/fuzzer/queue" 19 "github.com/google/syzkaller/pkg/ipc" 20 "github.com/google/syzkaller/pkg/osutil" 21 "github.com/google/syzkaller/pkg/testutil" 22 "github.com/google/syzkaller/prog" 23 "github.com/google/syzkaller/sys/targets" 24 _ "github.com/google/syzkaller/sys/test/gen" // pull in the test target 25 ) 26 27 // Can be used as: 28 // go test -v -run=Test/64_fork ./pkg/runtest -filter=nonfailing 29 // to select a subset of tests to run. 30 var flagFilter = flag.String("filter", "", "prefix to match test file names") 31 32 var flagDebug = flag.Bool("debug", false, "include debug output from the executor") 33 34 func Test(t *testing.T) { 35 switch runtime.GOOS { 36 case targets.OpenBSD: 37 t.Skipf("broken on %v", runtime.GOOS) 38 } 39 // Test only one target in short mode (each takes 5+ seconds to run). 40 shortTarget := targets.Get(targets.TestOS, targets.TestArch64) 41 for _, sysTarget := range targets.List[targets.TestOS] { 42 if testing.Short() && sysTarget != shortTarget { 43 continue 44 } 45 sysTarget1 := targets.Get(sysTarget.OS, sysTarget.Arch) 46 t.Run(sysTarget1.Arch, func(t *testing.T) { 47 t.Parallel() 48 test(t, sysTarget1) 49 }) 50 } 51 } 52 53 func test(t *testing.T, sysTarget *targets.Target) { 54 target, err := prog.GetTarget(sysTarget.OS, sysTarget.Arch) 55 if err != nil { 56 t.Fatal(err) 57 } 58 executor := csource.BuildExecutor(t, target, "../../", "-fsanitize-coverage=trace-pc") 59 calls := make(map[*prog.Syscall]bool) 60 for _, call := range target.Syscalls { 61 calls[call] = true 62 } 63 enabledCalls := map[string]map[*prog.Syscall]bool{ 64 "": calls, 65 "none": calls, 66 } 67 ctx := &Context{ 68 Dir: filepath.Join("..", "..", "sys", target.OS, targets.TestOS), 69 Target: target, 70 Tests: *flagFilter, 71 Features: 0, 72 EnabledCalls: enabledCalls, 73 LogFunc: func(text string) { 74 t.Helper() 75 t.Logf(text) 76 }, 77 Retries: 7, // empirical number that seem to reduce flakes to zero 78 Verbose: true, 79 Debug: *flagDebug, 80 } 81 82 executorCtx, cancel := context.WithCancel(context.Background()) 83 t.Cleanup(cancel) 84 go func() { 85 for { 86 select { 87 case <-time.After(time.Millisecond): 88 case <-executorCtx.Done(): 89 return 90 } 91 req := ctx.Next() 92 if req == nil { 93 continue 94 } 95 if req.BinaryFile != "" { 96 req.Done(runTestC(req)) 97 } else { 98 req.Done(runTest(req, executor)) 99 } 100 } 101 }() 102 if err := ctx.Run(); err != nil { 103 t.Fatal(err) 104 } 105 } 106 107 func runTest(req *queue.Request, executor string) *queue.Result { 108 cfg := new(ipc.Config) 109 sysTarget := targets.Get(req.Prog.Target.OS, req.Prog.Target.Arch) 110 cfg.UseShmem = sysTarget.ExecutorUsesShmem 111 cfg.UseForkServer = sysTarget.ExecutorUsesForkServer 112 cfg.Timeouts = sysTarget.Timeouts(1) 113 cfg.Executor = executor 114 env, err := ipc.MakeEnv(cfg, 0) 115 if err != nil { 116 return &queue.Result{ 117 Status: queue.ExecFailure, 118 Err: fmt.Errorf("failed to create ipc env: %w", err), 119 } 120 } 121 defer env.Close() 122 ret := &queue.Result{Status: queue.Success} 123 for run := 0; run < req.Repeat; run++ { 124 if run%2 == 0 { 125 // Recreate Env every few iterations, this allows to cover more paths. 126 env.ForceRestart() 127 } 128 output, info, hanged, err := env.Exec(&req.ExecOpts, req.Prog) 129 ret.Output = append(ret.Output, output...) 130 if err != nil { 131 return &queue.Result{ 132 Status: queue.ExecFailure, 133 Err: fmt.Errorf("run %v: failed to run: %w", run, err), 134 } 135 } 136 if hanged { 137 return &queue.Result{ 138 Status: queue.ExecFailure, 139 Err: fmt.Errorf("run %v: hanged", run), 140 } 141 } 142 if run == 0 { 143 ret.Info = info 144 } else { 145 ret.Info.Calls = append(ret.Info.Calls, info.Calls...) 146 } 147 } 148 return ret 149 } 150 151 func runTestC(req *queue.Request) *queue.Result { 152 tmpDir, err := os.MkdirTemp("", "syz-runtest") 153 if err != nil { 154 return &queue.Result{ 155 Status: queue.ExecFailure, 156 Err: fmt.Errorf("failed to create temp dir: %w", err), 157 } 158 } 159 defer os.RemoveAll(tmpDir) 160 cmd := osutil.Command(req.BinaryFile) 161 cmd.Dir = tmpDir 162 // Tell ASAN to not mess with our NONFAILING. 163 cmd.Env = append(append([]string{}, os.Environ()...), "ASAN_OPTIONS=handle_segv=0 allow_user_segv_handler=1") 164 res := &queue.Result{} 165 res.Output, res.Err = osutil.Run(20*time.Second, cmd) 166 var verr *osutil.VerboseError 167 if errors.As(res.Err, &verr) { 168 // The process can legitimately do something like exit_group(1). 169 // So we ignore the error and rely on the rest of the checks (e.g. syscall return values). 170 res.Err = nil 171 res.Output = verr.Output 172 } 173 return res 174 } 175 176 func TestParsing(t *testing.T) { 177 t.Parallel() 178 // Test only one target in race mode (we have gazillion of auto-generated Linux test). 179 raceTarget := targets.Get(targets.TestOS, targets.TestArch64) 180 for OS, arches := range targets.List { 181 if testutil.RaceEnabled && OS != raceTarget.OS { 182 continue 183 } 184 dir := filepath.Join("..", "..", "sys", OS, "test") 185 if !osutil.IsExist(dir) { 186 continue 187 } 188 files, err := progFileList(dir, "") 189 if err != nil { 190 t.Fatal(err) 191 } 192 for arch := range arches { 193 if testutil.RaceEnabled && arch != raceTarget.Arch { 194 continue 195 } 196 target, err := prog.GetTarget(OS, arch) 197 if err != nil { 198 t.Fatal(err) 199 } 200 sysTarget := targets.Get(target.OS, target.Arch) 201 t.Run(fmt.Sprintf("%v/%v", target.OS, target.Arch), func(t *testing.T) { 202 t.Parallel() 203 for _, file := range files { 204 p, requires, _, err := parseProg(target, dir, file) 205 if err != nil { 206 t.Errorf("failed to parse %v: %v", file, err) 207 } 208 if p == nil { 209 continue 210 } 211 if runtime.GOOS != sysTarget.BuildOS { 212 continue // we need at least preprocessor binary to generate sources 213 } 214 // syz_mount_image tests are very large and this test takes too long. 215 // syz-imagegen that generates does some of this testing (Deserialize/SerializeForExec). 216 if requires["manual"] { 217 continue 218 } 219 if _, err = csource.Write(p, csource.ExecutorOpts); err != nil { 220 t.Errorf("failed to generate C source for %v: %v", file, err) 221 } 222 } 223 }) 224 } 225 } 226 } 227 228 func TestRequires(t *testing.T) { 229 { 230 requires := parseRequires([]byte("# requires: manual arch=amd64")) 231 if !checkArch(requires, "amd64") { 232 t.Fatalf("amd64 does not pass check") 233 } 234 if checkArch(requires, "riscv64") { 235 t.Fatalf("riscv64 passes check") 236 } 237 } 238 { 239 requires := parseRequires([]byte("# requires: -arch=arm64 manual -arch=riscv64")) 240 if !checkArch(requires, "amd64") { 241 t.Fatalf("amd64 does not pass check") 242 } 243 if checkArch(requires, "riscv64") { 244 t.Fatalf("riscv64 passes check") 245 } 246 } 247 }