From 5681358a2a14647b64cdb97e0d19d3614986d31c Mon Sep 17 00:00:00 2001 From: Marco Elver Date: Tue, 22 Oct 2019 13:33:27 +0200 Subject: [PATCH] syz-fuzzer, executor: Add support for blacklisting data race frames This adds support to add frames that have already been in data races, to the KCSAN report blacklist. --- executor/common_linux.h | 26 +++++++++++++++++++++++- executor/executor.cc | 8 ++++++++ pkg/csource/generated.go | 26 +++++++++++++++++++++++- pkg/report/report.go | 7 +++++++ pkg/report/report_test.go | 2 ++ pkg/report/testdata/linux/report/427 | 2 ++ pkg/report/testdata/linux/report/428 | 6 ++++-- pkg/rpctype/rpctype.go | 1 + syz-fuzzer/fuzzer.go | 30 ++++++++++++++++++++++++---- syz-manager/manager.go | 15 ++++++++++++-- syz-manager/rpc.go | 12 ++++++++--- 11 files changed, 122 insertions(+), 13 deletions(-) diff --git a/executor/common_linux.h b/executor/common_linux.h index 5888b65cfb0a..b56453510f31 100644 --- a/executor/common_linux.h +++ b/executor/common_linux.h @@ -2784,9 +2784,33 @@ static void setup_binfmt_misc() #endif #if SYZ_EXECUTOR || SYZ_ENABLE_KCSAN +#define KCSAN_DEBUGFS_FILE "/sys/kernel/debug/kcsan" + static void setup_kcsan() { - if (!write_file("/sys/kernel/debug/kcsan", "on")) + if (!write_file(KCSAN_DEBUGFS_FILE, "on")) fail("failed to enable KCSAN"); } + +#if SYZ_EXECUTOR // currently only used by executor +static void setup_kcsan_filterlist(char** frames, int nframes, bool blacklist) +{ + int fd = open(KCSAN_DEBUGFS_FILE, O_WRONLY); + if (fd == -1) + fail("failed to open(\"%s\")", KCSAN_DEBUGFS_FILE); + + const char* const filtertype = blacklist ? "blacklist" : "whitelist"; + printf("adding functions to KCSAN %s: ", filtertype); + dprintf(fd, "%s\n", filtertype); + for (int i = 0; i < nframes; ++i) { + printf("'%s' ", frames[i]); + dprintf(fd, "!%s\n", frames[i]); + } + printf("\n"); + + close(fd); +} + +#define SYZ_HAVE_KCSAN 1 +#endif #endif diff --git a/executor/executor.cc b/executor/executor.cc index 1f51ec279153..30d497ea9653 100644 --- a/executor/executor.cc +++ b/executor/executor.cc @@ -344,6 +344,14 @@ int main(int argc, char** argv) check_leaks(argv + 2, argc - 2); #else fail("leak checking is not implemented"); +#endif + return 0; + } + if (argc >= 2 && strcmp(argv[1], "setup_kcsan_blacklist") == 0) { +#if SYZ_HAVE_KCSAN + setup_kcsan_filterlist(argv + 2, argc - 2, /*blacklist=*/true); +#else + fail("KCSAN is not implemented"); #endif return 0; } diff --git a/pkg/csource/generated.go b/pkg/csource/generated.go index a8d5d31149a6..2b91d15d214a 100644 --- a/pkg/csource/generated.go +++ b/pkg/csource/generated.go @@ -5331,11 +5331,35 @@ static void setup_binfmt_misc() #endif #if SYZ_EXECUTOR || SYZ_ENABLE_KCSAN +#define KCSAN_DEBUGFS_FILE "/sys/kernel/debug/kcsan" + static void setup_kcsan() { - if (!write_file("/sys/kernel/debug/kcsan", "on")) + if (!write_file(KCSAN_DEBUGFS_FILE, "on")) fail("failed to enable KCSAN"); } + +#if SYZ_EXECUTOR +static void setup_kcsan_filterlist(char** frames, int nframes, bool blacklist) +{ + int fd = open(KCSAN_DEBUGFS_FILE, O_WRONLY); + if (fd == -1) + fail("failed to open(\"%s\")", KCSAN_DEBUGFS_FILE); + + const char* const filtertype = blacklist ? "blacklist" : "whitelist"; + printf("adding functions to KCSAN %s: ", filtertype); + dprintf(fd, "%s\n", filtertype); + for (int i = 0; i < nframes; ++i) { + printf("'%s' ", frames[i]); + dprintf(fd, "!%s\n", frames[i]); + } + printf("\n"); + + close(fd); +} + +#define SYZ_HAVE_KCSAN 1 +#endif #endif #elif GOOS_test diff --git a/pkg/report/report.go b/pkg/report/report.go index a6e82736d763..a434b3313b70 100644 --- a/pkg/report/report.go +++ b/pkg/report/report.go @@ -62,6 +62,7 @@ const ( Unknown Type = iota Hang MemoryLeak + DataRace UnexpectedReboot ) @@ -73,6 +74,8 @@ func (t Type) String() string { return "HANG" case MemoryLeak: return "LEAK" + case DataRace: + return "DATARACE" case UnexpectedReboot: return "REBOOT" default: @@ -119,6 +122,7 @@ func NewReporter(cfg *mgrconfig.Config) (Reporter, error) { const ( unexpectedKernelReboot = "unexpected kernel reboot" memoryLeakPrefix = "memory leak in " + dataRacePrefix = "KCSAN: data-race" ) var ctors = map[string]fn{ @@ -187,6 +191,9 @@ func extractReportType(rep *Report) Type { if strings.HasPrefix(rep.Title, memoryLeakPrefix) { return MemoryLeak } + if strings.HasPrefix(rep.Title, dataRacePrefix) { + return DataRace + } if strings.HasPrefix(rep.Title, "INFO: rcu detected stall") || strings.HasPrefix(rep.Title, "INFO: task hung") || strings.HasPrefix(rep.Title, "BUG: soft lockup") { diff --git a/pkg/report/report_test.go b/pkg/report/report_test.go index 983da5d1e2dc..3c966e751928 100644 --- a/pkg/report/report_test.go +++ b/pkg/report/report_test.go @@ -112,6 +112,8 @@ func parseHeaderLine(t *testing.T, test *ParseTest, ln string) { test.Type = Hang case MemoryLeak.String(): test.Type = MemoryLeak + case DataRace.String(): + test.Type = DataRace case UnexpectedReboot.String(): test.Type = UnexpectedReboot default: diff --git a/pkg/report/testdata/linux/report/427 b/pkg/report/testdata/linux/report/427 index c5867c395650..0364e06b2b8b 100644 --- a/pkg/report/testdata/linux/report/427 +++ b/pkg/report/testdata/linux/report/427 @@ -1,4 +1,6 @@ TITLE: KCSAN: data-race in find_next_bit / rcu_report_exp_cpu_mult +TYPE: DATARACE +FRAME: find_next_bit [ 44.377931][ C4] ================================================================== [ 44.379001][ C4] BUG: KCSAN: data-race in find_next_bit / rcu_report_exp_cpu_mult diff --git a/pkg/report/testdata/linux/report/428 b/pkg/report/testdata/linux/report/428 index 64dc4f001fe4..48f5a69f9f38 100644 --- a/pkg/report/testdata/linux/report/428 +++ b/pkg/report/testdata/linux/report/428 @@ -1,7 +1,9 @@ -TITLE: KCSAN: racing read in e1000_clean_rx_irq +TITLE: KCSAN: data-race in e1000_clean_rx_irq +TYPE: DATARACE +FRAME: e1000_clean_rx_irq [ 44.381409][ C4] ================================================================== -[ 44.381969][ C4] BUG: KCSAN: racing read in e1000_clean_rx_irq+0x551/0xb10 +[ 44.381969][ C4] BUG: KCSAN: data-race in e1000_clean_rx_irq+0x551/0xb10 [ 44.382748][ C4] [ 44.383468][ C4] race at unknown origin, with read to 0xffff933db8a2ae6c of 1 bytes by interrupt on cpu 0: [ 44.384066][ C4] e1000_clean_rx_irq+0x551/0xb10 diff --git a/pkg/rpctype/rpctype.go b/pkg/rpctype/rpctype.go index 61d98dacccce..fcc01a99aeeb 100644 --- a/pkg/rpctype/rpctype.go +++ b/pkg/rpctype/rpctype.go @@ -35,6 +35,7 @@ type ConnectRes struct { AllSandboxes bool CheckResult *CheckArgs MemoryLeakFrames []string + DataRaceFrames []string } type CheckArgs struct { diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index b005f91b81ef..41da54d1cc1c 100644 --- a/syz-fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go @@ -235,11 +235,9 @@ func main() { comparisonTracingEnabled: r.CheckResult.Features[host.FeatureComparisons].Enabled, corpusHashes: make(map[hash.Sig]struct{}), } - var gateCallback func() - if r.CheckResult.Features[host.FeatureLeakChecking].Enabled { - gateCallback = func() { fuzzer.gateCallback(r.MemoryLeakFrames) } - } + gateCallback := fuzzer.useBugFrames(r, *flagProcs) fuzzer.gate = ipc.NewGate(2**flagProcs, gateCallback) + for i := 0; fuzzer.poll(i == 0, nil); i++ { } calls := make(map[*prog.Syscall]bool) @@ -261,6 +259,21 @@ func main() { fuzzer.pollLoop() } +// Returns gateCallback for leak checking if enabled. +func (fuzzer *Fuzzer) useBugFrames(r *rpctype.ConnectRes, flagProcs int) func() { + var gateCallback func() + + if r.CheckResult.Features[host.FeatureLeakChecking].Enabled { + gateCallback = func() { fuzzer.gateCallback(r.MemoryLeakFrames) } + } + + if r.CheckResult.Features[host.FeatureKCSAN].Enabled && len(r.DataRaceFrames) != 0 { + fuzzer.blacklistDataRaceFrames(r.DataRaceFrames) + } + + return gateCallback +} + func (fuzzer *Fuzzer) gateCallback(leakFrames []string) { // Leak checking is very slow so we don't do it while triaging the corpus // (otherwise it takes infinity). When we have presumably triaged the corpus @@ -285,6 +298,15 @@ func (fuzzer *Fuzzer) gateCallback(leakFrames []string) { } } +func (fuzzer *Fuzzer) blacklistDataRaceFrames(frames []string) { + args := append([]string{"setup_kcsan_blacklist"}, frames...) + output, err := osutil.RunCmd(10*time.Minute, "", fuzzer.config.Executor, args...) + if err != nil { + log.Fatalf("failed to set KCSAN blacklist: %v", err) + } + log.Logf(0, "%s", output) +} + func (fuzzer *Fuzzer) pollLoop() { var execTotal uint64 var lastPoll time.Time diff --git a/syz-manager/manager.go b/syz-manager/manager.go index f92b51724a5e..0aaee7828743 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -76,6 +76,7 @@ type Manager struct { newRepros [][]byte lastMinCorpus int memoryLeakFrames map[string]bool + dataRaceFrames map[string]bool needMoreRepros chan chan bool hubReproQueue chan *Crash @@ -169,6 +170,7 @@ func RunManager(cfg *mgrconfig.Config, target *prog.Target, sysTarget *targets.T corpus: make(map[string]rpctype.RPCInput), disabledHashes: make(map[string]struct{}), memoryLeakFrames: make(map[string]bool), + dataRaceFrames: make(map[string]bool), fresh: true, vmStop: make(chan bool), hubReproQueue: make(chan *Crash, 10), @@ -587,6 +589,11 @@ func (mgr *Manager) saveCrash(crash *Crash) bool { mgr.memoryLeakFrames[crash.Frame] = true mgr.mu.Unlock() } + if crash.Type == report.DataRace { + mgr.mu.Lock() + mgr.dataRaceFrames[crash.Frame] = true + mgr.mu.Unlock() + } if crash.Suppressed { log.Logf(0, "vm-%v: suppressed crash %v", crash.vmIndex, crash.Title) mgr.stats.crashSuppressed.inc() @@ -895,7 +902,7 @@ func (mgr *Manager) minimizeCorpus() { mgr.corpusDB.BumpVersion(currentDBVersion) } -func (mgr *Manager) fuzzerConnect() ([]rpctype.RPCInput, []string) { +func (mgr *Manager) fuzzerConnect() ([]rpctype.RPCInput, BugFrames) { mgr.mu.Lock() defer mgr.mu.Unlock() @@ -908,7 +915,11 @@ func (mgr *Manager) fuzzerConnect() ([]rpctype.RPCInput, []string) { for frame := range mgr.memoryLeakFrames { memoryLeakFrames = append(memoryLeakFrames, frame) } - return corpus, memoryLeakFrames + dataRaceFrames := make([]string, 0, len(mgr.dataRaceFrames)) + for frame := range mgr.dataRaceFrames { + dataRaceFrames = append(dataRaceFrames, frame) + } + return corpus, BugFrames{memoryLeaks: memoryLeakFrames, dataRaces: dataRaceFrames} } func (mgr *Manager) machineChecked(a *rpctype.CheckArgs) { diff --git a/syz-manager/rpc.go b/syz-manager/rpc.go index e4875c20f846..69d0d69a614c 100644 --- a/syz-manager/rpc.go +++ b/syz-manager/rpc.go @@ -36,9 +36,14 @@ type Fuzzer struct { newMaxSignal signal.Signal } +type BugFrames struct { + memoryLeaks []string + dataRaces []string +} + // RPCManagerView restricts interface between RPCServer and Manager. type RPCManagerView interface { - fuzzerConnect() ([]rpctype.RPCInput, []string) + fuzzerConnect() ([]rpctype.RPCInput, BugFrames) machineChecked(result *rpctype.CheckArgs) newInput(inp rpctype.RPCInput, sign signal.Signal) candidateBatch(size int) []rpctype.RPCCandidate @@ -70,7 +75,7 @@ func (serv *RPCServer) Connect(a *rpctype.ConnectArgs, r *rpctype.ConnectRes) er log.Logf(1, "fuzzer %v connected", a.Name) serv.stats.vmRestarts.inc() - corpus, memoryLeakFrames := serv.mgr.fuzzerConnect() + corpus, bugFrames := serv.mgr.fuzzerConnect() serv.mu.Lock() defer serv.mu.Unlock() @@ -80,7 +85,8 @@ func (serv *RPCServer) Connect(a *rpctype.ConnectArgs, r *rpctype.ConnectRes) er inputs: corpus, newMaxSignal: serv.maxSignal.Copy(), } - r.MemoryLeakFrames = memoryLeakFrames + r.MemoryLeakFrames = bugFrames.memoryLeaks + r.DataRaceFrames = bugFrames.dataRaces r.EnabledCalls = serv.enabledSyscalls r.CheckResult = serv.checkResult r.GitRevision = sys.GitRevision