From cb4ff871c418a7ac95116c02f4b8482d9e514ce4 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Fri, 9 Jan 2026 15:31:21 +0100 Subject: pkg/report: move TitleToCrashType to crash package TitleToCrashType is a simple function with no heavy dependencies that is used by the dashboard app. Currnetly we have to import pkg/report into dashboard/app, and this package has lots of heavy deps (symbolizer, demangler, coverage report generation, etc). Move TitleToCrashType to pkg/report/crash (where it arguably belongs anyway). --- pkg/report/crash/title_to_type.go | 317 ++++++++++++++++++++++++++++++++ pkg/report/crash/title_to_type_test.go | 39 ++++ pkg/report/crash/types.go | 16 +- pkg/report/impact_score.go | 2 +- pkg/report/linux.go | 2 +- pkg/report/report.go | 13 +- pkg/report/report_test.go | 2 +- pkg/report/title_to_type.go | 321 --------------------------------- pkg/report/title_to_type_test.go | 39 ---- 9 files changed, 375 insertions(+), 376 deletions(-) create mode 100644 pkg/report/crash/title_to_type.go create mode 100644 pkg/report/crash/title_to_type_test.go delete mode 100644 pkg/report/title_to_type.go delete mode 100644 pkg/report/title_to_type_test.go (limited to 'pkg') diff --git a/pkg/report/crash/title_to_type.go b/pkg/report/crash/title_to_type.go new file mode 100644 index 000000000..b6d96c41a --- /dev/null +++ b/pkg/report/crash/title_to_type.go @@ -0,0 +1,317 @@ +// 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 crash + +var titleToType = []struct { + includePrefixes []string + crashType Type +}{ + { + includePrefixes: []string{ + "KFENCE: use-after-free write", + }, + crashType: KFENCEUseAfterFreeWrite, + }, + { + includePrefixes: []string{ + "KFENCE: use-after-free read", + "KFENCE: use-after-free", // Read/Write is not clear. It is at least Read. + }, + crashType: KFENCEUseAfterFreeRead, + }, + { + includePrefixes: []string{ + "KFENCE: invalid write", + "KFENCE: out-of-bounds write", + }, + crashType: KFENCEWrite, + }, + { + includePrefixes: []string{ + // keep-sorted start + "KFENCE: invalid read", + "KFENCE: out-of-bounds read", + "KFENCE: out-of-bounds", // Read/Write is not clear. It is at least Read. + // keep-sorted end + }, + crashType: KFENCERead, + }, + { + includePrefixes: []string{ + "KFENCE: memory corruption", + }, + crashType: KFENCEMemoryCorruption, + }, + { + includePrefixes: []string{ + "KFENCE: invalid free", + }, + crashType: KFENCEInvalidFree, + }, + { + includePrefixes: []string{ + "KMSAN: uninit-value", + }, + crashType: KMSANUninitValue, + }, + { + includePrefixes: []string{ + // keep-sorted start + "KMSAN: kernel-infoleak-after-free", + "KMSAN: kernel-usb-infoleak-after-free", + "KMSAN: use-after-free", + // keep-sorted end + }, + crashType: KMSANUseAfterFreeRead, + }, + { + includePrefixes: []string{ + "KMSAN: kernel-infoleak", + "KMSAN: kernel-usb-infoleak", + }, + crashType: KMSANInfoLeak, + }, + { + includePrefixes: []string{ + "KASAN: null-ptr-deref Write", + }, + crashType: KASANNullPtrDerefWrite, + }, + { + includePrefixes: []string{ + "KASAN: null-ptr-deref Read", + }, + crashType: KASANNullPtrDerefRead, + }, + { + includePrefixes: []string{ + "BUG: unable to handle kernel NULL pointer dereference in", + }, + crashType: NullPtrDerefBUG, + }, + { + includePrefixes: []string{ + // keep-sorting start + "KASAN: global-out-of-bounds Write", + "KASAN: out-of-bounds Write", + "KASAN: slab-out-of-bounds Write", + "KASAN: stack-out-of-bounds Write", + "KASAN: user-memory-access Write", + "KASAN: vmalloc-out-of-bounds Write", + "KASAN: wild-memory-access Write", + // keep-sorting end + }, + crashType: KASANWrite, + }, + { + includePrefixes: []string{ + // keep-sorting start + "KASAN: global-out-of-bounds Read", + "KASAN: invalid-access Read", + "KASAN: out-of-bounds Read", + "KASAN: slab-out-of-bounds Read", + "KASAN: slab-out-of-bounds", // Read/Write is not clear. It is at least Read. + "KASAN: stack-out-of-bounds Read", + "KASAN: stack-out-of-bounds", // Read/Write is not clear. It is at least Read. + "KASAN: unknown-crash Read", + "KASAN: user-memory-access Read", + "KASAN: vmalloc-out-of-bounds Read", + "KASAN: wild-memory-access Read", + "KASAN: wild-memory-access", // Read/Write is not clear. It is at least Read. + // keep-sorting end + }, + crashType: KASANRead, + }, + { + includePrefixes: []string{ + "KASAN: double-free or invalid-free", + "KASAN: invalid-free", + }, + crashType: KASANInvalidFree, + }, + { + includePrefixes: []string{ + "KASAN: slab-use-after-free Write", + "KASAN: use-after-free Write", + }, + crashType: KASANUseAfterFreeWrite, + }, + { + includePrefixes: []string{ + "KASAN: slab-use-after-free Read", + "KASAN: use-after-free Read", + "KASAN: use-after-free", // Read/Write is not clear. It is at least Read. + }, + crashType: KASANUseAfterFreeRead, + }, + { + includePrefixes: []string{ + // keep-sorting start + "BUG: corrupted list", + "BUG: unable to handle kernel paging request", + // keep-sorting end + }, + crashType: MemorySafetyBUG, + }, + { + includePrefixes: []string{ + "WARNING: refcount bug", + }, + crashType: RefcountWARNING, + }, + { + includePrefixes: []string{ + "UBSAN: array-index-out-of-bounds", + }, + crashType: MemorySafetyUBSAN, + }, + { + includePrefixes: []string{"KCSAN: data-race"}, + crashType: KCSANDataRace, + }, + { + includePrefixes: []string{"KCSAN: assert: race in"}, + crashType: KCSANAssert, + }, + { + includePrefixes: []string{ + // keep-sorted start + "BUG: bad unlock balance in", + "BUG: held lock freed in", + "BUG: rwlock", + "BUG: spinlock", + "BUG: still has locks held in", + "BUG: using", // BUG: using ... in preemptible ... + "WARNING: bad unlock balance in", + "WARNING: held lock freed in", + "WARNING: lock held", + "WARNING: locking bug in", + "WARNING: nested lock was not taken in", + "WARNING: still has locks held in", + "WARNING: suspicious RCU usage in", + "inconsistent lock state in", + "possible deadlock in", + // keep-sorted end + }, + crashType: LockdepBug, + }, + { + includePrefixes: []string{ + // keep-sorted start + "BUG: scheduling while atomic in", + "BUG: sleeping function called from invalid context in", + // keep-sorted end + }, + crashType: AtomicSleep, + }, + { + includePrefixes: []string{"memory leak in"}, + crashType: MemoryLeak, + }, + { + includePrefixes: []string{ + // keep-sorted start + "BUG: bad usercopy in", + "kernel BUG", + // keep-sorted end + }, + crashType: Bug, + }, + { + includePrefixes: []string{"WARNING in"}, + crashType: Warning, + }, + { + includePrefixes: []string{ + // keep-sorted start + "BUG: stack guard page was hit in", + "WARNING: corrupted", + "WARNING: kernel stack frame pointer has bad value", + "WARNING: kernel stack regs has bad", + // keep-sorted end + }, + crashType: UnknownType, // This is printk(). + }, + { + includePrefixes: []string{ + // keep-sorted start + "BUG: soft lockup in", + "INFO: rcu detected stall in", + "INFO: task can't die in", + "INFO: task hung in", + // keep-sorted end + }, + crashType: Hang, + }, + { + includePrefixes: []string{ + // keep-sorted start + "Alignment trap in", + "BUG: Object already free", + "Internal error in", + "PANIC: double fault", + "Unhandled fault in", + "VFS: Busy inodes after unmount (use-after-free)", + "VFS: Close: file count is zero (use-after-free)", + "divide error in", + "general protection fault in", + "go runtime error", + "invalid opcode in", + "kernel panic:", + "kernel stack overflow", + "panic:", + "rust_kernel panicked", + "stack segment fault in", + "trusty:", + "unregister_netdevice: waiting for DEV to become free", + // keep-sorted end + }, + crashType: DoS, + }, + { + includePrefixes: []string{"unexpected kernel reboot"}, + crashType: UnexpectedReboot, + }, + { + includePrefixes: []string{ + "SYZFAIL", + "SYZFATAL:", + }, + crashType: SyzFailure, + }, + + // DEFAULTS. + { + includePrefixes: []string{"WARNING:"}, + crashType: Warning, + }, + { + includePrefixes: []string{"BUG:"}, + crashType: UnknownType, + }, + { + includePrefixes: []string{"INFO:"}, + crashType: UnknownType, + }, + { + includePrefixes: []string{"KASAN:"}, + crashType: KASANUnknown, + }, + { + includePrefixes: []string{"KFENCE:"}, + crashType: KFENCEUnknown, + }, + { + includePrefixes: []string{"KMSAN:"}, + crashType: KMSANUnknown, + }, + { + includePrefixes: []string{"UBSAN:"}, + crashType: UBSAN, + }, + { + includePrefixes: []string{"KCSAN:"}, + crashType: KCSANUnknown, + }, +} diff --git a/pkg/report/crash/title_to_type_test.go b/pkg/report/crash/title_to_type_test.go new file mode 100644 index 000000000..5ffa13412 --- /dev/null +++ b/pkg/report/crash/title_to_type_test.go @@ -0,0 +1,39 @@ +// 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 crash + +import ( + "strings" + "testing" +) + +func TestTitleToTypeDefinitions(t *testing.T) { + knownPrefixes := make(map[string]bool) + for _, def := range titleToType { + if len(def.includePrefixes) == 0 { + t.Errorf("title definition can't be empty") + } + for _, prefix := range def.includePrefixes { + if prefix == "" { + t.Errorf("title prefix can't be empty") + } + if knownPrefixes[prefix] { + t.Errorf("duplicate title prefix: %q", prefix) + } + if wasMatched, byPrefix := hasPrefix(knownPrefixes, prefix); wasMatched { + t.Errorf("%s was matched by %s", prefix, byPrefix) + } + knownPrefixes[prefix] = true + } + } +} + +func hasPrefix(prefixes map[string]bool, s string) (bool, string) { + for prefix := range prefixes { + if strings.HasPrefix(s, prefix) { + return true, prefix + } + } + return false, "" +} diff --git a/pkg/report/crash/types.go b/pkg/report/crash/types.go index 4410230f2..915f3b4ae 100644 --- a/pkg/report/crash/types.go +++ b/pkg/report/crash/types.go @@ -3,7 +3,10 @@ package crash -import "slices" +import ( + "slices" + "strings" +) type Type string @@ -50,6 +53,17 @@ const ( UnexpectedReboot = Type("REBOOT") ) +func TitleToType(title string) Type { + for _, t := range titleToType { + for _, prefix := range t.includePrefixes { + if strings.HasPrefix(title, prefix) { + return t.crashType + } + } + } + return UnknownType +} + func (t Type) String() string { if t == UnknownType { return "UNKNOWN" diff --git a/pkg/report/impact_score.go b/pkg/report/impact_score.go index 8139644d2..dec727d1d 100644 --- a/pkg/report/impact_score.go +++ b/pkg/report/impact_score.go @@ -55,7 +55,7 @@ var impactOrder = []crash.Type{ func TitlesToImpact(title string, otherTitles ...string) int { maxImpact := -1 for _, t := range append([]string{title}, otherTitles...) { - typ := TitleToCrashType(t) + typ := crash.TitleToType(t) for i, t := range impactOrder { if typ == t { maxImpact = max(maxImpact, len(impactOrder)-i) diff --git a/pkg/report/linux.go b/pkg/report/linux.go index f4266658c..01949f304 100644 --- a/pkg/report/linux.go +++ b/pkg/report/linux.go @@ -190,7 +190,7 @@ func (ctx *linux) Parse(output []byte) *Report { } rep.reportPrefixLen = len(rep.Report) rep.Report = append(rep.Report, report...) - rep.Type = TitleToCrashType(rep.Title) + rep.Type = crash.TitleToType(rep.Title) setExecutorInfo(rep) if !rep.Corrupted { rep.Corrupted, rep.CorruptedReason = isCorrupted(title, report, format) diff --git a/pkg/report/report.go b/pkg/report/report.go index b4f93e3ac..ce9c50f2c 100644 --- a/pkg/report/report.go +++ b/pkg/report/report.go @@ -800,7 +800,7 @@ func simpleLineParser(output []byte, oopses []*oops, params *stackParams, ignore rep.Report = output[rep.StartPos:] rep.Corrupted = corrupted != "" rep.CorruptedReason = corrupted - rep.Type = TitleToCrashType(rep.Title) + rep.Type = crash.TitleToType(rep.Title) return rep } @@ -933,17 +933,6 @@ var groupGoRuntimeErrors = oops{ }, } -func TitleToCrashType(title string) crash.Type { - for _, t := range titleToType { - for _, prefix := range t.includePrefixes { - if strings.HasPrefix(title, prefix) { - return t.crashType - } - } - } - return crash.UnknownType -} - const reportSeparator = "\n<<<<<<<<<<<<<<< tail report >>>>>>>>>>>>>>>\n\n" func MergeReportBytes(reps []*Report) []byte { diff --git a/pkg/report/report_test.go b/pkg/report/report_test.go index 8a4a4975d..6a0a3f6c3 100644 --- a/pkg/report/report_test.go +++ b/pkg/report/report_test.go @@ -210,7 +210,7 @@ func testFromReport(rep *Report) *ParseTest { Corrupted: rep.Corrupted, corruptedReason: rep.CorruptedReason, Suppressed: rep.Suppressed, - Type: TitleToCrashType(rep.Title), + Type: crash.TitleToType(rep.Title), Frame: rep.Frame, Report: rep.Report, } diff --git a/pkg/report/title_to_type.go b/pkg/report/title_to_type.go deleted file mode 100644 index a17edeeb9..000000000 --- a/pkg/report/title_to_type.go +++ /dev/null @@ -1,321 +0,0 @@ -// 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 ( - "github.com/google/syzkaller/pkg/report/crash" -) - -var titleToType = []struct { - includePrefixes []string - crashType crash.Type -}{ - { - includePrefixes: []string{ - "KFENCE: use-after-free write", - }, - crashType: crash.KFENCEUseAfterFreeWrite, - }, - { - includePrefixes: []string{ - "KFENCE: use-after-free read", - "KFENCE: use-after-free", // Read/Write is not clear. It is at least Read. - }, - crashType: crash.KFENCEUseAfterFreeRead, - }, - { - includePrefixes: []string{ - "KFENCE: invalid write", - "KFENCE: out-of-bounds write", - }, - crashType: crash.KFENCEWrite, - }, - { - includePrefixes: []string{ - // keep-sorted start - "KFENCE: invalid read", - "KFENCE: out-of-bounds read", - "KFENCE: out-of-bounds", // Read/Write is not clear. It is at least Read. - // keep-sorted end - }, - crashType: crash.KFENCERead, - }, - { - includePrefixes: []string{ - "KFENCE: memory corruption", - }, - crashType: crash.KFENCEMemoryCorruption, - }, - { - includePrefixes: []string{ - "KFENCE: invalid free", - }, - crashType: crash.KFENCEInvalidFree, - }, - { - includePrefixes: []string{ - "KMSAN: uninit-value", - }, - crashType: crash.KMSANUninitValue, - }, - { - includePrefixes: []string{ - // keep-sorted start - "KMSAN: kernel-infoleak-after-free", - "KMSAN: kernel-usb-infoleak-after-free", - "KMSAN: use-after-free", - // keep-sorted end - }, - crashType: crash.KMSANUseAfterFreeRead, - }, - { - includePrefixes: []string{ - "KMSAN: kernel-infoleak", - "KMSAN: kernel-usb-infoleak", - }, - crashType: crash.KMSANInfoLeak, - }, - { - includePrefixes: []string{ - "KASAN: null-ptr-deref Write", - }, - crashType: crash.KASANNullPtrDerefWrite, - }, - { - includePrefixes: []string{ - "KASAN: null-ptr-deref Read", - }, - crashType: crash.KASANNullPtrDerefRead, - }, - { - includePrefixes: []string{ - "BUG: unable to handle kernel NULL pointer dereference in", - }, - crashType: crash.NullPtrDerefBUG, - }, - { - includePrefixes: []string{ - // keep-sorting start - "KASAN: global-out-of-bounds Write", - "KASAN: out-of-bounds Write", - "KASAN: slab-out-of-bounds Write", - "KASAN: stack-out-of-bounds Write", - "KASAN: user-memory-access Write", - "KASAN: vmalloc-out-of-bounds Write", - "KASAN: wild-memory-access Write", - // keep-sorting end - }, - crashType: crash.KASANWrite, - }, - { - includePrefixes: []string{ - // keep-sorting start - "KASAN: global-out-of-bounds Read", - "KASAN: invalid-access Read", - "KASAN: out-of-bounds Read", - "KASAN: slab-out-of-bounds Read", - "KASAN: slab-out-of-bounds", // Read/Write is not clear. It is at least Read. - "KASAN: stack-out-of-bounds Read", - "KASAN: stack-out-of-bounds", // Read/Write is not clear. It is at least Read. - "KASAN: unknown-crash Read", - "KASAN: user-memory-access Read", - "KASAN: vmalloc-out-of-bounds Read", - "KASAN: wild-memory-access Read", - "KASAN: wild-memory-access", // Read/Write is not clear. It is at least Read. - // keep-sorting end - }, - crashType: crash.KASANRead, - }, - { - includePrefixes: []string{ - "KASAN: double-free or invalid-free", - "KASAN: invalid-free", - }, - crashType: crash.KASANInvalidFree, - }, - { - includePrefixes: []string{ - "KASAN: slab-use-after-free Write", - "KASAN: use-after-free Write", - }, - crashType: crash.KASANUseAfterFreeWrite, - }, - { - includePrefixes: []string{ - "KASAN: slab-use-after-free Read", - "KASAN: use-after-free Read", - "KASAN: use-after-free", // Read/Write is not clear. It is at least Read. - }, - crashType: crash.KASANUseAfterFreeRead, - }, - { - includePrefixes: []string{ - // keep-sorting start - "BUG: corrupted list", - "BUG: unable to handle kernel paging request", - // keep-sorting end - }, - crashType: crash.MemorySafetyBUG, - }, - { - includePrefixes: []string{ - "WARNING: refcount bug", - }, - crashType: crash.RefcountWARNING, - }, - { - includePrefixes: []string{ - "UBSAN: array-index-out-of-bounds", - }, - crashType: crash.MemorySafetyUBSAN, - }, - { - includePrefixes: []string{"KCSAN: data-race"}, - crashType: crash.KCSANDataRace, - }, - { - includePrefixes: []string{"KCSAN: assert: race in"}, - crashType: crash.KCSANAssert, - }, - { - includePrefixes: []string{ - // keep-sorted start - "BUG: bad unlock balance in", - "BUG: held lock freed in", - "BUG: rwlock", - "BUG: spinlock", - "BUG: still has locks held in", - "BUG: using", // BUG: using ... in preemptible ... - "WARNING: bad unlock balance in", - "WARNING: held lock freed in", - "WARNING: lock held", - "WARNING: locking bug in", - "WARNING: nested lock was not taken in", - "WARNING: still has locks held in", - "WARNING: suspicious RCU usage in", - "inconsistent lock state in", - "possible deadlock in", - // keep-sorted end - }, - crashType: crash.LockdepBug, - }, - { - includePrefixes: []string{ - // keep-sorted start - "BUG: scheduling while atomic in", - "BUG: sleeping function called from invalid context in", - // keep-sorted end - }, - crashType: crash.AtomicSleep, - }, - { - includePrefixes: []string{"memory leak in"}, - crashType: crash.MemoryLeak, - }, - { - includePrefixes: []string{ - // keep-sorted start - "BUG: bad usercopy in", - "kernel BUG", - // keep-sorted end - }, - crashType: crash.Bug, - }, - { - includePrefixes: []string{"WARNING in"}, - crashType: crash.Warning, - }, - { - includePrefixes: []string{ - // keep-sorted start - "BUG: stack guard page was hit in", - "WARNING: corrupted", - "WARNING: kernel stack frame pointer has bad value", - "WARNING: kernel stack regs has bad", - // keep-sorted end - }, - crashType: crash.UnknownType, // This is printk(). - }, - { - includePrefixes: []string{ - // keep-sorted start - "BUG: soft lockup in", - "INFO: rcu detected stall in", - "INFO: task can't die in", - "INFO: task hung in", - // keep-sorted end - }, - crashType: crash.Hang, - }, - { - includePrefixes: []string{ - // keep-sorted start - "Alignment trap in", - "BUG: Object already free", - "Internal error in", - "PANIC: double fault", - "Unhandled fault in", - "VFS: Busy inodes after unmount (use-after-free)", - "VFS: Close: file count is zero (use-after-free)", - "divide error in", - "general protection fault in", - "go runtime error", - "invalid opcode in", - "kernel panic:", - "kernel stack overflow", - "panic:", - "rust_kernel panicked", - "stack segment fault in", - "trusty:", - "unregister_netdevice: waiting for DEV to become free", - // keep-sorted end - }, - crashType: crash.DoS, - }, - { - includePrefixes: []string{"unexpected kernel reboot"}, - crashType: crash.UnexpectedReboot, - }, - { - includePrefixes: []string{ - "SYZFAIL", - "SYZFATAL:", - }, - crashType: crash.SyzFailure, - }, - - // DEFAULTS. - { - includePrefixes: []string{"WARNING:"}, - crashType: crash.Warning, - }, - { - includePrefixes: []string{"BUG:"}, - crashType: crash.UnknownType, - }, - { - includePrefixes: []string{"INFO:"}, - crashType: crash.UnknownType, - }, - { - includePrefixes: []string{"KASAN:"}, - crashType: crash.KASANUnknown, - }, - { - includePrefixes: []string{"KFENCE:"}, - crashType: crash.KFENCEUnknown, - }, - { - includePrefixes: []string{"KMSAN:"}, - crashType: crash.KMSANUnknown, - }, - { - includePrefixes: []string{"UBSAN:"}, - crashType: crash.UBSAN, - }, - { - includePrefixes: []string{"KCSAN:"}, - crashType: crash.KCSANUnknown, - }, -} diff --git a/pkg/report/title_to_type_test.go b/pkg/report/title_to_type_test.go deleted file mode 100644 index a152a3445..000000000 --- a/pkg/report/title_to_type_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// 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 ( - "strings" - "testing" -) - -func TestTitleToTypeDefinitions(t *testing.T) { - knownPrefixes := make(map[string]bool) - for _, def := range titleToType { - if len(def.includePrefixes) == 0 { - t.Errorf("title definition can't be empty") - } - for _, prefix := range def.includePrefixes { - if prefix == "" { - t.Errorf("title prefix can't be empty") - } - if knownPrefixes[prefix] { - t.Errorf("duplicate title prefix: %q", prefix) - } - if wasMatched, byPrefix := hasPrefix(knownPrefixes, prefix); wasMatched { - t.Errorf("%s was matched by %s", prefix, byPrefix) - } - knownPrefixes[prefix] = true - } - } -} - -func hasPrefix(prefixes map[string]bool, s string) (bool, string) { - for prefix := range prefixes { - if strings.HasPrefix(s, prefix) { - return true, prefix - } - } - return false, "" -} -- cgit mrf-deployment