diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2024-10-20 22:55:31 +0200 |
|---|---|---|
| committer | Taras Madan <tarasmadan@google.com> | 2024-10-25 12:08:02 +0000 |
| commit | f63b8696b67a1c47ecd4fced47215acd6805a14a (patch) | |
| tree | c7d5795c124fdc9a99b309db8ed75f56b2c6ffe9 /pkg/manager | |
| parent | c0390c277e5fcda8d7288b717ff952e01dcdcb8d (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.go | 4 | ||||
| -rw-r--r-- | pkg/manager/crash_test.go | 4 | ||||
| -rw-r--r-- | pkg/manager/diff.go | 139 | ||||
| -rw-r--r-- | pkg/manager/http.go | 144 | ||||
| -rw-r--r-- | pkg/manager/report_generator.go | 9 |
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 +} |
