aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2024-01-29 15:52:06 +0100
committerAleksandr Nogikh <nogikh@google.com>2024-01-30 10:29:21 +0000
commitfb343ecc19084bf98da750539377d508c9e077b3 (patch)
treea63a0d3ed4d1916a4b4e4128640384eb57b1e941
parent3c3c9007d75874c34398f9df1387c9eb8c54479e (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.go30
-rw-r--r--dashboard/app/repro_test.go35
-rw-r--r--dashboard/dashapi/dashapi.go8
-rw-r--r--syz-manager/manager.go54
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 {