diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2024-01-29 15:52:06 +0100 |
|---|---|---|
| committer | Aleksandr Nogikh <nogikh@google.com> | 2024-01-30 10:29:21 +0000 |
| commit | fb343ecc19084bf98da750539377d508c9e077b3 (patch) | |
| tree | a63a0d3ed4d1916a4b4e4128640384eb57b1e941 | |
| parent | 3c3c9007d75874c34398f9df1387c9eb8c54479e (diff) | |
all: record diverted bug reproductions
In some cases, we derail during bug reproduction and end up finding and
reporting a reproducer for another issue. It causes no problems since
it's bucketed using the new title, but it's difficult to trace such
situations - on the original bug page, there are no failed reproduction
logs and on the new bug page it looks as if we intended to find a
reproducer for the bug with the new title.
Let's record bug reproduction logs in this case, so that we'd also see
a failed bug reproduction attempt on the original bug page.
| -rw-r--r-- | dashboard/app/api.go | 30 | ||||
| -rw-r--r-- | dashboard/app/repro_test.go | 35 | ||||
| -rw-r--r-- | dashboard/dashapi/dashapi.go | 8 | ||||
| -rw-r--r-- | syz-manager/manager.go | 54 |
4 files changed, 93 insertions, 34 deletions
diff --git a/dashboard/app/api.go b/dashboard/app/api.go index 63a41c6d8..ba6212da1 100644 --- a/dashboard/app/api.go +++ b/dashboard/app/api.go @@ -721,10 +721,25 @@ func apiReportCrash(c context.Context, ns string, r *http.Request, payload []byt if !getNsConfig(c, ns).TransformCrash(build, req) { return new(dashapi.ReportCrashResp), nil } + var bug2 *Bug + if req.OriginalTitle != "" { + bug2, err = findExistingBugForCrash(c, ns, []string{req.OriginalTitle}) + if err != nil { + return nil, fmt.Errorf("original bug query failed: %w", err) + } + } bug, err := reportCrash(c, build, req) if err != nil { return nil, err } + if bug2 != nil && bug2.Title != bug.Title && len(req.ReproLog) > 0 { + // During bug reproduction, we have diverted to another bug. + // Let's remember this. + err = saveFailedReproLog(c, bug2, build, req.ReproLog) + if err != nil { + return nil, fmt.Errorf("failed to save failed repro log: %w", err) + } + } resp := &dashapi.ReportCrashResp{ NeedRepro: needRepro(c, bug), } @@ -1011,12 +1026,16 @@ func apiReportFailedRepro(c context.Context, ns string, r *http.Request, payload if bug == nil { return nil, fmt.Errorf("%v: can't find bug for crash %q", ns, req.Title) } - bugKey := bug.key(c) build, err := loadBuild(c, ns, req.BuildID) if err != nil { return nil, err } + return nil, saveFailedReproLog(c, bug, build, req.ReproLog) +} + +func saveFailedReproLog(c context.Context, bug *Bug, build *Build, log []byte) error { now := timeNow(c) + bugKey := bug.key(c) tx := func(c context.Context) error { bug := new(Bug) if err := db.Get(c, bugKey, bug); err != nil { @@ -1024,8 +1043,8 @@ func apiReportFailedRepro(c context.Context, ns string, r *http.Request, payload } bug.NumRepro++ bug.LastReproTime = now - if len(req.ReproLog) > 0 { - err := saveReproLog(c, bug, build, req.ReproLog) + if len(log) > 0 { + err := saveReproAttempt(c, bug, build, log) if err != nil { return fmt.Errorf("failed to save repro log: %w", err) } @@ -1035,16 +1054,15 @@ func apiReportFailedRepro(c context.Context, ns string, r *http.Request, payload } return nil } - err = db.RunInTransaction(c, tx, &db.TransactionOptions{ + return db.RunInTransaction(c, tx, &db.TransactionOptions{ XG: true, Attempts: 30, }) - return nil, err } const maxReproLogs = 5 -func saveReproLog(c context.Context, bug *Bug, build *Build, log []byte) error { +func saveReproAttempt(c context.Context, bug *Bug, build *Build, log []byte) error { var deleteKeys []*db.Key for len(bug.ReproAttempts)+1 > maxReproLogs { deleteKeys = append(deleteKeys, diff --git a/dashboard/app/repro_test.go b/dashboard/app/repro_test.go index c6b6bec4f..8c587324e 100644 --- a/dashboard/app/repro_test.go +++ b/dashboard/app/repro_test.go @@ -450,3 +450,38 @@ func TestLogToReproduce(t *testing.T) { c.expectOK(err) c.expectEQ(resp.CrashLog, []byte(nil)) } + +// A frequent case -- when trying to find a reproducer for one bug, +// we have found a reproducer for a different bug. +// We want to remember the reproduction log in this case. +func TestReproForDifferentCrash(t *testing.T) { + c := NewCtx(t) + defer c.Close() + + client := c.client + build := testBuild(1) + client.UploadBuild(build) + + // Original crash. + crash := &dashapi.Crash{ + BuildID: "build1", + Title: "title1", + Log: []byte("log1"), + Report: []byte("report1"), + } + client.ReportCrash(crash) + oldBug := client.pollBug() + + // Now we have "found" a reproducer with a different title. + crash.Title = "new title" + crash.ReproOpts = []byte("opts") + crash.ReproSyz = []byte("repro syz") + crash.ReproLog = []byte("repro log") + crash.OriginalTitle = "title1" + client.ReportCrash(crash) + client.pollBug() + + // Ensure that we have saved the reproduction log in this case. + dbBug, _, _ := c.loadBug(oldBug.ID) + c.expectEQ(len(dbBug.ReproAttempts), 1) +} diff --git a/dashboard/dashapi/dashapi.go b/dashboard/dashapi/dashapi.go index ed9152e09..37aebc631 100644 --- a/dashboard/dashapi/dashapi.go +++ b/dashboard/dashapi/dashapi.go @@ -323,9 +323,11 @@ type Crash struct { Assets []NewAsset GuiltyFiles []string // The following is optional and is filled only after repro. - ReproOpts []byte - ReproSyz []byte - ReproC []byte + ReproOpts []byte + ReproSyz []byte + ReproC []byte + ReproLog []byte + OriginalTitle string // Title before we began bug reproduction. } type ReportCrashResp struct { diff --git a/syz-manager/manager.go b/syz-manager/manager.go index 3ae8caf1a..1ef1f18ea 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -317,13 +317,14 @@ type RunResult struct { } type ReproResult struct { - instances []int - report0 *report.Report // the original report we started reproducing - repro *repro.Result - strace *repro.StraceResult - stats *repro.Stats - err error - external bool // repro came from hub or dashboard + instances []int + report0 *report.Report // the original report we started reproducing + repro *repro.Result + strace *repro.StraceResult + stats *repro.Stats + err error + external bool // repro came from hub or dashboard + originalTitle string // crash title before we started bug reproduction } // Manager needs to be refactored (#605). @@ -498,12 +499,13 @@ func (mgr *Manager) runRepro(crash *Crash, vmIndexes []int, putInstances func(.. features := mgr.checkResult.Features res, stats, err := repro.Run(crash.Output, mgr.cfg, features, mgr.reporter, mgr.vmPool, vmIndexes) ret := &ReproResult{ - instances: vmIndexes, - report0: crash.Report, - repro: res, - stats: stats, - err: err, - external: crash.external, + instances: vmIndexes, + report0: crash.Report, + repro: res, + stats: stats, + err: err, + external: crash.external, + originalTitle: crash.Title, } if err == nil && res != nil && mgr.cfg.StraceBin != "" { // We need only one instance to get strace output, release the rest. @@ -1101,18 +1103,20 @@ func (mgr *Manager) saveRepro(res *ReproResult) { } dc := &dashapi.Crash{ - BuildID: mgr.cfg.Tag, - Title: report.Title, - AltTitles: report.AltTitles, - Suppressed: report.Suppressed, - Recipients: report.Recipients.ToDash(), - Log: output, - Flags: crashFlags, - Report: report.Report, - ReproOpts: repro.Opts.Serialize(), - ReproSyz: progText, - ReproC: cprogText, - Assets: mgr.uploadReproAssets(repro), + BuildID: mgr.cfg.Tag, + Title: report.Title, + AltTitles: report.AltTitles, + Suppressed: report.Suppressed, + Recipients: report.Recipients.ToDash(), + Log: output, + Flags: crashFlags, + Report: report.Report, + ReproOpts: repro.Opts.Serialize(), + ReproSyz: progText, + ReproC: cprogText, + ReproLog: fullReproLog(res.stats), + Assets: mgr.uploadReproAssets(repro), + OriginalTitle: res.originalTitle, } setGuiltyFiles(dc, report) if _, err := mgr.dash.ReportCrash(dc); err != nil { |
