github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/dashboard/app/app_test.go (about) 1 // Copyright 2017 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 "errors" 8 "fmt" 9 "net/http" 10 "os" 11 "strconv" 12 "strings" 13 "testing" 14 "time" 15 16 "github.com/google/go-cmp/cmp" 17 "github.com/google/syzkaller/dashboard/dashapi" 18 "github.com/google/syzkaller/pkg/auth" 19 "github.com/google/syzkaller/pkg/subsystem" 20 _ "github.com/google/syzkaller/pkg/subsystem/lists" 21 "github.com/google/syzkaller/sys/targets" 22 "google.golang.org/appengine/v2/user" 23 ) 24 25 func init() { 26 // This is ugly but without this go test hangs with: 27 // panic: Metadata fetch failed for 'instance/attributes/gae_backend_version': 28 // Get http://metadata/computeMetadata/v1/instance/attributes/gae_backend_version: 29 // dial tcp: lookup metadata on 127.0.0.1:53: no such host 30 // It's unclear what's the proper fix for this. 31 os.Setenv("GAE_MODULE_VERSION", "1") 32 os.Setenv("GAE_MINOR_VERSION", "1") 33 34 trustedAuthDomain = "" // Devappserver environment value is "", prod value is "gmail.com". 35 obsoleteWhatWontBeFixBisected = true 36 notifyAboutUnsuccessfulBisections = true 37 ensureConfigImmutability = true 38 initMocks() 39 installConfig(testConfig) 40 } 41 42 // Config used in tests. 43 var testConfig = &GlobalConfig{ 44 AccessLevel: AccessPublic, 45 ACL: []*ACLItem{ 46 { 47 Domain: "syzkaller.com", 48 Access: AccessUser, 49 }, 50 { 51 Email: makeUser(AuthorizedAccessPublic).Email, 52 Access: AccessPublic, 53 }, 54 }, 55 Clients: map[string]string{ 56 "reporting": "reportingkeyreportingkeyreportingkey", 57 }, 58 EmailBlocklist: []string{ 59 "\"Bar\" <Blocked@Domain.com>", 60 }, 61 Obsoleting: ObsoletingConfig{ 62 MinPeriod: 80 * 24 * time.Hour, 63 MaxPeriod: 100 * 24 * time.Hour, 64 NonFinalMinPeriod: 40 * 24 * time.Hour, 65 NonFinalMaxPeriod: 60 * 24 * time.Hour, 66 ReproRetestPeriod: 100 * 24 * time.Hour, 67 }, 68 DiscussionEmails: []DiscussionEmailConfig{ 69 {"lore@email.com", dashapi.DiscussionLore}, 70 }, 71 DefaultNamespace: "test1", 72 Namespaces: map[string]*Config{ 73 "test1": { 74 AccessLevel: AccessAdmin, 75 Key: "test1keytest1keytest1key", 76 FixBisectionAutoClose: true, 77 SimilarityDomain: testDomain, 78 Clients: map[string]string{ 79 client1: password1, 80 "oauth": auth.OauthMagic + "111111122222222", 81 }, 82 Repos: []KernelRepo{ 83 { 84 URL: "git://syzkaller.org", 85 Branch: "branch10", 86 Alias: "repo10alias", 87 CC: CCConfig{ 88 Maintainers: []string{"maintainers@repo10.org", "bugs@repo10.org"}, 89 }, 90 }, 91 { 92 URL: "git://github.com/google/syzkaller", 93 Branch: "master", 94 Alias: "repo10alias1", 95 CC: CCConfig{ 96 Maintainers: []string{"maintainers@repo10.org", "bugs@repo10.org"}, 97 }, 98 }, 99 { 100 URL: "git://github.com/google/syzkaller", 101 Branch: "old_master", 102 Alias: "repo10alias2", 103 NoPoll: true, 104 }, 105 }, 106 Managers: map[string]ConfigManager{ 107 "special-obsoleting": { 108 ObsoletingMinPeriod: 10 * 24 * time.Hour, 109 ObsoletingMaxPeriod: 20 * 24 * time.Hour, 110 }, 111 }, 112 Reporting: []Reporting{ 113 { 114 Name: "reporting1", 115 DailyLimit: 5, 116 Embargo: 14 * 24 * time.Hour, 117 Filter: skipWithRepro, 118 Config: &TestConfig{ 119 Index: 1, 120 }, 121 }, 122 { 123 Name: "reporting2", 124 DailyLimit: 5, 125 Config: &TestConfig{ 126 Index: 2, 127 }, 128 }, 129 }, 130 Subsystems: SubsystemsConfig{ 131 Service: subsystem.MustMakeService(testSubsystems, 0), 132 }, 133 }, 134 "test2": { 135 AccessLevel: AccessAdmin, 136 Key: "test2keytest2keytest2key", 137 SimilarityDomain: testDomain, 138 Clients: map[string]string{ 139 client2: password2, 140 }, 141 Repos: []KernelRepo{ 142 { 143 URL: "git://syzkaller.org", 144 Branch: "branch10", 145 Alias: "repo10alias", 146 CC: CCConfig{ 147 Always: []string{"always@cc.me"}, 148 Maintainers: []string{"maintainers@repo10.org", "bugs@repo10.org"}, 149 BuildMaintainers: []string{"build-maintainers@repo10.org"}, 150 }, 151 }, 152 { 153 URL: "git://syzkaller.org", 154 Branch: "branch20", 155 Alias: "repo20", 156 CC: CCConfig{ 157 Maintainers: []string{"maintainers@repo20.org", "bugs@repo20.org"}, 158 }, 159 }, 160 }, 161 Managers: map[string]ConfigManager{ 162 noFixBisectionManager: { 163 FixBisectionDisabled: true, 164 }, 165 specialCCManager: { 166 CC: CCConfig{ 167 Always: []string{"always@manager.org"}, 168 Maintainers: []string{"maintainers@manager.org"}, 169 BuildMaintainers: []string{"build-maintainers@manager.org"}, 170 }, 171 }, 172 }, 173 Reporting: []Reporting{ 174 { 175 Name: "reporting1", 176 DailyLimit: 5, 177 Embargo: 14 * 24 * time.Hour, 178 Filter: skipWithRepro, 179 Config: &EmailConfig{ 180 Email: "test@syzkaller.com", 181 }, 182 }, 183 { 184 Name: "reporting2", 185 DailyLimit: 3, 186 Filter: skipWithRepro2, 187 Config: &EmailConfig{ 188 Email: "bugs@syzkaller.com", 189 DefaultMaintainers: []string{"default@maintainers.com"}, 190 SubjectPrefix: "[syzbot]", 191 MailMaintainers: true, 192 }, 193 }, 194 { 195 Name: "reporting3", 196 DailyLimit: 3, 197 Config: &EmailConfig{ 198 Email: "bugs2@syzkaller.com", 199 DefaultMaintainers: []string{"default2@maintainers.com"}, 200 MailMaintainers: true, 201 }, 202 }, 203 }, 204 }, 205 // Namespaces for access level testing. 206 "access-admin": { 207 AccessLevel: AccessAdmin, 208 Key: "adminkeyadminkeyadminkey", 209 Clients: map[string]string{ 210 clientAdmin: keyAdmin, 211 }, 212 Repos: []KernelRepo{ 213 { 214 URL: "git://syzkaller.org/access-admin.git", 215 Branch: "access-admin", 216 Alias: "access-admin", 217 }, 218 }, 219 Reporting: []Reporting{ 220 { 221 Name: "access-admin-reporting1", 222 DailyLimit: 1000, 223 Config: &TestConfig{Index: 1}, 224 }, 225 { 226 Name: "access-admin-reporting2", 227 DailyLimit: 1000, 228 Config: &TestConfig{Index: 2}, 229 }, 230 }, 231 }, 232 "access-user": { 233 AccessLevel: AccessUser, 234 Key: "userkeyuserkeyuserkey", 235 Clients: map[string]string{ 236 clientUser: keyUser, 237 }, 238 Repos: []KernelRepo{ 239 { 240 URL: "git://syzkaller.org/access-user.git", 241 Branch: "access-user", 242 Alias: "access-user", 243 }, 244 }, 245 Reporting: []Reporting{ 246 { 247 AccessLevel: AccessAdmin, 248 Name: "access-admin-reporting1", 249 DailyLimit: 1000, 250 Config: &TestConfig{Index: 1}, 251 }, 252 { 253 Name: "access-user-reporting2", 254 DailyLimit: 1000, 255 Config: &TestConfig{Index: 2}, 256 }, 257 }, 258 }, 259 "access-public": { 260 AccessLevel: AccessPublic, 261 Key: "publickeypublickeypublickey", 262 Clients: map[string]string{ 263 clientPublic: keyPublic, 264 }, 265 Repos: []KernelRepo{ 266 { 267 URL: "git://syzkaller.org/access-public.git", 268 Branch: "access-public", 269 Alias: "access-public", 270 DetectMissingBackports: true, 271 }, 272 }, 273 Reporting: []Reporting{ 274 { 275 AccessLevel: AccessUser, 276 Name: "access-user-reporting1", 277 DailyLimit: 1000, 278 Config: &TestConfig{Index: 1}, 279 }, 280 { 281 Name: "access-public-reporting2", 282 DailyLimit: 1000, 283 Config: &TestConfig{Index: 2}, 284 }, 285 }, 286 FindBugOriginTrees: true, 287 CacheUIPages: true, 288 RetestRepros: true, 289 }, 290 "access-public-email": { 291 AccessLevel: AccessPublic, 292 Key: "publickeypublickeypublickey", 293 Clients: map[string]string{ 294 clientPublicEmail: keyPublicEmail, 295 }, 296 Managers: map[string]ConfigManager{ 297 restrictedManager: { 298 RestrictedTestingRepo: "git://restricted.git/restricted.git", 299 RestrictedTestingReason: "you should test only on restricted.git", 300 }, 301 }, 302 Repos: []KernelRepo{ 303 { 304 URL: "git://syzkaller.org/access-public-email.git", 305 Branch: "access-public-email", 306 Alias: "access-public-email", 307 }, 308 { 309 // Needed for TestTreeOriginLtsBisection(). 310 URL: "https://upstream.repo/repo", 311 Branch: "upstream-master", 312 Alias: "upstream-master", 313 }, 314 }, 315 Reporting: []Reporting{ 316 { 317 AccessLevel: AccessPublic, 318 Name: "access-public-email-reporting1", 319 DailyLimit: 1000, 320 Config: &EmailConfig{ 321 Email: "test@syzkaller.com", 322 HandleListEmails: true, 323 SubjectPrefix: "[syzbot]", 324 }, 325 }, 326 }, 327 RetestRepros: true, 328 Subsystems: SubsystemsConfig{ 329 Service: subsystem.MustMakeService(testSubsystems, 0), 330 Redirect: map[string]string{ 331 "oldSubsystem": "subsystemA", 332 }, 333 }, 334 }, 335 // The second namespace reporting to the same mailing list. 336 "access-public-email-2": { 337 AccessLevel: AccessPublic, 338 Key: "publickeypublickeypublickey", 339 Clients: map[string]string{ 340 clientPublicEmail2: keyPublicEmail2, 341 }, 342 Repos: []KernelRepo{ 343 { 344 URL: "git://syzkaller.org/access-public-email2.git", 345 Branch: "access-public-email2", 346 Alias: "access-public-email2", 347 }, 348 }, 349 Reporting: []Reporting{ 350 { 351 AccessLevel: AccessPublic, 352 Name: "access-public-email2-reporting1", 353 DailyLimit: 1000, 354 Config: &EmailConfig{ 355 Email: "test@syzkaller.com", 356 HandleListEmails: true, 357 }, 358 }, 359 }, 360 }, 361 "fs-bugs-reporting": { 362 AccessLevel: AccessPublic, 363 Key: "fspublickeypublickeypublickey", 364 Clients: map[string]string{ 365 clientPublicFs: keyPublicFs, 366 }, 367 Repos: []KernelRepo{ 368 { 369 URL: "git://syzkaller.org/fs-bugs.git", 370 Branch: "fs-bugs", 371 Alias: "fs-bugs", 372 }, 373 }, 374 Reporting: []Reporting{ 375 { 376 Name: "wait-repro", 377 DailyLimit: 1000, 378 Filter: func(bug *Bug) FilterResult { 379 if canBeVfsBug(bug) && 380 bug.ReproLevel == dashapi.ReproLevelNone { 381 return FilterReport 382 } 383 return FilterSkip 384 }, 385 Config: &TestConfig{Index: 1}, 386 }, 387 { 388 AccessLevel: AccessPublic, 389 Name: "public", 390 DailyLimit: 1000, 391 Config: &EmailConfig{ 392 Email: "test@syzkaller.com", 393 HandleListEmails: true, 394 DefaultMaintainers: []string{"linux-kernel@vger.kernel.org"}, 395 MailMaintainers: true, 396 SubjectPrefix: "[syzbot]", 397 }, 398 }, 399 }, 400 Subsystems: SubsystemsConfig{ 401 Service: subsystem.ListService("linux"), 402 }, 403 }, 404 "test-decommission": { 405 AccessLevel: AccessAdmin, 406 Key: "testdecommissiontestdecommission", 407 SimilarityDomain: testDomain, 408 Clients: map[string]string{ 409 clientTestDecomm: keyTestDecomm, 410 }, 411 Repos: []KernelRepo{ 412 { 413 URL: "git://syzkaller.org", 414 Branch: "branch10", 415 Alias: "repo10alias", 416 }, 417 }, 418 Reporting: []Reporting{ 419 { 420 Name: "reporting1", 421 DailyLimit: 3, 422 Embargo: 14 * 24 * time.Hour, 423 Filter: skipWithRepro, 424 Config: &TestConfig{ 425 Index: 1, 426 }, 427 }, 428 { 429 Name: "reporting2", 430 DailyLimit: 3, 431 Config: &TestConfig{ 432 Index: 2, 433 }, 434 }, 435 }, 436 }, 437 "test-mgr-decommission": { 438 AccessLevel: AccessAdmin, 439 Key: "testmgrdecommissiontestmgrdecommission", 440 SimilarityDomain: testDomain, 441 Clients: map[string]string{ 442 clientMgrDecommission: keyMgrDecommission, 443 }, 444 Managers: map[string]ConfigManager{ 445 notYetDecommManger: {}, 446 delegateToManager: {}, 447 }, 448 Repos: []KernelRepo{ 449 { 450 URL: "git://syzkaller.org", 451 Branch: "branch10", 452 Alias: "repo10alias", 453 }, 454 }, 455 Reporting: []Reporting{ 456 { 457 Name: "reporting1", 458 DailyLimit: 5, 459 Embargo: 14 * 24 * time.Hour, 460 Filter: skipWithRepro, 461 Config: &EmailConfig{ 462 Email: "test@syzkaller.com", 463 }, 464 }, 465 { 466 Name: "reporting2", 467 DailyLimit: 3, 468 Filter: skipWithRepro2, 469 Config: &EmailConfig{ 470 Email: "bugs@syzkaller.com", 471 DefaultMaintainers: []string{"default@maintainers.com"}, 472 SubjectPrefix: "[syzbot]", 473 MailMaintainers: true, 474 }, 475 }, 476 }, 477 RetestRepros: true, 478 }, 479 "subsystem-reminders": { 480 AccessLevel: AccessPublic, 481 Key: "subsystemreminderssubsystemreminders", 482 Clients: map[string]string{ 483 clientSubsystemRemind: keySubsystemRemind, 484 }, 485 Repos: []KernelRepo{ 486 { 487 URL: "git://syzkaller.org/reminders.git", 488 Branch: "main", 489 Alias: "main", 490 }, 491 }, 492 Reporting: []Reporting{ 493 { 494 // Let's emulate public moderation. 495 AccessLevel: AccessPublic, 496 Name: "moderation", 497 DailyLimit: 1000, 498 Filter: func(bug *Bug) FilterResult { 499 if strings.Contains(bug.Title, "keep in moderation") { 500 return FilterReport 501 } 502 return FilterSkip 503 }, 504 Config: &TestConfig{Index: 1}, 505 }, 506 { 507 AccessLevel: AccessPublic, 508 Name: "public", 509 DailyLimit: 1000, 510 Config: &EmailConfig{ 511 Email: "bugs@syzkaller.com", 512 HandleListEmails: true, 513 MailMaintainers: true, 514 DefaultMaintainers: []string{"default@maintainers.com"}, 515 SubjectPrefix: "[syzbot]", 516 }, 517 }, 518 }, 519 Subsystems: SubsystemsConfig{ 520 Service: subsystem.MustMakeService(testSubsystems, 0), 521 Reminder: &BugListReportingConfig{ 522 SourceReporting: "public", 523 BugsInReport: 6, 524 ModerationConfig: &EmailConfig{ 525 Email: "moderation@syzkaller.com", 526 SubjectPrefix: "[moderation]", 527 }, 528 Config: &EmailConfig{ 529 Email: "bugs@syzkaller.com", 530 MailMaintainers: true, 531 SubjectPrefix: "[syzbot]", 532 }, 533 }, 534 }, 535 }, 536 "tree-tests": { 537 AccessLevel: AccessPublic, 538 FixBisectionAutoClose: true, 539 Key: "treeteststreeteststreeteststreeteststreeteststreetests", 540 Clients: map[string]string{ 541 clientTreeTests: keyTreeTests, 542 }, 543 Repos: []KernelRepo{ 544 { 545 URL: "git://syzkaller.org/test.git", 546 Branch: "main", 547 Alias: "main", 548 DetectMissingBackports: true, 549 }, 550 }, 551 Managers: map[string]ConfigManager{ 552 "better-manager": { 553 Priority: 1, 554 }, 555 }, 556 Reporting: []Reporting{ 557 { 558 AccessLevel: AccessAdmin, 559 Name: "non-public", 560 DailyLimit: 1000, 561 Filter: func(bug *Bug) FilterResult { 562 return FilterReport 563 }, 564 Config: &TestConfig{Index: 1}, 565 }, 566 { 567 AccessLevel: AccessUser, 568 Name: "user", 569 DailyLimit: 1000, 570 Config: &EmailConfig{ 571 Email: "bugs@syzkaller.com", 572 SubjectPrefix: "[syzbot]", 573 }, 574 Labels: map[string]string{ 575 "origin:downstream": "Bug presence analysis results: the bug reproduces only on the downstream tree.", 576 }, 577 }, 578 }, 579 FindBugOriginTrees: true, 580 RetestMissingBackports: true, 581 }, 582 "coverage-tests": { 583 Coverage: &CoverageConfig{ 584 EmailRegressionsTo: "test@test.test", 585 RegressionThreshold: 1, 586 }, 587 AccessLevel: AccessPublic, 588 Key: "coveragetestskeycoveragetestskeycoveragetestskey", 589 Repos: []KernelRepo{ 590 { 591 URL: "git://syzkaller.org/test.git", 592 Branch: "main", 593 Alias: "main", 594 }, 595 }, 596 Reporting: []Reporting{ 597 { 598 Name: "non-public", 599 DailyLimit: 1000, 600 Filter: func(bug *Bug) FilterResult { 601 return FilterReport 602 }, 603 Config: &TestConfig{Index: 1}, 604 }, 605 }, 606 }, 607 }, 608 } 609 610 var testSubsystems = []*subsystem.Subsystem{ 611 { 612 Name: "subsystemA", 613 PathRules: []subsystem.PathRule{{IncludeRegexp: `a\.c`}}, 614 Lists: []string{"subsystemA@list.com"}, 615 Maintainers: []string{"subsystemA@person.com"}, 616 }, 617 { 618 Name: "subsystemB", 619 PathRules: []subsystem.PathRule{{IncludeRegexp: `b\.c`}}, 620 Lists: []string{"subsystemB@list.com"}, 621 Maintainers: []string{"subsystemB@person.com"}, 622 }, 623 { 624 Name: "subsystemC", 625 PathRules: []subsystem.PathRule{{IncludeRegexp: `c\.c`}}, 626 Lists: []string{"subsystemC@list.com"}, 627 Maintainers: []string{"subsystemC@person.com"}, 628 NoReminders: true, 629 }, 630 } 631 632 const ( 633 client1 = "client1" 634 client2 = "client2" 635 password1 = "client1keyclient1keyclient1key" 636 password2 = "client2keyclient2keyclient2key" 637 clientAdmin = "client-admin" 638 keyAdmin = "clientadminkeyclientadminkey" 639 clientUser = "client-user" 640 keyUser = "clientuserkeyclientuserkey" 641 clientPublic = "client-public" 642 keyPublic = "clientpublickeyclientpublickey" 643 clientPublicEmail = "client-public-email" 644 keyPublicEmail = "clientpublicemailkeyclientpublicemailkey" 645 clientPublicEmail2 = "client-public-email2" 646 keyPublicEmail2 = "clientpublicemailkeyclientpublicemailkey2" 647 clientPublicFs = "client-public-fs" 648 keyPublicFs = "keypublicfskeypublicfskeypublicfs" 649 clientTestDecomm = "client-test-decomm" 650 keyTestDecomm = "keyTestDecommkeyTestDecomm" 651 clientMgrDecommission = "client-mgr-decommission" 652 keyMgrDecommission = "keyMgrDecommissionkeyMgrDecommission" 653 clientSubsystemRemind = "client-subystem-reminders" 654 keySubsystemRemind = "keySubsystemRemindkeySubsystemRemind" 655 clientTreeTests = "clientTreeTestsclientTreeTests" 656 keyTreeTests = "keyTreeTestskeyTreeTestskeyTreeTests" 657 658 restrictedManager = "restricted-manager" 659 noFixBisectionManager = "no-fix-bisection-manager" 660 specialCCManager = "special-cc-manager" 661 notYetDecommManger = "not-yet-decomm-manager" 662 delegateToManager = "delegate-to-manager" 663 664 testDomain = "test" 665 ) 666 667 func skipWithRepro(bug *Bug) FilterResult { 668 if strings.HasPrefix(bug.Title, "skip with repro") && 669 bug.ReproLevel != dashapi.ReproLevelNone { 670 return FilterSkip 671 } 672 return FilterReport 673 } 674 675 func skipWithRepro2(bug *Bug) FilterResult { 676 if strings.HasPrefix(bug.Title, "skip reporting2 with repro") && 677 bug.ReproLevel != dashapi.ReproLevelNone { 678 return FilterSkip 679 } 680 return FilterReport 681 } 682 683 type TestConfig struct { 684 Index int 685 } 686 687 func (cfg *TestConfig) Type() string { 688 return "test" 689 } 690 691 func (cfg *TestConfig) Validate() error { 692 return nil 693 } 694 695 func testBuild(id int) *dashapi.Build { 696 return &dashapi.Build{ 697 Manager: fmt.Sprintf("manager%v", id), 698 ID: fmt.Sprintf("build%v", id), 699 OS: targets.Linux, 700 Arch: targets.AMD64, 701 VMArch: targets.AMD64, 702 SyzkallerCommit: fmt.Sprintf("syzkaller_commit%v", id), 703 CompilerID: fmt.Sprintf("compiler%v", id), 704 KernelRepo: fmt.Sprintf("repo%v", id), 705 KernelBranch: fmt.Sprintf("branch%v", id), 706 KernelCommit: strings.Repeat(fmt.Sprint(id), 40)[:40], 707 KernelCommitTitle: fmt.Sprintf("kernel_commit_title%v", id), 708 KernelCommitDate: buildCommitDate, 709 KernelConfig: []byte(fmt.Sprintf("config%v", id)), 710 } 711 } 712 713 var buildCommitDate = time.Date(1, 2, 3, 4, 5, 6, 0, time.UTC) 714 715 func testCrash(build *dashapi.Build, id int) *dashapi.Crash { 716 return &dashapi.Crash{ 717 BuildID: build.ID, 718 Title: fmt.Sprintf("title%v", id), 719 Log: []byte(fmt.Sprintf("log%v", id)), 720 Report: []byte(fmt.Sprintf("report%v", id)), 721 MachineInfo: []byte(fmt.Sprintf("machine info %v", id)), 722 } 723 } 724 725 func testCrashWithRepro(build *dashapi.Build, id int) *dashapi.Crash { 726 crash := testCrash(build, id) 727 crash.ReproOpts = []byte(fmt.Sprintf("repro opts %v", id)) 728 crash.ReproSyz = []byte(fmt.Sprintf("syncfs(%v)", id)) 729 crash.ReproC = []byte(fmt.Sprintf("int main() { return %v; }", id)) 730 crash.ReproLog = []byte(fmt.Sprintf("repro log %d", id)) 731 return crash 732 } 733 734 func testCrashID(crash *dashapi.Crash) *dashapi.CrashID { 735 return &dashapi.CrashID{ 736 BuildID: crash.BuildID, 737 Title: crash.Title, 738 } 739 } 740 741 func TestApp(t *testing.T) { 742 c := NewCtx(t) 743 defer c.Close() 744 745 _, err := c.GET("/test1") 746 c.expectOK(err) 747 748 apiClient1 := c.makeClient(client1, password1, false) 749 apiClient2 := c.makeClient(client2, password2, false) 750 c.expectFail("unknown api method", apiClient1.Query("unsupported_method", nil, nil)) 751 c.client.LogError("name", "msg %s", "arg") 752 753 build := testBuild(1) 754 c.client.UploadBuild(build) 755 // Uploading the same build must be OK. 756 c.client.UploadBuild(build) 757 758 // Some bad combinations of client/key. 759 c.expectFail("unauthorized", c.makeClient(client1, "borked", false).Query("upload_build", build, nil)) 760 c.expectFail("unauthorized", c.makeClient("unknown", password1, false).Query("upload_build", build, nil)) 761 c.expectFail("unauthorized", c.makeClient(client1, password2, false).Query("upload_build", build, nil)) 762 763 crash1 := testCrash(build, 1) 764 c.client.ReportCrash(crash1) 765 c.client.pollBug() 766 767 // Test that namespace isolation works. 768 c.expectFail("unknown build", apiClient2.Query("report_crash", crash1, nil)) 769 770 crash2 := testCrashWithRepro(build, 2) 771 c.client.ReportCrash(crash2) 772 c.client.pollBug() 773 774 // Provoke purgeOldCrashes. 775 const purgeTestIters = 30 776 for i := 0; i < purgeTestIters; i++ { 777 // Also test how daily counts work. 778 if i == purgeTestIters/2 { 779 c.advanceTime(48 * time.Hour) 780 } 781 crash := testCrash(build, 3) 782 crash.Log = []byte(fmt.Sprintf("log%v", i)) 783 crash.Report = []byte(fmt.Sprintf("report%v", i)) 784 c.client.ReportCrash(crash) 785 } 786 rep := c.client.pollBug() 787 bug, _, _ := c.loadBug(rep.ID) 788 c.expectNE(bug, nil) 789 c.expectEQ(bug.DailyStats, []BugDailyStats{ 790 {20000101, purgeTestIters / 2}, 791 {20000103, purgeTestIters / 2}, 792 }) 793 794 cid := &dashapi.CrashID{ 795 BuildID: "build1", 796 Title: "title1", 797 } 798 c.client.ReportFailedRepro(cid) 799 800 c.client.ReportingPollBugs("test") 801 802 c.client.ReportingUpdate(&dashapi.BugUpdate{ 803 ID: "id", 804 Status: dashapi.BugStatusOpen, 805 ReproLevel: dashapi.ReproLevelC, 806 }) 807 } 808 809 func TestRedirects(t *testing.T) { 810 c := NewCtx(t) 811 defer c.Close() 812 813 checkRedirect(c, AccessUser, "/", "/test1", http.StatusFound) // redirect to default namespace 814 checkRedirect(c, AccessAdmin, "/", "/admin", http.StatusFound) 815 checkLoginRedirect(c, AccessPublic, "/access-user") // not accessible namespace 816 817 _, err := c.AuthGET(AccessUser, "/access-user") 818 c.expectOK(err) 819 } 820 821 func TestResponseStatusCode(t *testing.T) { 822 tests := []struct { 823 whatURL string 824 wantRespCode int 825 }{ 826 { 827 "/text?tag=CrashLog&x=13354bf5700000", 828 http.StatusNotFound, 829 }, 830 { 831 "/text?tag=CrashReport&x=17a2bedcb00000", 832 http.StatusNotFound, 833 }, 834 { 835 "/text?tag=ReproSyz&x=107e219b700000", 836 http.StatusNotFound, 837 }, 838 { 839 "/text?tag=ReproC&x=1762ad64f00000", 840 http.StatusNotFound, 841 }, 842 { 843 "/text?tag=CrashLog", 844 http.StatusBadRequest, 845 }, 846 { 847 "/text?tag=CrashReport", 848 http.StatusBadRequest, 849 }, 850 { 851 "/text?tag=ReproC", 852 http.StatusBadRequest, 853 }, 854 { 855 "/text?tag=ReproSyz", 856 http.StatusBadRequest, 857 }, 858 } 859 860 c := NewCtx(t) 861 defer c.Close() 862 863 for _, test := range tests { 864 checkResponseStatusCode(c, AccessUser, test.whatURL, test.wantRespCode) 865 } 866 } 867 868 func checkLoginRedirect(c *Ctx, accessLevel AccessLevel, url string) { 869 to, err := user.LoginURL(c.ctx, url) 870 if err != nil { 871 c.t.Fatal(err) 872 } 873 checkRedirect(c, accessLevel, url, to, http.StatusTemporaryRedirect) 874 } 875 876 func checkRedirect(c *Ctx, accessLevel AccessLevel, from, to string, status int) { 877 _, err := c.AuthGET(accessLevel, from) 878 c.expectNE(err, nil) 879 var httpErr *HTTPError 880 c.expectTrue(errors.As(err, &httpErr)) 881 c.expectEQ(httpErr.Code, status) 882 c.expectEQ(httpErr.Headers["Location"], []string{to}) 883 } 884 885 func checkResponseStatusCode(c *Ctx, accessLevel AccessLevel, url string, status int) { 886 _, err := c.AuthGET(accessLevel, url) 887 c.expectNE(err, nil) 888 var httpErr *HTTPError 889 c.expectTrue(errors.As(err, &httpErr)) 890 c.expectEQ(httpErr.Code, status) 891 } 892 893 // Test purging of old crashes for bugs with lots of crashes. 894 func TestPurgeOldCrashes(t *testing.T) { 895 if testing.Short() { 896 t.Skip() 897 } 898 c := NewCtx(t) 899 defer c.Close() 900 901 build := testBuild(1) 902 c.client.UploadBuild(build) 903 904 // First, send 3 crashes that are reported. These need to be preserved regardless. 905 crash := testCrash(build, 1) 906 crash.ReproOpts = []byte("no repro") 907 c.client.ReportCrash(crash) 908 rep := c.client.pollBug() 909 910 crash.ReproSyz = []byte("getpid()") 911 crash.ReproOpts = []byte("syz repro") 912 c.client.ReportCrash(crash) 913 c.client.pollBug() 914 915 crash.ReproC = []byte("int main() {}") 916 crash.ReproOpts = []byte("C repro") 917 c.client.ReportCrash(crash) 918 c.client.pollBug() 919 920 // Now report lots of bugs with/without repros. Some of the older ones should be purged. 921 var totalReported = 3 * maxCrashes() 922 for i := 0; i < totalReported; i++ { 923 c.advanceTime(2 * time.Hour) // This ensures that crashes are saved. 924 crash.ReproSyz = nil 925 crash.ReproC = nil 926 crash.ReproOpts = []byte(fmt.Sprintf("%v", i)) 927 c.client.ReportCrash(crash) 928 929 crash.ReproSyz = []byte("syz repro") 930 crash.ReproC = []byte("C repro") 931 crash.ReproOpts = []byte(fmt.Sprintf("%v", i)) 932 c.client.ReportCrash(crash) 933 } 934 bug, _, _ := c.loadBug(rep.ID) 935 crashes, _, err := queryCrashesForBug(c.ctx, bug.key(c.ctx), 10*totalReported) 936 c.expectOK(err) 937 // First, count how many crashes of different types we have. 938 // We should get all 3 reported crashes + some with repros and some without repros. 939 reported, norepro, repro := 0, 0, 0 940 for _, crash := range crashes { 941 if !crash.Reported.IsZero() { 942 reported++ 943 } else if crash.ReproSyz == 0 { 944 norepro++ 945 } else { 946 repro++ 947 } 948 } 949 c.t.Logf("got reported=%v, norepro=%v, repro=%v, maxCrashes=%v", 950 reported, norepro, repro, maxCrashes()) 951 if reported != 3 || 952 norepro < maxCrashes() || norepro > maxCrashes()+10 || 953 repro < maxCrashes() || repro > maxCrashes()+10 { 954 c.t.Fatalf("bad purged crashes") 955 } 956 // Then, check that latest crashes were preserved. 957 for _, crash := range crashes { 958 if !crash.Reported.IsZero() { 959 continue 960 } 961 idx, err := strconv.Atoi(string(crash.ReproOpts)) 962 c.expectOK(err) 963 count := norepro 964 if crash.ReproSyz != 0 { 965 count = repro 966 } 967 if idx < totalReported-count { 968 c.t.Errorf("preserved bad crash repro=%v: %v", crash.ReproC != 0, idx) 969 } 970 } 971 972 firstCrashExists := func() bool { 973 _, crashKeys, err := queryCrashesForBug(c.ctx, bug.key(c.ctx), 10*totalReported) 974 c.expectOK(err) 975 for _, key := range crashKeys { 976 if key.IntID() == rep.CrashID { 977 return true 978 } 979 } 980 return false 981 } 982 983 // A sanity check for the test itself. 984 if !firstCrashExists() { 985 t.Fatalf("the first reported crash should be present") 986 } 987 988 // Unreport the first crash. 989 reply, _ := c.client.ReportingUpdate(&dashapi.BugUpdate{ 990 ID: rep.ID, 991 Status: dashapi.BugStatusUpdate, 992 ReproLevel: dashapi.ReproLevelC, 993 UnreportCrashIDs: []int64{rep.CrashID}, 994 }) 995 c.expectEQ(reply.OK, true) 996 997 // Trigger more purge events. 998 var moreIterations = maxCrashes() 999 for i := 0; i < moreIterations; i++ { 1000 c.advanceTime(2 * time.Hour) // This ensures that crashes are saved. 1001 crash.ReproSyz = nil 1002 crash.ReproC = nil 1003 crash.ReproOpts = []byte(fmt.Sprintf("%v", i)) 1004 c.client.ReportCrash(crash) 1005 } 1006 // Check that the unreported crash was purged. 1007 if firstCrashExists() { 1008 t.Fatalf("the unreported crash should have been purged") 1009 } 1010 } 1011 1012 func TestManagerFailedBuild(t *testing.T) { 1013 c := NewCtx(t) 1014 defer c.Close() 1015 1016 // Upload and check first build. 1017 build := testBuild(1) 1018 c.client.UploadBuild(build) 1019 checkManagerBuild(c, build, nil, nil) 1020 1021 // Upload and check second build. 1022 build.ID = "id1" 1023 build.KernelCommit = "kern1" 1024 build.SyzkallerCommit = "syz1" 1025 c.client.UploadBuild(build) 1026 checkManagerBuild(c, build, nil, nil) 1027 1028 // Upload failed kernel build. 1029 failedBuild := new(dashapi.Build) 1030 *failedBuild = *build 1031 failedBuild.ID = "id2" 1032 failedBuild.KernelCommit = "kern2" 1033 failedBuild.KernelCommitTitle = "failed build 1" 1034 failedBuild.SyzkallerCommit = "syz2" 1035 c.expectOK(c.client.ReportBuildError(&dashapi.BuildErrorReq{ 1036 Build: *failedBuild, 1037 Crash: dashapi.Crash{ 1038 Title: "failed build 1", 1039 }, 1040 })) 1041 checkManagerBuild(c, build, failedBuild, nil) 1042 1043 // Now the old good build again, nothing should change. 1044 c.client.UploadBuild(build) 1045 checkManagerBuild(c, build, failedBuild, nil) 1046 1047 // New good kernel build, failed build must reset. 1048 build.ID = "id3" 1049 build.KernelCommit = "kern3" 1050 c.client.UploadBuild(build) 1051 checkManagerBuild(c, build, nil, nil) 1052 1053 // Now more complex scenario: OK -> failed kernel -> failed kernel+syzkaller -> failed syzkaller -> OK. 1054 failedBuild.ID = "id4" 1055 failedBuild.KernelCommit = "kern4" 1056 failedBuild.KernelCommitTitle = "failed build 4" 1057 failedBuild.SyzkallerCommit = "syz4" 1058 c.expectOK(c.client.ReportBuildError(&dashapi.BuildErrorReq{ 1059 Build: *failedBuild, 1060 Crash: dashapi.Crash{ 1061 Title: "failed build 4", 1062 }, 1063 })) 1064 checkManagerBuild(c, build, failedBuild, nil) 1065 1066 failedBuild2 := new(dashapi.Build) 1067 *failedBuild2 = *failedBuild 1068 failedBuild2.ID = "id5" 1069 failedBuild2.KernelCommit = "" 1070 failedBuild2.KernelCommitTitle = "failed build 5" 1071 failedBuild2.SyzkallerCommit = "syz5" 1072 c.expectOK(c.client.ReportBuildError(&dashapi.BuildErrorReq{ 1073 Build: *failedBuild2, 1074 Crash: dashapi.Crash{ 1075 Title: "failed build 5", 1076 }, 1077 })) 1078 checkManagerBuild(c, build, failedBuild, failedBuild2) 1079 1080 build.ID = "id6" 1081 build.KernelCommit = "kern6" 1082 c.client.UploadBuild(build) 1083 checkManagerBuild(c, build, nil, failedBuild2) 1084 1085 build.ID = "id7" 1086 build.KernelCommit = "kern6" 1087 build.SyzkallerCommit = "syz7" 1088 c.client.UploadBuild(build) 1089 checkManagerBuild(c, build, nil, nil) 1090 } 1091 1092 func checkManagerBuild(c *Ctx, build, failedKernelBuild, failedSyzBuild *dashapi.Build) { 1093 mgr, dbBuild := c.loadManager("test1", build.Manager) 1094 c.expectEQ(mgr.CurrentBuild, build.ID) 1095 compareBuilds(c, dbBuild, build) 1096 checkBuildBug(c, mgr.FailedBuildBug, failedKernelBuild) 1097 checkBuildBug(c, mgr.FailedSyzBuildBug, failedSyzBuild) 1098 } 1099 1100 func checkBuildBug(c *Ctx, hash string, build *dashapi.Build) { 1101 if build == nil { 1102 c.expectEQ(hash, "") 1103 return 1104 } 1105 c.expectNE(hash, "") 1106 bug, _, dbBuild := c.loadBugByHash(hash) 1107 c.expectEQ(bug.Title, build.KernelCommitTitle) 1108 compareBuilds(c, dbBuild, build) 1109 } 1110 1111 func compareBuilds(c *Ctx, dbBuild *Build, build *dashapi.Build) { 1112 c.expectEQ(dbBuild.ID, build.ID) 1113 c.expectEQ(dbBuild.KernelCommit, build.KernelCommit) 1114 c.expectEQ(dbBuild.SyzkallerCommit, build.SyzkallerCommit) 1115 } 1116 1117 func TestLinkifyReport(t *testing.T) { 1118 input := ` 1119 tipc_topsrv_stop net/tipc/topsrv.c:694 [inline] 1120 tipc_topsrv_exit_net+0x149/0x340 net/tipc/topsrv.c:715 1121 kernel BUG at fs/ext4/inode.c:2753! 1122 pkg/sentry/fsimpl/fuse/fusefs.go:278 +0x384 1123 kvm_vcpu_release+0x4d/0x70 arch/x86/kvm/../../../virt/kvm/kvm_main.c:3713 1124 arch/x86/entry/entry_64.S:298 1125 [<81751700>] (show_stack) from [<8176d3e0>] (dump_stack_lvl+0x48/0x54 lib/dump_stack.c:106) 1126 ` 1127 // nolint: lll 1128 output := ` 1129 tipc_topsrv_stop <a href='https://github.com/google/syzkaller/blob/111222/net/tipc/topsrv.c#L694'>net/tipc/topsrv.c:694</a> [inline] 1130 tipc_topsrv_exit_net+0x149/0x340 <a href='https://github.com/google/syzkaller/blob/111222/net/tipc/topsrv.c#L715'>net/tipc/topsrv.c:715</a> 1131 kernel BUG at <a href='https://github.com/google/syzkaller/blob/111222/fs/ext4/inode.c#L2753'>fs/ext4/inode.c:2753</a>! 1132 <a href='https://github.com/google/syzkaller/blob/111222/pkg/sentry/fsimpl/fuse/fusefs.go#L278'>pkg/sentry/fsimpl/fuse/fusefs.go:278</a> +0x384 1133 kvm_vcpu_release+0x4d/0x70 <a href='https://github.com/google/syzkaller/blob/111222/arch/x86/kvm/../../../virt/kvm/kvm_main.c#L3713'>arch/x86/kvm/../../../virt/kvm/kvm_main.c:3713</a> 1134 <a href='https://github.com/google/syzkaller/blob/111222/arch/x86/entry/entry_64.S#L298'>arch/x86/entry/entry_64.S:298</a> 1135 [<81751700>] (show_stack) from [<8176d3e0>] (dump_stack_lvl+0x48/0x54 <a href='https://github.com/google/syzkaller/blob/111222/lib/dump_stack.c#L106'>lib/dump_stack.c:106</a>) 1136 ` 1137 got := linkifyReport([]byte(input), "https://github.com/google/syzkaller", "111222") 1138 if diff := cmp.Diff(output, string(got)); diff != "" { 1139 t.Fatal(diff) 1140 } 1141 }