diff options
| -rw-r--r-- | pkg/host/host.go | 22 | ||||
| -rw-r--r-- | pkg/host/host_linux.go | 38 | ||||
| -rw-r--r-- | pkg/ipc/ipcconfig/ipcconfig.go | 41 | ||||
| -rw-r--r-- | pkg/rpctype/rpctype.go | 11 | ||||
| -rw-r--r-- | pkg/runtest/run.go | 12 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer.go | 16 | ||||
| -rw-r--r-- | syz-manager/manager.go | 71 |
7 files changed, 111 insertions, 100 deletions
diff --git a/pkg/host/host.go b/pkg/host/host.go index b23dd9ab2..36c42de65 100644 --- a/pkg/host/host.go +++ b/pkg/host/host.go @@ -65,7 +65,7 @@ type Features [numFeatures]Feature var checkFeature [numFeatures]func() string var setupFeature [numFeatures]func() error -var callbFeature [numFeatures]func() +var callbFeature [numFeatures]func(leakFrames [][]byte) func unconditionallyEnabled() string { return "" } @@ -104,11 +104,11 @@ func Check(target *prog.Target) (*Features, error) { // Setup enables and does any one-time setup for the requested features on the host. // Note: this can be called multiple times and must be idempotent. -func Setup(target *prog.Target, features *Features) (func(), error) { +func Setup(target *prog.Target, features *Features) (func(leakFrames [][]byte), error) { if target.OS == "akaros" || target.OS == "test" { return nil, nil } - var callback func() + var callback func([][]byte) for n, setup := range setupFeature { if setup == nil || !features[n].Enabled { continue @@ -117,15 +117,15 @@ func Setup(target *prog.Target, features *Features) (func(), error) { return nil, err } cb := callbFeature[n] - if cb != nil { - prev := callback - callback = func() { - cb() - if prev != nil { - prev() - } + if cb == nil { + continue + } + prev := callback + callback = func(leakFrames [][]byte) { + cb(leakFrames) + if prev != nil { + prev(leakFrames) } - } } return callback, nil diff --git a/pkg/host/host_linux.go b/pkg/host/host_linux.go index 8214b35af..5c5db793b 100644 --- a/pkg/host/host_linux.go +++ b/pkg/host/host_linux.go @@ -469,7 +469,7 @@ func setupLeakChecking() error { return nil } -func callbackLeakChecking() { +func callbackLeakChecking(leakFrames [][]byte) { start := time.Now() fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) if err != nil { @@ -506,11 +506,15 @@ func callbackLeakChecking() { if _, err := syscall.Write(fd, []byte("scan")); err != nil { panic(err) } + if _, err := syscall.Seek(fd, 0, 0); err != nil { + panic(err) + } n, err := syscall.Read(fd, buf) if err != nil { panic(err) } nleaks := 0 + nextLeak: for buf = buf[:n]; len(buf) != 0; { end := bytes.Index(buf[1:], []byte("unreferenced object")) if end != -1 { @@ -520,14 +524,18 @@ func callbackLeakChecking() { } report := buf[:end] buf = buf[end:] - if kmemleakIgnore(report) { - continue + for _, frame := range leakFrames { + if bytes.Contains(report, frame) { + continue nextLeak + } } // BUG in output should be recognized by manager. fmt.Printf("BUG: memory leak\n%s\n", report) nleaks++ } if nleaks != 0 { + // If we exit right away, dying executors will dump lots of garbage to console. + time.Sleep(time.Hour) os.Exit(1) } } @@ -536,30 +544,6 @@ func callbackLeakChecking() { } } -func kmemleakIgnore(report []byte) bool { - // kmemleak has a bunch of false positives (at least what looks like - // false positives at first glance). So we are conservative with what we report. - // First, we filter out any allocations that don't come from executor processes. - // Second, we ignore a bunch of functions entirely. - // Ideally, someone should debug/fix all these cases and remove ignores. - if !bytes.Contains(report, []byte(`comm "syz-executor`)) { - return true - } - for _, ignore := range []string{ - " copy_process", - " do_execveat_common", - " __ext4_", - " get_empty_filp", - " do_filp_open", - " new_inode", - } { - if bytes.Contains(report, []byte(ignore)) { - return true - } - } - return false -} - func checkSandboxNamespace() string { if err := osutil.IsAccessible("/proc/self/ns/user"); err != nil { return err.Error() diff --git a/pkg/ipc/ipcconfig/ipcconfig.go b/pkg/ipc/ipcconfig/ipcconfig.go index 021978274..b94579102 100644 --- a/pkg/ipc/ipcconfig/ipcconfig.go +++ b/pkg/ipc/ipcconfig/ipcconfig.go @@ -33,18 +33,11 @@ func Default(target *prog.Target) (*ipc.Config, *ipc.ExecOpts, error) { if *flagDebug { c.Flags |= ipc.FlagDebug } - switch *flagSandbox { - case "none": - case "setuid": - c.Flags |= ipc.FlagSandboxSetuid - case "namespace": - c.Flags |= ipc.FlagSandboxNamespace - case "android_untrusted_app": - c.Flags |= ipc.FlagSandboxAndroidUntrustedApp - default: - return nil, nil, fmt.Errorf("flag sandbox must contain one of none/setuid/namespace/android_untrusted_app") + sandboxFlags, err := SandboxToFlags(*flagSandbox) + if err != nil { + return nil, nil, err } - + c.Flags |= sandboxFlags sysTarget := targets.Get(target.OS, target.Arch) if sysTarget.ExecutorUsesShmem { c.Flags |= ipc.FlagUseShmem @@ -65,3 +58,29 @@ func Default(target *prog.Target) (*ipc.Config, *ipc.ExecOpts, error) { return c, opts, nil } + +func SandboxToFlags(sandbox string) (ipc.EnvFlags, error) { + switch sandbox { + case "none": + return 0, nil + case "setuid": + return ipc.FlagSandboxSetuid, nil + case "namespace": + return ipc.FlagSandboxNamespace, nil + case "android_untrusted_app": + return ipc.FlagSandboxAndroidUntrustedApp, nil + default: + return 0, fmt.Errorf("sandbox must contain one of none/setuid/namespace/android_untrusted_app") + } +} + +func FlagsToSandbox(flags ipc.EnvFlags) string { + if flags&ipc.FlagSandboxSetuid != 0 { + return "setuid" + } else if flags&ipc.FlagSandboxNamespace != 0 { + return "namespace" + } else if flags&ipc.FlagSandboxAndroidUntrustedApp != 0 { + return "android_untrusted_app" + } + return "none" +} diff --git a/pkg/rpctype/rpctype.go b/pkg/rpctype/rpctype.go index eb2303950..68f85eedc 100644 --- a/pkg/rpctype/rpctype.go +++ b/pkg/rpctype/rpctype.go @@ -29,11 +29,12 @@ type ConnectArgs struct { } type ConnectRes struct { - EnabledCalls []int - GitRevision string - TargetRevision string - AllSandboxes bool - CheckResult *CheckArgs + EnabledCalls []int + GitRevision string + TargetRevision string + AllSandboxes bool + CheckResult *CheckArgs + MemoryLeakFrames [][]byte } type CheckArgs struct { diff --git a/pkg/runtest/run.go b/pkg/runtest/run.go index 115f5ecb7..0ec0bb125 100644 --- a/pkg/runtest/run.go +++ b/pkg/runtest/run.go @@ -26,6 +26,7 @@ import ( "github.com/google/syzkaller/pkg/csource" "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/ipc" + "github.com/google/syzkaller/pkg/ipc/ipcconfig" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/sys/targets" @@ -307,14 +308,11 @@ func (ctx *Context) createSyzTest(p *prog.Prog, sandbox string, threaded, cov bo if sysTarget.ExecutorUsesForkServer { cfg.Flags |= ipc.FlagUseForkServer } - switch sandbox { - case "namespace": - cfg.Flags |= ipc.FlagSandboxNamespace - case "setuid": - cfg.Flags |= ipc.FlagSandboxSetuid - case "android_untrusted_app": - cfg.Flags |= ipc.FlagSandboxAndroidUntrustedApp + sandboxFlags, err := ipcconfig.SandboxToFlags(sandbox) + if err != nil { + return nil, err } + cfg.Flags |= sandboxFlags if threaded { opts.Flags |= ipc.FlagThreaded | ipc.FlagCollide } diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index cb259a914..fce4b48b8 100644 --- a/syz-fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go @@ -116,15 +116,7 @@ func main() { if err != nil { log.Fatalf("failed to create default ipc config: %v", err) } - sandbox := "none" - if config.Flags&ipc.FlagSandboxSetuid != 0 { - sandbox = "setuid" - } else if config.Flags&ipc.FlagSandboxNamespace != 0 { - sandbox = "namespace" - } else if config.Flags&ipc.FlagSandboxAndroidUntrustedApp != 0 { - sandbox = "android_untrusted_app" - } - + sandbox := ipcconfig.FlagsToSandbox(config.Flags) shutdown := make(chan struct{}) osutil.HandleInterrupts(shutdown) go func() { @@ -191,6 +183,10 @@ func main() { if err != nil { log.Fatalf("BUG: %v", err) } + var gateCallback func() + if periodicCallback != nil { + gateCallback = func() { periodicCallback(r.MemoryLeakFrames) } + } if r.CheckResult.Features[host.FeatureNetworkInjection].Enabled { config.Flags |= ipc.FlagEnableTun } @@ -213,7 +209,7 @@ func main() { outputType: outputType, config: config, execOpts: execOpts, - gate: ipc.NewGate(2**flagProcs, periodicCallback), + gate: ipc.NewGate(2**flagProcs, gateCallback), workQueue: newWorkQueue(*flagProcs, needPoll), needPoll: needPoll, manager: manager, diff --git a/syz-manager/manager.go b/syz-manager/manager.go index 57e2e5073..09f27d657 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -13,6 +13,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "sync" "sync/atomic" "time" @@ -70,15 +71,16 @@ type Manager struct { phase int enabledSyscalls []int - candidates []rpctype.RPCCandidate // untriaged inputs from corpus and hub - disabledHashes map[string]struct{} - corpus map[string]rpctype.RPCInput - corpusCover cover.Cover - corpusSignal signal.Signal - maxSignal signal.Signal - prios [][]float32 - newRepros [][]byte - lastMinCorpus int + candidates []rpctype.RPCCandidate // untriaged inputs from corpus and hub + disabledHashes map[string]struct{} + corpus map[string]rpctype.RPCInput + corpusCover cover.Cover + corpusSignal signal.Signal + maxSignal signal.Signal + prios [][]float32 + newRepros [][]byte + lastMinCorpus int + memoryLeakFrames map[string]bool fuzzers map[string]*Fuzzer needMoreRepros chan chan bool @@ -171,26 +173,27 @@ func RunManager(cfg *mgrconfig.Config, target *prog.Target, sysTarget *targets.T } mgr := &Manager{ - cfg: cfg, - vmPool: vmPool, - target: target, - sysTarget: sysTarget, - reporter: reporter, - crashdir: crashdir, - startTime: time.Now(), - stats: new(Stats), - fuzzerStats: make(map[string]uint64), - crashTypes: make(map[string]bool), - enabledSyscalls: enabledSyscalls, - corpus: make(map[string]rpctype.RPCInput), - disabledHashes: make(map[string]struct{}), - fuzzers: make(map[string]*Fuzzer), - fresh: true, - vmStop: make(chan bool), - hubReproQueue: make(chan *Crash, 10), - needMoreRepros: make(chan chan bool), - reproRequest: make(chan chan map[string]bool), - usedFiles: make(map[string]time.Time), + cfg: cfg, + vmPool: vmPool, + target: target, + sysTarget: sysTarget, + reporter: reporter, + crashdir: crashdir, + startTime: time.Now(), + stats: new(Stats), + fuzzerStats: make(map[string]uint64), + crashTypes: make(map[string]bool), + enabledSyscalls: enabledSyscalls, + corpus: make(map[string]rpctype.RPCInput), + disabledHashes: make(map[string]struct{}), + memoryLeakFrames: make(map[string]bool), + fuzzers: make(map[string]*Fuzzer), + fresh: true, + vmStop: make(chan bool), + hubReproQueue: make(chan *Crash, 10), + needMoreRepros: make(chan chan bool), + reproRequest: make(chan chan map[string]bool), + usedFiles: make(map[string]time.Time), } log.Logf(0, "loading corpus...") @@ -602,6 +605,12 @@ func (mgr *Manager) emailCrash(crash *Crash) { } func (mgr *Manager) saveCrash(crash *Crash) bool { + if strings.HasPrefix(crash.Title, report.MemoryLeakPrefix) { + frame := crash.Title[len(report.MemoryLeakPrefix):] + mgr.mu.Lock() + mgr.memoryLeakFrames[frame] = true + mgr.mu.Unlock() + } if crash.Suppressed { log.Logf(0, "vm-%v: suppressed crash %v", crash.vmIndex, crash.Title) mgr.stats.crashSuppressed.inc() @@ -910,6 +919,10 @@ func (mgr *Manager) Connect(a *rpctype.ConnectArgs, r *rpctype.ConnectRes) error for _, inp := range mgr.corpus { f.inputs = append(f.inputs, inp) } + r.MemoryLeakFrames = make([][]byte, 0, len(mgr.memoryLeakFrames)) + for frame := range mgr.memoryLeakFrames { + r.MemoryLeakFrames = append(r.MemoryLeakFrames, []byte(frame)) + } r.EnabledCalls = mgr.enabledSyscalls r.CheckResult = mgr.checkResult r.GitRevision = sys.GitRevision |
