aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/host
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 /pkg/host
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
Diffstat (limited to 'pkg/host')
-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
9 files changed, 335 insertions, 24 deletions
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
-}