Golang程序  |  475行  |  14.66 KB

// 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.

// +build aetest

package dash

import (
	"fmt"
	"strings"
	"testing"

	"github.com/google/syzkaller/pkg/email"
)

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" <foo@bar.com>`, `bar@foo.com`}
	c.client2.ReportCrash(crash)

	// Report the crash over email and check all fields.
	var sender0, extBugID0, body0 string
	{
		c.expectOK(c.GET("/email_poll"))
		c.expectEQ(len(c.emailSink), 1)
		msg := <-c.emailSink
		sender0 = msg.Sender
		body0 = msg.Body
		sender, extBugID, err := email.RemoveAddrContext(msg.Sender)
		if err != nil {
			t.Fatalf("failed to remove sender context: %v", err)
		}
		extBugID0 = extBugID
		_, dbCrash, dbBuild := c.loadBug(extBugID0)
		crashLogLink := externalLink(c.ctx, textCrashLog, dbCrash.Log)
		kernelConfigLink := externalLink(c.ctx, textKernelConfig, dbBuild.KernelConfig)
		c.expectEQ(sender, fromAddr(c.ctx))
		to := 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)
		body := fmt.Sprintf(`Hello,

syzbot found the following crash 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]

Unfortunately, I don't have any reproducer for this crash yet.

IMPORTANT: if you fix the bug, please add the following tag to the commit:
Reported-by: syzbot+%[1]v@testapp.appspotmail.com

report1

---
This bug 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 bug report. See:
https://goo.gl/tpsmEJ#bug-status-tracking for how to communicate with syzbot.`,
			extBugID0, crashLogLink, kernelConfigLink)
		if msg.Body != body {
			t.Fatalf("got email body:\n%s\n\nwant:\n%s", msg.Body, body)
		}
		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 bug report.
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)

	c.expectOK(c.POST("/_ah/mail/", incoming1))

	// 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)

	// Now report syz reproducer and check updated email.
	crash.ReproOpts = []byte("repro opts")
	crash.ReproSyz = []byte("getpid()")
	syzRepro := []byte(fmt.Sprintf("%s#%s\n%s", syzReproPrefix, crash.ReproOpts, crash.ReproSyz))
	c.client2.ReportCrash(crash)

	{
		c.expectOK(c.GET("/email_poll"))
		c.expectEQ(len(c.emailSink), 1)
		msg := <-c.emailSink
		c.expectEQ(msg.Sender, sender0)
		sender, _, err := email.RemoveAddrContext(msg.Sender)
		if err != nil {
			t.Fatalf("failed to remove sender context: %v", 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{
			"bugs@syzkaller.com",
			"default@sender.com", // This is from incomingEmail.
			"foo@bar.com",
			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>"})
		body := fmt.Sprintf(`syzbot has found a reproducer for the following crash on:

HEAD commit:    111111111111 kernel_commit_title1
git tree:       repo1/branch1
console output: %[3]v
kernel config:  %[4]v
dashboard link: https://testapp.appspot.com/bug?extid=%[1]v
compiler:       compiler1
syzkaller repro:%[2]v
CC:             [bar@foo.com foo@bar.com]

IMPORTANT: if you fix the bug, please add the following tag to the commit:
Reported-by: syzbot+%[1]v@testapp.appspotmail.com

report1
`, extBugID0, reproSyzLink, crashLogLink, kernelConfigLink)
		if msg.Body != body {
			t.Fatalf("got email body:\n%s\n\nwant:\n%s", msg.Body, body)
		}
		c.checkURLContents(reproSyzLink, syzRepro)
		c.checkURLContents(crashLogLink, crash.Log)
		c.checkURLContents(kernelConfigLink, build.KernelConfig)
	}

	// Now upstream the bug and check that it reaches the next reporting.
	c.incomingEmail(sender0, "#syz upstream")

	sender1, extBugID1 := "", ""
	{
		c.expectOK(c.GET("/email_poll"))
		c.expectEQ(len(c.emailSink), 1)
		msg := <-c.emailSink
		sender1 = msg.Sender
		if sender1 == sender0 {
			t.Fatalf("same ID in different reporting")
		}
		sender, extBugID, err := email.RemoveAddrContext(msg.Sender)
		if err != nil {
			t.Fatalf("failed to remove sender context: %v", 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{"bar@foo.com", "bugs@syzkaller.com",
			"default@maintainers.com", "foo@bar.com"})
		c.expectEQ(msg.Subject, crash.Title)
		c.expectEQ(len(msg.Attachments), 0)
		body := fmt.Sprintf(`Hello,

syzbot found the following crash on:

HEAD commit:    111111111111 kernel_commit_title1
git tree:       repo1/branch1
console output: %[3]v
kernel config:  %[4]v
dashboard link: https://testapp.appspot.com/bug?extid=%[1]v
compiler:       compiler1
syzkaller repro:%[2]v

IMPORTANT: if you fix the bug, please add the following tag to the commit:
Reported-by: syzbot+%[1]v@testapp.appspotmail.com

report1

---
This bug 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 bug report. See:
https://goo.gl/tpsmEJ#bug-status-tracking for how to communicate with syzbot.
syzbot can test patches for this bug, for details see:
https://goo.gl/tpsmEJ#testing-patches`,
			extBugID1, reproSyzLink, crashLogLink, kernelConfigLink)
		if msg.Body != body {
			t.Fatalf("got email body:\n%s\n\nwant:\n%s", msg.Body, body)
		}
		c.checkURLContents(reproSyzLink, syzRepro)
		c.checkURLContents(crashLogLink, crash.Log)
		c.checkURLContents(kernelConfigLink, build.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" <another@another.com>, bar@foo.com, bugs@syzkaller.com, foo@bar.com
Content-Type: text/plain

+more people
`, sender1)

	c.expectOK(c.POST("/_ah/mail/", incoming3))

	// Now upload a C reproducer.
	build2 := testBuild(2)
	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.ReproC = []byte("int main() {}")
	crash.Maintainers = []string{"\"qux\" <qux@qux.com>"}
	c.client2.ReportCrash(crash)

	{
		c.expectOK(c.GET("/email_poll"))
		c.expectEQ(len(c.emailSink), 1)
		msg := <-c.emailSink
		c.expectEQ(msg.Sender, sender1)
		sender, _, err := email.RemoveAddrContext(msg.Sender)
		if err != nil {
			t.Fatalf("failed to remove sender context: %v", 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{"another@another.com", "bar@foo.com", "bugs@syzkaller.com",
			"default@maintainers.com", "foo@bar.com", "new@new.com", "qux@qux.com"})
		c.expectEQ(msg.Subject, "Re: "+crash.Title)
		c.expectEQ(len(msg.Attachments), 0)
		body := fmt.Sprintf(`syzbot has found a reproducer for the following crash on:

HEAD commit:    222222222222 a really long title, longer than 80 chars, re..
git tree:       repo2/branch2
console output: %[4]v
kernel config:  %[5]v
dashboard link: https://testapp.appspot.com/bug?extid=%[1]v
compiler:       compiler2
syzkaller repro:%[3]v
C reproducer:   %[2]v

IMPORTANT: if you fix the bug, please add the following tag to the commit:
Reported-by: syzbot+%[1]v@testapp.appspotmail.com

report1
`, extBugID1, reproCLink, reproSyzLink, crashLogLink, kernelConfigLink)
		if msg.Body != body {
			t.Fatalf("got email body:\n%s\n\nwant:\n%s", msg.Body, body)
		}
		c.checkURLContents(reproCLink, crash.ReproC)
		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: <abcdef>
Subject: title1
From: foo@bar.com
To: %v
Content-Type: text/plain

#syz bad-command
`, sender1)

	c.expectOK(c.POST("/_ah/mail/", incoming4))

	{
		c.expectOK(c.GET("/email_poll"))
		c.expectEQ(len(c.emailSink), 1)
		msg := <-c.emailSink
		c.expectEQ(msg.To, []string{"<foo@bar.com>"})
		c.expectEQ(msg.Subject, "Re: title1")
		c.expectEQ(msg.Headers["In-Reply-To"], []string{"<abcdef>"})
		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")
	c.expectOK(c.GET("/email_poll"))
	c.expectEQ(len(c.emailSink), 0)

	// 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)
	{
		c.expectOK(c.GET("/email_poll"))
		c.expectEQ(len(c.emailSink), 1)
		msg := <-c.emailSink
		c.expectEQ(msg.Subject, crash.Title+" (2)")
		if msg.Sender == sender0 {
			t.Fatalf("same reporting ID for new bug")
		}
	}
}

// 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)

	c.expectOK(c.GET("/email_poll"))
	c.expectEQ(len(c.emailSink), 1)
	sender := (<-c.emailSink).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)
	c.expectOK(c.POST("/_ah/mail/", incoming1))

	c.expectOK(c.GET("/email_poll"))
	c.expectEQ(len(c.emailSink), 0)
}

// 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)
	crash1.Title = "KASAN: another title"
	c.client2.ReportCrash(crash2)

	c.expectOK(c.GET("/email_poll"))
	c.expectEQ(len(c.emailSink), 2)
	msg1 := <-c.emailSink
	msg2 := <-c.emailSink

	// Dup crash2 to crash1.
	c.incomingEmail(msg2.Sender, "#syz dup: BUG: slightly more elaborate title")
	c.expectOK(c.GET("/email_poll"))
	c.expectEQ(len(c.emailSink), 0)

	// Second crash happens again
	crash2.ReproC = []byte("int main() {}")
	c.client2.ReportCrash(crash2)
	c.expectOK(c.GET("/email_poll"))
	c.expectEQ(len(c.emailSink), 0)

	// Now close the original bug, and check that new bugs for dup are now created.
	c.incomingEmail(msg1.Sender, "#syz invalid")

	// New crash must produce new bug in the first reporting.
	c.client2.ReportCrash(crash2)
	{
		c.expectOK(c.GET("/email_poll"))
		c.expectEQ(len(c.emailSink), 1)
		msg := <-c.emailSink
		c.expectEQ(msg.Subject, crash2.Title+" (2)")
	}
}

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)

	c.expectOK(c.GET("/email_poll"))
	c.expectEQ(len(c.emailSink), 2)
	msg1 := <-c.emailSink
	msg2 := <-c.emailSink

	// Dup crash2 to crash1.
	c.incomingEmail(msg2.Sender, "#syz dup: BUG: slightly more elaborate title")
	c.expectOK(c.GET("/email_poll"))
	c.expectEQ(len(c.emailSink), 0)

	// Undup crash2.
	c.incomingEmail(msg2.Sender, "#syz undup")
	c.expectOK(c.GET("/email_poll"))
	c.expectEQ(len(c.emailSink), 0)

	// 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.expectOK(c.GET("/email_poll"))
	c.expectEQ(len(c.emailSink), 0)
}