// Copyright 2017 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. package main import ( "context" "fmt" "strings" "testing" "time" "github.com/google/syzkaller/dashboard/dashapi" "github.com/google/syzkaller/pkg/email" "github.com/google/syzkaller/sys/targets" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // nolint: funlen func TestEmailReport(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) crash := testCrash(build, 1) crash.Maintainers = []string{`"Foo Bar" `, `bar@foo.com`, `idont@want.EMAILS`} c.client2.ReportCrash(crash) // Report the crash over email and check all fields. var sender0, extBugID0, body0 string var dbBug0 *Bug { msg := c.pollEmailBug() sender0 = msg.Sender body0 = msg.Body sender, extBugID, err := email.RemoveAddrContext(msg.Sender) c.expectOK(err) extBugID0 = extBugID dbBug, dbCrash, dbBuild := c.loadBug(extBugID0) dbBug0 = dbBug crashLogLink := externalLink(c.ctx, textCrashLog, dbCrash.Log) kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig) c.expectEQ(sender, fromAddr(c.ctx)) to := c.config().Namespaces["test2"].Reporting[0].Config.(*EmailConfig).Email c.expectEQ(msg.To, []string{to}) c.expectEQ(msg.Subject, crash.Title) c.expectEQ(len(msg.Attachments), 0) c.expectEQ(msg.Body, fmt.Sprintf(`Hello, syzbot found the following issue on: HEAD commit: 111111111111 kernel_commit_title1 git tree: repo1 branch1 console output: %[2]v kernel config: %[3]v dashboard link: https://testapp.appspot.com/bug?extid=%[1]v compiler: compiler1 CC: [bar@foo.com foo@bar.com idont@want.EMAILS] Unfortunately, I don't have any reproducer for this issue yet. IMPORTANT: if you fix the issue, please add the following tag to the commit: Reported-by: syzbot+%[1]v@testapp.appspotmail.com report1 --- This report is generated by a bot. It may contain errors. See https://goo.gl/tpsmEJ for more information about syzbot. syzbot engineers can be reached at syzkaller@googlegroups.com. syzbot will keep track of this issue. See: https://goo.gl/tpsmEJ#status for how to communicate with syzbot. If the report is already addressed, let syzbot know by replying with: #syz fix: exact-commit-title If you want to overwrite report's subsystems, reply with: #syz set subsystems: new-subsystem (See the list of subsystem names on the web dashboard) If the report is a duplicate of another one, reply with: #syz dup: exact-subject-of-another-report If you want to undo deduplication, reply with: #syz undup`, extBugID0, crashLogLink, kernelConfigLink)) c.checkURLContents(crashLogLink, crash.Log) c.checkURLContents(kernelConfigLink, build.KernelConfig) } // Emulate receive of the report from a mailing list. // This should update the bug with the link/Message-ID. // nolint: lll incoming1 := fmt.Sprintf(`Sender: syzkaller@googlegroups.com Date: Tue, 15 Aug 2017 14:59:00 -0700 Message-ID: <1234> Subject: crash1 From: %v To: foo@bar.com Content-Type: text/plain Hello syzbot will keep track of this issue. If you forgot to add the Reported-by tag, once the fix for this bug is merged into any tree, please reply to this email with: #syz fix: exact-commit-title To mark this as a duplicate of another syzbot report, please reply with: #syz dup: exact-subject-of-another-report If it's a one-off invalid bug report, please reply with: #syz invalid -- You received this message because you are subscribed to the Google Groups "syzkaller" group. To unsubscribe from this group and stop receiving emails from it, send an email to syzkaller+unsubscribe@googlegroups.com. To post to this group, send email to syzkaller@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/syzkaller/1234@google.com. For more options, visit https://groups.google.com/d/optout. `, sender0) _, err := c.POST("/_ah/mail/", incoming1) c.expectOK(err) // Emulate that somebody sends us our own email back without quoting. // We used to extract "#syz fix: exact-commit-title" from it. c.incomingEmail(sender0, body0) c.incomingEmail(sender0, "I don't want emails", EmailOptFrom(`"idont" `)) c.expectNoEmail() // This person sends an email and is listed as a maintainer, but opt-out of emails. // We should not send anything else to them for this bug. Also don't warn about no mailing list in CC. c.incomingEmail(sender0, "#syz uncc", EmailOptFrom(`"IDONT" `), EmailOptCC(nil)) c.expectNoEmail() // Now report syz reproducer and check updated email. build2 := testBuild(10) build2.Arch = targets.I386 build2.KernelRepo, build2.KernelBranch = testConfig.Namespaces["test2"].mainRepoBranch() build2.KernelCommitTitle = "a really long title, longer than 80 chars, really long-long-long-long-long-long title" c.client2.UploadBuild(build2) crash.BuildID = build2.ID crash.ReproOpts = []byte("repro opts") crash.ReproSyz = []byte("getpid()") syzRepro := []byte(fmt.Sprintf("# https://testapp.appspot.com/bug?id=%v\n%s#%s\n%s", dbBug0.keyHash(c.ctx), syzReproPrefix, crash.ReproOpts, crash.ReproSyz)) c.client2.ReportCrash(crash) { msg := c.pollEmailBug() c.expectEQ(msg.Sender, sender0) sender, _, err := email.RemoveAddrContext(msg.Sender) c.expectOK(err) _, dbCrash, dbBuild := c.loadBug(extBugID0) reproSyzLink := externalLink(c.ctx, textReproSyz, dbCrash.ReproSyz) crashLogLink := externalLink(c.ctx, textCrashLog, dbCrash.Log) kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig) c.expectEQ(sender, fromAddr(c.ctx)) to := []string{ "always@cc.me", "bugs2@syzkaller.com", "bugs@syzkaller.com", // This is from incomingEmail. "default@sender.com", // This is from incomingEmail. "foo@bar.com", c.config().Namespaces["test2"].Reporting[0].Config.(*EmailConfig).Email, } c.expectEQ(msg.To, to) c.expectEQ(msg.Subject, "Re: "+crash.Title) c.expectEQ(len(msg.Attachments), 0) c.expectEQ(msg.Headers["In-Reply-To"], []string{"<1234>"}) c.expectEQ(msg.Body, fmt.Sprintf(`syzbot has found a reproducer for the following issue on: HEAD commit: 101010101010 a really long title, longer than 80 chars, re.. git tree: repo10alias console output: %[3]v kernel config: %[4]v dashboard link: https://testapp.appspot.com/bug?extid=%[1]v compiler: compiler10 userspace arch: i386 syz repro: %[2]v CC: [bar@foo.com foo@bar.com maintainers@repo10.org bugs@repo10.org] IMPORTANT: if you fix the issue, please add the following tag to the commit: Reported-by: syzbot+%[1]v@testapp.appspotmail.com report1 --- If you want syzbot to run the reproducer, reply with: #syz test: git://repo/address.git branch-or-commit-hash If you attach or paste a git patch, syzbot will apply it before testing. `, extBugID0, reproSyzLink, crashLogLink, kernelConfigLink)) c.checkURLContents(reproSyzLink, syzRepro) c.checkURLContents(crashLogLink, crash.Log) c.checkURLContents(kernelConfigLink, build2.KernelConfig) } // Now upstream the bug and check that it reaches the next reporting. c.incomingEmail(sender0, "#syz upstream") sender1, extBugID1 := "", "" { msg := c.pollEmailBug() sender1 = msg.Sender c.expectNE(sender1, sender0) sender, extBugID, err := email.RemoveAddrContext(msg.Sender) c.expectOK(err) extBugID1 = extBugID _, dbCrash, dbBuild := c.loadBug(extBugID1) reproSyzLink := externalLink(c.ctx, textReproSyz, dbCrash.ReproSyz) crashLogLink := externalLink(c.ctx, textCrashLog, dbCrash.Log) kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig) c.expectEQ(sender, fromAddr(c.ctx)) c.expectEQ(msg.To, []string{ "always@cc.me", "bar@foo.com", "bugs@repo10.org", "bugs@syzkaller.com", "default@maintainers.com", "foo@bar.com", "maintainers@repo10.org", }) c.expectEQ(msg.Subject, "[syzbot] "+crash.Title) c.expectEQ(len(msg.Attachments), 0) c.expectEQ(msg.Body, fmt.Sprintf(`Hello, syzbot found the following issue on: HEAD commit: 101010101010 a really long title, longer than 80 chars, re.. git tree: repo10alias console output: %[3]v kernel config: %[4]v dashboard link: https://testapp.appspot.com/bug?extid=%[1]v compiler: compiler10 userspace arch: i386 syz repro: %[2]v CC: [bar@foo.com foo@bar.com maintainers@repo10.org bugs@repo10.org] IMPORTANT: if you fix the issue, please add the following tag to the commit: Reported-by: syzbot+%[1]v@testapp.appspotmail.com report1 --- This report is generated by a bot. It may contain errors. See https://goo.gl/tpsmEJ for more information about syzbot. syzbot engineers can be reached at syzkaller@googlegroups.com. syzbot will keep track of this issue. See: https://goo.gl/tpsmEJ#status for how to communicate with syzbot. If the report is already addressed, let syzbot know by replying with: #syz fix: exact-commit-title If you want syzbot to run the reproducer, reply with: #syz test: git://repo/address.git branch-or-commit-hash If you attach or paste a git patch, syzbot will apply it before testing. If you want to overwrite report's subsystems, reply with: #syz set subsystems: new-subsystem (See the list of subsystem names on the web dashboard) If the report is a duplicate of another one, reply with: #syz dup: exact-subject-of-another-report If you want to undo deduplication, reply with: #syz undup`, extBugID1, reproSyzLink, crashLogLink, kernelConfigLink)) c.checkURLContents(reproSyzLink, syzRepro) c.checkURLContents(crashLogLink, crash.Log) c.checkURLContents(kernelConfigLink, build2.KernelConfig) } // Model that somebody adds more emails to CC list. incoming3 := fmt.Sprintf(`Sender: syzkaller@googlegroups.com Date: Tue, 15 Aug 2017 14:59:00 -0700 Message-ID: <1234> Subject: crash1 From: foo@bar.com To: %v CC: new@new.com, "another" , bar@foo.com, bugs@syzkaller.com, foo@bar.com Content-Type: text/plain +more people `, sender1) _, err = c.POST("/_ah/mail/", incoming3) c.expectOK(err) // Now upload a C reproducer. crash.ReproC = []byte("int main() {}") crash.Maintainers = []string{"\"qux\" "} c.client2.ReportCrash(crash) cRepro := []byte(fmt.Sprintf("// https://testapp.appspot.com/bug?id=%v\n%s", dbBug0.keyHash(c.ctx), crash.ReproC)) { msg := c.pollEmailBug() c.expectEQ(msg.Sender, sender1) sender, _, err := email.RemoveAddrContext(msg.Sender) c.expectOK(err) _, dbCrash, dbBuild := c.loadBug(extBugID1) reproCLink := externalLink(c.ctx, textReproC, dbCrash.ReproC) reproSyzLink := externalLink(c.ctx, textReproSyz, dbCrash.ReproSyz) crashLogLink := externalLink(c.ctx, textCrashLog, dbCrash.Log) kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig) c.expectEQ(sender, fromAddr(c.ctx)) c.expectEQ(msg.To, []string{ "always@cc.me", "another@another.com", "bar@foo.com", "bugs@repo10.org", "bugs@syzkaller.com", "default@maintainers.com", "foo@bar.com", "maintainers@repo10.org", "new@new.com", "qux@qux.com"}) c.expectEQ(msg.Subject, "Re: [syzbot] "+crash.Title) c.expectEQ(len(msg.Attachments), 0) c.expectEQ(msg.Body, fmt.Sprintf(`syzbot has found a reproducer for the following issue on: HEAD commit: 101010101010 a really long title, longer than 80 chars, re.. git tree: repo10alias console output: %[4]v kernel config: %[5]v dashboard link: https://testapp.appspot.com/bug?extid=%[1]v compiler: compiler10 userspace arch: i386 syz repro: %[3]v C reproducer: %[2]v CC: [qux@qux.com maintainers@repo10.org bugs@repo10.org] IMPORTANT: if you fix the issue, please add the following tag to the commit: Reported-by: syzbot+%[1]v@testapp.appspotmail.com report1 --- If you want syzbot to run the reproducer, reply with: #syz test: git://repo/address.git branch-or-commit-hash If you attach or paste a git patch, syzbot will apply it before testing. `, extBugID1, reproCLink, reproSyzLink, crashLogLink, kernelConfigLink)) c.checkURLContents(reproCLink, cRepro) c.checkURLContents(reproSyzLink, syzRepro) c.checkURLContents(crashLogLink, crash.Log) c.checkURLContents(kernelConfigLink, build2.KernelConfig) } // Send an invalid command. incoming4 := fmt.Sprintf(`Sender: syzkaller@googlegroups.com Date: Tue, 15 Aug 2017 14:59:00 -0700 Message-ID: Subject: title1 From: foo@bar.com To: %v Content-Type: text/plain #syz bad-command `, sender1) _, err = c.POST("/_ah/mail/", incoming4) c.expectOK(err) { msg := c.pollEmailBug() c.expectEQ(msg.To, []string{"foo@bar.com"}) c.expectEQ(msg.Subject, "Re: title1") c.expectEQ(msg.Headers["In-Reply-To"], []string{""}) if !strings.Contains(msg.Body, `> #syz bad-command unknown command "bad-command" `) { t.Fatal("no unknown command reply for bad command") } } // Now mark the bug as fixed. c.incomingEmail(sender1, "#syz fix: some: commit title", EmailOptCC([]string{"bugs@syzkaller.com", "default@maintainers.com"}), EmailOptSubject("fix bug title")) // Check that the commit is now passed to builders. builderPollResp, _ := c.client2.BuilderPoll(build.Manager) c.expectEQ(len(builderPollResp.PendingCommits), 1) c.expectEQ(builderPollResp.PendingCommits[0], "some: commit title") build3 := testBuild(3) build3.Manager = build.Manager build3.Commits = []string{"some: commit title"} c.client2.UploadBuild(build3) build4 := testBuild(4) build4.Manager = build2.Manager build4.Commits = []string{"some: commit title"} c.client2.UploadBuild(build4) // New crash must produce new bug in the first reporting. c.client2.ReportCrash(crash) { msg := c.pollEmailBug() c.expectEQ(msg.Subject, crash.Title+" (2)") c.expectNE(msg.Sender, sender0) } } // Bug must not be mailed to maintainers if maintainers list is empty. func TestEmailNoMaintainers(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) crash := testCrash(build, 1) c.client2.ReportCrash(crash) sender := c.pollEmailBug().Sender incoming1 := fmt.Sprintf(`Sender: syzkaller@googlegroups.com Date: Tue, 15 Aug 2017 14:59:00 -0700 Message-ID: <1234> Subject: crash1 From: %v To: foo@bar.com Content-Type: text/plain #syz upstream `, sender) _, err := c.POST("/_ah/mail/", incoming1) c.expectOK(err) } // Basic dup scenario: mark one bug as dup of another. func TestEmailDup(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) crash1 := testCrash(build, 1) crash1.Title = "BUG: slightly more elaborate title" c.client2.ReportCrash(crash1) crash2 := testCrash(build, 2) crash2.Title = "KASAN: another title" c.client2.ReportCrash(crash2) msg1 := c.pollEmailBug() msg2 := c.pollEmailBug() // Dup crash2 to crash1. c.incomingEmail(msg2.Sender, "#syz dup: BUG: slightly more elaborate title") c.expectNoEmail() // Second crash happens again. crash2.ReproC = []byte("int main() {}") c.client2.ReportCrash(crash2) c.expectNoEmail() // Now close the original bug, and check that new bugs for dup are now created. c.incomingEmail(msg1.Sender, "#syz invalid") // "uncc" command must not trugger error reply even for closed bug. c.incomingEmail(msg1.Sender, "#syz uncc", EmailOptCC(nil)) c.expectNoEmail() // New crash must produce new bug in the first reporting. c.client2.ReportCrash(crash2) { msg := c.pollEmailBug() c.expectEQ(msg.Subject, crash2.Title+" (2)") } } func TestEmailDup2(t *testing.T) { for i := 0; i < 4; i++ { t.Run(fmt.Sprint(i), func(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) crash1 := testCrash(build, 1) crash1.Title = "BUG: something bad" c.client2.ReportCrash(crash1) msg1 := c.pollEmailBug() c.incomingEmail(msg1.Sender, "#syz upstream") msg1 = c.pollEmailBug() c.expectEQ(msg1.Subject, "[syzbot] BUG: something bad") crash2 := testCrash(build, 2) crash2.Title = "KASAN: another bad thing" c.client2.ReportCrash(crash2) msg2 := c.pollEmailBug() c.incomingEmail(msg2.Sender, "#syz upstream") msg2 = c.pollEmailBug() c.expectEQ(msg2.Subject, "[syzbot] KASAN: another bad thing") cc := EmailOptCC([]string{"bugs@syzkaller.com", "default@maintainers.com"}) switch i { case 0: c.incomingEmail(msg2.Sender, "#syz dup: BUG: something bad", cc) case 1: c.incomingEmail(msg2.Sender, "#syz dup: [syzbot] BUG: something bad", cc) case 2: c.incomingEmail(msg2.Sender, "#syz dup: [syzbot] [subsystemA?] BUG: something bad", cc) default: c.incomingEmail(msg2.Sender, "#syz dup: syzbot: BUG: something bad", cc) reply := c.pollEmailBug() c.expectTrue(strings.Contains(reply.Body, "can't find the dup bug")) } }) } } func TestEmailUndup(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) crash1 := testCrash(build, 1) crash1.Title = "BUG: slightly more elaborate title" c.client2.ReportCrash(crash1) crash2 := testCrash(build, 2) crash1.Title = "KASAN: another title" c.client2.ReportCrash(crash2) msg1 := c.pollEmailBug() msg2 := c.pollEmailBug() // Dup crash2 to crash1. c.incomingEmail(msg2.Sender, "#syz dup BUG: slightly more elaborate title") c.expectNoEmail() // Undup crash2. c.incomingEmail(msg2.Sender, "#syz undup") c.expectNoEmail() // Now close the original bug, and check that new crashes for the dup does not create bugs. c.incomingEmail(msg1.Sender, "#syz invalid") c.client2.ReportCrash(crash2) c.expectNoEmail() } func TestEmailCrossReportingDup(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) tests := []struct { bug int dup int result bool }{ {0, 0, true}, {0, 1, false}, {0, 2, false}, {1, 0, false}, {1, 1, true}, {1, 2, true}, {2, 0, false}, {2, 1, false}, {2, 2, true}, } for i, test := range tests { t.Logf("duping %v->%v, expect %v", test.bug, test.dup, test.result) c.advanceTime(24 * time.Hour) // to not hit email limit per day crash1 := testCrash(build, 1) crash1.Title = fmt.Sprintf("bug_%v", i) c.client2.ReportCrash(crash1) bugSender := c.pollEmailBug().Sender cc := EmailOptCC([]string{"default@maintainers.com", "test@syzkaller.com", "bugs@syzkaller.com", "default2@maintainers.com", "bugs2@syzkaller.com"}) for j := 0; j < test.bug; j++ { c.incomingEmail(bugSender, "#syz upstream", cc) bugSender = c.pollEmailBug().Sender } crash2 := testCrash(build, 2) crash2.Title = fmt.Sprintf("dup_%v", i) c.client2.ReportCrash(crash2) dupSender := c.pollEmailBug().Sender for j := 0; j < test.dup; j++ { c.incomingEmail(dupSender, "#syz upstream", cc) dupSender = c.pollEmailBug().Sender } c.incomingEmail(bugSender, "#syz dup: "+crash2.Title, cc) if test.result { c.expectNoEmail() } else { msg := c.pollEmailBug() if !strings.Contains(msg.Body, "> #syz dup:") || !strings.Contains(msg.Body, "Can't dup bug to a bug in different reporting") { c.t.Fatalf("bad reply body:\n%v", msg.Body) } } } } func TestEmailErrors(t *testing.T) { c := NewCtx(t) defer c.Close() // No reply for email without bug hash and no commands. c.incomingEmail("syzbot@testapp.appspotmail.com", "Investment Proposal") c.expectNoEmail() // If email contains a command we need to reply. c.incomingEmail("syzbot@testapp.appspotmail.com", "#syz invalid") reply := c.pollEmailBug() c.expectEQ(reply.To, []string{"default@sender.com"}) c.expectEQ(reply.Body, `> #syz invalid I see the command but can't find the corresponding bug. Please resend the email to syzbot+HASH@testapp.appspotmail.com address that is the sender of the bug report (also present in the Reported-by tag). `) c.incomingEmail("syzbot+123@testapp.appspotmail.com", "#syz invalid") reply = c.pollEmailBug() c.expectEQ(reply.Body, `> #syz invalid I see the command but can't find the corresponding bug. The email is sent to syzbot+HASH@testapp.appspotmail.com address but the HASH does not correspond to any known bug. Please double check the address. `) } func TestEmailFailedBuild(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) failedBuild := testBuild(10) failedBuild.KernelRepo, failedBuild.KernelBranch = testConfig.Namespaces["test2"].mainRepoBranch() failedBuild.KernelCommit = "kern2" failedBuild.KernelCommitTitle = "failed build 1" failedBuild.SyzkallerCommit = "syz2" buildErrorReq := &dashapi.BuildErrorReq{ Build: *failedBuild, Crash: dashapi.Crash{ Title: "failed build 1", Report: []byte("report line 1\nreport line 2\n"), Log: []byte("log line 1\nlog line 2\n"), Maintainers: []string{"maintainer@crash"}, }, } c.expectOK(c.client2.ReportBuildError(buildErrorReq)) msg := c.pollEmailBug() sender, extBugID, err := email.RemoveAddrContext(msg.Sender) c.expectOK(err) _, dbCrash, dbBuild := c.loadBug(extBugID) crashLogLink := externalLink(c.ctx, textCrashLog, dbCrash.Log) kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig) c.expectEQ(sender, fromAddr(c.ctx)) c.expectEQ(msg.To, []string{ "always@cc.me", "test@syzkaller.com", }) c.expectEQ(msg.Subject, buildErrorReq.Crash.Title) c.expectEQ(len(msg.Attachments), 0) c.expectEQ(msg.Body, fmt.Sprintf(`Hello, syzbot found the following issue on: HEAD commit: kern2 failed build 1 git tree: repo10alias console output: %[2]v kernel config: %[3]v dashboard link: https://testapp.appspot.com/bug?extid=%[1]v compiler: compiler10 CC: [maintainer@crash maintainers@repo10.org bugs@repo10.org build-maintainers@repo10.org] IMPORTANT: if you fix the issue, please add the following tag to the commit: Reported-by: syzbot+%[1]v@testapp.appspotmail.com report line 1 report line 2 --- This report is generated by a bot. It may contain errors. See https://goo.gl/tpsmEJ for more information about syzbot. syzbot engineers can be reached at syzkaller@googlegroups.com. syzbot will keep track of this issue. See: https://goo.gl/tpsmEJ#status for how to communicate with syzbot. If the report is already addressed, let syzbot know by replying with: #syz fix: exact-commit-title If you want to overwrite report's subsystems, reply with: #syz set subsystems: new-subsystem (See the list of subsystem names on the web dashboard) If the report is a duplicate of another one, reply with: #syz dup: exact-subject-of-another-report If you want to undo deduplication, reply with: #syz undup`, extBugID, crashLogLink, kernelConfigLink)) } // Test for unfix command which should unmark a bug as fixed by any commits. func TestEmailUnfix(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) crash := testCrash(build, 1) c.client2.ReportCrash(crash) msg := c.pollEmailBug() c.incomingEmail(msg.Sender, "#syz fix: some commit") c.expectNoEmail() c.incomingEmail(msg.Sender, "#syz unfix") c.expectNoEmail() build2 := testBuild(2) build2.Manager = build.Manager build2.Commits = []string{"some commit"} c.client2.UploadBuild(build2) // The bug should be still unfixed, since we unmarked it. c.client2.ReportCrash(crash) c.expectNoEmail() } func TestEmailManagerCC(t *testing.T) { c := NewCtx(t) defer c.Close() // Test that we add manager CC. build1 := testBuild(1) build1.Manager = specialCCManager c.client2.UploadBuild(build1) crash := testCrash(build1, 1) c.client2.ReportCrash(crash) msg := c.pollEmailBug() c.expectEQ(msg.To, []string{ "always@manager.org", "test@syzkaller.com", }) // Test that we add manager maintainers. c.incomingEmail(msg.Sender, "#syz upstream") msg = c.pollEmailBug() c.expectEQ(msg.To, []string{ "always@manager.org", "bugs@syzkaller.com", "default@maintainers.com", "maintainers@manager.org", }) // Test that we add manager build maintainers. build2 := testBuild(2) build2.Manager = specialCCManager buildErrorReq := &dashapi.BuildErrorReq{ Build: *build2, Crash: dashapi.Crash{ Title: "failed build 1", Report: []byte("report\n"), Log: []byte("log\n"), }, } c.expectOK(c.client2.ReportBuildError(buildErrorReq)) msg = c.pollEmailBug() c.expectEQ(msg.To, []string{ "always@manager.org", "test@syzkaller.com", }) c.incomingEmail(msg.Sender, "#syz upstream") msg = c.pollEmailBug() c.expectEQ(msg.To, []string{ "always@manager.org", "bugs@syzkaller.com", "build-maintainers@manager.org", "default@maintainers.com", "maintainers@manager.org", }) // Test that we don't add manager CC when the crash happened on 1+ managers. build3 := testBuild(3) build1.Manager = specialCCManager c.client2.UploadBuild(build3) crash = testCrash(build3, 2) c.client2.ReportCrash(crash) build4 := testBuild(4) c.client2.UploadBuild(build4) crash = testCrash(build4, 2) c.client2.ReportCrash(crash) msg = c.pollEmailBug() c.expectEQ(msg.To, []string{ "test@syzkaller.com", }) c.incomingEmail(msg.Sender, "#syz upstream") msg = c.pollEmailBug() c.expectEQ(msg.To, []string{ "bugs@syzkaller.com", "default@maintainers.com", }) } func TestStraceReport(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) crash := testCrash(build, 1) crash.Flags = dashapi.CrashUnderStrace crash.Maintainers = []string{`"Foo Bar" `, `bar@foo.com`, `idont@want.EMAILS`} c.client2.ReportCrash(crash) // Report the crash over email and check all fields. msg := c.pollEmailBug() _, extBugID, err := email.RemoveAddrContext(msg.Sender) c.expectOK(err) _, dbCrash, dbBuild := c.loadBug(extBugID) crashLogLink := externalLink(c.ctx, textCrashLog, dbCrash.Log) kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig) c.expectEQ(msg.Body, fmt.Sprintf(`Hello, syzbot found the following issue on: HEAD commit: 111111111111 kernel_commit_title1 git tree: repo1 branch1 console+strace: %[2]v kernel config: %[3]v dashboard link: https://testapp.appspot.com/bug?extid=%[1]v compiler: compiler1 CC: [bar@foo.com foo@bar.com idont@want.EMAILS] Unfortunately, I don't have any reproducer for this issue yet. IMPORTANT: if you fix the issue, please add the following tag to the commit: Reported-by: syzbot+%[1]v@testapp.appspotmail.com report1 --- This report is generated by a bot. It may contain errors. See https://goo.gl/tpsmEJ for more information about syzbot. syzbot engineers can be reached at syzkaller@googlegroups.com. syzbot will keep track of this issue. See: https://goo.gl/tpsmEJ#status for how to communicate with syzbot. If the report is already addressed, let syzbot know by replying with: #syz fix: exact-commit-title If you want to overwrite report's subsystems, reply with: #syz set subsystems: new-subsystem (See the list of subsystem names on the web dashboard) If the report is a duplicate of another one, reply with: #syz dup: exact-subject-of-another-report If you want to undo deduplication, reply with: #syz undup`, extBugID, crashLogLink, kernelConfigLink)) c.checkURLContents(crashLogLink, crash.Log) } func TestSubjectTitleParser(t *testing.T) { tests := []struct { inSubject string outTitle string outSeq int64 }{ { inSubject: "Re: kernel BUG in blk_mq_dispatch_rq_list (4)", outTitle: "kernel BUG in blk_mq_dispatch_rq_list", outSeq: 3, }, { inSubject: "Re: [syzbot] kernel BUG in blk_mq_dispatch_rq_list (4)", outTitle: "kernel BUG in blk_mq_dispatch_rq_list", outSeq: 3, }, { // Make sure we always take the (number) at the end. inSubject: "Re: kernel BUG in blk_mq_dispatch_rq_list(6) (4)", outTitle: "kernel BUG in blk_mq_dispatch_rq_list(6)", outSeq: 3, }, { inSubject: "RE: kernel BUG in blk_mq_dispatch_rq_list", outTitle: "kernel BUG in blk_mq_dispatch_rq_list", outSeq: 0, }, { // Make sure we trim the title. inSubject: "RE: kernel BUG in blk_mq_dispatch_rq_list ", outTitle: "kernel BUG in blk_mq_dispatch_rq_list", outSeq: 0, }, { inSubject: "Re: ", outTitle: "", outSeq: 0, }, { inSubject: "Re: [syzbot]", outTitle: "", outSeq: 0, }, { inSubject: "Re: [syzbot] [subsystemA?]", outTitle: "", outSeq: 0, }, { // Make sure we delete filesystem tags. inSubject: "Re: [syzbot] [ntfs3?] [ext4?] kernel BUG in blk_mq_dispatch_rq_list (4)", outTitle: "kernel BUG in blk_mq_dispatch_rq_list", outSeq: 3, }, } p := makeSubjectTitleParser(context.Background()) for _, test := range tests { title, seq, err := p.parseTitle(test.inSubject) if test.outTitle == "" { if err == nil { t.Fatalf("subj: %q, expected error, got none (%q)", test.inSubject, title) } } else if title != test.outTitle { t.Fatalf("subj: %q, expected title=%q, got %q", test.inSubject, test.outTitle, title) } else if seq != test.outSeq { t.Fatalf("subj: %q, expected seq=%q, got %q", test.inSubject, test.outSeq, seq) } } } func TestBugFromSubjectInference(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.makeClient(clientPublicEmail, keyPublicEmail, true) client2 := c.makeClient(clientPublicEmail2, keyPublicEmail2, true) build := testBuild(1) client.UploadBuild(build) build2 := testBuild(2) client2.UploadBuild(build2) const crashTitle = "WARNING in corrupted" upstreamCrash := func(client *apiClient, build *dashapi.Build, title string) string { // Upload some garbage crashes. crash := testCrash(build, 1) crash.Title = title crash.Log = []byte(fmt.Sprintf("log%v", title)) crash.Maintainers = []string{"maintainer@kernel.org"} client.ReportCrash(crash) sender := c.pollEmailBug().Sender c.incomingEmail(sender, "#syz upstream\n") return c.pollEmailBug().Sender } upstreamCrash(client, build, "unrelated crash") origSender := upstreamCrash(client, build, crashTitle) upstreamCrash(client, build, "unrelated crash 2") mailingList := "<" + c.config().Namespaces["access-public-email"].Reporting[0].Config.(*EmailConfig).Email + ">" // First try to ping some non-existing bug. subject := "Re: unknown-bug" c.incomingEmail("bugs@syzkaller.com", syzTestGitBranchSamplePatch, EmailOptOrigFrom("test@requester.com"), EmailOptFrom(mailingList), EmailOptSubject(subject), ) syzbotReply := c.pollEmailBug() c.expectNE(syzbotReply.Sender, origSender) c.expectEQ(strings.Contains(syzbotReply.Body, "can't find the corresponding bug"), true) // Now try to test the exiting bug, but with the wrong mailing list. subject = "Re: " + crashTitle c.incomingEmail("bugs@syzkaller.com", syzTestGitBranchSamplePatch, EmailOptOrigFrom("test@requester.com"), EmailOptFrom(""), EmailOptSubject(subject), ) body := c.pollEmailBug().Body c.expectEQ(strings.Contains(body, "can't find the corresponding bug"), true) // Now try to test the exiting bug with the proper mailing list. c.incomingEmail("bugs@syzkaller.com", syzTestGitBranchSamplePatch, EmailOptFrom(mailingList), EmailOptOrigFrom("test@requester.com"), EmailOptSubject(subject), ) syzbotReply = c.pollEmailBug() c.expectEQ(syzbotReply.Sender, origSender) c.expectEQ(strings.Contains(syzbotReply.Body, "This crash does not have a reproducer"), true) // Test that a different type of email headers is also parsed fine. c.incomingEmail("bugs@syzkaller.com", syzTestGitBranchSamplePatch, EmailOptSender(mailingList), EmailOptFrom("test@requester.com"), EmailOptSubject(subject), ) body = c.pollEmailBug().Body c.expectEQ(strings.Contains(body, "This crash does not have a reproducer"), true) // Upstream a same-titled bug in another namespace. upstreamCrash(client2, build2, crashTitle) // Ensure that the inference fails with the proper title. c.incomingEmail("bugs@syzkaller.com", syzTestGitBranchSamplePatch, EmailOptSender(mailingList), EmailOptFrom("test@requester.com"), EmailOptSubject(subject), ) body = c.pollEmailBug().Body c.expectEQ(strings.Contains(body, "Several bugs with the exact same title"), true) // Close the existing bug. c.incomingEmail("bugs@syzkaller.com", "#syz invalid", EmailOptFrom("test@requester.com"), EmailOptSubject(subject), EmailOptCC([]string{mailingList, origSender}), ) c.expectNoEmail() // Create the (2) of the bug. upstreamCrash(client, build, crashTitle) // Make sure syzbot can understand the (2) version. subject = "Re: " + crashTitle + " (2)" c.incomingEmail("bugs@syzkaller.com", syzTestGitBranchSamplePatch, EmailOptFrom(mailingList), EmailOptOrigFrom(""), EmailOptSubject(subject), ) email := c.pollEmailBug() c.expectEQ(email.To, []string{"test@requester.com"}) c.expectEQ(strings.Contains(email.Body, "This crash does not have a reproducer"), true) } func TestEmailLinks(t *testing.T) { c := NewCtx(t) defer c.Close() build := testBuild(1) c.client2.UploadBuild(build) crash := testCrash(build, 1) crash.Maintainers = []string{`"Foo Bar" `} c.client2.ReportCrash(crash) // Report the crash over email. msg := c.pollEmailBug() // Emulate receive of the report from a mailing list. // This should update the bug with the link/Message-ID. // nolint: lll incoming1 := fmt.Sprintf(`Sender: syzkaller@googlegroups.com Date: Tue, 15 Aug 2017 14:59:00 -0700 Message-ID: <1234> Subject: crash1 From: %v To: foo@bar.com Content-Type: text/plain Hello syzbot will keep track of this issue. If you forgot to add the Reported-by tag, once the fix for this bug is merged into any tree, please reply to this email with: #syz fix: exact-commit-title To mark this as a duplicate of another syzbot report, please reply with: #syz dup: exact-subject-of-another-report If it's a one-off invalid bug report, please reply with: #syz invalid -- You received this message because you are subscribed to the Google Groups "syzkaller" group. To unsubscribe from this group and stop receiving emails from it, send an email to syzkaller+unsubscribe@googlegroups.com. To post to this group, send email to syzkaller@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/syzkaller/1234@google.com. For more options, visit https://groups.google.com/d/optout. `, msg.Sender) _, err := c.POST("/_ah/mail/", incoming1) c.expectOK(err) _, extBugID, err := email.RemoveAddrContext(msg.Sender) c.expectOK(err) // Make sure Link is set for the last Reporting. dbBug, _, _ := c.loadBug(extBugID) reporting := lastReportedReporting(dbBug) c.expectNE(reporting.Link, "") } func TestEmailPatchTestingAccess(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.client2 build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 1) client.ReportCrash(crash) sender := c.pollEmailBug().Sender c.incomingEmail(sender, syzTestGitBranchSamplePatch, EmailOptFrom("user@kernel.org"), EmailOptSubject("Re: "+crash.Title), ) // We expect syzbot to just ignore this patch testing request. c.expectNoEmail() // The patch test job should also not be created. pollResp := client.pollJobs(build.Manager) c.expectEQ(pollResp.ID, "") } func TestEmailSetInvalidSubsystems(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.makeClient(clientPublicEmail, keyPublicEmail, true) mailingList := c.config().Namespaces["access-public-email"].Reporting[0].Config.(*EmailConfig).Email build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 1) client.ReportCrash(crash) c.incomingEmail(c.pollEmailBug().Sender, "#syz upstream\n") sender := c.pollEmailBug().Sender // Invalid subsystem name. c.incomingEmail(sender, "#syz set subsystems: non-existent", EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) c.expectEQ(c.pollEmailBug().Body, `> #syz set subsystems: non-existent The specified label value is incorrect. "non-existent" is not among the allowed values. Please use one of the supported label values. The following labels are suported: missing-backport, no-reminders, prio: {low, normal, high}, subsystems: {.. see below ..} The list of subsystems: https://testapp.appspot.com/access-public-email/subsystems?all=true `) } func TestEmailSetSubsystems(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.makeClient(clientPublicEmail, keyPublicEmail, true) mailingList := c.config().Namespaces["access-public-email"].Reporting[0].Config.(*EmailConfig).Email build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 1) client.ReportCrash(crash) c.incomingEmail(c.pollEmailBug().Sender, "#syz upstream\n") sender := c.pollEmailBug().Sender _, extBugID, err := email.RemoveAddrContext(sender) c.expectOK(err) // At the beginning, there are no subsystems. expectLabels(t, client, extBugID) // Set one subsystem. c.incomingEmail(sender, "#syz set subsystems: subsystemA\n", EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) expectLabels(t, client, extBugID, "subsystems:subsystemA") // Set two subsystems. c.incomingEmail(sender, "#syz set subsystems: subsystemA, subsystemB\n", EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) expectLabels(t, client, extBugID, "subsystems:subsystemA", "subsystems:subsystemB") } func TestEmailBugLabels(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.makeClient(clientPublicEmail, keyPublicEmail, true) mailingList := c.config().Namespaces["access-public-email"].Reporting[0].Config.(*EmailConfig).Email build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 1) client.ReportCrash(crash) sender := c.pollEmailBug().Sender _, extBugID, err := email.RemoveAddrContext(sender) c.expectOK(err) // At the beginning, there are no tags. expectLabels(t, client, extBugID) // Set a tag. c.incomingEmail(sender, "#syz set prio: low\n", EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) expectLabels(t, client, extBugID, "prio:low") // Notice that medium prio supercedes low prio since they are of the oneOf type. c.incomingEmail(sender, "#syz set prio: high\n", EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) expectLabels(t, client, extBugID, "prio:high") // Also set a flag label. c.incomingEmail(sender, "#syz set no-reminders\n", EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) expectLabels(t, client, extBugID, "prio:high", "no-reminders") // Remove a tag. c.incomingEmail(sender, "#syz unset prio\n", EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) expectLabels(t, client, extBugID, "no-reminders") // Remove another tag. c.incomingEmail(sender, "#syz unset no-reminders\n", EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) expectLabels(t, client, extBugID) } func TestInvalidEmailBugLabels(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.makeClient(clientPublicEmail, keyPublicEmail, true) mailingList := c.config().Namespaces["access-public-email"].Reporting[0].Config.(*EmailConfig).Email build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 1) client.ReportCrash(crash) c.incomingEmail(c.pollEmailBug().Sender, "#syz upstream\n") sender := c.pollEmailBug().Sender // Non-existing label. c.incomingEmail(sender, "#syz set label: tag", EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) body := c.pollEmailBug().Body c.expectEQ(body, `> #syz set label: tag The specified label "label" is unknown. Please use one of the supported labels. The following labels are suported: missing-backport, no-reminders, prio: {low, normal, high}, subsystems: {.. see below ..} The list of subsystems: https://testapp.appspot.com/access-public-email/subsystems?all=true `) // Existing label, wrong value. c.incomingEmail(sender, "#syz set prio: unknown\n", EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) c.expectEQ(strings.Contains(c.pollEmailBug().Body, `The specified label value is incorrect. "unknown" is not among the allowed values`), true) // Existing label, too many values. c.incomingEmail(sender, "#syz set prio: low, high\n", EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) c.expectEQ(strings.Contains(c.pollEmailBug().Body, `The specified label value is incorrect. You must specify only one of the allowed values.`), true) // Removing a non-existing label. c.incomingEmail(sender, "#syz unset tag2\n", EmailOptFrom("test@requester.com"), EmailOptCC([]string{mailingList})) syzbotReply := c.pollEmailBug() c.expectEQ(strings.Contains(syzbotReply.Body, "The following labels did not exist: tag2"), true) } func expectLabels(t *testing.T, client *apiClient, extID string, labels ...string) { t.Helper() bug, _, _ := client.Ctx.loadBug(extID) names := []string{} for _, item := range bug.Labels { names = append(names, item.String()) } assert.ElementsMatch(t, names, labels) } var forwardEmailConfig = EmailConfig{ Email: "test@syzkaller.com", HandleListEmails: true, SubjectPrefix: "[syzbot]", MailMaintainers: true, DefaultMaintainers: []string{"some@list.com"}, } func TestSingleListForward(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.makeClient(clientPublicEmail, keyPublicEmail, true) c.updateReporting("access-public-email", "access-public-email-reporting1", func(r Reporting) Reporting { r.Config = &forwardEmailConfig return r }) build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 1) client.ReportCrash(crash) sender := c.pollEmailBug().Sender c.incomingEmail(sender, "#syz fix: some: commit title", EmailOptCC([]string{"some@list.com"}), EmailOptSubject("fix bug title")) forwarded := c.pollEmailBug() c.expectEQ(forwarded.Subject, "Forwarded: fix bug title") c.expectEQ(forwarded.Sender, sender) c.expectEQ(forwarded.To, []string{"test@syzkaller.com"}) c.expectEQ(len(forwarded.Cc), 0) c.expectEQ(forwarded.Body, `For archival purposes, forwarding an incoming command email to test@syzkaller.com. *** Subject: fix bug title Author: default@sender.com #syz fix: some: commit title `) } func TestTwoListsForward(t *testing.T) { c := NewCtx(t) defer c.Close() client := c.makeClient(clientPublicEmail, keyPublicEmail, true) c.updateReporting("access-public-email", "access-public-email-reporting1", func(r Reporting) Reporting { r.Config = &forwardEmailConfig return r }) build := testBuild(1) client.UploadBuild(build) crash := testCrash(build, 1) client.ReportCrash(crash) sender := c.pollEmailBug().Sender c.incomingEmail(sender, "#syz fix: some: commit title", EmailOptCC(nil), EmailOptSubject("fix bug title")) forwarded := c.pollEmailBug() c.expectEQ(forwarded.Subject, "Forwarded: fix bug title") c.expectEQ(forwarded.Sender, sender) c.expectEQ(forwarded.To, []string{"some@list.com", "test@syzkaller.com"}) c.expectEQ(len(forwarded.Cc), 0) c.expectEQ(forwarded.Body, `For archival purposes, forwarding an incoming command email to some@list.com, test@syzkaller.com. *** Subject: fix bug title Author: default@sender.com #syz fix: some: commit title `) } func TestForwardEmailInbox(t *testing.T) { c := NewCtx(t) defer c.Close() c.transformContext = func(c context.Context) context.Context { newConfig := *getConfig(c) newConfig.MonitoredInboxes = []*PerInboxConfig{ { InboxRe: `^syzbot\+prefix.*@testapp\.appspotmail\.com$`, ForwardTo: []string{`forward@a.com`, `forward@b.com`}, }, } return contextWithConfig(c, &newConfig) } t.Run("forwarded", func(t *testing.T) { from := "syzbot+prefixABCD@testapp.appspotmail.com" c.incomingEmail(from, "#syz invalid", EmailOptSubject("test subject"), EmailOptMessageID(1), EmailOptFrom("someone@mail.com"), EmailOptCC([]string{"some@list.com"})) msg := c.pollEmailBug() require.NotNil(t, msg) assert.Equal(t, `"syzbot" `, msg.Sender) assert.Equal(t, "Forwarded: test subject", msg.Subject) assert.ElementsMatch(t, []string{"forward@a.com", "forward@b.com"}, msg.To, "must be sent to the author and the missing lists") assert.ElementsMatch(t, []string{"\"syzbot\" <" + from + ">", "someone@mail.com"}, msg.Cc) assert.Equal(t, "<1>", msg.Headers.Get("In-Reply-To")) assert.Equal(t, `For archival purposes, forwarding an incoming command email to forward@a.com, forward@b.com. *** Subject: test subject Author: someone@mail.com #syz invalid `, msg.Body) t.Run("no-loop", func(t *testing.T) { // Ensure that we don't react to replies. c.incomingEmail("syzbot@testapp.appspotmail.com", msg.Body, EmailOptFrom("syzbot@testapp.appspotmail.com"), EmailOptCC(append(append([]string{}, msg.Cc...), msg.To...))) c.expectNoEmail() }) }) t.Run("no command", func(t *testing.T) { c.incomingEmail("syzbot+prefixABCD@testapp.appspotmail.com", "Some spam message", EmailOptMessageID(1), EmailOptFrom("someone@mail.com")) c.expectNoEmail() }) t.Run("unrelated", func(t *testing.T) { // It will react as if the email targeted the bug ABCD. c.incomingEmail("syzbot+ABCD@testapp.appspotmail.com", "#syz invalid", EmailOptMessageID(1), EmailOptFrom("someone@mail.com"), EmailOptCC([]string{"some@list.com"})) msg := c.pollEmailBug() require.NotNil(t, msg) assert.Contains(t, msg.Body, "I see the command but can't find the corresponding bug") }) }