aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2018-06-12 14:05:02 +0200
committerDmitry Vyukov <dvyukov@google.com>2018-06-12 14:53:22 +0200
commit06ece2ca663d0565d9e4cd932c4c2d86767a5396 (patch)
tree0ba1566273ece79a1570afc79a030cd78df8e3ef
parent62d1af2467768d46623d446efaaf2f2cb6e8350e (diff)
pkg/host: rework host feature detection/setup
Currently host feature detection/setup code is spread across platform-independent fuzzer code, pkg/host, pkg/ipc and executor. Move this all into pkg/host and show readable info about features on manager start. Fixes #46
-rw-r--r--docs/configuration.md1
-rw-r--r--pkg/host/host.go79
-rw-r--r--pkg/host/host_akaros.go4
-rw-r--r--pkg/host/host_darwin.go12
-rw-r--r--pkg/host/host_freebsd.go4
-rw-r--r--pkg/host/host_fuchsia.go4
-rw-r--r--pkg/host/host_linux.go235
-rw-r--r--pkg/host/host_netbsd.go4
-rw-r--r--pkg/host/host_test.go13
-rw-r--r--pkg/host/host_windows.go4
-rw-r--r--pkg/ipc/ipc.go8
-rw-r--r--pkg/rpctype/rpctype.go36
-rw-r--r--syz-fuzzer/fuzzer.go179
-rw-r--r--syz-fuzzer/fuzzer_freebsd.go21
-rw-r--r--syz-fuzzer/fuzzer_fuchsia.go23
-rw-r--r--syz-fuzzer/fuzzer_linux.go180
-rw-r--r--syz-fuzzer/fuzzer_netbsd.go21
-rw-r--r--syz-fuzzer/fuzzer_windows.go21
-rw-r--r--syz-fuzzer/testing.go161
-rw-r--r--syz-manager/html.go23
-rw-r--r--syz-manager/manager.go67
-rw-r--r--syz-manager/mgrconfig/mgrconfig.go2
-rw-r--r--tools/syz-crush/crush.go2
-rw-r--r--tools/syz-execprog/execprog.go15
-rw-r--r--tools/syz-stress/stress.go11
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