github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/vm/vm_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 vm 5 6 import ( 7 "bytes" 8 "fmt" 9 "testing" 10 "time" 11 12 "github.com/google/syzkaller/pkg/mgrconfig" 13 "github.com/google/syzkaller/pkg/report" 14 "github.com/google/syzkaller/sys/targets" 15 "github.com/google/syzkaller/vm/vmimpl" 16 ) 17 18 type testPool struct { 19 } 20 21 func (pool *testPool) Count() int { 22 return 1 23 } 24 25 func (pool *testPool) Create(workdir string, index int) (vmimpl.Instance, error) { 26 return &testInstance{ 27 outc: make(chan []byte, 10), 28 errc: make(chan error, 1), 29 }, nil 30 } 31 32 func (pool *testPool) Close() error { 33 return nil 34 } 35 36 type testInstance struct { 37 outc chan []byte 38 errc chan error 39 diagnoseBug bool 40 diagnoseNoWait bool 41 } 42 43 func (inst *testInstance) Copy(hostSrc string) (string, error) { 44 return "", nil 45 } 46 47 func (inst *testInstance) Forward(port int) (string, error) { 48 return "", nil 49 } 50 51 func (inst *testInstance) Run(timeout time.Duration, stop <-chan bool, command string) ( 52 outc <-chan []byte, errc <-chan error, err error) { 53 return inst.outc, inst.errc, nil 54 } 55 56 func (inst *testInstance) Diagnose(rep *report.Report) ([]byte, bool) { 57 var diag []byte 58 if inst.diagnoseBug { 59 diag = []byte("BUG: DIAGNOSE\n") 60 } else { 61 diag = []byte("DIAGNOSE\n") 62 } 63 64 if inst.diagnoseNoWait { 65 return diag, false 66 } 67 68 inst.outc <- diag 69 return nil, true 70 } 71 72 func (inst *testInstance) Close() { 73 } 74 75 func init() { 76 beforeContextDefault = maxErrorLength + 100 77 tickerPeriod = 1 * time.Second 78 waitForOutputTimeout = 3 * time.Second 79 80 ctor := func(env *vmimpl.Env) (vmimpl.Pool, error) { 81 return &testPool{}, nil 82 } 83 vmimpl.Register("test", ctor, false) 84 } 85 86 type Test struct { 87 Name string 88 Exit ExitCondition 89 DiagnoseBug bool // Diagnose produces output that is detected as kernel crash. 90 DiagnoseNoWait bool // Diagnose returns output directly rather than to console. 91 InjectOutput string 92 Body func(outc chan []byte, errc chan error) 93 Report *report.Report 94 } 95 96 // nolint: goconst // "DIAGNOSE\n", "BUG: bad\n" and "other output\n" 97 var tests = []*Test{ 98 { 99 Name: "program-exits-normally", 100 Exit: ExitNormal, 101 Body: func(outc chan []byte, errc chan error) { 102 time.Sleep(time.Second) 103 errc <- nil 104 }, 105 }, 106 { 107 Name: "program-exits-when-it-should-not", 108 Body: func(outc chan []byte, errc chan error) { 109 time.Sleep(time.Second) 110 errc <- nil 111 }, 112 Report: &report.Report{ 113 Title: lostConnectionCrash, 114 }, 115 }, 116 { 117 Name: "#875-diagnose-bugs", 118 Exit: ExitNormal, 119 DiagnoseBug: true, 120 Body: func(outc chan []byte, errc chan error) { 121 errc <- nil 122 }, 123 }, 124 { 125 Name: "#875-diagnose-bugs-2", 126 Body: func(outc chan []byte, errc chan error) { 127 errc <- nil 128 }, 129 Report: &report.Report{ 130 Title: lostConnectionCrash, 131 Output: []byte( 132 "DIAGNOSE\n", 133 ), 134 }, 135 }, 136 { 137 Name: "diagnose-no-wait", 138 Body: func(outc chan []byte, errc chan error) { 139 errc <- nil 140 }, 141 DiagnoseNoWait: true, 142 Report: &report.Report{ 143 Title: lostConnectionCrash, 144 Output: []byte( 145 "\n" + 146 "VM DIAGNOSIS:\n" + 147 "DIAGNOSE\n", 148 ), 149 }, 150 }, 151 { 152 Name: "diagnose-bug-no-wait", 153 Body: func(outc chan []byte, errc chan error) { 154 outc <- []byte("BUG: bad\n") 155 time.Sleep(time.Second) 156 outc <- []byte("other output\n") 157 }, 158 DiagnoseNoWait: true, 159 Report: &report.Report{ 160 Title: "BUG: bad", 161 Report: []byte( 162 "BUG: bad\n" + 163 "other output\n", 164 ), 165 Output: []byte( 166 "BUG: bad\n" + 167 "other output\n" + 168 "\n" + 169 "VM DIAGNOSIS:\n" + 170 "DIAGNOSE\n", 171 ), 172 }, 173 }, 174 { 175 Name: "kernel-crashes", 176 Body: func(outc chan []byte, errc chan error) { 177 outc <- []byte("BUG: bad\n") 178 time.Sleep(time.Second) 179 outc <- []byte("other output\n") 180 }, 181 Report: &report.Report{ 182 Title: "BUG: bad", 183 Report: []byte( 184 "BUG: bad\n" + 185 "DIAGNOSE\n" + 186 "other output\n", 187 ), 188 }, 189 }, 190 { 191 Name: "fuzzer-is-preempted", 192 Body: func(outc chan []byte, errc chan error) { 193 outc <- []byte("BUG: bad\n") 194 outc <- []byte(fuzzerPreemptedStr + "\n") 195 }, 196 }, 197 { 198 Name: "program-exits-but-kernel-crashes-afterwards", 199 Exit: ExitNormal, 200 Body: func(outc chan []byte, errc chan error) { 201 errc <- nil 202 time.Sleep(time.Second) 203 outc <- []byte("BUG: bad\n") 204 }, 205 Report: &report.Report{ 206 Title: "BUG: bad", 207 Report: []byte( 208 "BUG: bad\n" + 209 "DIAGNOSE\n", 210 ), 211 }, 212 }, 213 { 214 Name: "timeout", 215 Exit: ExitTimeout, 216 Body: func(outc chan []byte, errc chan error) { 217 errc <- vmimpl.ErrTimeout 218 }, 219 }, 220 { 221 Name: "bad-timeout", 222 Body: func(outc chan []byte, errc chan error) { 223 errc <- vmimpl.ErrTimeout 224 }, 225 Report: &report.Report{ 226 Title: timeoutCrash, 227 }, 228 }, 229 { 230 Name: "program-crashes", 231 Body: func(outc chan []byte, errc chan error) { 232 errc <- fmt.Errorf("error") 233 }, 234 Report: &report.Report{ 235 Title: lostConnectionCrash, 236 }, 237 }, 238 { 239 Name: "program-crashes-expected", 240 Exit: ExitError, 241 Body: func(outc chan []byte, errc chan error) { 242 errc <- fmt.Errorf("error") 243 }, 244 }, 245 { 246 Name: "no-output-1", 247 Body: func(outc chan []byte, errc chan error) { 248 }, 249 Report: &report.Report{ 250 Title: noOutputCrash, 251 }, 252 }, 253 { 254 Name: "no-output-2", 255 Body: func(outc chan []byte, errc chan error) { 256 for i := 0; i < 5; i++ { 257 time.Sleep(time.Second) 258 outc <- []byte("something\n") 259 } 260 }, 261 Report: &report.Report{ 262 Title: noOutputCrash, 263 }, 264 }, 265 { 266 Name: "no-no-output-1", 267 Exit: ExitNormal, 268 Body: func(outc chan []byte, errc chan error) { 269 for i := 0; i < 5; i++ { 270 time.Sleep(time.Second) 271 outc <- append(executingProgram1, '\n') 272 } 273 errc <- nil 274 }, 275 }, 276 { 277 Name: "no-no-output-2", 278 Exit: ExitNormal, 279 Body: func(outc chan []byte, errc chan error) { 280 for i := 0; i < 5; i++ { 281 time.Sleep(time.Second) 282 outc <- append(executingProgram2, '\n') 283 } 284 errc <- nil 285 }, 286 }, 287 { 288 Name: "outc-closed", 289 Exit: ExitTimeout, 290 Body: func(outc chan []byte, errc chan error) { 291 close(outc) 292 time.Sleep(time.Second) 293 errc <- vmimpl.ErrTimeout 294 }, 295 }, 296 { 297 Name: "lots-of-output", 298 Exit: ExitTimeout, 299 Body: func(outc chan []byte, errc chan error) { 300 for i := 0; i < 100; i++ { 301 outc <- []byte("something\n") 302 } 303 time.Sleep(time.Second) 304 errc <- vmimpl.ErrTimeout 305 }, 306 }, 307 { 308 Name: "split-line", 309 Exit: ExitNormal, 310 Body: func(outc chan []byte, errc chan error) { 311 // "ODEBUG:" lines should be ignored, however the matchPos logic 312 // used to trim the lines so that we could see just "BUG:" later 313 // and detect it as crash. 314 buf := new(bytes.Buffer) 315 for i := 0; i < 50; i++ { 316 buf.WriteString("[ 2886.597572] ODEBUG: Out of memory. ODEBUG disabled\n") 317 buf.Write(bytes.Repeat([]byte{'-'}, i)) 318 buf.WriteByte('\n') 319 } 320 output := buf.Bytes() 321 for i := range output { 322 outc <- output[i : i+1] 323 } 324 errc <- nil 325 }, 326 }, 327 { 328 Name: "inject-error", 329 Exit: ExitNormal, 330 InjectOutput: "BUG: foo\n", 331 Body: func(outc chan []byte, errc chan error) { 332 time.Sleep(time.Second) 333 errc <- nil 334 }, 335 Report: &report.Report{ 336 Title: "BUG: foo", 337 Report: []byte("BUG: foo\nDIAGNOSE\n"), 338 }, 339 }, 340 { 341 Name: "inject-output", 342 Exit: ExitNormal, 343 InjectOutput: "INJECTED\n", 344 Body: func(outc chan []byte, errc chan error) { 345 time.Sleep(time.Second) 346 outc <- []byte("BUG: foo\n") 347 }, 348 Report: &report.Report{ 349 Title: "BUG: foo", 350 Report: []byte("INJECTED\nBUG: foo\nDIAGNOSE\n"), 351 }, 352 }, 353 } 354 355 func TestMonitorExecution(t *testing.T) { 356 for _, test := range tests { 357 test := test 358 t.Run(test.Name, func(t *testing.T) { 359 t.Parallel() 360 testMonitorExecution(t, test) 361 }) 362 } 363 } 364 365 func testMonitorExecution(t *testing.T, test *Test) { 366 dir := t.TempDir() 367 cfg := &mgrconfig.Config{ 368 Derived: mgrconfig.Derived{ 369 TargetOS: targets.Linux, 370 TargetArch: targets.AMD64, 371 TargetVMArch: targets.AMD64, 372 Timeouts: targets.Timeouts{ 373 Scale: 1, 374 Slowdown: 1, 375 NoOutput: 5 * time.Second, 376 }, 377 SysTarget: targets.Get(targets.Linux, targets.AMD64), 378 }, 379 Workdir: dir, 380 Type: "test", 381 } 382 pool, err := Create(cfg, false) 383 if err != nil { 384 t.Fatal(err) 385 } 386 defer pool.Close() 387 reporter, err := report.NewReporter(cfg) 388 if err != nil { 389 t.Fatal(err) 390 } 391 inst, err := pool.Create(0) 392 if err != nil { 393 t.Fatal(err) 394 } 395 defer inst.Close() 396 testInst := inst.impl.(*testInstance) 397 testInst.diagnoseBug = test.DiagnoseBug 398 testInst.diagnoseNoWait = test.DiagnoseNoWait 399 done := make(chan bool) 400 go func() { 401 test.Body(testInst.outc, testInst.errc) 402 done <- true 403 }() 404 finishCalled := 0 405 finishCb := EarlyFinishCb(func() { finishCalled++ }) 406 opts := []any{test.Exit, finishCb} 407 if test.InjectOutput != "" { 408 c := make(chan []byte, 1) 409 c <- []byte(test.InjectOutput) 410 opts = append(opts, InjectOutput(c)) 411 } 412 _, rep, err := inst.Run(time.Second, reporter, "", opts...) 413 if err != nil { 414 t.Fatal(err) 415 } 416 <-done 417 if finishCalled != 1 { 418 t.Fatalf("finish callback is called %v times", finishCalled) 419 } 420 if test.Report != nil && rep == nil { 421 t.Fatalf("got no report") 422 } 423 if test.Report == nil && rep != nil { 424 t.Fatalf("got unexpected report: %v", rep.Title) 425 } 426 if test.Report == nil { 427 return 428 } 429 if test.Report.Title != rep.Title { 430 t.Fatalf("want title %q, got title %q", test.Report.Title, rep.Title) 431 } 432 if !bytes.Equal(test.Report.Report, rep.Report) { 433 t.Fatalf("want report:\n%s\n\ngot report:\n%s", test.Report.Report, rep.Report) 434 } 435 if test.Report.Output != nil && !bytes.Equal(test.Report.Output, rep.Output) { 436 t.Fatalf("want output:\n%s\n\ngot output:\n%s", test.Report.Output, rep.Output) 437 } 438 } 439 440 func TestVMType(t *testing.T) { 441 testCases := []struct { 442 in string 443 want string 444 }{ 445 {"gvisor", "gvisor"}, 446 {"proxyapp:android", "proxyapp"}, 447 } 448 449 for _, tc := range testCases { 450 if got := vmType(tc.in); got != tc.want { 451 t.Errorf("vmType(%q) = %q, want %q", tc.in, got, tc.want) 452 } 453 } 454 }