github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/dashboard/app/repro_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 main 5 6 import ( 7 "fmt" 8 "net/http" 9 "net/url" 10 "testing" 11 "time" 12 13 "github.com/google/syzkaller/dashboard/dashapi" 14 ) 15 16 // Normal workflow: 17 // - upload crash -> need repro 18 // - upload syz repro -> still need repro 19 // - upload C repro -> don't need repro 20 func testNeedRepro1(t *testing.T, crashCtor func(c *Ctx) *dashapi.Crash, newBug bool) { 21 c := NewCtx(t) 22 defer c.Close() 23 24 crash1 := crashCtor(c) 25 resp, _ := c.client.ReportCrash(crash1) 26 c.expectEQ(resp.NeedRepro, true) 27 28 cid := testCrashID(crash1) 29 needRepro, _ := c.client.NeedRepro(cid) 30 c.expectEQ(needRepro, true) 31 32 // Still need repro for this crash. 33 resp, _ = c.client.ReportCrash(crash1) 34 c.expectEQ(resp.NeedRepro, true) 35 needRepro, _ = c.client.NeedRepro(cid) 36 c.expectEQ(needRepro, true) 37 38 crash2 := new(dashapi.Crash) 39 *crash2 = *crash1 40 crash2.ReproOpts = []byte("opts") 41 crash2.ReproSyz = []byte("repro syz") 42 resp, _ = c.client.ReportCrash(crash2) 43 c.expectEQ(resp.NeedRepro, true) 44 needRepro, _ = c.client.NeedRepro(cid) 45 c.expectEQ(needRepro, true) 46 47 // MayBeMissing flag must not affect bugs that actually exist. 48 cidMissing := testCrashID(crash1) 49 cidMissing.MayBeMissing = true 50 needRepro, _ = c.client.NeedRepro(cidMissing) 51 c.expectEQ(needRepro, true) 52 53 crash2.ReproC = []byte("repro C") 54 resp, _ = c.client.ReportCrash(crash2) 55 c.expectEQ(resp.NeedRepro, false) 56 needRepro, _ = c.client.NeedRepro(cid) 57 c.expectEQ(needRepro, false) 58 59 needRepro, _ = c.client.NeedRepro(cidMissing) 60 c.expectEQ(needRepro, false) 61 62 resp, _ = c.client.ReportCrash(crash2) 63 c.expectEQ(resp.NeedRepro, false) 64 if newBug { 65 c.client.pollBug() 66 } 67 } 68 69 func TestNeedRepro1_normal(t *testing.T) { testNeedRepro1(t, normalCrash, true) } 70 func TestNeedRepro1_dup(t *testing.T) { testNeedRepro1(t, dupCrash, false) } 71 func TestNeedRepro1_closed(t *testing.T) { testNeedRepro1(t, closedCrash, true) } 72 func TestNeedRepro1_closedRepro(t *testing.T) { testNeedRepro1(t, closedWithReproCrash, true) } 73 74 // Upload C repro with first crash -> don't need repro. 75 func testNeedRepro2(t *testing.T, crashCtor func(c *Ctx) *dashapi.Crash, newBug bool) { 76 c := NewCtx(t) 77 defer c.Close() 78 79 crash1 := crashCtor(c) 80 crash1.ReproOpts = []byte("opts") 81 crash1.ReproSyz = []byte("repro syz") 82 crash1.ReproC = []byte("repro C") 83 resp, _ := c.client.ReportCrash(crash1) 84 c.expectEQ(resp.NeedRepro, false) 85 86 needRepro, _ := c.client.NeedRepro(testCrashID(crash1)) 87 c.expectEQ(needRepro, false) 88 if newBug { 89 c.client.pollBug() 90 } 91 } 92 93 func TestNeedRepro2_normal(t *testing.T) { testNeedRepro2(t, normalCrash, true) } 94 func TestNeedRepro2_dup(t *testing.T) { testNeedRepro2(t, dupCrash, false) } 95 func TestNeedRepro2_closed(t *testing.T) { testNeedRepro2(t, closedCrash, true) } 96 func TestNeedRepro2_closedRepro(t *testing.T) { testNeedRepro2(t, closedWithReproCrash, true) } 97 98 // Test that after uploading 5 failed repros, app stops requesting repros. 99 func testNeedRepro3(t *testing.T, crashCtor func(c *Ctx) *dashapi.Crash) { 100 c := NewCtx(t) 101 defer c.Close() 102 103 crash1 := crashCtor(c) 104 for i := 0; i < maxReproPerBug; i++ { 105 resp, _ := c.client.ReportCrash(crash1) 106 c.expectEQ(resp.NeedRepro, true) 107 needRepro, _ := c.client.NeedRepro(testCrashID(crash1)) 108 c.expectEQ(needRepro, true) 109 c.client.ReportFailedRepro(testCrashID(crash1)) 110 } 111 112 for i := 0; i < 3; i++ { 113 // No more repros today. 114 c.advanceTime(time.Hour) 115 resp, _ := c.client.ReportCrash(crash1) 116 c.expectEQ(resp.NeedRepro, false) 117 needRepro, _ := c.client.NeedRepro(testCrashID(crash1)) 118 c.expectEQ(needRepro, false) 119 120 // Then another repro after a day. 121 c.advanceTime(25 * time.Hour) 122 for j := 0; j < 2; j++ { 123 resp, _ := c.client.ReportCrash(crash1) 124 c.expectEQ(resp.NeedRepro, true) 125 needRepro, _ := c.client.NeedRepro(testCrashID(crash1)) 126 c.expectEQ(needRepro, true) 127 } 128 c.client.ReportFailedRepro(testCrashID(crash1)) 129 } 130 } 131 132 func TestNeedRepro3_normal(t *testing.T) { testNeedRepro3(t, normalCrash) } 133 func TestNeedRepro3_dup(t *testing.T) { testNeedRepro3(t, dupCrash) } 134 func TestNeedRepro3_closed(t *testing.T) { testNeedRepro3(t, closedCrash) } 135 func TestNeedRepro3_closedRepro(t *testing.T) { testNeedRepro3(t, closedWithReproCrash) } 136 137 func normalCrash(c *Ctx) *dashapi.Crash { 138 build := testBuild(1) 139 c.client.UploadBuild(build) 140 crash := testCrash(build, 1) 141 c.client.ReportCrash(crash) 142 c.client.pollBug() 143 return crash 144 } 145 146 func dupCrash(c *Ctx) *dashapi.Crash { 147 build := testBuild(1) 148 c.client.UploadBuild(build) 149 c.client.ReportCrash(testCrash(build, 1)) 150 crash2 := testCrash(build, 2) 151 c.client.ReportCrash(crash2) 152 reports := c.client.pollBugs(2) 153 c.client.updateBug(reports[1].ID, dashapi.BugStatusDup, reports[0].ID) 154 return crash2 155 } 156 157 func closedCrash(c *Ctx) *dashapi.Crash { 158 return closedCrashImpl(c, false) 159 } 160 161 func closedWithReproCrash(c *Ctx) *dashapi.Crash { 162 return closedCrashImpl(c, true) 163 } 164 165 func closedCrashImpl(c *Ctx, withRepro bool) *dashapi.Crash { 166 build := testBuild(1) 167 c.client.UploadBuild(build) 168 169 crash := testCrash(build, 1) 170 if withRepro { 171 crash.ReproC = []byte("repro C") 172 } 173 resp, _ := c.client.ReportCrash(crash) 174 c.expectEQ(resp.NeedRepro, !withRepro) 175 176 rep := c.client.pollBug() 177 c.client.updateBug(rep.ID, dashapi.BugStatusInvalid, "") 178 179 crash.ReproC = nil 180 c.client.ReportCrash(crash) 181 c.client.pollBug() 182 return crash 183 } 184 185 func TestNeedReproMissing(t *testing.T) { 186 c := NewCtx(t) 187 defer c.Close() 188 189 client := c.makeClient(client1, password1, false) 190 191 cid := &dashapi.CrashID{ 192 BuildID: "some missing build", 193 Title: "some missing title", 194 } 195 needRepro, err := client.NeedRepro(cid) 196 c.expectNE(err, nil) 197 c.expectEQ(needRepro, false) 198 199 cid.MayBeMissing = true 200 needRepro, err = client.NeedRepro(cid) 201 c.expectEQ(err, nil) 202 c.expectEQ(needRepro, true) 203 } 204 205 // In addition to the above, do a number of quick tests of the needReproForBug function. 206 func TestNeedReproIsolated(t *testing.T) { 207 c := NewCtx(t) 208 defer c.Close() 209 210 nowTime := c.mockedTime 211 tests := []struct { 212 bug *Bug 213 needRepro bool 214 }{ 215 { 216 // A bug without a repro. 217 bug: &Bug{ 218 Title: "normal bug without a repro", 219 }, 220 needRepro: true, 221 }, 222 { 223 // Corrupted bug. 224 bug: &Bug{ 225 Title: corruptedReportTitle, 226 }, 227 needRepro: false, 228 }, 229 { 230 // Suppressed bug. 231 bug: &Bug{ 232 Title: suppressedReportTitle, 233 }, 234 needRepro: false, 235 }, 236 { 237 // A bug without a C repro. 238 bug: &Bug{ 239 Title: "some normal bug with a syz-repro", 240 ReproLevel: ReproLevelSyz, 241 HeadReproLevel: ReproLevelSyz, 242 }, 243 needRepro: true, 244 }, 245 { 246 // A bug for which we have recently found a repro. 247 bug: &Bug{ 248 Title: "some normal recent bug", 249 ReproLevel: ReproLevelC, 250 HeadReproLevel: ReproLevelC, 251 LastReproTime: nowTime.Add(-time.Hour * 24), 252 }, 253 needRepro: false, 254 }, 255 { 256 // A bug which has an old C repro. 257 bug: &Bug{ 258 Title: "some normal bug with old repro", 259 ReproLevel: ReproLevelC, 260 HeadReproLevel: ReproLevelC, 261 NumRepro: 2 * maxReproPerBug, 262 LastReproTime: nowTime.Add(-reproStalePeriod), 263 }, 264 needRepro: true, 265 }, 266 { 267 // Several failed repro attepts are OK. 268 bug: &Bug{ 269 Title: "some normal bug with several fails", 270 NumRepro: maxReproPerBug - 1, 271 LastReproTime: nowTime, 272 }, 273 needRepro: true, 274 }, 275 { 276 // ... but there are limits. 277 bug: &Bug{ 278 Title: "some normal bug with too much fails", 279 NumRepro: maxReproPerBug, 280 LastReproTime: nowTime, 281 }, 282 needRepro: false, 283 }, 284 { 285 // Make sure we try until we find a C repro, not just a syz repro. 286 bug: &Bug{ 287 Title: "too many fails, but only a syz repro", 288 ReproLevel: ReproLevelSyz, 289 HeadReproLevel: ReproLevelSyz, 290 NumRepro: maxReproPerBug, 291 LastReproTime: nowTime.Add(-24 * time.Hour), 292 }, 293 needRepro: true, 294 }, 295 { 296 // We don't need a C repro for SYZFATAL: bugs. 297 bug: &Bug{ 298 Title: "SYZFATAL: Manager.Check call failed", 299 ReproLevel: ReproLevelSyz, 300 HeadReproLevel: ReproLevelSyz, 301 LastReproTime: nowTime.Add(-24 * time.Hour), 302 }, 303 needRepro: false, 304 }, 305 { 306 // .. and for SYZFAIL: bugs. 307 bug: &Bug{ 308 Title: "SYZFAIL: clock_gettime failed", 309 ReproLevel: ReproLevelSyz, 310 HeadReproLevel: ReproLevelSyz, 311 LastReproTime: nowTime.Add(-24 * time.Hour), 312 }, 313 needRepro: false, 314 }, 315 { 316 // Yet make sure that we request at least a syz repro. 317 bug: &Bug{ 318 Title: "SYZFATAL: Manager.Check call failed", 319 }, 320 needRepro: true, 321 }, 322 { 323 // A bug with a revoked repro. 324 bug: &Bug{ 325 Title: "some normal bug with a syz-repro", 326 ReproLevel: ReproLevelC, 327 HeadReproLevel: ReproLevelSyz, 328 LastReproTime: nowTime.Add(-24 * time.Hour), 329 }, 330 needRepro: true, 331 }, 332 } 333 334 for _, test := range tests { 335 bug := test.bug 336 if bug.Namespace == "" { 337 bug.Namespace = "test1" 338 } 339 funcResult := needReproForBug(c.ctx, bug) 340 if funcResult != test.needRepro { 341 t.Errorf("for %#v expected needRepro=%v, got needRepro=%v", 342 bug, test.needRepro, funcResult) 343 } 344 } 345 } 346 347 func TestFailedReproLogs(t *testing.T) { 348 c := NewCtx(t) 349 defer c.Close() 350 351 build := testBuild(1) 352 c.client.UploadBuild(build) 353 354 crash1 := &dashapi.Crash{ 355 BuildID: "build1", 356 Title: "title1", 357 Log: []byte("log1"), 358 Report: []byte("report1"), 359 } 360 c.client.ReportCrash(crash1) 361 362 resp, _ := c.client.ReportingPollBugs("test") 363 c.expectEQ(len(resp.Reports), 1) 364 rep := resp.Reports[0] 365 c.client.ReportingUpdate(&dashapi.BugUpdate{ 366 ID: rep.ID, 367 Status: dashapi.BugStatusOpen, 368 }) 369 370 // Report max attempts. 371 cid := &dashapi.CrashID{ 372 BuildID: crash1.BuildID, 373 Title: crash1.Title, 374 } 375 for i := 0; i < maxReproLogs; i++ { 376 c.advanceTime(time.Minute) 377 cid.ReproLog = []byte(fmt.Sprintf("report log %#v", i)) 378 err := c.client.ReportFailedRepro(cid) 379 c.expectOK(err) 380 } 381 382 dbBug, _, _ := c.loadBug(rep.ID) 383 firstRecords := dbBug.ReproAttempts 384 c.expectEQ(len(firstRecords), maxReproLogs) 385 386 // Report one more. 387 cid.ReproLog = []byte(fmt.Sprintf("report log %#v", maxReproLogs)) 388 err := c.client.ReportFailedRepro(cid) 389 c.expectOK(err) 390 391 dbBug, _, _ = c.loadBug(rep.ID) 392 lastRecords := dbBug.ReproAttempts 393 c.expectEQ(len(firstRecords), maxReproLogs) 394 395 // Ensure the first record was dropped. 396 checkResponseStatusCode(c, AccessAdmin, 397 textLink(textReproLog, firstRecords[0].Log), http.StatusNotFound) 398 399 // Ensure that the second record is readable. 400 reply, err := c.AuthGET(AccessAdmin, textLink(textReproLog, lastRecords[0].Log)) 401 c.expectOK(err) 402 c.expectEQ(reply, []byte("report log 1")) 403 } 404 405 func TestLogToReproduce(t *testing.T) { 406 c := NewCtx(t) 407 defer c.Close() 408 client := c.client 409 410 build := testBuild(1) 411 client.UploadBuild(build) 412 413 // Also add some unrelated crash, which should not appear in responses. 414 build2 := testBuild(2) 415 client.UploadBuild(build2) 416 client.ReportCrash(testCrash(build2, 3)) 417 client.pollBug() 418 419 // Bug with a reproducer. 420 crash1 := testCrashWithRepro(build, 1) 421 client.ReportCrash(crash1) 422 client.pollBug() 423 resp, err := client.LogToRepro(&dashapi.LogToReproReq{BuildID: "build1"}) 424 c.expectOK(err) 425 c.expectEQ(resp.CrashLog, []byte(nil)) 426 427 // Bug without a reproducer. 428 crash2 := &dashapi.Crash{ 429 BuildID: "build1", 430 Title: "title2", 431 Log: []byte("log2"), 432 Report: []byte("report2"), 433 } 434 client.ReportCrash(crash2) 435 client.pollBug() 436 resp, err = client.LogToRepro(&dashapi.LogToReproReq{BuildID: "build1"}) 437 c.expectOK(err) 438 c.expectEQ(resp.Title, "title2") 439 c.expectEQ(resp.CrashLog, []byte("log2")) 440 c.expectEQ(resp.Type, dashapi.RetryReproLog) 441 442 // Suppose we tried to find a repro, but failed. 443 err = client.ReportFailedRepro(&dashapi.CrashID{ 444 BuildID: crash2.BuildID, 445 Title: crash2.Title, 446 ReproLog: []byte("abcd"), 447 }) 448 c.expectOK(err) 449 450 // Now this crash should not be suggested. 451 resp, err = client.LogToRepro(&dashapi.LogToReproReq{BuildID: "build1"}) 452 c.expectOK(err) 453 c.expectEQ(resp.CrashLog, []byte(nil)) 454 } 455 456 // A frequent case -- when trying to find a reproducer for one bug, 457 // we have found a reproducer for a different bug. 458 // We want to remember the reproduction log in this case. 459 func TestReproForDifferentCrash(t *testing.T) { 460 c := NewCtx(t) 461 defer c.Close() 462 463 client := c.client 464 build := testBuild(1) 465 client.UploadBuild(build) 466 467 // Original crash. 468 crash := &dashapi.Crash{ 469 BuildID: "build1", 470 Title: "title1", 471 Log: []byte("log1"), 472 Report: []byte("report1"), 473 } 474 client.ReportCrash(crash) 475 oldBug := client.pollBug() 476 477 // Now we have "found" a reproducer with a different title. 478 crash.Title = "new title" 479 crash.ReproOpts = []byte("opts") 480 crash.ReproSyz = []byte("repro syz") 481 crash.ReproLog = []byte("repro log") 482 crash.OriginalTitle = "title1" 483 client.ReportCrash(crash) 484 client.pollBug() 485 486 // Ensure that we have saved the reproduction log in this case. 487 dbBug, _, _ := c.loadBug(oldBug.ID) 488 c.expectEQ(len(dbBug.ReproAttempts), 1) 489 } 490 491 func TestReproTask(t *testing.T) { 492 c := NewCtx(t) 493 defer c.Close() 494 client := c.client 495 496 build := testBuild(1) 497 build.Manager = "test-manager" 498 client.UploadBuild(build) 499 500 form := url.Values{} 501 const reproValue = "Some repro text" 502 form.Add("send-repro", reproValue) 503 504 c.POSTForm("/test1/manager/test-manager", form) 505 506 // We run the reproducer request 2 times. 507 for i := 0; i < 2; i++ { 508 resp, err := client.LogToRepro(&dashapi.LogToReproReq{BuildID: build.ID}) 509 c.expectOK(err) 510 c.expectEQ(string(resp.CrashLog), reproValue) 511 c.expectEQ(resp.Type, dashapi.ManualLog) 512 } 513 514 // But no more. 515 resp, err := client.LogToRepro(&dashapi.LogToReproReq{BuildID: build.ID}) 516 c.expectOK(err) 517 c.expectEQ(resp.CrashLog, []byte(nil)) 518 }