diff options
| -rw-r--r-- | docs/configuration.md | 1 | ||||
| -rw-r--r-- | pkg/host/host.go | 79 | ||||
| -rw-r--r-- | pkg/host/host_akaros.go | 4 | ||||
| -rw-r--r-- | pkg/host/host_darwin.go | 12 | ||||
| -rw-r--r-- | pkg/host/host_freebsd.go | 4 | ||||
| -rw-r--r-- | pkg/host/host_fuchsia.go | 4 | ||||
| -rw-r--r-- | pkg/host/host_linux.go | 235 | ||||
| -rw-r--r-- | pkg/host/host_netbsd.go | 4 | ||||
| -rw-r--r-- | pkg/host/host_test.go | 13 | ||||
| -rw-r--r-- | pkg/host/host_windows.go | 4 | ||||
| -rw-r--r-- | pkg/ipc/ipc.go | 8 | ||||
| -rw-r--r-- | pkg/rpctype/rpctype.go | 36 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer.go | 179 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer_freebsd.go | 21 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer_fuchsia.go | 23 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer_linux.go | 180 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer_netbsd.go | 21 | ||||
| -rw-r--r-- | syz-fuzzer/fuzzer_windows.go | 21 | ||||
| -rw-r--r-- | syz-fuzzer/testing.go | 161 | ||||
| -rw-r--r-- | syz-manager/html.go | 23 | ||||
| -rw-r--r-- | syz-manager/manager.go | 67 | ||||
| -rw-r--r-- | syz-manager/mgrconfig/mgrconfig.go | 2 | ||||
| -rw-r--r-- | tools/syz-crush/crush.go | 2 | ||||
| -rw-r--r-- | tools/syz-execprog/execprog.go | 15 | ||||
| -rw-r--r-- | tools/syz-stress/stress.go | 11 |
25 files changed, 592 insertions, 538 deletions
diff --git a/docs/configuration.md b/docs/configuration.md index 301fee770..c53074444 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -18,7 +18,6 @@ following keys in its top-level object: - `vmlinux`: Location of the `vmlinux` file that corresponds to the kernel being tested (used for report symbolization and coverage reports, optional). - `procs`: Number of parallel test processes in each VM (4 or 8 would be a reasonable number). - - `leak`: Detect memory leaks with kmemleak. - `image`: Location of the disk image file for the QEMU instance; a copy of this file is passed as the `-hda` option to `qemu-system-x86_64`. - `sshkey`: Location (on the host machine) of a root SSH identity to use for communicating with diff --git a/pkg/host/host.go b/pkg/host/host.go index c8f786e5a..d4f4d71a8 100644 --- a/pkg/host/host.go +++ b/pkg/host/host.go @@ -26,3 +26,82 @@ func DetectSupportedSyscalls(target *prog.Target, sandbox string) ( } return supported, unsupported, nil } + +const ( + FeatureCoverage = iota + FeatureComparisons + FeatureSandboxSetuid + FeatureSandboxNamespace + FeatureFaultInjection + FeatureLeakChecking + FeatureNetworkInjection + numFeatures +) + +type Feature struct { + Name string + Enabled bool + Reason string +} + +type Features [numFeatures]Feature + +var checkFeature [numFeatures]func() string +var setupFeature [numFeatures]func() error +var callbFeature [numFeatures]func() + +func unconditionallyEnabled() string { return "" } + +// Check detects features supported on the host. +// Empty string for a feature means the feature is supported, +// otherwise the string contains the reason why the feature is not supported. +func Check() (*Features, error) { + const unsupported = "support is not implemented in syzkaller" + res := &Features{ + FeatureCoverage: {Name: "code coverage", Reason: unsupported}, + FeatureComparisons: {Name: "comparison tracing", Reason: unsupported}, + FeatureSandboxSetuid: {Name: "setuid sandbox", Reason: unsupported}, + FeatureSandboxNamespace: {Name: "namespace sandbox", Reason: unsupported}, + FeatureFaultInjection: {Name: "fault injection", Reason: unsupported}, + FeatureLeakChecking: {Name: "leak checking", Reason: unsupported}, + FeatureNetworkInjection: {Name: "net packed injection", Reason: unsupported}, + } + for n, check := range checkFeature { + if check == nil { + continue + } + if reason := check(); reason == "" { + res[n].Enabled = true + res[n].Reason = "enabled" + } else { + res[n].Reason = reason + } + } + return res, nil +} + +// 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(features *Features) (func(), error) { + var callback func() + for n, setup := range setupFeature { + if setup == nil || !features[n].Enabled { + continue + } + if err := setup(); err != nil { + return nil, err + } + cb := callbFeature[n] + if cb != nil { + prev := callback + callback = func() { + cb() + if prev != nil { + prev() + } + } + + } + } + return callback, nil +} diff --git a/pkg/host/host_akaros.go b/pkg/host/host_akaros.go index 9478c7cac..806bdb50f 100644 --- a/pkg/host/host_akaros.go +++ b/pkg/host/host_akaros.go @@ -12,7 +12,3 @@ import ( func isSupported(c *prog.Syscall, sandbox string) (bool, string) { return true, "" } - -func EnableFaultInjection() error { - return nil -} diff --git a/pkg/host/host_darwin.go b/pkg/host/host_darwin.go new file mode 100644 index 000000000..9fbe752bd --- /dev/null +++ b/pkg/host/host_darwin.go @@ -0,0 +1,12 @@ +// Copyright 2018 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 host + +import ( + "github.com/google/syzkaller/prog" +) + +func isSupported(c *prog.Syscall, sandbox string) (bool, string) { + return false, "" +} diff --git a/pkg/host/host_freebsd.go b/pkg/host/host_freebsd.go index bada10ec7..238edd46d 100644 --- a/pkg/host/host_freebsd.go +++ b/pkg/host/host_freebsd.go @@ -11,6 +11,6 @@ func isSupported(c *prog.Syscall, sandbox string) (bool, string) { return true, "" } -func EnableFaultInjection() error { - return nil +func init() { + checkFeature[FeatureCoverage] = unconditionallyEnabled } diff --git a/pkg/host/host_fuchsia.go b/pkg/host/host_fuchsia.go index 87143d768..ea6a92700 100644 --- a/pkg/host/host_fuchsia.go +++ b/pkg/host/host_fuchsia.go @@ -12,7 +12,3 @@ import ( func isSupported(c *prog.Syscall, sandbox string) (bool, string) { return true, "" } - -func EnableFaultInjection() error { - return nil -} diff --git a/pkg/host/host_linux.go b/pkg/host/host_linux.go index 3cd096ba1..1e629b796 100644 --- a/pkg/host/host_linux.go +++ b/pkg/host/host_linux.go @@ -7,14 +7,18 @@ import ( "bytes" "fmt" "io/ioutil" + "os" "runtime" "strconv" "strings" "sync" "syscall" + "time" + "unsafe" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys/linux" ) func isSupported(c *prog.Syscall, sandbox string) (bool, string) { @@ -110,12 +114,8 @@ func isSupportedSyzkall(sandbox string, c *prog.Syscall) (bool, string) { } return onlySandboxNoneOrNamespace(sandbox) case "syz_emit_ethernet", "syz_extract_tcp_res": - fd, err := syscall.Open("/dev/net/tun", syscall.O_RDWR, 0) - if err != nil { - return false, fmt.Sprintf("open(/dev/net/tun) failed: %v", err) - } - syscall.Close(fd) - return true, "" + reason := checkNetworkInjection() + return reason == "", reason case "syz_kvm_setup_cpu": switch c.Name { case "syz_kvm_setup_cpu$x86": @@ -213,7 +213,87 @@ func extractStringConst(typ prog.Type) (string, bool) { return v, true } -func EnableFaultInjection() error { +func init() { + checkFeature[FeatureCoverage] = checkCoverage + checkFeature[FeatureComparisons] = checkComparisons + checkFeature[FeatureSandboxSetuid] = unconditionallyEnabled + checkFeature[FeatureSandboxNamespace] = checkSandboxNamespace + checkFeature[FeatureFaultInjection] = checkFaultInjection + setupFeature[FeatureFaultInjection] = setupFaultInjection + checkFeature[FeatureLeakChecking] = checkLeakChecking + setupFeature[FeatureLeakChecking] = setupLeakChecking + callbFeature[FeatureLeakChecking] = callbackLeakChecking + checkFeature[FeatureNetworkInjection] = checkNetworkInjection +} + +func checkCoverage() string { + if !osutil.IsExist("/sys/kernel/debug") { + return "debugfs is not enabled or not mounted" + } + if !osutil.IsExist("/sys/kernel/debug/kcov") { + return "CONFIG_KCOV is not enabled" + } + return "" +} + +func checkComparisons() string { + if !osutil.IsExist("/sys/kernel/debug") { + return "debugfs is not enabled or not mounted" + } + // TODO(dvyukov): this should run under target arch. + // E.g. KCOV ioctls were initially not supported on 386 (missing compat_ioctl), + // and a 386 executor won't be able to use them, but an amd64 fuzzer will be. + fd, err := syscall.Open("/sys/kernel/debug/kcov", syscall.O_RDWR, 0) + if err != nil { + return "CONFIG_KCOV is not enabled" + } + defer syscall.Close(fd) + // Trigger host target lazy initialization, it will fill linux.KCOV_INIT_TRACE. + // It's all wrong and needs to be refactored. + if _, err := prog.GetTarget(runtime.GOOS, runtime.GOARCH); err != nil { + return fmt.Sprintf("failed to get target: %v", err) + } + coverSize := uintptr(64 << 10) + _, _, errno := syscall.Syscall( + syscall.SYS_IOCTL, uintptr(fd), linux.KCOV_INIT_TRACE, coverSize) + if errno != 0 { + return fmt.Sprintf("ioctl(KCOV_INIT_TRACE) failed: %v", errno) + } + mem, err := syscall.Mmap(fd, 0, int(coverSize*unsafe.Sizeof(uintptr(0))), + syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) + if err != nil { + return fmt.Sprintf("KCOV mmap failed: %v", err) + } + defer syscall.Munmap(mem) + _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, + uintptr(fd), linux.KCOV_ENABLE, linux.KCOV_TRACE_CMP) + if errno != 0 { + if errno == syscall.ENOTTY { + return "kernel does not have comparison tracing support" + } + return fmt.Sprintf("ioctl(KCOV_TRACE_CMP) failed: %v", errno) + } + defer syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), linux.KCOV_DISABLE, 0) + return "" +} + +func checkFaultInjection() string { + if !osutil.IsExist("/proc/self/make-it-fail") { + return "CONFIG_FAULT_INJECTION is not enabled" + } + if !osutil.IsExist("/proc/thread-self/fail-nth") { + return "kernel does not have systematic fault injection support" + } + if !osutil.IsExist("/sys/kernel/debug") { + return "debugfs is not enabled or not mounted" + } + if !osutil.IsExist("/sys/kernel/debug/failslab/ignore-gfp-wait") { + return "CONFIG_FAULT_INJECTION_DEBUG_FS is not enabled" + } + return "" +} + +func setupFaultInjection() error { if err := osutil.WriteFile("/sys/kernel/debug/failslab/ignore-gfp-wait", []byte("N")); err != nil { return fmt.Errorf("failed to write /failslab/ignore-gfp-wait: %v", err) } @@ -231,3 +311,144 @@ func EnableFaultInjection() error { } return nil } + +func checkLeakChecking() string { + if !osutil.IsExist("/sys/kernel/debug") { + return "debugfs is not enabled or not mounted" + } + if !osutil.IsExist("/sys/kernel/debug/kmemleak") { + return "CONFIG_DEBUG_KMEMLEAK is not enabled" + } + return "" +} + +func setupLeakChecking() error { + fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) + if err != nil { + return fmt.Errorf("failed to open /sys/kernel/debug/kmemleak: %v", err) + } + defer syscall.Close(fd) + if _, err := syscall.Write(fd, []byte("scan=off")); err != nil { + // kmemleak returns EBUSY when kmemleak is already turned off. + if err != syscall.EBUSY { + return fmt.Errorf("write(kmemleak, scan=off) failed: %v", err) + } + } + // Flush boot leaks. + if _, err := syscall.Write(fd, []byte("scan")); err != nil { + return fmt.Errorf("write(kmemleak, scan) failed: %v", err) + } + time.Sleep(5 * time.Second) // account for MSECS_MIN_AGE + if _, err := syscall.Write(fd, []byte("scan")); err != nil { + return fmt.Errorf("write(kmemleak, scan) failed: %v", err) + } + if _, err := syscall.Write(fd, []byte("clear")); err != nil { + return fmt.Errorf("write(kmemleak, clear) failed: %v", err) + } + return nil +} + +func callbackLeakChecking() { + start := time.Now() + fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) + if err != nil { + panic(err) + } + defer syscall.Close(fd) + // KMEMLEAK has false positives. To mitigate most of them, it checksums + // potentially leaked objects, and reports them only on the next scan + // iff the checksum does not change. Because of that we do the following + // intricate dance: + // Scan, sleep, scan again. At this point we can get some leaks. + // If there are leaks, we sleep and scan again, this can remove + // false leaks. Then, read kmemleak again. If we get leaks now, then + // hopefully these are true positives during the previous testing cycle. + if _, err := syscall.Write(fd, []byte("scan")); err != nil { + panic(err) + } + time.Sleep(time.Second) + // Account for MSECS_MIN_AGE + // (1 second less because scanning will take at least a second). + for time.Since(start) < 4*time.Second { + time.Sleep(time.Second) + } + if _, err := syscall.Write(fd, []byte("scan")); err != nil { + panic(err) + } + buf := make([]byte, 128<<10) + n, err := syscall.Read(fd, buf) + if err != nil { + panic(err) + } + if n != 0 { + time.Sleep(time.Second) + if _, err := syscall.Write(fd, []byte("scan")); err != nil { + panic(err) + } + n, err := syscall.Read(fd, buf) + if err != nil { + panic(err) + } + nleaks := 0 + for buf = buf[:n]; len(buf) != 0; { + end := bytes.Index(buf[1:], []byte("unreferenced object")) + if end != -1 { + end++ + } else { + end = len(buf) + } + report := buf[:end] + buf = buf[end:] + if kmemleakIgnore(report) { + continue + } + // BUG in output should be recognized by manager. + fmt.Printf("BUG: memory leak\n%s\n", report) + nleaks++ + } + if nleaks != 0 { + os.Exit(1) + } + } + if _, err := syscall.Write(fd, []byte("clear")); err != nil { + panic(err) + } +} + +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 !osutil.IsExist("/proc/self/ns/user") { + return "/proc/self/ns/user is not present" + } + return "" +} + +func checkNetworkInjection() string { + if !osutil.IsExist("/dev/net/tun") { + return "/dev/net/tun is not present" + } + return "" +} diff --git a/pkg/host/host_netbsd.go b/pkg/host/host_netbsd.go index bada10ec7..238edd46d 100644 --- a/pkg/host/host_netbsd.go +++ b/pkg/host/host_netbsd.go @@ -11,6 +11,6 @@ func isSupported(c *prog.Syscall, sandbox string) (bool, string) { return true, "" } -func EnableFaultInjection() error { - return nil +func init() { + checkFeature[FeatureCoverage] = unconditionallyEnabled } diff --git a/pkg/host/host_test.go b/pkg/host/host_test.go index aa7020942..8a16d5e5d 100644 --- a/pkg/host/host_test.go +++ b/pkg/host/host_test.go @@ -11,7 +11,7 @@ import ( _ "github.com/google/syzkaller/sys" ) -func TestLog(t *testing.T) { +func TestDetectSupportedSyscalls(t *testing.T) { t.Parallel() target, err := prog.GetTarget(runtime.GOOS, runtime.GOARCH) if err != nil { @@ -37,3 +37,14 @@ func TestLog(t *testing.T) { t.Logf("%v: %v", c.Name, reason) } } + +func TestCheck(t *testing.T) { + t.Parallel() + features, err := Check() + if err != nil { + t.Fatal(err) + } + for _, feat := range features { + t.Logf("%-24v: %v", feat.Name, feat.Reason) + } +} diff --git a/pkg/host/host_windows.go b/pkg/host/host_windows.go index bada10ec7..1d01cf743 100644 --- a/pkg/host/host_windows.go +++ b/pkg/host/host_windows.go @@ -10,7 +10,3 @@ import ( func isSupported(c *prog.Syscall, sandbox string) (bool, string) { return true, "" } - -func EnableFaultInjection() error { - return nil -} diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go index 695218e4f..c7dc03753 100644 --- a/pkg/ipc/ipc.go +++ b/pkg/ipc/ipc.go @@ -19,7 +19,6 @@ import ( "time" "unsafe" - "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/sys/targets" @@ -278,13 +277,6 @@ var enableFaultOnce sync.Once // hanged: program hanged and was killed // err0: failed to start process, or executor has detected a logical error func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info []CallInfo, failed, hanged bool, err0 error) { - if opts.Flags&FlagInjectFault != 0 { - enableFaultOnce.Do(func() { - if err := host.EnableFaultInjection(); err != nil { - panic(err) - } - }) - } // Copy-in serialized program. progSize, err := p.SerializeForExec(env.in) if err != nil { diff --git a/pkg/rpctype/rpctype.go b/pkg/rpctype/rpctype.go index 3f90e1f7e..f5835914e 100644 --- a/pkg/rpctype/rpctype.go +++ b/pkg/rpctype/rpctype.go @@ -6,6 +6,7 @@ package rpctype import ( + "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/signal" ) @@ -27,33 +28,26 @@ type ConnectArgs struct { } type ConnectRes struct { - Prios [][]float32 - Inputs []RPCInput - MaxSignal signal.Serial - Candidates []RPCCandidate - EnabledCalls []int - NeedCheck bool + Prios [][]float32 + Inputs []RPCInput + MaxSignal signal.Serial + Candidates []RPCCandidate + EnabledCalls []int + GitRevision string + TargetRevision string + CheckResult *CheckArgs } type CheckArgs struct { - Name string - Error string - Kcov bool - Leak bool - Fault bool - UserNamespaces bool - CompsSupported bool - Calls []string - DisabledCalls []SyscallReason - FuzzerGitRev string - FuzzerSyzRev string - ExecutorGitRev string - ExecutorSyzRev string - ExecutorArch string + Name string + Error string + EnabledCalls []int + DisabledCalls []SyscallReason + Features *host.Features } type SyscallReason struct { - Name string + ID int Reason string } diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index 32d411538..14d6ababf 100644 --- a/syz-fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go @@ -11,10 +11,8 @@ import ( "os" "runtime" "runtime/debug" - "strings" "sync" "sync/atomic" - "syscall" "time" "github.com/google/syzkaller/pkg/hash" @@ -25,7 +23,7 @@ import ( "github.com/google/syzkaller/pkg/rpctype" "github.com/google/syzkaller/pkg/signal" "github.com/google/syzkaller/prog" - "github.com/google/syzkaller/sys" + _ "github.com/google/syzkaller/sys" ) type Fuzzer struct { @@ -46,7 +44,6 @@ type Fuzzer struct { comparisonTracingEnabled bool coverageEnabled bool leakCheckEnabled bool - leakCheckReady uint32 corpusMu sync.RWMutex corpus []*prog.Prog @@ -102,7 +99,6 @@ func main() { flagArch = flag.String("arch", runtime.GOARCH, "target arch") flagManager = flag.String("manager", "", "manager rpc address") flagProcs = flag.Int("procs", 1, "number of parallel test processes") - flagLeak = flag.Bool("leak", false, "detect memory leaks") flagOutput = flag.String("output", "stdout", "write programs to none/stdout/dmesg/file") flagPprof = flag.String("pprof", "", "address to serve pprof profiles") flagTest = flag.Bool("test", false, "enable image testing mode") // used by syz-ci @@ -131,7 +127,7 @@ func main() { config, execOpts, err := ipc.DefaultConfig() if err != nil { - panic(err) + log.Fatalf("failed to create default ipc config: %v", err) } sandbox := "none" if config.Flags&ipc.FlagSandboxSetuid != 0 { @@ -149,8 +145,14 @@ func main() { os.Exit(1) }() + checkArgs := &checkArgs{ + target: target, + sandbox: sandbox, + ipcConfig: config, + ipcExecOpts: execOpts, + } if *flagTest { - testImage(*flagManager, target, sandbox) + testImage(*flagManager, checkArgs) return } @@ -167,77 +169,46 @@ func main() { a := &rpctype.ConnectArgs{Name: *flagName} r := &rpctype.ConnectRes{} if err := rpctype.RPCCall(*flagManager, "Manager.Connect", a, r); err != nil { - panic(err) + log.Fatalf("failed to connect to manager: %v ", err) } - envCheckError := "" // this will be passed to manager to print to user and abort - - calls, disabled, err := buildCallList(target, r.EnabledCalls, sandbox) - if err != nil { - envCheckError += err.Error() + "\n" - } else if len(calls) == 0 { - envCheckError += "all system calls are disabled\n" + if r.CheckResult == nil { + checkArgs.gitRevision = r.GitRevision + checkArgs.targetRevision = r.TargetRevision + checkArgs.enabledCalls = r.EnabledCalls + r.CheckResult, err = checkMachine(checkArgs) + if err != nil { + r.CheckResult = &rpctype.CheckArgs{ + Error: err.Error(), + } + } + r.CheckResult.Name = *flagName + if err := rpctype.RPCCall(*flagManager, "Manager.Check", r.CheckResult, nil); err != nil { + log.Fatalf("Manager.Check call failed: %v", err) + } + if r.CheckResult.Error != "" { + log.Fatalf("%v", r.CheckResult.Error) + } } - ct := target.BuildChoiceTable(r.Prios, calls) - - // This requires "fault-inject: support systematic fault injection" kernel commit. - // TODO(dvykov): also need to check presence of /sys/kernel/debug/failslab/ignore-gfp-wait - // and /sys/kernel/debug/fail_futex/ignore-private, they can be missing if - // CONFIG_FAULT_INJECTION_DEBUG_FS is not enabled. - // Also need to move this somewhere else (to linux-specific part). - faultInjectionEnabled := false - if fd, err := syscall.Open("/proc/self/fail-nth", syscall.O_RDWR, 0); err == nil { - syscall.Close(fd) - faultInjectionEnabled = true + log.Logf(0, "syscalls: %v", len(r.CheckResult.EnabledCalls)) + for _, feat := range r.CheckResult.Features { + log.Logf(0, "%v: %v", feat.Name, feat.Reason) } - - if calls[target.SyscallMap["syz_emit_ethernet"]] || - calls[target.SyscallMap["syz_extract_tcp_res"]] { + periodicCallback, err := host.Setup(r.CheckResult.Features) + if err != nil { + log.Fatalf("BUG: %v", err) + } + if r.CheckResult.Features[host.FeatureNetworkInjection].Enabled { config.Flags |= ipc.FlagEnableTun } - if faultInjectionEnabled { + if r.CheckResult.Features[host.FeatureFaultInjection].Enabled { config.Flags |= ipc.FlagEnableFault } coverageEnabled := config.Flags&ipc.FlagSignal != 0 - - kcov, comparisonTracingEnabled := checkCompsSupported() - log.Logf(0, "kcov=%v, comps=%v", kcov, comparisonTracingEnabled) - if r.NeedCheck { - out, err := osutil.RunCmd(time.Minute, "", config.Executor, "version") - if err != nil { - panic(err) - } - vers := strings.Split(strings.TrimSpace(string(out)), " ") - if len(vers) != 4 { - panic(fmt.Sprintf("bad executor version: %q", string(out))) - } - a := &rpctype.CheckArgs{ - Name: *flagName, - Error: envCheckError, - UserNamespaces: osutil.IsExist("/proc/self/ns/user"), - FuzzerGitRev: sys.GitRevision, - FuzzerSyzRev: target.Revision, - ExecutorGitRev: vers[3], - ExecutorSyzRev: vers[2], - ExecutorArch: vers[1], - DisabledCalls: disabled, - } - a.Kcov = kcov - if fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0); err == nil { - syscall.Close(fd) - a.Leak = true - } - a.Fault = faultInjectionEnabled - a.CompsSupported = comparisonTracingEnabled - for c := range calls { - a.Calls = append(a.Calls, c.Name) - } - if err := rpctype.RPCCall(*flagManager, "Manager.Check", a, nil); err != nil { - log.Fatalf("Manager.Check call failed: %v", err) - } - } - if envCheckError != "" { - log.Fatalf("%v", envCheckError) + calls := make(map[*prog.Syscall]bool) + for _, id := range r.CheckResult.EnabledCalls { + calls[target.Syscalls[id]] = true } + ct := target.BuildChoiceTable(r.Prios, calls) // Manager.Connect reply can ve very large and that memory will be permanently cached in the connection. // So we do the call on a transient connection, free all memory and reconnect. @@ -245,11 +216,9 @@ func main() { debug.FreeOSMemory() manager, err := rpctype.NewRPCClient(*flagManager) if err != nil { - panic(err) + log.Fatalf("failed to connect to manager: %v ", err) } - kmemleakInit(*flagLeak) - needPoll := make(chan struct{}, 1) needPoll <- struct{}{} fuzzer := &Fuzzer{ @@ -262,13 +231,12 @@ func main() { choiceTable: ct, manager: manager, target: target, - faultInjectionEnabled: faultInjectionEnabled, - comparisonTracingEnabled: comparisonTracingEnabled, + faultInjectionEnabled: r.CheckResult.Features[host.FeatureFaultInjection].Enabled, + comparisonTracingEnabled: r.CheckResult.Features[host.FeatureComparisons].Enabled, coverageEnabled: coverageEnabled, - leakCheckEnabled: *flagLeak, corpusHashes: make(map[hash.Sig]struct{}), } - fuzzer.gate = ipc.NewGate(2**flagProcs, fuzzer.leakCheckCallback) + fuzzer.gate = ipc.NewGate(2**flagProcs, periodicCallback) for _, inp := range r.Inputs { fuzzer.addInputFromAnotherFuzzer(inp) @@ -277,7 +245,7 @@ func main() { for _, candidate := range r.Candidates { p, err := fuzzer.target.Deserialize(candidate.Prog) if err != nil { - panic(err) + log.Fatalf("failed to deserialize program: %v", err) } if coverageEnabled { flags := ProgCandidate @@ -350,7 +318,7 @@ func (fuzzer *Fuzzer) pollLoop() { r := &rpctype.PollRes{} if err := fuzzer.manager.Call("Manager.Poll", a, r); err != nil { - panic(err) + log.Fatalf("Manager.Poll call failed: %v", err) } maxSignal := r.MaxSignal.Deserialize() log.Logf(1, "poll: candidates=%v inputs=%v signal=%v", @@ -362,7 +330,7 @@ func (fuzzer *Fuzzer) pollLoop() { for _, candidate := range r.Candidates { p, err := fuzzer.target.Deserialize(candidate.Prog) if err != nil { - panic(err) + log.Fatalf("failed to parse program from manager: %v", err) } if fuzzer.coverageEnabled { flags := ProgCandidate @@ -380,11 +348,6 @@ func (fuzzer *Fuzzer) pollLoop() { fuzzer.addInputToCorpus(p, nil, hash.Hash(candidate.Prog)) } } - if len(r.Candidates) == 0 && fuzzer.leakCheckEnabled && - atomic.LoadUint32(&fuzzer.leakCheckReady) == 0 { - kmemleakScan(false) // ignore boot leaks - atomic.StoreUint32(&fuzzer.leakCheckReady, 1) - } if len(r.NewInputs) == 0 && len(r.Candidates) == 0 { lastPoll = time.Now() } @@ -392,52 +355,13 @@ func (fuzzer *Fuzzer) pollLoop() { } } -func buildCallList(target *prog.Target, enabledCalls []int, sandbox string) ( - map[*prog.Syscall]bool, []rpctype.SyscallReason, error) { - calls := make(map[*prog.Syscall]bool) - for _, n := range enabledCalls { - if n >= len(target.Syscalls) { - return nil, nil, fmt.Errorf("unknown enabled syscall: %v", n) - } - calls[target.Syscalls[n]] = true - } - - var disabled []rpctype.SyscallReason - _, unsupported, err := host.DetectSupportedSyscalls(target, sandbox) - if err != nil { - return nil, nil, fmt.Errorf("failed to detect host supported syscalls: %v", err) - } - for c := range calls { - if reason, ok := unsupported[c]; ok { - log.Logf(1, "unsupported syscall: %v: %v", c.Name, reason) - disabled = append(disabled, rpctype.SyscallReason{ - Name: c.Name, - Reason: reason, - }) - delete(calls, c) - } - } - _, unsupported = target.TransitivelyEnabledCalls(calls) - for c := range calls { - if reason, ok := unsupported[c]; ok { - log.Logf(1, "transitively unsupported: %v: %v", c.Name, reason) - disabled = append(disabled, rpctype.SyscallReason{ - Name: c.Name, - Reason: reason, - }) - delete(calls, c) - } - } - return calls, disabled, nil -} - func (fuzzer *Fuzzer) sendInputToManager(inp rpctype.RPCInput) { a := &rpctype.NewInputArgs{ Name: fuzzer.name, RPCInput: inp, } if err := fuzzer.manager.Call("Manager.NewInput", a, nil); err != nil { - panic(err) + log.Fatalf("Manager.NewInput call failed: %v", err) } } @@ -530,10 +454,3 @@ func signalPrio(target *prog.Target, c *prog.Call, ci *ipc.CallInfo) (prio uint8 } return } - -func (fuzzer *Fuzzer) leakCheckCallback() { - if atomic.LoadUint32(&fuzzer.leakCheckReady) != 0 { - // Scan for leaks once in a while (it is damn slow). - kmemleakScan(true) - } -} diff --git a/syz-fuzzer/fuzzer_freebsd.go b/syz-fuzzer/fuzzer_freebsd.go deleted file mode 100644 index 7f569e5fa..000000000 --- a/syz-fuzzer/fuzzer_freebsd.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 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 main - -import ( - "github.com/google/syzkaller/pkg/log" -) - -func kmemleakInit(enable bool) { - if enable { - log.Fatalf("leak checking is not supported on freebsd") - } -} - -func kmemleakScan(report bool) { -} - -func checkCompsSupported() (kcov, comps bool) { - return true, false -} diff --git a/syz-fuzzer/fuzzer_fuchsia.go b/syz-fuzzer/fuzzer_fuchsia.go deleted file mode 100644 index 190635629..000000000 --- a/syz-fuzzer/fuzzer_fuchsia.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2017 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. - -// +build fuchsia - -package main - -import ( - "github.com/google/syzkaller/pkg/log" -) - -func kmemleakInit(enable bool) { - if enable { - log.Fatalf("leak checking is not supported on fuchsia") - } -} - -func kmemleakScan(report bool) { -} - -func checkCompsSupported() (kcov, comps bool) { - return false, false -} diff --git a/syz-fuzzer/fuzzer_linux.go b/syz-fuzzer/fuzzer_linux.go deleted file mode 100644 index ac3882846..000000000 --- a/syz-fuzzer/fuzzer_linux.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2017 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 main - -import ( - "bytes" - "os" - "runtime" - "syscall" - "time" - "unsafe" - - "github.com/google/syzkaller/pkg/log" - "github.com/google/syzkaller/prog" - "github.com/google/syzkaller/sys/linux" -) - -func kmemleakInit(enable bool) { - fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) - if err != nil { - if !enable { - return - } - log.Fatalf("BUG: /sys/kernel/debug/kmemleak is missing (%v). Enable CONFIG_KMEMLEAK and mount debugfs.", err) - } - defer syscall.Close(fd) - what := "scan=off" - if !enable { - what = "off" - } - if _, err := syscall.Write(fd, []byte(what)); err != nil { - // kmemleak returns EBUSY when kmemleak is already turned off. - if err != syscall.EBUSY { - panic(err) - } - } -} - -var kmemleakBuf []byte - -func kmemleakScan(report bool) { - start := time.Now() - fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) - if err != nil { - panic(err) - } - defer syscall.Close(fd) - // Kmemleak has false positives. To mitigate most of them, it checksums - // potentially leaked objects, and reports them only on the next scan - // iff the checksum does not change. Because of that we do the following - // intricate dance: - // Scan, sleep, scan again. At this point we can get some leaks. - // If there are leaks, we sleep and scan again, this can remove - // false leaks. Then, read kmemleak again. If we get leaks now, then - // hopefully these are true positives during the previous testing cycle. - if _, err := syscall.Write(fd, []byte("scan")); err != nil { - panic(err) - } - time.Sleep(time.Second) - // Account for MSECS_MIN_AGE - // (1 second less because scanning will take at least a second). - for time.Since(start) < 4*time.Second { - time.Sleep(time.Second) - } - if _, err := syscall.Write(fd, []byte("scan")); err != nil { - panic(err) - } - if report { - if kmemleakBuf == nil { - kmemleakBuf = make([]byte, 128<<10) - } - n, err := syscall.Read(fd, kmemleakBuf) - if err != nil { - panic(err) - } - if n != 0 { - time.Sleep(time.Second) - if _, err := syscall.Write(fd, []byte("scan")); err != nil { - panic(err) - } - n, err := syscall.Read(fd, kmemleakBuf) - if err != nil { - panic(err) - } - nleaks := 0 - for kmemleakBuf = kmemleakBuf[:n]; len(kmemleakBuf) != 0; { - end := bytes.Index(kmemleakBuf[1:], []byte("unreferenced object")) - if end != -1 { - end++ - } else { - end = len(kmemleakBuf) - } - report := kmemleakBuf[:end] - kmemleakBuf = kmemleakBuf[end:] - if kmemleakIgnore(report) { - continue - } - // BUG in output should be recognized by manager. - log.Logf(0, "BUG: memory leak\n%s\n", report) - nleaks++ - } - if nleaks != 0 { - os.Exit(1) - } - } - - } - if _, err := syscall.Write(fd, []byte("clear")); err != nil { - panic(err) - } -} - -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 -} - -// Checks if the KCOV device supports comparisons. -// Returns a pair of bools: -// First - is the kcov device present in the system. -// Second - is the kcov device supporting comparisons. -func checkCompsSupported() (kcov, comps bool) { - // TODO(dvyukov): this should run under target arch. - // E.g. KCOV ioctls were initially not supported on 386 (missing compat_ioctl), - // and a 386 executor won't be able to use them, but an amd64 fuzzer will be. - fd, err := syscall.Open("/sys/kernel/debug/kcov", syscall.O_RDWR, 0) - if err != nil { - return - } - defer syscall.Close(fd) - // Trigger host target lazy initialization, it will fill linux.KCOV_INIT_TRACE. - // It's all wrong and needs to be refactored. - if _, err := prog.GetTarget(runtime.GOOS, runtime.GOARCH); err != nil { - log.Fatalf("%v", err) - } - kcov = true - coverSize := uintptr(64 << 10) - _, _, errno := syscall.Syscall( - syscall.SYS_IOCTL, uintptr(fd), linux.KCOV_INIT_TRACE, coverSize) - if errno != 0 { - log.Logf(1, "KCOV_CHECK: KCOV_INIT_TRACE = %v", errno) - return - } - mem, err := syscall.Mmap(fd, 0, int(coverSize*unsafe.Sizeof(uintptr(0))), - syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) - if err != nil { - log.Logf(1, "KCOV_CHECK: mmap = %v", err) - return - } - defer syscall.Munmap(mem) - _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, - uintptr(fd), linux.KCOV_ENABLE, linux.KCOV_TRACE_CMP) - if errno != 0 { - log.Logf(1, "KCOV_CHECK: KCOV_ENABLE = %v", errno) - return - } - defer syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), linux.KCOV_DISABLE, 0) - comps = true - return -} diff --git a/syz-fuzzer/fuzzer_netbsd.go b/syz-fuzzer/fuzzer_netbsd.go deleted file mode 100644 index 8921abe5f..000000000 --- a/syz-fuzzer/fuzzer_netbsd.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 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 main - -import ( - "github.com/google/syzkaller/pkg/log" -) - -func kmemleakInit(enable bool) { - if enable { - log.Fatalf("leak checking is not supported on netbsd") - } -} - -func kmemleakScan(report bool) { -} - -func checkCompsSupported() (kcov, comps bool) { - return true, false -} diff --git a/syz-fuzzer/fuzzer_windows.go b/syz-fuzzer/fuzzer_windows.go deleted file mode 100644 index e09b389ac..000000000 --- a/syz-fuzzer/fuzzer_windows.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 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 main - -import ( - "github.com/google/syzkaller/pkg/log" -) - -func kmemleakInit(enable bool) { - if enable { - log.Fatalf("leak checking is not supported on windows") - } -} - -func kmemleakScan(report bool) { -} - -func checkCompsSupported() (kcov, comps bool) { - return false, false -} diff --git a/syz-fuzzer/testing.go b/syz-fuzzer/testing.go index 604f33051..b2dc60411 100644 --- a/syz-fuzzer/testing.go +++ b/syz-fuzzer/testing.go @@ -4,68 +4,177 @@ package main import ( + "fmt" "net" + "strings" + "time" "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/osutil" + "github.com/google/syzkaller/pkg/rpctype" "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys" ) -func testImage(hostAddr string, target *prog.Target, sandbox string) { +type checkArgs struct { + target *prog.Target + sandbox string + gitRevision string + targetRevision string + enabledCalls []int + ipcConfig *ipc.Config + ipcExecOpts *ipc.ExecOpts +} + +func testImage(hostAddr string, args *checkArgs) { log.Logf(0, "connecting to host at %v", hostAddr) conn, err := net.Dial("tcp", hostAddr) if err != nil { log.Fatalf("failed to connect: %v", err) } conn.Close() + if _, err := checkMachine(args); err != nil { + log.Fatalf("%v", err) + } +} - log.Logf(0, "checking config...") - config, execOpts, err := ipc.DefaultConfig() +func checkMachine(args *checkArgs) (*rpctype.CheckArgs, error) { + if err := checkRevisions(args); err != nil { + return nil, err + } + features, err := host.Check() if err != nil { - log.Fatalf("failed to create ipc config: %v", err) + return nil, err + } + if feat := features[host.FeatureCoverage]; !feat.Enabled && + args.ipcConfig.Flags&ipc.FlagSignal != 0 { + return nil, fmt.Errorf("coverage is not supported (%v)", feat.Reason) + } + if feat := features[host.FeatureSandboxSetuid]; !feat.Enabled && + args.ipcConfig.Flags&ipc.FlagSandboxSetuid != 0 { + return nil, fmt.Errorf("sandbox=setuid is not supported (%v)", feat.Reason) } - if kcov, _ := checkCompsSupported(); !kcov && config.Flags&ipc.FlagSignal != 0 { - log.Fatalf("coverage is not supported by kernel") + if feat := features[host.FeatureSandboxNamespace]; !feat.Enabled && + args.ipcConfig.Flags&ipc.FlagSandboxNamespace != 0 { + return nil, fmt.Errorf("sandbox=namespace is not supported (%v)", feat.Reason) } - if config.Flags&ipc.FlagSandboxNamespace != 0 && !osutil.IsExist("/proc/self/ns/user") { - log.Fatalf("/proc/self/ns/user is not present for namespace sandbox") + if err := checkSimpleProgram(args); err != nil { + return nil, err } - calls, _, err := host.DetectSupportedSyscalls(target, sandbox) + enabledCalls, disabledCalls, err := buildCallList(args.target, args.enabledCalls, args.sandbox) if err != nil { - log.Fatalf("failed to detect supported syscalls: %v", err) + return nil, err } - calls, _ = target.TransitivelyEnabledCalls(calls) - log.Logf(0, "enabled syscalls: %v", len(calls)) - if calls[target.SyscallMap["syz_emit_ethernet"]] || - calls[target.SyscallMap["syz_extract_tcp_res"]] { - config.Flags |= ipc.FlagEnableTun + res := &rpctype.CheckArgs{ + EnabledCalls: enabledCalls, + DisabledCalls: disabledCalls, + Features: features, } + return res, nil +} +func checkRevisions(args *checkArgs) error { + log.Logf(0, "checking revisions...") + out, err := osutil.RunCmd(time.Minute, "", args.ipcConfig.Executor, "version") + if err != nil { + return fmt.Errorf("failed to run executor version: %v", err) + } + vers := strings.Split(strings.TrimSpace(string(out)), " ") + if len(vers) != 4 { + return fmt.Errorf("executor version returned bad result: %q", string(out)) + } + if args.target.Arch != vers[1] { + return fmt.Errorf("mismatching target/executor arches: %v vs %v", args.target.Arch, vers[1]) + } + if sys.GitRevision != vers[3] { + return fmt.Errorf("mismatching fuzzer/executor git revisions: %v vs %v", + sys.GitRevision, vers[3]) + } + if args.gitRevision != "" && args.gitRevision != sys.GitRevision { + return fmt.Errorf("mismatching manager/fuzzer git revisions: %v vs %v", + args.gitRevision, sys.GitRevision) + } + if args.target.Revision != vers[2] { + return fmt.Errorf("mismatching fuzzer/executor system call descriptions: %v vs %v", + args.target.Revision, vers[2]) + } + if args.targetRevision != "" && args.targetRevision != args.target.Revision { + return fmt.Errorf("mismatching manager/fuzzer system call descriptions: %v vs %v", + args.targetRevision, args.target.Revision) + } + return nil +} + +func checkSimpleProgram(args *checkArgs) error { log.Logf(0, "testing simple program...") - env, err := ipc.MakeEnv(config, 0) + env, err := ipc.MakeEnv(args.ipcConfig, 0) if err != nil { - log.Fatalf("failed to create ipc env: %v", err) + return fmt.Errorf("failed to create ipc env: %v", err) } - p := target.GenerateSimpleProg() - output, info, failed, hanged, err := env.Exec(execOpts, p) + p := args.target.GenerateSimpleProg() + output, info, failed, hanged, err := env.Exec(args.ipcExecOpts, p) if err != nil { - log.Fatalf("execution failed: %v\n%s", err, output) + return fmt.Errorf("program execution failed: %v\n%s", err, output) } if hanged { - log.Fatalf("program hanged:\n%s", output) + return fmt.Errorf("program hanged:\n%s", output) } if failed { - log.Fatalf("program failed:\n%s", output) + return fmt.Errorf("program failed:\n%s", output) } if len(info) == 0 { - log.Fatalf("no calls executed:\n%s", output) + return fmt.Errorf("no calls executed:\n%s", output) } if info[0].Errno != 0 { - log.Fatalf("simple call failed: %v\n%s", info[0].Errno, output) + return fmt.Errorf("simple call failed: %v\n%s", info[0].Errno, output) + } + if args.ipcConfig.Flags&ipc.FlagSignal != 0 && len(info[0].Signal) == 0 { + return fmt.Errorf("got no coverage:\n%s", output) + } + return nil +} + +func buildCallList(target *prog.Target, enabledCalls []int, sandbox string) ( + enabled []int, disabled []rpctype.SyscallReason, err error) { + calls := make(map[*prog.Syscall]bool) + for _, n := range enabledCalls { + if n >= len(target.Syscalls) { + return nil, nil, fmt.Errorf("unknown enabled syscall %v", n) + } + calls[target.Syscalls[n]] = true + } + _, unsupported, err := host.DetectSupportedSyscalls(target, sandbox) + if err != nil { + return nil, nil, fmt.Errorf("failed to detect host supported syscalls: %v", err) + } + for c := range calls { + if reason, ok := unsupported[c]; ok { + log.Logf(1, "unsupported syscall: %v: %v", c.Name, reason) + disabled = append(disabled, rpctype.SyscallReason{ + ID: c.ID, + Reason: reason, + }) + delete(calls, c) + } + } + _, unsupported = target.TransitivelyEnabledCalls(calls) + for c := range calls { + if reason, ok := unsupported[c]; ok { + log.Logf(1, "transitively unsupported: %v: %v", c.Name, reason) + disabled = append(disabled, rpctype.SyscallReason{ + ID: c.ID, + Reason: reason, + }) + delete(calls, c) + } + } + if len(calls) == 0 { + return nil, nil, fmt.Errorf("all system calls are disabled") } - if config.Flags&ipc.FlagSignal != 0 && len(info[0].Signal) == 0 { - log.Fatalf("got no coverage:\n%s", output) + for c := range calls { + enabled = append(enabled, c.ID) } + return enabled, disabled, nil } diff --git a/syz-manager/html.go b/syz-manager/html.go index c101bb135..d8997c0d9 100644 --- a/syz-manager/html.go +++ b/syz-manager/html.go @@ -100,14 +100,21 @@ func (mgr *Manager) collectStats() []UIStat { mgr.mu.Lock() defer mgr.mu.Unlock() - var stats []UIStat - stats = append(stats, UIStat{Name: "uptime", Value: fmt.Sprint(time.Since(mgr.startTime) / 1e9 * 1e9)}) - stats = append(stats, UIStat{Name: "fuzzing", Value: fmt.Sprint(mgr.fuzzingTime / 60e9 * 60e9)}) - stats = append(stats, UIStat{Name: "corpus", Value: fmt.Sprint(len(mgr.corpus))}) - stats = append(stats, UIStat{Name: "triage queue", Value: fmt.Sprint(len(mgr.candidates))}) - stats = append(stats, UIStat{Name: "cover", Value: fmt.Sprint(len(mgr.corpusCover)), Link: "/cover"}) - stats = append(stats, UIStat{Name: "signal", Value: fmt.Sprint(mgr.corpusSignal.Len())}) - stats = append(stats, UIStat{Name: "syscalls", Value: fmt.Sprint(len(mgr.enabledCalls)), Link: "/syscalls"}) + stats := []UIStat{ + {Name: "uptime", Value: fmt.Sprint(time.Since(mgr.startTime) / 1e9 * 1e9)}, + {Name: "fuzzing", Value: fmt.Sprint(mgr.fuzzingTime / 60e9 * 60e9)}, + {Name: "corpus", Value: fmt.Sprint(len(mgr.corpus))}, + {Name: "triage queue", Value: fmt.Sprint(len(mgr.candidates))}, + {Name: "cover", Value: fmt.Sprint(len(mgr.corpusCover)), Link: "/cover"}, + {Name: "signal", Value: fmt.Sprint(mgr.corpusSignal.Len())}, + } + if mgr.checkResult != nil { + stats = append(stats, UIStat{ + Name: "syscalls", + Value: fmt.Sprint(len(mgr.checkResult.EnabledCalls)), + Link: "/syscalls", + }) + } secs := uint64(1) if !mgr.firstConnect.IsZero() { diff --git a/syz-manager/manager.go b/syz-manager/manager.go index 455c2136b..0af9a24de 100644 --- a/syz-manager/manager.go +++ b/syz-manager/manager.go @@ -56,7 +56,7 @@ type Manager struct { stats map[string]uint64 crashTypes map[string]bool vmStop chan bool - vmChecked bool + checkResult *rpctype.CheckArgs fresh bool numFuzzing uint32 numReproducing uint32 @@ -66,7 +66,6 @@ type Manager struct { mu sync.Mutex phase int enabledSyscalls []int - enabledCalls []string // as determined by fuzzer candidates []rpctype.RPCCandidate // untriaged inputs from corpus and hub disabledHashes map[string]struct{} @@ -227,7 +226,7 @@ func RunManager(cfg *mgrconfig.Config, target *prog.Target, syscalls map[int]boo // This program contains a disabled syscall. // We won't execute it, but remember its hash so // it is not deleted during minimization. - // TODO: use mgr.enabledCalls which accounts for missing devices, etc. + // TODO: use mgr.checkResult.EnabledCalls which accounts for missing devices, etc. // But it is available only after vm check. mgr.disabledHashes[hash.String(rec.Val)] = struct{}{} continue @@ -554,8 +553,6 @@ func (mgr *Manager) runInstance(index int) (*Crash, error) { return nil, fmt.Errorf("failed to copy binary: %v", err) } - // Leak detection significantly slows down fuzzing, so detect leaks only on the first instance. - leak := mgr.cfg.Leak && index == 0 fuzzerV := 0 procs := mgr.cfg.Procs if *flagDebug { @@ -568,9 +565,9 @@ func (mgr *Manager) runInstance(index int) (*Crash, error) { atomic.AddUint32(&mgr.numFuzzing, 1) defer atomic.AddUint32(&mgr.numFuzzing, ^uint32(0)) cmd := fmt.Sprintf("%v -executor=%v -name=vm-%v -arch=%v -manager=%v -procs=%v"+ - " -leak=%v -cover=%v -sandbox=%v -debug=%v -v=%d", + " -cover=%v -sandbox=%v -debug=%v -v=%d", fuzzerBin, executorBin, index, mgr.cfg.TargetArch, fwdAddr, procs, - leak, mgr.cfg.Cover, mgr.cfg.Sandbox, *flagDebug, fuzzerV) + mgr.cfg.Cover, mgr.cfg.Sandbox, *flagDebug, fuzzerV) outc, errc, err := inst.Run(time.Hour, mgr.vmStop, cmd) if err != nil { return nil, fmt.Errorf("failed to run fuzzer: %v", err) @@ -864,11 +861,6 @@ func (mgr *Manager) Connect(a *rpctype.ConnectArgs, r *rpctype.ConnectRes) error mgr.mu.Lock() defer mgr.mu.Unlock() - if mgr.firstConnect.IsZero() { - mgr.firstConnect = time.Now() - log.Logf(0, "received first connection from test machine %v", a.Name) - } - mgr.stats["vm restarts"]++ f := &Fuzzer{ name: a.Name, @@ -904,8 +896,10 @@ func (mgr *Manager) Connect(a *rpctype.ConnectArgs, r *rpctype.ConnectRes) error } r.Prios = mgr.prios r.EnabledCalls = mgr.enabledSyscalls - r.NeedCheck = !mgr.vmChecked + r.CheckResult = mgr.checkResult r.MaxSignal = mgr.maxSignal.Serialize() + r.GitRevision = sys.GitRevision + r.TargetRevision = mgr.target.Revision for i := 0; i < mgr.cfg.Procs && len(mgr.candidates) > 0; i++ { last := len(mgr.candidates) - 1 r.Candidates = append(r.Candidates, mgr.candidates[last]) @@ -921,42 +915,13 @@ func (mgr *Manager) Check(a *rpctype.CheckArgs, r *int) error { mgr.mu.Lock() defer mgr.mu.Unlock() - if mgr.vmChecked { + if mgr.checkResult != nil { return nil } - log.Logf(0, "machine check: %v calls enabled, kcov=%v, kleakcheck=%v, faultinjection=%v, comps=%v", - len(a.Calls), a.Kcov, a.Leak, a.Fault, a.CompsSupported) - if mgr.cfg.Cover && !a.Kcov { - log.Fatalf("/sys/kernel/debug/kcov is missing on target machine. Enable CONFIG_KCOV and mount debugfs") - } - if mgr.cfg.Sandbox == "namespace" && !a.UserNamespaces { - log.Fatalf("/proc/self/ns/user is missing on target machine or permission is denied." + - " Can't use requested namespace sandbox. Enable CONFIG_USER_NS") - } - if mgr.vmPool != nil { - if mgr.target.Arch != a.ExecutorArch { - log.Fatalf("mismatching target/executor arch: target=%v executor=%v", - mgr.target.Arch, a.ExecutorArch) - } - if sys.GitRevision != a.FuzzerGitRev || sys.GitRevision != a.ExecutorGitRev { - log.Fatalf("syz-manager, syz-fuzzer and syz-executor binaries are built"+ - " on different git revisions\n"+ - "manager= %v\nfuzzer= %v\nexecutor=%v\n"+ - "this is not supported, rebuild all binaries with make", - sys.GitRevision, a.FuzzerGitRev, a.ExecutorGitRev) - } - if mgr.target.Revision != a.FuzzerSyzRev || mgr.target.Revision != a.ExecutorSyzRev { - log.Fatalf("syz-manager, syz-fuzzer and syz-executor binaries have different"+ - " versions of system call descriptions compiled in\n"+ - "manager= %v\nfuzzer= %v\nexecutor=%v\n"+ - "this is not supported, rebuild all binaries with make", - mgr.target.Revision, a.FuzzerSyzRev, a.ExecutorSyzRev) - } - } if len(mgr.cfg.EnabledSyscalls) != 0 && len(a.DisabledCalls) != 0 { disabled := make(map[string]string) for _, dc := range a.DisabledCalls { - disabled[dc.Name] = dc.Reason + disabled[mgr.target.Syscalls[dc.ID].Name] = dc.Reason } for _, id := range mgr.enabledSyscalls { name := mgr.target.Syscalls[id].Name @@ -968,8 +933,14 @@ func (mgr *Manager) Check(a *rpctype.CheckArgs, r *int) error { if a.Error != "" { log.Fatalf("machine check: %v", a.Error) } - mgr.vmChecked = true - mgr.enabledCalls = a.Calls + log.Logf(0, "machine check:") + log.Logf(0, "%-24v: %v", "syscalls", len(a.EnabledCalls)) + for _, feat := range a.Features { + log.Logf(0, "%-24v: %v", feat.Name, feat.Reason) + } + a.DisabledCalls = nil + mgr.checkResult = a + mgr.firstConnect = time.Now() return nil } @@ -1105,7 +1076,9 @@ func (mgr *Manager) hubSync() { Key: mgr.cfg.HubKey, Manager: mgr.cfg.Name, Fresh: mgr.fresh, - Calls: mgr.enabledCalls, + } + for _, id := range mgr.checkResult.EnabledCalls { + a.Calls = append(a.Calls, mgr.target.Syscalls[id].Name) } hubCorpus := make(map[hash.Sig]bool) for _, inp := range mgr.corpus { diff --git a/syz-manager/mgrconfig/mgrconfig.go b/syz-manager/mgrconfig/mgrconfig.go index 0aa6ac3ba..7e671a137 100644 --- a/syz-manager/mgrconfig/mgrconfig.go +++ b/syz-manager/mgrconfig/mgrconfig.go @@ -67,8 +67,6 @@ type Config struct { // Use KCOV coverage (default: true). Cover bool `json:"cover"` - // Do memory leak checking. - Leak bool `json:"leak"` // Reproduce, localize and minimize crashers (default: true). Reproduce bool `json:"reproduce"` diff --git a/tools/syz-crush/crush.go b/tools/syz-crush/crush.go index 531b43888..90a7b6185 100644 --- a/tools/syz-crush/crush.go +++ b/tools/syz-crush/crush.go @@ -101,7 +101,7 @@ func runInstance(cfg *mgrconfig.Config, reporter report.Reporter, vmPool *vm.Poo return } - cmd := fmt.Sprintf("%v -executor=%v -repeat=0 -procs=%v -cover=0 -sandbox=%v %v", + cmd := fmt.Sprintf("%v -executor=%v -repeat=0 -procs=%v -sandbox=%v %v", execprogBin, executorBin, cfg.Procs, cfg.Sandbox, logFile) outc, errc, err := inst.Run(time.Hour, nil, cmd) if err != nil { diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go index 40aa521ce..8fc0a4aa1 100644 --- a/tools/syz-execprog/execprog.go +++ b/tools/syz-execprog/execprog.go @@ -16,6 +16,7 @@ import ( "time" "github.com/google/syzkaller/pkg/cover" + "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/osutil" @@ -53,7 +54,15 @@ func main() { return } - config, execOpts := createConfig(entries) + features, err := host.Check() + if err != nil { + log.Fatalf("%v", err) + } + if _, err = host.Setup(features); err != nil { + log.Fatalf("%v", err) + } + + config, execOpts := createConfig(entries, features) var wg sync.WaitGroup wg.Add(*flagProcs) @@ -195,7 +204,7 @@ func loadPrograms(target *prog.Target, files []string) []*prog.LogEntry { return entries } -func createConfig(entries []*prog.LogEntry) (*ipc.Config, *ipc.ExecOpts) { +func createConfig(entries []*prog.LogEntry, features *host.Features) (*ipc.Config, *ipc.ExecOpts) { config, execOpts, err := ipc.DefaultConfig() if err != nil { log.Fatalf("%v", err) @@ -226,7 +235,7 @@ func createConfig(entries []*prog.LogEntry) (*ipc.Config, *ipc.ExecOpts) { handled[call.Meta.CallName] = true } } - if handled["syz_emit_ethernet"] || handled["syz_extract_tcp_res"] { + if features[host.FeatureNetworkInjection].Enabled { config.Flags |= ipc.FlagEnableTun } return config, execOpts diff --git a/tools/syz-stress/stress.go b/tools/syz-stress/stress.go index 504e5b58b..29ab24046 100644 --- a/tools/syz-stress/stress.go +++ b/tools/syz-stress/stress.go @@ -48,6 +48,14 @@ func main() { log.Fatalf("nothing to mutate (-generate=false and no corpus)") } + features, err := host.Check() + if err != nil { + log.Fatalf("%v", err) + } + if _, err = host.Setup(features); err != nil { + log.Fatalf("%v", err) + } + calls := buildCallList(target) prios := target.CalculatePriorities(corpus) ct := target.BuildChoiceTable(prios, calls) @@ -56,6 +64,9 @@ func main() { if err != nil { log.Fatalf("%v", err) } + if features[host.FeatureNetworkInjection].Enabled { + config.Flags |= ipc.FlagEnableTun + } gate = ipc.NewGate(2**flagProcs, nil) for pid := 0; pid < *flagProcs; pid++ { pid := pid |
