Skip to content

Commit

Permalink
syz-fuzzer, executor: Add support for blacklisting data race frames
Browse files Browse the repository at this point in the history
This adds support to add frames that have already been in data races, to
the KCSAN report blacklist.
  • Loading branch information
melver committed Oct 22, 2019
1 parent 4ee855e commit 5681358
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 13 deletions.
26 changes: 25 additions & 1 deletion executor/common_linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions executor/executor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
26 changes: 25 additions & 1 deletion pkg/csource/generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions pkg/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const (
Unknown Type = iota
Hang
MemoryLeak
DataRace
UnexpectedReboot
)

Expand All @@ -73,6 +74,8 @@ func (t Type) String() string {
return "HANG"
case MemoryLeak:
return "LEAK"
case DataRace:
return "DATARACE"
case UnexpectedReboot:
return "REBOOT"
default:
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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") {
Expand Down
2 changes: 2 additions & 0 deletions pkg/report/report_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions pkg/report/testdata/linux/report/427
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 4 additions & 2 deletions pkg/report/testdata/linux/report/428
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions pkg/rpctype/rpctype.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type ConnectRes struct {
AllSandboxes bool
CheckResult *CheckArgs
MemoryLeakFrames []string
DataRaceFrames []string
}

type CheckArgs struct {
Expand Down
30 changes: 26 additions & 4 deletions syz-fuzzer/fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand Down
15 changes: 13 additions & 2 deletions syz-manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()

Expand All @@ -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) {
Expand Down
12 changes: 9 additions & 3 deletions syz-manager/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down

0 comments on commit 5681358

Please sign in to comment.