From b257a9b7546c59d44cd69160b5a65a1bf1f050eb Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Tue, 18 Feb 2025 16:33:07 +0100 Subject: syz-cluster/pkg/report: add an email template The first revision of the email template that will be used for reporting the findings. This PR adds more fields to the pkg/api package, but these are not filled by the implementation yet. That will be done separately. --- syz-cluster/pkg/api/api.go | 29 +++++++--- syz-cluster/pkg/report/email.go | 40 +++++++++++++ syz-cluster/pkg/report/email_test.go | 71 ++++++++++++++++++++++++ syz-cluster/pkg/report/template.txt | 57 +++++++++++++++++++ syz-cluster/pkg/report/testdata/1.in.json | 43 ++++++++++++++ syz-cluster/pkg/report/testdata/1.moderation.txt | 56 +++++++++++++++++++ syz-cluster/pkg/report/testdata/1.upstream.txt | 49 ++++++++++++++++ syz-cluster/pkg/service/finding.go | 3 +- syz-cluster/reporter/api_test.go | 4 +- 9 files changed, 340 insertions(+), 12 deletions(-) create mode 100644 syz-cluster/pkg/report/email.go create mode 100644 syz-cluster/pkg/report/email_test.go create mode 100644 syz-cluster/pkg/report/template.txt create mode 100644 syz-cluster/pkg/report/testdata/1.in.json create mode 100644 syz-cluster/pkg/report/testdata/1.moderation.txt create mode 100644 syz-cluster/pkg/report/testdata/1.upstream.txt diff --git a/syz-cluster/pkg/api/api.go b/syz-cluster/pkg/api/api.go index b7d2992ce..535808e29 100644 --- a/syz-cluster/pkg/api/api.go +++ b/syz-cluster/pkg/api/api.go @@ -119,18 +119,29 @@ type NewSession struct { } type SessionReport struct { - ID string `json:"id"` - Moderation bool `json:"moderation"` - // TODO: add some session info? - Series *Series `json:"series"` - Findings []*Finding `json:"findings"` - Link string `json:"link"` // URL to the web dashboard. + ID string `json:"id"` + Cc []string `json:"cc"` + Moderation bool `json:"moderation"` + BaseRepo string `json:"base_repo"` + BaseCommit string `json:"base_commit"` + Series *Series `json:"series"` + Findings []*Finding `json:"findings"` + Link string `json:"link"` // URL to the web dashboard. } type Finding struct { - Title string `json:"title"` - Report []byte `json:"report"` - LogURL string `json:"log_url"` + Title string `json:"title"` + Report string `json:"report"` + LogURL string `json:"log_url"` + Build BuildInfo `json:"build"` + LinkCRepro string `json:"c_repro"` + LinkSyzRepro string `json:"syz_repro"` +} + +type BuildInfo struct { + Arch string `json:"arch"` + Compiler string `json:"compiler"` + ConfigLink string `json:"config_link"` } // Let them stay here until we find a better place. diff --git a/syz-cluster/pkg/report/email.go b/syz-cluster/pkg/report/email.go new file mode 100644 index 000000000..8317d46a6 --- /dev/null +++ b/syz-cluster/pkg/report/email.go @@ -0,0 +1,40 @@ +// Copyright 2025 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 report + +import ( + "bytes" + "embed" + "html/template" + + "github.com/google/syzkaller/syz-cluster/pkg/api" +) + +type Config struct { + Name string + DocsLink string + SupportEmail string +} + +//go:embed template.txt +var templateFS embed.FS + +func Render(rep *api.SessionReport, config *Config) ([]byte, error) { + tmpl, err := template.ParseFS(templateFS, "template.txt") + if err != nil { + return nil, err + } + data := struct { + Report *api.SessionReport + Config *Config + }{ + Report: rep, + Config: config, + } + var buf bytes.Buffer + if err := tmpl.Execute(&buf, data); err != nil { + return nil, err + } + return buf.Bytes(), nil +} diff --git a/syz-cluster/pkg/report/email_test.go b/syz-cluster/pkg/report/email_test.go new file mode 100644 index 000000000..5f33fbfda --- /dev/null +++ b/syz-cluster/pkg/report/email_test.go @@ -0,0 +1,71 @@ +// Copyright 2025 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 report + +import ( + "encoding/json" + "flag" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/google/syzkaller/syz-cluster/pkg/api" + "github.com/stretchr/testify/assert" +) + +var flagWrite = flag.Bool("write", false, "overwrite out.txt files") + +func TestRender(t *testing.T) { + config := &Config{ + Name: "syzbot", + DocsLink: "http://docs/link", + SupportEmail: "support@email.com", + } + flag.Parse() + basePath := "testdata" + files, err := os.ReadDir(basePath) + if err != nil { + t.Fatal(err) + } + for _, file := range files { + if filepath.Ext(file.Name()) != ".json" { + continue + } + fullName := file.Name() + name := strings.TrimSuffix(fullName, ".in.json") + t.Run(name, func(t *testing.T) { + t.Parallel() + inPath := filepath.Join(basePath, fullName) + inputData, err := os.ReadFile(inPath) + assert.NoError(t, err) + + var report api.SessionReport + err = json.Unmarshal(inputData, &report) + assert.NoError(t, err) + + for _, value := range []bool{false, true} { + report.Moderation = value + suffix := "upstream" + if value { + suffix = "moderation" + } + t.Run(suffix, func(t *testing.T) { + output, err := Render(&report, config) + assert.NoError(t, err) + + outPath := filepath.Join(basePath, name+"."+suffix+".txt") + if *flagWrite { + err := os.WriteFile(outPath, output, 0644) + assert.NoError(t, err) + } else { + expected, err := os.ReadFile(outPath) + assert.NoError(t, err) + assert.Equal(t, expected, output) + } + }) + } + }) + } +} diff --git a/syz-cluster/pkg/report/template.txt b/syz-cluster/pkg/report/template.txt new file mode 100644 index 000000000..687cec214 --- /dev/null +++ b/syz-cluster/pkg/report/template.txt @@ -0,0 +1,57 @@ +{{.Config.Name}} has processed the following series + +[v{{.Report.Series.Version}}] {{.Report.Series.Title}} +{{.Report.Series.Link}} +{{- range .Report.Series.Patches}} +* {{.Title}} +{{- end}} + +and found the following issues: +{{- range .Report.Findings}} +* {{.Title}} +{{- end}} + +The series was applied to the following base tree: +* Tree: {{.Report.BaseRepo}} +* Commit: {{.Report.BaseCommit}} + +Full report is available here: +{{.Report.Link}} + +{{- range .Report.Findings}} + +*** + +{{.Title}} +{{if .Build.Arch}} +arch: {{.Build.Arch}} +{{- end}} +{{- if .Build.Compiler}} +compiler: {{.Build.Compiler}} +{{- end}} +{{- if .Build.ConfigLink}} +config: {{.Build.ConfigLink}} +{{- end}} +{{- if .LinkCRepro}} +C repro: {{.LinkCRepro}} +{{- end}} +{{- if .LinkSyzRepro}} +syz repro: {{.LinkSyzRepro}} +{{- end}} + +{{.Report -}} +{{end}} + +--- +This report is generated by a bot. It may contain errors. +See {{.Config.DocsLink}} for more information about {{.Config.Name}}. +{{.Config.Name}} engineers can be reached at {{.Config.SupportEmail}}. + +{{- if .Report.Moderation}} + +The email will later be sent to: +{{.Report.Cc}} + +If the report looks fine to you, reply with: +#syz upstream +{{end}} diff --git a/syz-cluster/pkg/report/testdata/1.in.json b/syz-cluster/pkg/report/testdata/1.in.json new file mode 100644 index 000000000..e6ff0696e --- /dev/null +++ b/syz-cluster/pkg/report/testdata/1.in.json @@ -0,0 +1,43 @@ +{ + "id": "abcd", + "base_repo": "git://repo", + "base_commit": "abcd0123", + "series": { + "title": "Series title", + "version": 2, + "link": "http://link/to/series", + "patches": [ + { + "title": "first patch" + }, + { + "title": "second patch" + } + ] + }, + "findings": [ + { + "title": "WARNING in abcd", + "report": "Report Line A\nReport Line B\nReport Line C", + "c_repro": "http://link/to/c/repro", + "syz_repro": "http://link/to/syz/repro", + "build": { + "arch": "amd64", + "config_link": "http://link/to/config/1", + "compiler": "clang" + } + }, + { + "title": "KASAN: use-after-free Write in abcd", + "report": "Report Line D\nReport Line E\nReport Line F", + "syz_repro": "http://link/to/syz/repro2", + "build": { + "arch": "arm64", + "config_link": "http://link/to/config/2", + "compiler": "clang" + } + } + ], + "cc": ["a@a.com", "b@b.com"], + "link": "http://some/link/to/report" +} diff --git a/syz-cluster/pkg/report/testdata/1.moderation.txt b/syz-cluster/pkg/report/testdata/1.moderation.txt new file mode 100644 index 000000000..7f5a98f2b --- /dev/null +++ b/syz-cluster/pkg/report/testdata/1.moderation.txt @@ -0,0 +1,56 @@ +syzbot has processed the following series + +[v2] Series title +http://link/to/series +* first patch +* second patch + +and found the following issues: +* WARNING in abcd +* KASAN: use-after-free Write in abcd + +The series was applied to the following base tree: +* Tree: git://repo +* Commit: abcd0123 + +Full report is available here: +http://some/link/to/report + +*** + +WARNING in abcd + +arch: amd64 +compiler: clang +config: http://link/to/config/1 +C repro: http://link/to/c/repro +syz repro: http://link/to/syz/repro + +Report Line A +Report Line B +Report Line C + +*** + +KASAN: use-after-free Write in abcd + +arch: arm64 +compiler: clang +config: http://link/to/config/2 +syz repro: http://link/to/syz/repro2 + +Report Line D +Report Line E +Report Line F + +--- +This report is generated by a bot. It may contain errors. +See http://docs/link for more information about syzbot. +syzbot engineers can be reached at support@email.com. + +The email will later be sent to: +[a@a.com b@b.com] + +If the report looks fine to you, reply with: +#syz upstream + diff --git a/syz-cluster/pkg/report/testdata/1.upstream.txt b/syz-cluster/pkg/report/testdata/1.upstream.txt new file mode 100644 index 000000000..510e82cd2 --- /dev/null +++ b/syz-cluster/pkg/report/testdata/1.upstream.txt @@ -0,0 +1,49 @@ +syzbot has processed the following series + +[v2] Series title +http://link/to/series +* first patch +* second patch + +and found the following issues: +* WARNING in abcd +* KASAN: use-after-free Write in abcd + +The series was applied to the following base tree: +* Tree: git://repo +* Commit: abcd0123 + +Full report is available here: +http://some/link/to/report + +*** + +WARNING in abcd + +arch: amd64 +compiler: clang +config: http://link/to/config/1 +C repro: http://link/to/c/repro +syz repro: http://link/to/syz/repro + +Report Line A +Report Line B +Report Line C + +*** + +KASAN: use-after-free Write in abcd + +arch: arm64 +compiler: clang +config: http://link/to/config/2 +syz repro: http://link/to/syz/repro2 + +Report Line D +Report Line E +Report Line F + +--- +This report is generated by a bot. It may contain errors. +See http://docs/link for more information about syzbot. +syzbot engineers can be reached at support@email.com. diff --git a/syz-cluster/pkg/service/finding.go b/syz-cluster/pkg/service/finding.go index b0530a42c..bb605ba73 100644 --- a/syz-cluster/pkg/service/finding.go +++ b/syz-cluster/pkg/service/finding.go @@ -67,10 +67,11 @@ func (s *FindingService) List(ctx context.Context, sessionID string) ([]*api.Fin Title: item.Title, LogURL: "TODO", // TODO: where to take it from? } - finding.Report, err = blob.ReadAllBytes(s.blobStorage, item.ReportURI) + bytes, err := blob.ReadAllBytes(s.blobStorage, item.ReportURI) if err != nil { return nil, fmt.Errorf("failed to read the report: %w", err) } + finding.Report = string(bytes) ret = append(ret, finding) } return ret, nil diff --git a/syz-cluster/reporter/api_test.go b/syz-cluster/reporter/api_test.go index b4933afd7..22ccb13e8 100644 --- a/syz-cluster/reporter/api_test.go +++ b/syz-cluster/reporter/api_test.go @@ -80,12 +80,12 @@ func TestAPIReportFlow(t *testing.T) { Findings: []*api.Finding{ { Title: "finding 0", - Report: []byte("report 0"), + Report: "report 0", LogURL: "TODO", // TODO }, { Title: "finding 1", - Report: []byte("report 1"), + Report: "report 1", LogURL: "TODO", // TODO }, }, -- cgit mrf-deployment