From 8d85129b3cff7398f5aba861f0049afffb566865 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Thu, 14 Nov 2019 17:09:07 +0100 Subject: pkg/host: split files into syscalls/features pkg/host does 2 things: detects supported syscalls and supported features. There is enough code for each for a separate file. --- pkg/host/features.go | 100 +++++++ pkg/host/features_linux.go | 212 +++++++++++++++ pkg/host/host.go | 152 ----------- pkg/host/host_linux.go | 574 ---------------------------------------- pkg/host/host_linux_test.go | 151 ----------- pkg/host/syscalls.go | 61 +++++ pkg/host/syscalls_linux.go | 375 ++++++++++++++++++++++++++ pkg/host/syscalls_linux_test.go | 151 +++++++++++ 8 files changed, 899 insertions(+), 877 deletions(-) create mode 100644 pkg/host/features.go create mode 100644 pkg/host/features_linux.go delete mode 100644 pkg/host/host.go delete mode 100644 pkg/host/host_linux.go delete mode 100644 pkg/host/host_linux_test.go create mode 100644 pkg/host/syscalls.go create mode 100644 pkg/host/syscalls_linux.go create mode 100644 pkg/host/syscalls_linux_test.go (limited to 'pkg') diff --git a/pkg/host/features.go b/pkg/host/features.go new file mode 100644 index 000000000..8c3945a98 --- /dev/null +++ b/pkg/host/features.go @@ -0,0 +1,100 @@ +// 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 ( + "time" + + "github.com/google/syzkaller/pkg/csource" + "github.com/google/syzkaller/pkg/osutil" + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys/targets" +) + +const ( + FeatureCoverage = iota + FeatureComparisons + FeatureExtraCoverage + FeatureSandboxSetuid + FeatureSandboxNamespace + FeatureSandboxAndroidUntrustedApp + FeatureFaultInjection + FeatureLeakChecking + FeatureNetworkInjection + FeatureNetworkDevices + FeatureKCSAN + FeatureDevlinkPCI + numFeatures +) + +type Feature struct { + Name string + Enabled bool + Reason string +} + +type Features [numFeatures]Feature + +var checkFeature [numFeatures]func() string + +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(target *prog.Target) (*Features, error) { + const unsupported = "support is not implemented in syzkaller" + res := &Features{ + FeatureCoverage: {Name: "code coverage", Reason: unsupported}, + FeatureComparisons: {Name: "comparison tracing", Reason: unsupported}, + FeatureExtraCoverage: {Name: "extra coverage", Reason: unsupported}, + FeatureSandboxSetuid: {Name: "setuid sandbox", Reason: unsupported}, + FeatureSandboxNamespace: {Name: "namespace sandbox", Reason: unsupported}, + FeatureSandboxAndroidUntrustedApp: {Name: "Android sandbox", Reason: unsupported}, + FeatureFaultInjection: {Name: "fault injection", Reason: unsupported}, + FeatureLeakChecking: {Name: "leak checking", Reason: unsupported}, + FeatureNetworkInjection: {Name: "net packet injection", Reason: unsupported}, + FeatureNetworkDevices: {Name: "net device setup", Reason: unsupported}, + FeatureKCSAN: {Name: "concurrency sanitizer", Reason: unsupported}, + FeatureDevlinkPCI: {Name: "devlink PCI setup", Reason: unsupported}, + } + if targets.Get(target.OS, target.Arch).HostFuzzer { + return res, nil + } + 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(target *prog.Target, features *Features, featureFlags csource.Features, executor string) error { + if targets.Get(target.OS, target.Arch).HostFuzzer { + return nil + } + args := []string{"setup"} + if features[FeatureLeakChecking].Enabled { + args = append(args, "leak") + } + if features[FeatureFaultInjection].Enabled { + args = append(args, "fault") + } + if target.OS == "linux" && featureFlags["binfmt_misc"].Enabled { + args = append(args, "binfmt_misc") + } + if features[FeatureKCSAN].Enabled { + args = append(args, "kcsan") + } + _, err := osutil.RunCmd(time.Minute, "", executor, args...) + return err +} diff --git a/pkg/host/features_linux.go b/pkg/host/features_linux.go new file mode 100644 index 000000000..780c938a1 --- /dev/null +++ b/pkg/host/features_linux.go @@ -0,0 +1,212 @@ +// Copyright 2015 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 ( + "fmt" + "runtime" + "syscall" + "unsafe" + + "github.com/google/syzkaller/pkg/osutil" + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys/linux" +) + +func init() { + checkFeature[FeatureCoverage] = checkCoverage + checkFeature[FeatureComparisons] = checkComparisons + checkFeature[FeatureExtraCoverage] = checkExtraCoverage + checkFeature[FeatureSandboxSetuid] = unconditionallyEnabled + checkFeature[FeatureSandboxNamespace] = checkSandboxNamespace + checkFeature[FeatureSandboxAndroidUntrustedApp] = checkSandboxAndroidUntrustedApp + checkFeature[FeatureFaultInjection] = checkFaultInjection + checkFeature[FeatureLeakChecking] = checkLeakChecking + checkFeature[FeatureNetworkInjection] = checkNetworkInjection + checkFeature[FeatureNetworkDevices] = unconditionallyEnabled + checkFeature[FeatureKCSAN] = checkKCSAN + checkFeature[FeatureDevlinkPCI] = checkDevlinkPCI +} + +func checkCoverage() string { + if reason := checkDebugFS(); reason != "" { + return reason + } + if !osutil.IsExist("/sys/kernel/debug/kcov") { + return "CONFIG_KCOV is not enabled" + } + if err := osutil.IsAccessible("/sys/kernel/debug/kcov"); err != nil { + return err.Error() + } + return "" +} + +func checkComparisons() (reason string) { + return checkCoverageFeature(FeatureComparisons) +} + +func checkExtraCoverage() (reason string) { + return checkCoverageFeature(FeatureExtraCoverage) +} + +func checkCoverageFeature(feature int) (reason string) { + if reason = checkDebugFS(); reason != "" { + return reason + } + // 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 func() { + if err := syscall.Munmap(mem); err != nil { + reason = fmt.Sprintf("munmap failed: %v", err) + } + }() + switch feature { + case FeatureComparisons: + _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, + uintptr(fd), linux.KCOV_ENABLE, linux.KCOV_TRACE_CMP) + if errno != 0 { + if errno == 524 { // ENOTSUPP + return "CONFIG_KCOV_ENABLE_COMPARISONS is not enabled" + } + return fmt.Sprintf("ioctl(KCOV_TRACE_CMP) failed: %v", errno) + } + case FeatureExtraCoverage: + arg := KcovRemoteArg{ + TraceMode: uint32(linux.KCOV_TRACE_PC), + AreaSize: uint32(coverSize * unsafe.Sizeof(uintptr(0))), + NumHandles: 0, + CommonHandle: 0, + } + _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, + uintptr(fd), linux.KCOV_REMOTE_ENABLE, uintptr(unsafe.Pointer(&arg))) + if errno != 0 { + if errno == 25 { // ENOTTY + return "extra coverage is not supported by the kernel" + } + return fmt.Sprintf("ioctl(KCOV_REMOTE_ENABLE) failed: %v", errno) + } + default: + panic("unknown feature in checkCoverageFeature") + } + defer func() { + _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), linux.KCOV_DISABLE, 0) + if errno != 0 { + reason = fmt.Sprintf("ioctl(KCOV_DISABLE) failed: %v", errno) + } + }() + return "" +} + +type KcovRemoteArg struct { + TraceMode uint32 + AreaSize uint32 + NumHandles uint32 + CommonHandle uint64 + // Handles []uint64 goes here. +} + +func checkFaultInjection() string { + if err := osutil.IsAccessible("/proc/self/make-it-fail"); err != nil { + return "CONFIG_FAULT_INJECTION is not enabled" + } + if err := osutil.IsAccessible("/proc/thread-self/fail-nth"); err != nil { + return "kernel does not have systematic fault injection support" + } + if reason := checkDebugFS(); reason != "" { + return reason + } + if err := osutil.IsAccessible("/sys/kernel/debug/failslab/ignore-gfp-wait"); err != nil { + return "CONFIG_FAULT_INJECTION_DEBUG_FS or CONFIG_FAILSLAB are not enabled" + } + return "" +} + +func checkLeakChecking() string { + if reason := checkDebugFS(); reason != "" { + return reason + } + fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) + if err != nil { + return "CONFIG_DEBUG_KMEMLEAK is not enabled" + } + defer syscall.Close(fd) + if _, err := syscall.Write(fd, []byte("scan=off")); err != nil { + if err == syscall.EBUSY { + return "KMEMLEAK disabled: increase CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE or unset CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF" + } + return fmt.Sprintf("/sys/kernel/debug/kmemleak write failed: %v", err) + } + return "" +} + +func checkSandboxNamespace() string { + if err := osutil.IsAccessible("/proc/self/ns/user"); err != nil { + return err.Error() + } + return "" +} + +func checkSandboxAndroidUntrustedApp() string { + if err := osutil.IsAccessible("/sys/fs/selinux/policy"); err != nil { + return err.Error() + } + return "" +} + +func checkNetworkInjection() string { + if err := osutil.IsAccessible("/dev/net/tun"); err != nil { + return err.Error() + } + return "" +} + +func checkUSBInjection() string { + if err := osutil.IsAccessible("/dev/raw-gadget"); err != nil { + return err.Error() + } + return "" +} + +func checkDebugFS() string { + if err := osutil.IsAccessible("/sys/kernel/debug"); err != nil { + return "debugfs is not enabled or not mounted" + } + return "" +} + +func checkKCSAN() string { + if err := osutil.IsAccessible("/sys/kernel/debug/kcsan"); err != nil { + return err.Error() + } + return "" +} + +func checkDevlinkPCI() string { + if err := osutil.IsAccessible("/sys/bus/pci/devices/0000:00:10.0/"); err != nil { + return "PCI device 0000:00:10.0 is not available" + } + return "" +} diff --git a/pkg/host/host.go b/pkg/host/host.go deleted file mode 100644 index 216b9158d..000000000 --- a/pkg/host/host.go +++ /dev/null @@ -1,152 +0,0 @@ -// 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 ( - "time" - - "github.com/google/syzkaller/pkg/csource" - "github.com/google/syzkaller/pkg/log" - "github.com/google/syzkaller/pkg/osutil" - "github.com/google/syzkaller/prog" - "github.com/google/syzkaller/sys/targets" -) - -// DetectSupportedSyscalls returns list on supported and unsupported syscalls on the host. -// For unsupported syscalls it also returns reason as to why it is unsupported. -func DetectSupportedSyscalls(target *prog.Target, sandbox string) ( - map[*prog.Syscall]bool, map[*prog.Syscall]string, error) { - log.Logf(1, "detecting supported syscalls") - supported := make(map[*prog.Syscall]bool) - unsupported := make(map[*prog.Syscall]string) - // These do not have own host and parasitize on some other OS. - if targets.Get(target.OS, target.Arch).HostFuzzer { - for _, c := range target.Syscalls { - supported[c] = true - } - return supported, unsupported, nil - } - for _, c := range target.Syscalls { - ok, reason := false, "" - switch c.CallName { - case "syz_execute_func": - // syz_execute_func caused multiple problems: - // 1. First it lead to corpus exploision. The program used existing values in registers - // to pollute output area. We tried to zero registers (though, not reliably). - // 2. It lead to explosion again. The exact mechanics are unknown, here is one sample: - // syz_execute_func(&(0x7f0000000440)="f2af91930f0124eda133fa20430fbafce842f66188d0d4 - // 430fc7f314c1ab5bf9e2f9660f3a0fae5e090000ba023c1fb63ac4817d73d74ec482310d46f44 - // 9f216c863fa438036a91bdbae95aaaa420f383c02c401405c6bfd49d768d768f833fefbab6464 - // 660f38323c8f26dbc1a1fe5ff6f6df0804f4c4efa59c0f01c4288ba6452e000054c4431d5cc100") - // 3. The code can also execute syscalls (and it is know to), but it's not subject to - // target.SanitizeCall. As the result it can do things that programs are not supposed to do. - // 4. Besides linux, corpus explosion also happens on freebsd and is clearly attributable - // to syz_execute_func based on corpus contents. Mechanics are also not known. - // It also did not cause finding of any new bugs (at least not that I know of). - // Let's disable it for now until we figure out how to resolve all these problems. - ok = false - reason = "always disabled for now" - default: - ok, reason = isSupported(c, target, sandbox) - } - if ok { - supported[c] = true - } else { - if reason == "" { - reason = "unknown" - } - unsupported[c] = reason - } - } - return supported, unsupported, nil -} - -var testFallback = false - -const ( - FeatureCoverage = iota - FeatureComparisons - FeatureExtraCoverage - FeatureSandboxSetuid - FeatureSandboxNamespace - FeatureSandboxAndroidUntrustedApp - FeatureFaultInjection - FeatureLeakChecking - FeatureNetworkInjection - FeatureNetworkDevices - FeatureKCSAN - FeatureDevlinkPCI - numFeatures -) - -type Feature struct { - Name string - Enabled bool - Reason string -} - -type Features [numFeatures]Feature - -var checkFeature [numFeatures]func() string - -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(target *prog.Target) (*Features, error) { - const unsupported = "support is not implemented in syzkaller" - res := &Features{ - FeatureCoverage: {Name: "code coverage", Reason: unsupported}, - FeatureComparisons: {Name: "comparison tracing", Reason: unsupported}, - FeatureExtraCoverage: {Name: "extra coverage", Reason: unsupported}, - FeatureSandboxSetuid: {Name: "setuid sandbox", Reason: unsupported}, - FeatureSandboxNamespace: {Name: "namespace sandbox", Reason: unsupported}, - FeatureSandboxAndroidUntrustedApp: {Name: "Android sandbox", Reason: unsupported}, - FeatureFaultInjection: {Name: "fault injection", Reason: unsupported}, - FeatureLeakChecking: {Name: "leak checking", Reason: unsupported}, - FeatureNetworkInjection: {Name: "net packet injection", Reason: unsupported}, - FeatureNetworkDevices: {Name: "net device setup", Reason: unsupported}, - FeatureKCSAN: {Name: "concurrency sanitizer", Reason: unsupported}, - FeatureDevlinkPCI: {Name: "devlink PCI setup", Reason: unsupported}, - } - if targets.Get(target.OS, target.Arch).HostFuzzer { - return res, nil - } - 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(target *prog.Target, features *Features, featureFlags csource.Features, executor string) error { - if targets.Get(target.OS, target.Arch).HostFuzzer { - return nil - } - args := []string{"setup"} - if features[FeatureLeakChecking].Enabled { - args = append(args, "leak") - } - if features[FeatureFaultInjection].Enabled { - args = append(args, "fault") - } - if target.OS == "linux" && featureFlags["binfmt_misc"].Enabled { - args = append(args, "binfmt_misc") - } - if features[FeatureKCSAN].Enabled { - args = append(args, "kcsan") - } - _, err := osutil.RunCmd(time.Minute, "", executor, args...) - return err -} diff --git a/pkg/host/host_linux.go b/pkg/host/host_linux.go deleted file mode 100644 index d9a8c24f9..000000000 --- a/pkg/host/host_linux.go +++ /dev/null @@ -1,574 +0,0 @@ -// Copyright 2015 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 ( - "bytes" - "fmt" - "io/ioutil" - "os" - "regexp" - "runtime" - "strconv" - "strings" - "sync" - "syscall" - "time" - "unsafe" - - "github.com/google/syzkaller/pkg/log" - "github.com/google/syzkaller/pkg/osutil" - "github.com/google/syzkaller/prog" - "github.com/google/syzkaller/sys/linux" -) - -type KcovRemoteArg struct { - TraceMode uint32 - AreaSize uint32 - NumHandles uint32 - CommonHandle uint64 - // Handles []uint64 goes here. -} - -func isSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - log.Logf(2, "checking support for %v", c.Name) - if strings.HasPrefix(c.CallName, "syz_") { - return isSupportedSyzkall(sandbox, c) - } - if strings.HasPrefix(c.Name, "socket$") || - strings.HasPrefix(c.Name, "socketpair$") { - return isSupportedSocket(c) - } - if strings.HasPrefix(c.Name, "openat$") { - return isSupportedOpenAt(c) - } - if strings.HasPrefix(c.Name, "mount$") { - return isSupportedMount(c, sandbox) - } - if c.Name == "ioctl$EXT4_IOC_SHUTDOWN" && sandbox == "none" { - // Don't shutdown root filesystem. - return false, "unsafe with sandbox=none" - } - // There are 3 possible strategies for detecting supported syscalls: - // 1. Executes all syscalls with presumably invalid arguments and check for ENOprog. - // But not all syscalls are safe to execute. For example, pause will hang, - // while setpgrp will push the process into own process group. - // 2. Check presence of /sys/kernel/debug/tracing/events/syscalls/sys_enter_* files. - // This requires root and CONFIG_FTRACE_SYSCALLS. Also it lies for some syscalls. - // For example, on x86_64 it says that sendfile is not present (only sendfile64). - // 3. Check sys_syscallname in /proc/kallsyms. - // Requires CONFIG_KALLSYMS. - // Kallsyms seems to be the most reliable and fast. That's what we use first. - // If kallsyms is not present, we fallback to execution of syscalls. - kallsymsOnce.Do(func() { - kallsyms, _ := ioutil.ReadFile("/proc/kallsyms") - if len(kallsyms) == 0 { - return - } - kallsymsSyscallSet = parseKallsyms(kallsyms, target.Arch) - }) - if !testFallback && len(kallsymsSyscallSet) != 0 { - r, v := isSupportedKallsyms(c) - return r, v - } - return isSupportedTrial(c) -} - -func parseKallsyms(kallsyms []byte, arch string) map[string]bool { - set := make(map[string]bool) - var re *regexp.Regexp - switch arch { - case "386", "amd64": - re = regexp.MustCompile(` T (__ia32_|__x64_)?sys_([^\n]+)\n`) - case "arm", "arm64": - re = regexp.MustCompile(` T (__arm64_)?sys_([^\n]+)\n`) - case "ppc64le": - re = regexp.MustCompile(` T ()?sys_([^\n]+)\n`) - default: - panic("unsupported arch for kallsyms parsing") - } - matches := re.FindAllSubmatch(kallsyms, -1) - for _, m := range matches { - name := string(m[2]) - log.Logf(2, "found in kallsyms: %v", name) - set[name] = true - } - return set -} - -func isSupportedKallsyms(c *prog.Syscall) (bool, string) { - name := c.CallName - if newname := kallsymsRenameMap[name]; newname != "" { - name = newname - } - if !kallsymsSyscallSet[name] { - return false, fmt.Sprintf("sys_%v is not present in /proc/kallsyms", name) - } - return true, "" -} - -func isSupportedTrial(c *prog.Syscall) (bool, string) { - switch c.CallName { - // These known to cause hangs. - case "exit", "pause": - return true, "" - } - trialMu.Lock() - defer trialMu.Unlock() - if res, ok := trialSupported[c.NR]; ok { - return res, "ENOSYS" - } - cmd := osutil.Command(os.Args[0]) - cmd.Env = []string{fmt.Sprintf("SYZ_TRIAL_TEST=%v", c.NR)} - _, err := osutil.Run(10*time.Second, cmd) - res := err != nil - trialSupported[c.NR] = res - return res, "ENOSYS" -} - -func init() { - str := os.Getenv("SYZ_TRIAL_TEST") - if str == "" { - return - } - nr, err := strconv.Atoi(str) - if err != nil { - panic(err) - } - arg := ^uintptr(0) - 1e4 // something as invalid as possible - _, _, err = syscall.Syscall6(uintptr(nr), arg, arg, arg, arg, arg, arg) - if err == syscall.ENOSYS { - os.Exit(0) - } - os.Exit(1) -} - -// Some syscall names diverge in __NR_* consts and kallsyms. -// umount2 is renamed to umount in arch/x86/entry/syscalls/syscall_64.tbl. -// Where umount is renamed to oldumount is unclear. -var ( - kallsymsOnce sync.Once - kallsymsSyscallSet map[string]bool - kallsymsRenameMap = map[string]string{ - "umount": "oldumount", - "umount2": "umount", - "stat": "newstat", - } - trialMu sync.Mutex - trialSupported = make(map[uint64]bool) - filesystems []byte - filesystemsOnce sync.Once -) - -// The function is lengthy as it handles all pseudo-syscalls, -// but it does not seem to cause comprehension problems as there is no shared state. -// Splitting this per-syscall will only increase code size. -// nolint: gocyclo -func isSupportedSyzkall(sandbox string, c *prog.Syscall) (bool, string) { - switch c.CallName { - case "syz_open_dev": - if _, ok := c.Args[0].(*prog.ConstType); ok { - // This is for syz_open_dev$char/block. - // They are currently commented out, but in case one enables them. - return true, "" - } - fname, ok := extractStringConst(c.Args[0]) - if !ok { - panic("first open arg is not a pointer to string const") - } - if checkUSBInjection() == "" { - // These entries might not be available at boot time, - // but will be created by connected USB devices. - USBDevicePrefixes := []string{ - "/dev/hidraw", "/dev/usb/hiddev", "/dev/input/", - } - for _, prefix := range USBDevicePrefixes { - if strings.HasPrefix(fname, prefix) { - return true, "" - } - } - } - var check func(dev string) bool - check = func(dev string) bool { - if !strings.Contains(dev, "#") { - // Note: don't try to open them all, some can hang (e.g. /dev/snd/pcmC#D#p). - return osutil.IsExist(dev) - } - for i := 0; i < 10; i++ { - if check(strings.Replace(dev, "#", strconv.Itoa(i), 1)) { - return true - } - } - return false - } - if !check(fname) { - return false, fmt.Sprintf("file %v does not exist", fname) - } - return onlySandboxNoneOrNamespace(sandbox) - case "syz_open_procfs": - return true, "" - case "syz_open_pts": - return true, "" - case "syz_emit_ethernet", "syz_extract_tcp_res": - reason := checkNetworkInjection() - return reason == "", reason - case "syz_usb_connect", "syz_usb_disconnect", "syz_usb_control_io", "syz_usb_ep_write", "syz_usb_ep_read": - reason := checkUSBInjection() - return reason == "", reason - case "syz_kvm_setup_cpu": - switch c.Name { - case "syz_kvm_setup_cpu$x86": - if runtime.GOARCH == "amd64" || runtime.GOARCH == "386" { - return true, "" - } - case "syz_kvm_setup_cpu$arm64": - if runtime.GOARCH == "arm64" { - return true, "" - } - } - return false, "unsupported arch" - case "syz_init_net_socket": - // Unfortunately this only works with sandbox none at the moment. - // The problem is that setns of a network namespace requires CAP_SYS_ADMIN - // in the target namespace, and we've lost all privs in the init namespace - // during creation of a user namespace. - if ok, reason := onlySandboxNone(sandbox); !ok { - return false, reason - } - return isSupportedSocket(c) - case "syz_genetlink_get_family_id": - fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_GENERIC) - if fd == -1 { - return false, fmt.Sprintf("socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC) failed: %v", err) - } - syscall.Close(fd) - return true, "" - case "syz_mount_image": - if ok, reason := onlySandboxNone(sandbox); !ok { - return ok, reason - } - fstype, ok := extractStringConst(c.Args[0]) - if !ok { - panic("syz_mount_image arg is not string") - } - return isSupportedFilesystem(fstype) - case "syz_read_part_table": - return onlySandboxNone(sandbox) - case "syz_execute_func": - return true, "" - } - panic("unknown syzkall: " + c.Name) -} - -func onlySandboxNone(sandbox string) (bool, string) { - if syscall.Getuid() != 0 || sandbox != "none" { - return false, "only supported under root with sandbox=none" - } - return true, "" -} - -func onlySandboxNoneOrNamespace(sandbox string) (bool, string) { - if syscall.Getuid() != 0 || sandbox == "setuid" { - return false, "only supported under root with sandbox=none/namespace" - } - return true, "" -} - -func isSupportedSocket(c *prog.Syscall) (bool, string) { - af, ok := c.Args[0].(*prog.ConstType) - if !ok { - panic("socket family is not const") - } - fd, err := syscall.Socket(int(af.Val), 0, 0) - if fd != -1 { - syscall.Close(fd) - } - if err == syscall.ENOSYS { - return false, "socket syscall returns ENOSYS" - } - if err == syscall.EAFNOSUPPORT { - return false, "socket family is not supported (EAFNOSUPPORT)" - } - proto, ok := c.Args[2].(*prog.ConstType) - if !ok { - return true, "" - } - var typ uint64 - if arg, ok := c.Args[1].(*prog.ConstType); ok { - typ = arg.Val - } else if arg, ok := c.Args[1].(*prog.FlagsType); ok { - typ = arg.Vals[0] - } else { - return true, "" - } - fd, err = syscall.Socket(int(af.Val), int(typ), int(proto.Val)) - if fd != -1 { - syscall.Close(fd) - return true, "" - } - return false, err.Error() -} - -func isSupportedOpenAt(c *prog.Syscall) (bool, string) { - var fd int - var err error - - fname, ok := extractStringConst(c.Args[1]) - if !ok || len(fname) == 0 || fname[0] != '/' { - return true, "" - } - - modes := []int{syscall.O_RDONLY, syscall.O_WRONLY, syscall.O_RDWR} - - // Attempt to extract flags from the syscall description - if mode, ok := c.Args[2].(*prog.ConstType); ok { - modes = []int{int(mode.Val)} - } - - for _, mode := range modes { - fd, err = syscall.Open(fname, mode, 0) - if fd != -1 { - syscall.Close(fd) - } - if err == nil { - return true, "" - } - } - - return false, fmt.Sprintf("open(%v) failed: %v", fname, err) -} - -func isSupportedMount(c *prog.Syscall, sandbox string) (bool, string) { - fstype, ok := extractStringConst(c.Args[2]) - if !ok { - panic(fmt.Sprintf("%v: filesystem is not string const", c.Name)) - } - if ok, reason := isSupportedFilesystem(fstype); !ok { - return ok, reason - } - switch fstype { - case "fuse", "fuseblk": - if err := osutil.IsAccessible("/dev/fuse"); err != nil { - return false, err.Error() - } - return onlySandboxNoneOrNamespace(sandbox) - default: - return onlySandboxNone(sandbox) - } -} - -func isSupportedFilesystem(fstype string) (bool, string) { - filesystemsOnce.Do(func() { - filesystems, _ = ioutil.ReadFile("/proc/filesystems") - }) - if !bytes.Contains(filesystems, []byte("\t"+fstype+"\n")) { - return false, fmt.Sprintf("/proc/filesystems does not contain %v", fstype) - } - return true, "" -} - -func extractStringConst(typ prog.Type) (string, bool) { - ptr, ok := typ.(*prog.PtrType) - if !ok { - panic("first open arg is not a pointer to string const") - } - str, ok := ptr.Type.(*prog.BufferType) - if !ok || str.Kind != prog.BufferString || len(str.Values) == 0 { - return "", false - } - v := str.Values[0] - for len(v) != 0 && v[len(v)-1] == 0 { - v = v[:len(v)-1] // string terminating \x00 - } - return v, true -} - -func init() { - checkFeature[FeatureCoverage] = checkCoverage - checkFeature[FeatureComparisons] = checkComparisons - checkFeature[FeatureExtraCoverage] = checkExtraCoverage - checkFeature[FeatureSandboxSetuid] = unconditionallyEnabled - checkFeature[FeatureSandboxNamespace] = checkSandboxNamespace - checkFeature[FeatureSandboxAndroidUntrustedApp] = checkSandboxAndroidUntrustedApp - checkFeature[FeatureFaultInjection] = checkFaultInjection - checkFeature[FeatureLeakChecking] = checkLeakChecking - checkFeature[FeatureNetworkInjection] = checkNetworkInjection - checkFeature[FeatureNetworkDevices] = unconditionallyEnabled - checkFeature[FeatureKCSAN] = checkKCSAN - checkFeature[FeatureDevlinkPCI] = checkDevlinkPCI -} - -func checkCoverage() string { - if reason := checkDebugFS(); reason != "" { - return reason - } - if !osutil.IsExist("/sys/kernel/debug/kcov") { - return "CONFIG_KCOV is not enabled" - } - if err := osutil.IsAccessible("/sys/kernel/debug/kcov"); err != nil { - return err.Error() - } - return "" -} - -func checkComparisons() (reason string) { - return checkCoverageFeature(FeatureComparisons) -} - -func checkExtraCoverage() (reason string) { - return checkCoverageFeature(FeatureExtraCoverage) -} - -func checkCoverageFeature(feature int) (reason string) { - if reason = checkDebugFS(); reason != "" { - return reason - } - // 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 func() { - if err := syscall.Munmap(mem); err != nil { - reason = fmt.Sprintf("munmap failed: %v", err) - } - }() - switch feature { - case FeatureComparisons: - _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, - uintptr(fd), linux.KCOV_ENABLE, linux.KCOV_TRACE_CMP) - if errno != 0 { - if errno == 524 { // ENOTSUPP - return "CONFIG_KCOV_ENABLE_COMPARISONS is not enabled" - } - return fmt.Sprintf("ioctl(KCOV_TRACE_CMP) failed: %v", errno) - } - case FeatureExtraCoverage: - arg := KcovRemoteArg{ - TraceMode: uint32(linux.KCOV_TRACE_PC), - AreaSize: uint32(coverSize * unsafe.Sizeof(uintptr(0))), - NumHandles: 0, - CommonHandle: 0, - } - _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, - uintptr(fd), linux.KCOV_REMOTE_ENABLE, uintptr(unsafe.Pointer(&arg))) - if errno != 0 { - if errno == 25 { // ENOTTY - return "extra coverage is not supported by the kernel" - } - return fmt.Sprintf("ioctl(KCOV_REMOTE_ENABLE) failed: %v", errno) - } - default: - panic("unknown feature in checkCoverageFeature") - } - defer func() { - _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), linux.KCOV_DISABLE, 0) - if errno != 0 { - reason = fmt.Sprintf("ioctl(KCOV_DISABLE) failed: %v", errno) - } - }() - return "" -} - -func checkFaultInjection() string { - if err := osutil.IsAccessible("/proc/self/make-it-fail"); err != nil { - return "CONFIG_FAULT_INJECTION is not enabled" - } - if err := osutil.IsAccessible("/proc/thread-self/fail-nth"); err != nil { - return "kernel does not have systematic fault injection support" - } - if reason := checkDebugFS(); reason != "" { - return reason - } - if err := osutil.IsAccessible("/sys/kernel/debug/failslab/ignore-gfp-wait"); err != nil { - return "CONFIG_FAULT_INJECTION_DEBUG_FS or CONFIG_FAILSLAB are not enabled" - } - return "" -} - -func checkLeakChecking() string { - if reason := checkDebugFS(); reason != "" { - return reason - } - fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0) - if err != nil { - return "CONFIG_DEBUG_KMEMLEAK is not enabled" - } - defer syscall.Close(fd) - if _, err := syscall.Write(fd, []byte("scan=off")); err != nil { - if err == syscall.EBUSY { - return "KMEMLEAK disabled: increase CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE or unset CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF" - } - return fmt.Sprintf("/sys/kernel/debug/kmemleak write failed: %v", err) - } - return "" -} - -func checkSandboxNamespace() string { - if err := osutil.IsAccessible("/proc/self/ns/user"); err != nil { - return err.Error() - } - return "" -} - -func checkSandboxAndroidUntrustedApp() string { - if err := osutil.IsAccessible("/sys/fs/selinux/policy"); err != nil { - return err.Error() - } - return "" -} - -func checkNetworkInjection() string { - if err := osutil.IsAccessible("/dev/net/tun"); err != nil { - return err.Error() - } - return "" -} - -func checkUSBInjection() string { - if err := osutil.IsAccessible("/dev/raw-gadget"); err != nil { - return err.Error() - } - return "" -} - -func checkDebugFS() string { - if err := osutil.IsAccessible("/sys/kernel/debug"); err != nil { - return "debugfs is not enabled or not mounted" - } - return "" -} - -func checkKCSAN() string { - if err := osutil.IsAccessible("/sys/kernel/debug/kcsan"); err != nil { - return err.Error() - } - return "" -} - -func checkDevlinkPCI() string { - if err := osutil.IsAccessible("/sys/bus/pci/devices/0000:00:10.0/"); err != nil { - return "PCI device 0000:00:10.0 is not available" - } - return "" -} diff --git a/pkg/host/host_linux_test.go b/pkg/host/host_linux_test.go deleted file mode 100644 index 2064a10e3..000000000 --- a/pkg/host/host_linux_test.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2015 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 linux - -package host - -import ( - "runtime" - "syscall" - "testing" - - "github.com/google/syzkaller/prog" -) - -func TestSupportedSyscalls(t *testing.T) { - t.Parallel() - target, err := prog.GetTarget("linux", runtime.GOARCH) - if err != nil { - t.Fatal(err) - } - supp, _, err := DetectSupportedSyscalls(target, "none") - if err != nil { - t.Skipf("skipping: %v", err) - } - // These are safe to execute with invalid arguments. - safe := []string{ - "memfd_create", - "sendfile", - "bpf$MAP_CREATE", - "open", - "openat", - "read", - "write", - "stat", - } - for _, name := range safe { - c := target.SyscallMap[name] - if c == nil { - t.Fatalf("can't find syscall '%v'", name) - } - a := ^uintptr(0) - 4097 // hopefully invalid - _, _, err := syscall.Syscall6(uintptr(c.NR), a, a, a, a, a, a) - if err == 0 { - t.Fatalf("%v did not fail", name) - } - if ok := err != syscall.ENOSYS; ok != supp[c] { - t.Fatalf("syscall %v: perse=%v kallsyms=%v", name, ok, supp[c]) - } - } -} - -func TestKallsymsParse(t *testing.T) { - tests := []struct { - Arch string - Kallsyms []byte - ParsedSyscalls []string - SupportedSyscalls []string - }{ - { - "amd64", - []byte(` -ffffffff817cdcc0 T __sys_bind -ffffffff817cdda0 T __x64_sys_bind -ffffffff817cddc0 T __ia32_sys_bind -ffffffff817cdde0 T __sys_listen -ffffffff817cde80 T __x64_sys_listen -ffffffff817cde90 T __ia32_sys_listen -ffffffff817cdea0 T __sys_accept4 -ffffffff817ce080 T __x64_sys_accept4 -ffffffff817ce0a0 T __ia32_sys_accept4 - `), - []string{"bind", "listen", "accept4"}, - []string{"bind", "listen", "accept4"}, - }, - { - "arm64", - []byte(` -ffff000010a3ddf8 T __sys_bind -ffff000010a3def8 T __arm64_sys_bind -ffff000010a3df20 T __sys_listen -ffff000010a3dfd8 T __arm64_sys_listen -ffff000010a3e000 T __sys_accept4 -ffff000010a3e1f0 T __arm64_sys_accept4 - `), - []string{"bind", "listen", "accept4"}, - []string{"bind", "listen", "accept4"}, - }, - { - "ppc64le", - []byte(` -c0000000011ec810 T __sys_bind -c0000000011eca10 T sys_bind -c0000000011eca10 T __se_sys_bind -c0000000011eca70 T __sys_listen -c0000000011ecc10 T sys_listen -c0000000011ecc10 T __se_sys_listen -c0000000011ecc70 T __sys_accept4 -c0000000011ed050 T sys_accept4 -c0000000011ed050 T __se_sys_accept4 - `), - []string{"bind", "listen", "accept4"}, - []string{"bind", "listen", "accept4"}, - }, - { - "arm", - []byte(` -c037c67c T __se_sys_setfsuid -c037c694 T __sys_setfsgid -c037c790 T sys_setfsgid -c037c790 T __se_sys_setfsgid -c037c7a8 T sys_getpid -c037c7d0 T sys_gettid -c037c7f8 T sys_getppid - `), - []string{"setfsgid", "getpid", "gettid", "getppid"}, - []string{"setfsgid", "getpid", "gettid", "getppid"}, - }, - // Test kallsymsRenameMap - { - "ppc64le", - []byte(` -c00000000037eb00 T sys_newstat - `), - []string{"newstat"}, - []string{"stat"}, - }, - } - - for _, test := range tests { - syscallSet := parseKallsyms(test.Kallsyms, test.Arch) - if len(syscallSet) != len(test.ParsedSyscalls) { - t.Fatalf("wrong number of parse syscalls, expected: %v, got: %v", - len(test.ParsedSyscalls), len(syscallSet)) - } - for _, syscall := range test.ParsedSyscalls { - if _, ok := syscallSet[syscall]; !ok { - t.Fatalf("syscall %v not found in parsed syscall list", syscall) - } - } - for _, syscall := range test.SupportedSyscalls { - if newname := kallsymsRenameMap[syscall]; newname != "" { - syscall = newname - } - - if _, ok := syscallSet[syscall]; !ok { - t.Fatalf("syscall %v not found in supported syscall list", syscall) - } - } - } -} diff --git a/pkg/host/syscalls.go b/pkg/host/syscalls.go new file mode 100644 index 000000000..17ec0f25a --- /dev/null +++ b/pkg/host/syscalls.go @@ -0,0 +1,61 @@ +// 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/pkg/log" + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys/targets" +) + +// DetectSupportedSyscalls returns list on supported and unsupported syscalls on the host. +// For unsupported syscalls it also returns reason as to why it is unsupported. +func DetectSupportedSyscalls(target *prog.Target, sandbox string) ( + map[*prog.Syscall]bool, map[*prog.Syscall]string, error) { + log.Logf(1, "detecting supported syscalls") + supported := make(map[*prog.Syscall]bool) + unsupported := make(map[*prog.Syscall]string) + // These do not have own host and parasitize on some other OS. + if targets.Get(target.OS, target.Arch).HostFuzzer { + for _, c := range target.Syscalls { + supported[c] = true + } + return supported, unsupported, nil + } + for _, c := range target.Syscalls { + ok, reason := false, "" + switch c.CallName { + case "syz_execute_func": + // syz_execute_func caused multiple problems: + // 1. First it lead to corpus exploision. The program used existing values in registers + // to pollute output area. We tried to zero registers (though, not reliably). + // 2. It lead to explosion again. The exact mechanics are unknown, here is one sample: + // syz_execute_func(&(0x7f0000000440)="f2af91930f0124eda133fa20430fbafce842f66188d0d4 + // 430fc7f314c1ab5bf9e2f9660f3a0fae5e090000ba023c1fb63ac4817d73d74ec482310d46f44 + // 9f216c863fa438036a91bdbae95aaaa420f383c02c401405c6bfd49d768d768f833fefbab6464 + // 660f38323c8f26dbc1a1fe5ff6f6df0804f4c4efa59c0f01c4288ba6452e000054c4431d5cc100") + // 3. The code can also execute syscalls (and it is know to), but it's not subject to + // target.SanitizeCall. As the result it can do things that programs are not supposed to do. + // 4. Besides linux, corpus explosion also happens on freebsd and is clearly attributable + // to syz_execute_func based on corpus contents. Mechanics are also not known. + // It also did not cause finding of any new bugs (at least not that I know of). + // Let's disable it for now until we figure out how to resolve all these problems. + ok = false + reason = "always disabled for now" + default: + ok, reason = isSupported(c, target, sandbox) + } + if ok { + supported[c] = true + } else { + if reason == "" { + reason = "unknown" + } + unsupported[c] = reason + } + } + return supported, unsupported, nil +} + +var testFallback = false diff --git a/pkg/host/syscalls_linux.go b/pkg/host/syscalls_linux.go new file mode 100644 index 000000000..dff19fb01 --- /dev/null +++ b/pkg/host/syscalls_linux.go @@ -0,0 +1,375 @@ +// Copyright 2015 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 ( + "bytes" + "fmt" + "io/ioutil" + "os" + "regexp" + "runtime" + "strconv" + "strings" + "sync" + "syscall" + "time" + + "github.com/google/syzkaller/pkg/log" + "github.com/google/syzkaller/pkg/osutil" + "github.com/google/syzkaller/prog" +) + +func isSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { + log.Logf(2, "checking support for %v", c.Name) + if strings.HasPrefix(c.CallName, "syz_") { + return isSupportedSyzkall(sandbox, c) + } + if strings.HasPrefix(c.Name, "socket$") || + strings.HasPrefix(c.Name, "socketpair$") { + return isSupportedSocket(c) + } + if strings.HasPrefix(c.Name, "openat$") { + return isSupportedOpenAt(c) + } + if strings.HasPrefix(c.Name, "mount$") { + return isSupportedMount(c, sandbox) + } + if c.Name == "ioctl$EXT4_IOC_SHUTDOWN" && sandbox == "none" { + // Don't shutdown root filesystem. + return false, "unsafe with sandbox=none" + } + // There are 3 possible strategies for detecting supported syscalls: + // 1. Executes all syscalls with presumably invalid arguments and check for ENOprog. + // But not all syscalls are safe to execute. For example, pause will hang, + // while setpgrp will push the process into own process group. + // 2. Check presence of /sys/kernel/debug/tracing/events/syscalls/sys_enter_* files. + // This requires root and CONFIG_FTRACE_SYSCALLS. Also it lies for some syscalls. + // For example, on x86_64 it says that sendfile is not present (only sendfile64). + // 3. Check sys_syscallname in /proc/kallsyms. + // Requires CONFIG_KALLSYMS. + // Kallsyms seems to be the most reliable and fast. That's what we use first. + // If kallsyms is not present, we fallback to execution of syscalls. + kallsymsOnce.Do(func() { + kallsyms, _ := ioutil.ReadFile("/proc/kallsyms") + if len(kallsyms) == 0 { + return + } + kallsymsSyscallSet = parseKallsyms(kallsyms, target.Arch) + }) + if !testFallback && len(kallsymsSyscallSet) != 0 { + r, v := isSupportedKallsyms(c) + return r, v + } + return isSupportedTrial(c) +} + +func parseKallsyms(kallsyms []byte, arch string) map[string]bool { + set := make(map[string]bool) + var re *regexp.Regexp + switch arch { + case "386", "amd64": + re = regexp.MustCompile(` T (__ia32_|__x64_)?sys_([^\n]+)\n`) + case "arm", "arm64": + re = regexp.MustCompile(` T (__arm64_)?sys_([^\n]+)\n`) + case "ppc64le": + re = regexp.MustCompile(` T ()?sys_([^\n]+)\n`) + default: + panic("unsupported arch for kallsyms parsing") + } + matches := re.FindAllSubmatch(kallsyms, -1) + for _, m := range matches { + name := string(m[2]) + log.Logf(2, "found in kallsyms: %v", name) + set[name] = true + } + return set +} + +func isSupportedKallsyms(c *prog.Syscall) (bool, string) { + name := c.CallName + if newname := kallsymsRenameMap[name]; newname != "" { + name = newname + } + if !kallsymsSyscallSet[name] { + return false, fmt.Sprintf("sys_%v is not present in /proc/kallsyms", name) + } + return true, "" +} + +func isSupportedTrial(c *prog.Syscall) (bool, string) { + switch c.CallName { + // These known to cause hangs. + case "exit", "pause": + return true, "" + } + trialMu.Lock() + defer trialMu.Unlock() + if res, ok := trialSupported[c.NR]; ok { + return res, "ENOSYS" + } + cmd := osutil.Command(os.Args[0]) + cmd.Env = []string{fmt.Sprintf("SYZ_TRIAL_TEST=%v", c.NR)} + _, err := osutil.Run(10*time.Second, cmd) + res := err != nil + trialSupported[c.NR] = res + return res, "ENOSYS" +} + +func init() { + str := os.Getenv("SYZ_TRIAL_TEST") + if str == "" { + return + } + nr, err := strconv.Atoi(str) + if err != nil { + panic(err) + } + arg := ^uintptr(0) - 1e4 // something as invalid as possible + _, _, err = syscall.Syscall6(uintptr(nr), arg, arg, arg, arg, arg, arg) + if err == syscall.ENOSYS { + os.Exit(0) + } + os.Exit(1) +} + +// Some syscall names diverge in __NR_* consts and kallsyms. +// umount2 is renamed to umount in arch/x86/entry/syscalls/syscall_64.tbl. +// Where umount is renamed to oldumount is unclear. +var ( + kallsymsOnce sync.Once + kallsymsSyscallSet map[string]bool + kallsymsRenameMap = map[string]string{ + "umount": "oldumount", + "umount2": "umount", + "stat": "newstat", + } + trialMu sync.Mutex + trialSupported = make(map[uint64]bool) + filesystems []byte + filesystemsOnce sync.Once +) + +// The function is lengthy as it handles all pseudo-syscalls, +// but it does not seem to cause comprehension problems as there is no shared state. +// Splitting this per-syscall will only increase code size. +// nolint: gocyclo +func isSupportedSyzkall(sandbox string, c *prog.Syscall) (bool, string) { + switch c.CallName { + case "syz_open_dev": + if _, ok := c.Args[0].(*prog.ConstType); ok { + // This is for syz_open_dev$char/block. + // They are currently commented out, but in case one enables them. + return true, "" + } + fname, ok := extractStringConst(c.Args[0]) + if !ok { + panic("first open arg is not a pointer to string const") + } + if checkUSBInjection() == "" { + // These entries might not be available at boot time, + // but will be created by connected USB devices. + USBDevicePrefixes := []string{ + "/dev/hidraw", "/dev/usb/hiddev", "/dev/input/", + } + for _, prefix := range USBDevicePrefixes { + if strings.HasPrefix(fname, prefix) { + return true, "" + } + } + } + var check func(dev string) bool + check = func(dev string) bool { + if !strings.Contains(dev, "#") { + // Note: don't try to open them all, some can hang (e.g. /dev/snd/pcmC#D#p). + return osutil.IsExist(dev) + } + for i := 0; i < 10; i++ { + if check(strings.Replace(dev, "#", strconv.Itoa(i), 1)) { + return true + } + } + return false + } + if !check(fname) { + return false, fmt.Sprintf("file %v does not exist", fname) + } + return onlySandboxNoneOrNamespace(sandbox) + case "syz_open_procfs": + return true, "" + case "syz_open_pts": + return true, "" + case "syz_emit_ethernet", "syz_extract_tcp_res": + reason := checkNetworkInjection() + return reason == "", reason + case "syz_usb_connect", "syz_usb_disconnect", "syz_usb_control_io", "syz_usb_ep_write", "syz_usb_ep_read": + reason := checkUSBInjection() + return reason == "", reason + case "syz_kvm_setup_cpu": + switch c.Name { + case "syz_kvm_setup_cpu$x86": + if runtime.GOARCH == "amd64" || runtime.GOARCH == "386" { + return true, "" + } + case "syz_kvm_setup_cpu$arm64": + if runtime.GOARCH == "arm64" { + return true, "" + } + } + return false, "unsupported arch" + case "syz_init_net_socket": + // Unfortunately this only works with sandbox none at the moment. + // The problem is that setns of a network namespace requires CAP_SYS_ADMIN + // in the target namespace, and we've lost all privs in the init namespace + // during creation of a user namespace. + if ok, reason := onlySandboxNone(sandbox); !ok { + return false, reason + } + return isSupportedSocket(c) + case "syz_genetlink_get_family_id": + fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_GENERIC) + if fd == -1 { + return false, fmt.Sprintf("socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC) failed: %v", err) + } + syscall.Close(fd) + return true, "" + case "syz_mount_image": + if ok, reason := onlySandboxNone(sandbox); !ok { + return ok, reason + } + fstype, ok := extractStringConst(c.Args[0]) + if !ok { + panic("syz_mount_image arg is not string") + } + return isSupportedFilesystem(fstype) + case "syz_read_part_table": + return onlySandboxNone(sandbox) + case "syz_execute_func": + return true, "" + } + panic("unknown syzkall: " + c.Name) +} + +func onlySandboxNone(sandbox string) (bool, string) { + if syscall.Getuid() != 0 || sandbox != "none" { + return false, "only supported under root with sandbox=none" + } + return true, "" +} + +func onlySandboxNoneOrNamespace(sandbox string) (bool, string) { + if syscall.Getuid() != 0 || sandbox == "setuid" { + return false, "only supported under root with sandbox=none/namespace" + } + return true, "" +} + +func isSupportedSocket(c *prog.Syscall) (bool, string) { + af, ok := c.Args[0].(*prog.ConstType) + if !ok { + panic("socket family is not const") + } + fd, err := syscall.Socket(int(af.Val), 0, 0) + if fd != -1 { + syscall.Close(fd) + } + if err == syscall.ENOSYS { + return false, "socket syscall returns ENOSYS" + } + if err == syscall.EAFNOSUPPORT { + return false, "socket family is not supported (EAFNOSUPPORT)" + } + proto, ok := c.Args[2].(*prog.ConstType) + if !ok { + return true, "" + } + var typ uint64 + if arg, ok := c.Args[1].(*prog.ConstType); ok { + typ = arg.Val + } else if arg, ok := c.Args[1].(*prog.FlagsType); ok { + typ = arg.Vals[0] + } else { + return true, "" + } + fd, err = syscall.Socket(int(af.Val), int(typ), int(proto.Val)) + if fd != -1 { + syscall.Close(fd) + return true, "" + } + return false, err.Error() +} + +func isSupportedOpenAt(c *prog.Syscall) (bool, string) { + var fd int + var err error + + fname, ok := extractStringConst(c.Args[1]) + if !ok || len(fname) == 0 || fname[0] != '/' { + return true, "" + } + + modes := []int{syscall.O_RDONLY, syscall.O_WRONLY, syscall.O_RDWR} + + // Attempt to extract flags from the syscall description + if mode, ok := c.Args[2].(*prog.ConstType); ok { + modes = []int{int(mode.Val)} + } + + for _, mode := range modes { + fd, err = syscall.Open(fname, mode, 0) + if fd != -1 { + syscall.Close(fd) + } + if err == nil { + return true, "" + } + } + + return false, fmt.Sprintf("open(%v) failed: %v", fname, err) +} + +func isSupportedMount(c *prog.Syscall, sandbox string) (bool, string) { + fstype, ok := extractStringConst(c.Args[2]) + if !ok { + panic(fmt.Sprintf("%v: filesystem is not string const", c.Name)) + } + if ok, reason := isSupportedFilesystem(fstype); !ok { + return ok, reason + } + switch fstype { + case "fuse", "fuseblk": + if err := osutil.IsAccessible("/dev/fuse"); err != nil { + return false, err.Error() + } + return onlySandboxNoneOrNamespace(sandbox) + default: + return onlySandboxNone(sandbox) + } +} + +func isSupportedFilesystem(fstype string) (bool, string) { + filesystemsOnce.Do(func() { + filesystems, _ = ioutil.ReadFile("/proc/filesystems") + }) + if !bytes.Contains(filesystems, []byte("\t"+fstype+"\n")) { + return false, fmt.Sprintf("/proc/filesystems does not contain %v", fstype) + } + return true, "" +} + +func extractStringConst(typ prog.Type) (string, bool) { + ptr, ok := typ.(*prog.PtrType) + if !ok { + panic("first open arg is not a pointer to string const") + } + str, ok := ptr.Type.(*prog.BufferType) + if !ok || str.Kind != prog.BufferString || len(str.Values) == 0 { + return "", false + } + v := str.Values[0] + for len(v) != 0 && v[len(v)-1] == 0 { + v = v[:len(v)-1] // string terminating \x00 + } + return v, true +} diff --git a/pkg/host/syscalls_linux_test.go b/pkg/host/syscalls_linux_test.go new file mode 100644 index 000000000..2064a10e3 --- /dev/null +++ b/pkg/host/syscalls_linux_test.go @@ -0,0 +1,151 @@ +// Copyright 2015 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 linux + +package host + +import ( + "runtime" + "syscall" + "testing" + + "github.com/google/syzkaller/prog" +) + +func TestSupportedSyscalls(t *testing.T) { + t.Parallel() + target, err := prog.GetTarget("linux", runtime.GOARCH) + if err != nil { + t.Fatal(err) + } + supp, _, err := DetectSupportedSyscalls(target, "none") + if err != nil { + t.Skipf("skipping: %v", err) + } + // These are safe to execute with invalid arguments. + safe := []string{ + "memfd_create", + "sendfile", + "bpf$MAP_CREATE", + "open", + "openat", + "read", + "write", + "stat", + } + for _, name := range safe { + c := target.SyscallMap[name] + if c == nil { + t.Fatalf("can't find syscall '%v'", name) + } + a := ^uintptr(0) - 4097 // hopefully invalid + _, _, err := syscall.Syscall6(uintptr(c.NR), a, a, a, a, a, a) + if err == 0 { + t.Fatalf("%v did not fail", name) + } + if ok := err != syscall.ENOSYS; ok != supp[c] { + t.Fatalf("syscall %v: perse=%v kallsyms=%v", name, ok, supp[c]) + } + } +} + +func TestKallsymsParse(t *testing.T) { + tests := []struct { + Arch string + Kallsyms []byte + ParsedSyscalls []string + SupportedSyscalls []string + }{ + { + "amd64", + []byte(` +ffffffff817cdcc0 T __sys_bind +ffffffff817cdda0 T __x64_sys_bind +ffffffff817cddc0 T __ia32_sys_bind +ffffffff817cdde0 T __sys_listen +ffffffff817cde80 T __x64_sys_listen +ffffffff817cde90 T __ia32_sys_listen +ffffffff817cdea0 T __sys_accept4 +ffffffff817ce080 T __x64_sys_accept4 +ffffffff817ce0a0 T __ia32_sys_accept4 + `), + []string{"bind", "listen", "accept4"}, + []string{"bind", "listen", "accept4"}, + }, + { + "arm64", + []byte(` +ffff000010a3ddf8 T __sys_bind +ffff000010a3def8 T __arm64_sys_bind +ffff000010a3df20 T __sys_listen +ffff000010a3dfd8 T __arm64_sys_listen +ffff000010a3e000 T __sys_accept4 +ffff000010a3e1f0 T __arm64_sys_accept4 + `), + []string{"bind", "listen", "accept4"}, + []string{"bind", "listen", "accept4"}, + }, + { + "ppc64le", + []byte(` +c0000000011ec810 T __sys_bind +c0000000011eca10 T sys_bind +c0000000011eca10 T __se_sys_bind +c0000000011eca70 T __sys_listen +c0000000011ecc10 T sys_listen +c0000000011ecc10 T __se_sys_listen +c0000000011ecc70 T __sys_accept4 +c0000000011ed050 T sys_accept4 +c0000000011ed050 T __se_sys_accept4 + `), + []string{"bind", "listen", "accept4"}, + []string{"bind", "listen", "accept4"}, + }, + { + "arm", + []byte(` +c037c67c T __se_sys_setfsuid +c037c694 T __sys_setfsgid +c037c790 T sys_setfsgid +c037c790 T __se_sys_setfsgid +c037c7a8 T sys_getpid +c037c7d0 T sys_gettid +c037c7f8 T sys_getppid + `), + []string{"setfsgid", "getpid", "gettid", "getppid"}, + []string{"setfsgid", "getpid", "gettid", "getppid"}, + }, + // Test kallsymsRenameMap + { + "ppc64le", + []byte(` +c00000000037eb00 T sys_newstat + `), + []string{"newstat"}, + []string{"stat"}, + }, + } + + for _, test := range tests { + syscallSet := parseKallsyms(test.Kallsyms, test.Arch) + if len(syscallSet) != len(test.ParsedSyscalls) { + t.Fatalf("wrong number of parse syscalls, expected: %v, got: %v", + len(test.ParsedSyscalls), len(syscallSet)) + } + for _, syscall := range test.ParsedSyscalls { + if _, ok := syscallSet[syscall]; !ok { + t.Fatalf("syscall %v not found in parsed syscall list", syscall) + } + } + for _, syscall := range test.SupportedSyscalls { + if newname := kallsymsRenameMap[syscall]; newname != "" { + syscall = newname + } + + if _, ok := syscallSet[syscall]; !ok { + t.Fatalf("syscall %v not found in supported syscall list", syscall) + } + } + } +} -- cgit mrf-deployment