aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/manager
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2024-10-20 22:55:31 +0200
committerTaras Madan <tarasmadan@google.com>2024-10-25 12:08:02 +0000
commitf63b8696b67a1c47ecd4fced47215acd6805a14a (patch)
treec7d5795c124fdc9a99b309db8ed75f56b2c6ffe9 /pkg/manager
parentc0390c277e5fcda8d7288b717ff952e01dcdcb8d (diff)
tools: add a syz-diff tool
This is the prototype version of the patch series fuzzing functionality based on the syzkaller fuzzing engine. The tool takes two syzkaller configs -- one for the base kernel, one for the patched kernel. Optionally the patch itself can be also provided. syz-diff will consider a bug patched-only if: 1) It happened while fuzzing the patched kernel. 2) It was never observed on the base kernel. 3) The tool found a repro on the patched kernel. 4) The repro did not crash the base kernel.
Diffstat (limited to 'pkg/manager')
-rw-r--r--pkg/manager/crash.go4
-rw-r--r--pkg/manager/crash_test.go4
-rw-r--r--pkg/manager/diff.go139
-rw-r--r--pkg/manager/http.go144
-rw-r--r--pkg/manager/report_generator.go9
5 files changed, 286 insertions, 14 deletions
diff --git a/pkg/manager/crash.go b/pkg/manager/crash.go
index 5edc9f5a9..56142695a 100644
--- a/pkg/manager/crash.go
+++ b/pkg/manager/crash.go
@@ -306,11 +306,11 @@ func (cs *CrashStore) BugList() ([]*BugInfo, error) {
return ret, nil
}
-func (cs *CrashStore) titleToID(title string) string {
+func crashHash(title string) string {
sig := hash.Hash([]byte(title))
return sig.String()
}
func (cs *CrashStore) path(title string) string {
- return filepath.Join(cs.BaseDir, "crashes", cs.titleToID(title))
+ return filepath.Join(cs.BaseDir, "crashes", crashHash(title))
}
diff --git a/pkg/manager/crash_test.go b/pkg/manager/crash_test.go
index f017f0175..4348dc2f1 100644
--- a/pkg/manager/crash_test.go
+++ b/pkg/manager/crash_test.go
@@ -76,7 +76,7 @@ func TestMaxCrashLogs(t *testing.T) {
assert.NoError(t, err)
}
- info, err := crashStore.BugInfo(crashStore.titleToID("Title A"), false)
+ info, err := crashStore.BugInfo(crashHash("Title A"), false)
assert.NoError(t, err)
assert.Len(t, info.Crashes, 5)
}
@@ -105,7 +105,7 @@ func TestCrashRepro(t *testing.T) {
}, []byte("prog text"), []byte("c prog text"))
assert.NoError(t, err)
- report, err := crashStore.Report(crashStore.titleToID("Some title"))
+ report, err := crashStore.Report(crashHash("Some title"))
assert.NoError(t, err)
assert.Equal(t, "Some title", report.Title)
assert.Equal(t, "abcd", report.Tag)
diff --git a/pkg/manager/diff.go b/pkg/manager/diff.go
new file mode 100644
index 000000000..76e2b97ff
--- /dev/null
+++ b/pkg/manager/diff.go
@@ -0,0 +1,139 @@
+// Copyright 2024 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 manager
+
+import (
+ "fmt"
+ "path/filepath"
+ "sync"
+ "time"
+
+ "github.com/google/syzkaller/pkg/log"
+ "github.com/google/syzkaller/pkg/osutil"
+)
+
+type DiffBug struct {
+ Title string
+ Base DiffBugInfo
+ Patched DiffBugInfo
+}
+
+func (bug DiffBug) PatchedOnly() bool {
+ return bug.Base.NotCrashed && bug.Patched.Crashes > 0
+}
+
+func (bug DiffBug) AffectsBoth() bool {
+ return bug.Base.Crashes > 0 && bug.Patched.Crashes > 0
+}
+
+type DiffBugInfo struct {
+ Crashes int // Count of detected crashes.
+ NotCrashed bool // If were proven not to crash by running a repro.
+
+ // File paths.
+ Report string
+ Repro string
+ ReproLog string
+ CrashLog string
+}
+
+// DiffFuzzerStore provides the functionality of a database of the patch fuzzing.
+type DiffFuzzerStore struct {
+ BasePath string
+
+ mu sync.Mutex
+ bugs map[string]*DiffBug
+}
+
+func (s *DiffFuzzerStore) BaseCrashed(title string, report []byte) {
+ s.patch(title, func(obj *DiffBug) {
+ obj.Base.Crashes++
+ if len(report) > 0 {
+ obj.Base.Report = s.saveFile(title, "base_report", report)
+ }
+ })
+}
+
+func (s *DiffFuzzerStore) EverCrashedBase(title string) bool {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ obj := s.bugs[title]
+ return obj != nil && obj.Base.Crashes > 0
+}
+
+func (s *DiffFuzzerStore) BaseNotCrashed(title string) {
+ s.patch(title, func(obj *DiffBug) {
+ if obj.Base.Crashes == 0 {
+ obj.Base.NotCrashed = true
+ }
+ })
+}
+
+func (s *DiffFuzzerStore) PatchedCrashed(title string, report, log []byte) {
+ s.patch(title, func(obj *DiffBug) {
+ obj.Patched.Crashes++
+ if len(report) > 0 {
+ obj.Patched.Report = s.saveFile(title, "patched_report", report)
+ }
+ if len(log) > 0 && obj.Patched.CrashLog == "" {
+ obj.Patched.CrashLog = s.saveFile(title, "patched_crash_log", log)
+ }
+ })
+}
+
+func (s *DiffFuzzerStore) SaveRepro(result *ReproResult) {
+ title := result.Crash.Report.Title
+ if result.Repro != nil {
+ // If there's a repro, save under the new title.
+ title = result.Repro.Report.Title
+ }
+
+ now := time.Now().Unix()
+ crashLog := fmt.Sprintf("%v.crash.log", now)
+ s.saveFile(title, crashLog, result.Crash.Output)
+ log.Logf(0, "%q: saved crash log into %s", title, crashLog)
+
+ s.patch(title, func(obj *DiffBug) {
+ if result.Repro != nil {
+ obj.Patched.Repro = s.saveFile(title, reproFileName, result.Repro.Prog.Serialize())
+ }
+ if result.Stats != nil {
+ reproLog := fmt.Sprintf("%v.repro.log", now)
+ obj.Patched.ReproLog = s.saveFile(title, reproLog, result.Stats.FullLog())
+ log.Logf(0, "%q: saved repro log into %s", title, reproLog)
+ }
+ })
+}
+
+func (s *DiffFuzzerStore) List() []DiffBug {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ var list []DiffBug
+ for _, obj := range s.bugs {
+ list = append(list, *obj)
+ }
+ return list
+}
+
+func (s *DiffFuzzerStore) saveFile(title, name string, data []byte) string {
+ hash := crashHash(title)
+ path := filepath.Join(s.BasePath, "crashes", hash)
+ osutil.MkdirAll(path)
+ osutil.WriteFile(filepath.Join(path, name), data)
+ return filepath.Join("crashes", hash, name)
+}
+
+func (s *DiffFuzzerStore) patch(title string, cb func(*DiffBug)) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ if s.bugs == nil {
+ s.bugs = map[string]*DiffBug{}
+ }
+ obj, ok := s.bugs[title]
+ if !ok {
+ obj = &DiffBug{Title: title}
+ s.bugs[title] = obj
+ }
+ cb(obj)
+}
diff --git a/pkg/manager/http.go b/pkg/manager/http.go
index 155765534..c94ce727f 100644
--- a/pkg/manager/http.go
+++ b/pkg/manager/http.go
@@ -51,6 +51,7 @@ type HTTPServer struct {
StartTime time.Time
Corpus *corpus.Corpus
CrashStore *CrashStore
+ DiffStore *DiffFuzzerStore
// Set dynamically.
Fuzzer atomic.Pointer[fuzzer.Fuzzer]
@@ -77,13 +78,15 @@ func (serv *HTTPServer) Serve() {
handle("/syscalls", serv.httpSyscalls)
handle("/corpus", serv.httpCorpus)
handle("/corpus.db", serv.httpDownloadCorpus)
- handle("/crash", serv.httpCrash)
+ if serv.CrashStore != nil {
+ handle("/crash", serv.httpCrash)
+ handle("/report", serv.httpReport)
+ }
handle("/cover", serv.httpCover)
handle("/subsystemcover", serv.httpSubsystemCover)
handle("/modulecover", serv.httpModuleCover)
handle("/prio", serv.httpPrio)
handle("/file", serv.httpFile)
- handle("/report", serv.httpReport)
handle("/rawcover", serv.httpRawCover)
handle("/rawcoverfiles", serv.httpRawCoverFiles)
handle("/filterpcs", serv.httpFilterPCs)
@@ -125,11 +128,15 @@ func (serv *HTTPServer) httpSummary(w http.ResponseWriter, r *http.Request) {
Link: stat.Link,
})
}
-
- var err error
- if data.Crashes, err = serv.collectCrashes(serv.Cfg.Workdir); err != nil {
- http.Error(w, fmt.Sprintf("failed to collect crashes: %v", err), http.StatusInternalServerError)
- return
+ if serv.CrashStore != nil {
+ var err error
+ if data.Crashes, err = serv.collectCrashes(serv.Cfg.Workdir); err != nil {
+ http.Error(w, fmt.Sprintf("failed to collect crashes: %v", err), http.StatusInternalServerError)
+ return
+ }
+ }
+ if serv.DiffStore != nil {
+ data.PatchedOnly, data.AffectsBoth, data.InProgress = serv.collectDiffCrashes()
}
executeTemplate(w, summaryTemplate, data)
}
@@ -695,15 +702,61 @@ func (serv *HTTPServer) httpFilterPCs(w http.ResponseWriter, r *http.Request) {
serv.httpCoverCover(w, r, DoFilterPCs)
}
-func (serv *HTTPServer) collectCrashes(workdir string) ([]*UICrashType, error) {
- var repros map[string]bool
+func (serv *HTTPServer) collectDiffCrashes() (patchedOnly, both, inProgress *UIDiffTable) {
+ for _, item := range serv.allDiffCrashes() {
+ if item.PatchedOnly() {
+ if patchedOnly == nil {
+ patchedOnly = &UIDiffTable{Title: "Patched-only"}
+ }
+ patchedOnly.List = append(patchedOnly.List, item)
+ } else if item.AffectsBoth() {
+ if both == nil {
+ both = &UIDiffTable{Title: "Affects both"}
+ }
+ both.List = append(both.List, item)
+ } else {
+ if inProgress == nil {
+ inProgress = &UIDiffTable{Title: "In Progress"}
+ }
+ inProgress.List = append(inProgress.List, item)
+ }
+ }
+ return
+}
+
+func (serv *HTTPServer) allDiffCrashes() []UIDiffBug {
+ repros := serv.nowReproducing()
+ var list []UIDiffBug
+ for _, bug := range serv.DiffStore.List() {
+ list = append(list, UIDiffBug{
+ DiffBug: bug,
+ Reproducing: repros[bug.Title],
+ })
+ }
+ sort.Slice(list, func(i, j int) bool {
+ first, second := list[i], list[j]
+ firstPatched, secondPatched := first.PatchedOnly(), second.PatchedOnly()
+ if firstPatched != secondPatched {
+ return firstPatched
+ }
+ return first.Title < second.Title
+ })
+ return list
+}
+
+func (serv *HTTPServer) nowReproducing() map[string]bool {
if reproLoop := serv.ReproLoop.Load(); reproLoop != nil {
- repros = reproLoop.Reproducing()
+ return reproLoop.Reproducing()
}
+ return nil
+}
+
+func (serv *HTTPServer) collectCrashes(workdir string) ([]*UICrashType, error) {
list, err := serv.CrashStore.BugList()
if err != nil {
return nil, err
}
+ repros := serv.nowReproducing()
var ret []*UICrashType
for _, info := range list {
ret = append(ret, makeUICrashType(info, serv.StartTime, repros))
@@ -788,9 +841,17 @@ type UISummaryData struct {
Expert bool
Stats []UIStat
Crashes []*UICrashType
+ PatchedOnly *UIDiffTable
+ AffectsBoth *UIDiffTable
+ InProgress *UIDiffTable
Log string
}
+type UIDiffTable struct {
+ Title string
+ List []UIDiffBug
+}
+
type UIVMData struct {
Name string
VMs []UIVMInfo
@@ -825,6 +886,11 @@ type UICrash struct {
Active bool
}
+type UIDiffBug struct {
+ DiffBug
+ Reproducing bool
+}
+
type UIStat struct {
Name string
Value string
@@ -881,6 +947,7 @@ var summaryTemplate = pages.Create(`
{{end}}
</table>
+{{if .Crashes}}
<table class="list_table">
<caption>Crashes:</caption>
<tr>
@@ -905,6 +972,63 @@ var summaryTemplate = pages.Create(`
</tr>
{{end}}
</table>
+{{end}}
+
+{{define "diff_crashes"}}
+<table class="list_table">
+ <caption>{{.Title}}:</caption>
+ <tr>
+ <th>Description</th>
+ <th>Base</th>
+ <th>Patched</th>
+ </tr>
+ {{range $bug := .List}}
+ <tr>
+ <td class="title">{{$bug.Title}}</td>
+ <td class="title">
+ {{if gt $bug.Base.Crashes 0}}
+ {{$bug.Base.Crashes}} crashes
+ {{else if $bug.Base.NotCrashed}}
+ Not affected
+ {{else}} ? {{end}}
+ {{if $bug.Base.Report}}
+ <a href="/file?name={{$bug.Base.Report}}">[report]</a>
+ {{end}}
+ </td>
+ <td class="title">
+ {{if gt $bug.Patched.Crashes 0}}
+ {{$bug.Patched.Crashes}} crashes
+ {{else}} ? {{end}}
+ {{if $bug.Patched.Report}}
+ <a href="/file?name={{$bug.Patched.Report}}">[report]</a>
+ {{end}}
+ {{if $bug.Patched.CrashLog}}
+ <a href="/file?name={{$bug.Patched.CrashLog}}">[crash log]</a>
+ {{end}}
+ {{if $bug.Patched.Repro}}
+ <a href="/file?name={{$bug.Patched.Repro}}">[syz repro]</a>
+ {{end}}
+ {{if $bug.Patched.ReproLog}}
+ <a href="/file?name={{$bug.Patched.ReproLog}}">[repro log]</a>
+ {{end}}
+ {{if $bug.Reproducing}}[reproducing]{{end}}
+ </td>
+ </tr>
+ {{end}}
+</table>
+{{end}}
+
+{{if .PatchedOnly}}
+{{template "diff_crashes" .PatchedOnly}}
+{{end}}
+
+{{if .AffectsBoth}}
+{{template "diff_crashes" .AffectsBoth}}
+{{end}}
+
+{{if .InProgress}}
+{{template "diff_crashes" .InProgress}}
+{{end}}
<b>Log:</b>
<br>
diff --git a/pkg/manager/report_generator.go b/pkg/manager/report_generator.go
index b3d293ef1..6dcdbf0a3 100644
--- a/pkg/manager/report_generator.go
+++ b/pkg/manager/report_generator.go
@@ -68,3 +68,12 @@ func CoverToPCs(cfg *mgrconfig.Config, cov []uint64) []uint64 {
}
return pcs
}
+
+func PCsToCover(cfg *mgrconfig.Config, pcs map[uint64]struct{}) map[uint64]struct{} {
+ ret := make(map[uint64]struct{})
+ for pc := range pcs {
+ next := backend.NextInstructionPC(cfg.SysTarget, cfg.Type, pc)
+ ret[next] = struct{}{}
+ }
+ return ret
+}