diff options
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/host/features.go | 227 | ||||
| -rw-r--r-- | pkg/host/features_linux.go | 355 | ||||
| -rw-r--r-- | pkg/host/features_linux_test.go | 23 | ||||
| -rw-r--r-- | pkg/host/host_darwin.go | 16 | ||||
| -rw-r--r-- | pkg/host/host_freebsd.go | 19 | ||||
| -rw-r--r-- | pkg/host/host_fuchsia.go | 14 | ||||
| -rw-r--r-- | pkg/host/host_netbsd.go | 42 | ||||
| -rw-r--r-- | pkg/host/host_openbsd.go | 38 | ||||
| -rw-r--r-- | pkg/host/host_test.go | 70 | ||||
| -rw-r--r-- | pkg/host/host_windows.go | 12 | ||||
| -rw-r--r-- | pkg/host/syscalls.go | 45 | ||||
| -rw-r--r-- | pkg/host/syscalls_linux.go | 568 | ||||
| -rw-r--r-- | pkg/host/syscalls_linux_test.go | 307 | ||||
| -rw-r--r-- | pkg/ipc/ipc.go | 31 | ||||
| -rw-r--r-- | pkg/ipc/ipc_test.go | 2 | ||||
| -rw-r--r-- | pkg/rpctype/rpctype.go | 7 | ||||
| -rw-r--r-- | pkg/runtest/run_test.go | 24 | ||||
| -rw-r--r-- | pkg/vminfo/features.go | 192 | ||||
| -rw-r--r-- | pkg/vminfo/linux_syscalls.go | 20 | ||||
| -rw-r--r-- | pkg/vminfo/linux_test.go | 12 | ||||
| -rw-r--r-- | pkg/vminfo/netbsd.go | 38 | ||||
| -rw-r--r-- | pkg/vminfo/openbsd.go | 36 | ||||
| -rw-r--r-- | pkg/vminfo/syscalls.go | 73 | ||||
| -rw-r--r-- | pkg/vminfo/vminfo.go | 14 | ||||
| -rw-r--r-- | pkg/vminfo/vminfo_test.go | 26 |
25 files changed, 443 insertions, 1768 deletions
diff --git a/pkg/host/features.go b/pkg/host/features.go index 8a8629084..ae0771785 100644 --- a/pkg/host/features.go +++ b/pkg/host/features.go @@ -4,212 +4,73 @@ package host import ( + "bytes" "fmt" "strings" "time" "github.com/google/syzkaller/pkg/csource" "github.com/google/syzkaller/pkg/flatrpc" + "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/sys/targets" ) -const ( - FeatureCoverage = iota - FeatureComparisons - FeatureExtraCoverage - FeatureDelayKcovMmap - FeatureSandboxSetuid - FeatureSandboxNamespace - FeatureSandboxAndroid - FeatureFault - FeatureLeak - FeatureNetInjection - FeatureNetDevices - FeatureKCSAN - FeatureDevlinkPCI - FeatureNicVF - FeatureUSBEmulation - FeatureVhciInjection - FeatureWifiEmulation - Feature802154Emulation - FeatureSwap - numFeatures -) - -type Feature struct { - Name string - Enabled bool - Reason string -} - -type Features [numFeatures]Feature - -func (features *Features) Supported() *Features { - return features -} - -func (features *Features) ToFlatRPC() flatrpc.Feature { - var result flatrpc.Feature - if features[FeatureCoverage].Enabled { - result |= flatrpc.FeatureCoverage - } - if features[FeatureComparisons].Enabled { - result |= flatrpc.FeatureComparisons - } - if features[FeatureExtraCoverage].Enabled { - result |= flatrpc.FeatureExtraCoverage - } - if features[FeatureDelayKcovMmap].Enabled { - result |= flatrpc.FeatureDelayKcovMmap - } - if features[FeatureSandboxSetuid].Enabled { - result |= flatrpc.FeatureSandboxSetuid - } - if features[FeatureSandboxNamespace].Enabled { - result |= flatrpc.FeatureSandboxNamespace - } - if features[FeatureSandboxAndroid].Enabled { - result |= flatrpc.FeatureSandboxAndroid - } - if features[FeatureFault].Enabled { - result |= flatrpc.FeatureFault - } - if features[FeatureLeak].Enabled { - result |= flatrpc.FeatureLeak - } - if features[FeatureNetInjection].Enabled { - result |= flatrpc.FeatureNetInjection - } - if features[FeatureNetDevices].Enabled { - result |= flatrpc.FeatureNetDevices - } - if features[FeatureKCSAN].Enabled { - result |= flatrpc.FeatureKCSAN - } - if features[FeatureDevlinkPCI].Enabled { - result |= flatrpc.FeatureDevlinkPCI - } - if features[FeatureNicVF].Enabled { - result |= flatrpc.FeatureNicVF - } - if features[FeatureUSBEmulation].Enabled { - result |= flatrpc.FeatureUSBEmulation - } - if features[FeatureVhciInjection].Enabled { - result |= flatrpc.FeatureVhciInjection - } - if features[FeatureWifiEmulation].Enabled { - result |= flatrpc.FeatureWifiEmulation - } - if features[Feature802154Emulation].Enabled { - result |= flatrpc.FeatureLRWPANEmulation - } - if features[FeatureSwap].Enabled { - result |= flatrpc.FeatureSwap - } - return result -} - -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}, - FeatureDelayKcovMmap: {Name: "delay kcov mmap", Reason: unsupported}, - FeatureSandboxSetuid: {Name: "setuid sandbox", Reason: unsupported}, - FeatureSandboxNamespace: {Name: "namespace sandbox", Reason: unsupported}, - FeatureSandboxAndroid: {Name: "Android sandbox", Reason: unsupported}, - FeatureFault: {Name: "fault injection", Reason: unsupported}, - FeatureLeak: {Name: "leak checking", Reason: unsupported}, - FeatureNetInjection: {Name: "net packet injection", Reason: unsupported}, - FeatureNetDevices: {Name: "net device setup", Reason: unsupported}, - FeatureKCSAN: {Name: "concurrency sanitizer", Reason: unsupported}, - FeatureDevlinkPCI: {Name: "devlink PCI setup", Reason: unsupported}, - FeatureNicVF: {Name: "NIC VF setup", Reason: unsupported}, - FeatureUSBEmulation: {Name: "USB emulation", Reason: unsupported}, - FeatureVhciInjection: {Name: "hci packet injection", Reason: unsupported}, - FeatureWifiEmulation: {Name: "wifi device emulation", Reason: unsupported}, - Feature802154Emulation: {Name: "802.15.4 emulation", Reason: unsupported}, - FeatureSwap: {Name: "swap file", Reason: unsupported}, +// SetupFeatures 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 SetupFeatures(target *prog.Target, executor string, mask flatrpc.Feature, flags csource.Features) ( + []flatrpc.FeatureInfo, error) { + if noHostChecks(target) { + return nil, nil } - for n, check := range featureCheckers(target) { - if check == nil { + var results []flatrpc.FeatureInfo + resultC := make(chan flatrpc.FeatureInfo) + for feat := range flatrpc.EnumNamesFeature { + feat := feat + if mask&feat == 0 { continue } - if reason := check(); reason == "" { - if n == FeatureCoverage && !target.ExecutorUsesShmem { - return nil, fmt.Errorf("enabling FeatureCoverage requires enabling ExecutorUsesShmem") - } - res[n].Enabled = true - res[n].Reason = "enabled" - } else { - res[n].Reason = reason + opt := ipc.FlatRPCFeaturesToCSource[feat] + if opt != "" && flags != nil && !flags["binfmt_misc"].Enabled { + continue } + results = append(results, flatrpc.FeatureInfo{}) + go setupFeature(executor, feat, resultC) } - return res, nil + // Feature 0 setups common things that are not part of any feature. + setupFeature(executor, 0, nil) + for i := range results { + results[i] = <-resultC + } + return results, 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 noHostChecks(target) { - return nil - } +func setupFeature(executor string, feat flatrpc.Feature, resultC chan flatrpc.FeatureInfo) { args := strings.Split(executor, " ") executor = args[0] - args = append(args[1:], "setup") - if features[FeatureLeak].Enabled { - args = append(args, "leak") - } - if features[FeatureFault].Enabled { - args = append(args, "fault") - } - if target.OS == targets.Linux && featureFlags["binfmt_misc"].Enabled { - args = append(args, "binfmt_misc") - } - if features[FeatureKCSAN].Enabled { - args = append(args, "kcsan") - } - if features[FeatureUSBEmulation].Enabled { - args = append(args, "usb") - } - if featureFlags["ieee802154"].Enabled && features[Feature802154Emulation].Enabled { - args = append(args, "802154") - } - if features[FeatureSwap].Enabled { - args = append(args, "swap") - } - output, err := osutil.RunCmd(5*time.Minute, "", executor, args...) - log.Logf(1, "executor %v\n%s", args, output) - return err -} - -func featureCheckers(target *prog.Target) [numFeatures]func() string { - ret := [numFeatures]func() string{} - if target.OS == targets.TestOS { - sysTarget := targets.Get(target.OS, target.Arch) - ret[FeatureCoverage] = func() string { - if sysTarget.ExecutorUsesShmem && sysTarget.PtrSize == 8 { - return "" - } - return "disabled" + args = append(args[1:], "setup", fmt.Sprint(uint64(feat))) + output, err := osutil.RunCmd(3*time.Minute, "", executor, args...) + log.Logf(1, "executor %v\n%s", args, bytes.ReplaceAll(output, []byte("SYZFAIL:"), nil)) + outputStr := string(output) + if err == nil { + outputStr = "" + } else if outputStr == "" { + outputStr = err.Error() + } + needSetup := true + if strings.Contains(outputStr, "feature setup is not needed") { + needSetup = false + outputStr = "" + } + if resultC != nil { + resultC <- flatrpc.FeatureInfo{ + Id: feat, + NeedSetup: needSetup, + Reason: outputStr, } } - if noHostChecks(target) { - return ret - } - return checkFeature } func noHostChecks(target *prog.Target) bool { diff --git a/pkg/host/features_linux.go b/pkg/host/features_linux.go deleted file mode 100644 index 393617e45..000000000 --- a/pkg/host/features_linux.go +++ /dev/null @@ -1,355 +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 ( - "fmt" - "os" - "os/exec" - "regexp" - "runtime" - "runtime/debug" - "strconv" - "syscall" - "unsafe" - - "github.com/google/syzkaller/pkg/osutil" - "github.com/google/syzkaller/prog" - "github.com/google/syzkaller/sys/linux" - "golang.org/x/sys/unix" -) - -func init() { - checkFeature[FeatureCoverage] = checkCoverage - checkFeature[FeatureComparisons] = checkComparisons - checkFeature[FeatureExtraCoverage] = checkExtraCoverage - checkFeature[FeatureDelayKcovMmap] = checkDelayKcovMmap - checkFeature[FeatureSandboxSetuid] = unconditionallyEnabled - checkFeature[FeatureSandboxNamespace] = checkSandboxNamespace - checkFeature[FeatureSandboxAndroid] = checkSandboxAndroid - checkFeature[FeatureFault] = checkFault - checkFeature[FeatureLeak] = checkLeak - checkFeature[FeatureNetInjection] = checkNetInjection - checkFeature[FeatureNetDevices] = unconditionallyEnabled - checkFeature[FeatureKCSAN] = checkKCSAN - checkFeature[FeatureDevlinkPCI] = checkDevlinkPCI - checkFeature[FeatureNicVF] = checkNicVF - checkFeature[FeatureUSBEmulation] = checkUSBEmulation - checkFeature[FeatureVhciInjection] = checkVhciInjection - checkFeature[FeatureWifiEmulation] = checkWifiEmulation - checkFeature[Feature802154Emulation] = check802154Emulation - checkFeature[FeatureSwap] = checkSwap -} - -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 checkDelayKcovMmap() string { - // The kcov implementation in Linux (currently) does not adequately handle subsequent - // mmap invocations on the same descriptor. When that problem is fixed, we want - // syzkaller to differentiate between distributions as this allows to noticeably speed - // up fuzzing. - return checkCoverageFeature(FeatureDelayKcovMmap) -} - -func kcovTestMmap(fd int, coverSize uintptr) (reason string) { - 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) - } - }() - // Now the tricky part - mmap may say it has succeeded, but the memory is - // in fact not allocated. - // We try to access it. If go does not panic, everything is fine. - prevValue := debug.SetPanicOnFault(true) - defer debug.SetPanicOnFault(prevValue) - defer func() { - if r := recover(); r != nil { - reason = "mmap returned an invalid pointer" - } - }() - mem[0] = 0 - return "" -} - -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) - } - if reason = kcovTestMmap(fd, coverSize); reason != "" { - return reason - } - disableKcov := 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) - } - } - 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) - } - defer disableKcov() - 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) - } - defer disableKcov() - case FeatureDelayKcovMmap: - if reason = kcovTestMmap(fd, coverSize); reason != "" { - return reason - } - default: - panic("unknown feature in checkCoverageFeature") - } - return "" -} - -type KcovRemoteArg struct { - TraceMode uint32 - AreaSize uint32 - NumHandles uint32 - _ uint32 - CommonHandle uint64 - // Handles []uint64 goes here. -} - -func checkFault() 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 checkLeak() 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 checkSandboxAndroid() string { - if err := osutil.IsAccessible("/sys/fs/selinux/policy"); err != nil { - return err.Error() - } - return "" -} - -func checkNetInjection() string { - if err := osutil.IsAccessible("/dev/net/tun"); err != nil { - return err.Error() - } - return "" -} - -func checkUSBEmulation() string { - if err := osutil.IsAccessible("/dev/raw-gadget"); err != nil { - return err.Error() - } - return "" -} - -func checkVhciInjection() string { - if err := osutil.IsAccessible("/dev/vhci"); 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" - } - if err := osutil.IsAccessible("/proc/self/ns/net"); err != nil { - // Initialize_devlink_pci in executor fails w/o this. - return "/proc/self/ns/net does not exist" - } - return "" -} - -func checkNicVF() string { - if err := osutil.IsAccessible("/sys/bus/pci/devices/0000:00:11.0/"); err != nil { - return "PCI device 0000:00:11.0 is not available" - } - return "" -} - -func checkWifiEmulation() string { - if err := osutil.IsAccessible("/sys/class/mac80211_hwsim/"); err != nil { - return err.Error() - } - // We use HWSIM_ATTR_PERM_ADDR which was added in 4.17. - return requireKernel(4, 17) -} - -func check802154Emulation() string { - if err := osutil.IsAccessible("/sys/bus/platform/devices/mac802154_hwsim"); err != nil { - return err.Error() - } - return "" -} - -func checkSwap() string { - if err := osutil.IsAccessible("/proc/swaps"); err != nil { - return err.Error() - } - if _, err := exec.LookPath("mkswap"); err != nil { - return "mkswap is not available" - } - // We use fallocate in syz-executor, so let's check if the filesystem supports it. - // /tmp might not always be the best choice for this - // (on some systems, a different filesystem might be used for /tmp). - dir, err := os.UserHomeDir() - if err != nil { - return fmt.Sprintf("failed to get home dir: %v", err) - } - f, err := os.CreateTemp(dir, "any-file") - if err != nil { - return fmt.Sprintf("failed to create temp file: %v", err) - } - defer os.Remove(f.Name()) - err = syscall.Fallocate(int(f.Fd()), unix.FALLOC_FL_ZERO_RANGE, 0, 2048) - if err != nil { - return fmt.Sprintf("fallocate failed: %v", err) - } - return "" -} - -func requireKernel(x, y int) string { - info := new(unix.Utsname) - if err := unix.Uname(info); err != nil { - return fmt.Sprintf("uname failed: %v", err) - } - ver := string(info.Release[:]) - if ok, bad := matchKernelVersion(ver, x, y); bad { - return fmt.Sprintf("failed to parse kernel version (%v)", ver) - } else if !ok { - return fmt.Sprintf("kernel %v.%v required (have %v)", x, y, ver) - } - return "" -} - -func matchKernelVersion(ver string, x, y int) (bool, bool) { - match := kernelVersionRe.FindStringSubmatch(ver) - if match == nil { - return false, true - } - major, err := strconv.Atoi(match[1]) - if err != nil { - return false, true - } - if major <= 0 || major > 999 { - return false, true - } - minor, err := strconv.Atoi(match[2]) - if err != nil { - return false, true - } - if minor <= 0 || minor > 999 { - return false, true - } - return major*1000+minor >= x*1000+y, false -} - -var kernelVersionRe = regexp.MustCompile(`^([0-9]+)\.([0-9]+)`) diff --git a/pkg/host/features_linux_test.go b/pkg/host/features_linux_test.go deleted file mode 100644 index 35d6197c1..000000000 --- a/pkg/host/features_linux_test.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2021 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 ( - "strings" - "testing" -) - -func TestRequireKernel(t *testing.T) { - if what := requireKernel(2, 999); what != "" { - t.Fatalf("requireKernel(2, 999) failed: %v", what) - } - if what := requireKernel(3, 0); what != "" { - t.Fatalf("requireKernel(3, 0) failed: %v", what) - } - if what := requireKernel(99, 1); what == "" { - t.Fatalf("requireKernel(99, 1) succeeded") - } else if !strings.HasPrefix(what, "kernel 99.1 required") { - t.Fatalf("requireKernel(99, 1) failed: %v", what) - } -} diff --git a/pkg/host/host_darwin.go b/pkg/host/host_darwin.go deleted file mode 100644 index c400d2895..000000000 --- a/pkg/host/host_darwin.go +++ /dev/null @@ -1,16 +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 ( - "github.com/google/syzkaller/prog" -) - -func isSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - return true, "" -} - -func init() { - checkFeature[FeatureCoverage] = unconditionallyEnabled -} diff --git a/pkg/host/host_freebsd.go b/pkg/host/host_freebsd.go deleted file mode 100644 index 726429c18..000000000 --- a/pkg/host/host_freebsd.go +++ /dev/null @@ -1,19 +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 host - -import ( - "github.com/google/syzkaller/prog" -) - -func isSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - return true, "" -} - -func init() { - checkFeature[FeatureCoverage] = unconditionallyEnabled - checkFeature[FeatureComparisons] = unconditionallyEnabled - checkFeature[FeatureDelayKcovMmap] = unconditionallyEnabled - checkFeature[FeatureNetInjection] = unconditionallyEnabled -} diff --git a/pkg/host/host_fuchsia.go b/pkg/host/host_fuchsia.go deleted file mode 100644 index 49f1b6406..000000000 --- a/pkg/host/host_fuchsia.go +++ /dev/null @@ -1,14 +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. - -//go:build fuchsia - -package host - -import ( - "github.com/google/syzkaller/prog" -) - -func isSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - return true, "" -} diff --git a/pkg/host/host_netbsd.go b/pkg/host/host_netbsd.go deleted file mode 100644 index e4de2d408..000000000 --- a/pkg/host/host_netbsd.go +++ /dev/null @@ -1,42 +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 host - -import ( - "github.com/google/syzkaller/pkg/osutil" - "github.com/google/syzkaller/prog" -) - -func isSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - switch c.CallName { - case "syz_usb_connect", "syz_usb_disconnect": - reason := checkUSBEmulation() - return reason == "", reason - default: - return true, "" - } -} - -func init() { - checkFeature[FeatureCoverage] = unconditionallyEnabled - checkFeature[FeatureComparisons] = unconditionallyEnabled - checkFeature[FeatureUSBEmulation] = checkUSBEmulation - checkFeature[FeatureExtraCoverage] = checkUSBEmulation - checkFeature[FeatureDelayKcovMmap] = unconditionallyEnabled - checkFeature[FeatureFault] = checkFault -} - -func checkUSBEmulation() string { - if err := osutil.IsAccessible("/dev/vhci0"); err != nil { - return err.Error() - } - return "" -} - -func checkFault() string { - if err := osutil.IsAccessible("/dev/fault"); err != nil { - return err.Error() - } - return "" -} diff --git a/pkg/host/host_openbsd.go b/pkg/host/host_openbsd.go deleted file mode 100644 index 3099bc771..000000000 --- a/pkg/host/host_openbsd.go +++ /dev/null @@ -1,38 +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 host - -import ( - "fmt" - "strings" - "syscall" - - "github.com/google/syzkaller/prog" -) - -func isSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - if strings.HasPrefix(c.CallName, "ioctl$VMM_") { - return isSupportedVMM() - } - return true, "" -} - -func isSupportedVMM() (bool, string) { - device := "/dev/vmm" - fd, err := syscall.Open(device, syscall.O_RDONLY, 0) - if fd == -1 { - return false, fmt.Sprintf("open(%v) failed: %v", device, err) - } - syscall.Close(fd) - return true, "" -} - -func init() { - checkFeature[FeatureCoverage] = unconditionallyEnabled - checkFeature[FeatureComparisons] = unconditionallyEnabled - checkFeature[FeatureExtraCoverage] = unconditionallyEnabled - checkFeature[FeatureDelayKcovMmap] = unconditionallyEnabled - checkFeature[FeatureNetInjection] = unconditionallyEnabled - checkFeature[FeatureSandboxSetuid] = unconditionallyEnabled -} diff --git a/pkg/host/host_test.go b/pkg/host/host_test.go deleted file mode 100644 index f39b6bc68..000000000 --- a/pkg/host/host_test.go +++ /dev/null @@ -1,70 +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 ( - "fmt" - "runtime" - "testing" - - "github.com/google/syzkaller/pkg/testutil" - "github.com/google/syzkaller/prog" - _ "github.com/google/syzkaller/sys" -) - -func TestDetectSupportedSyscalls(t *testing.T) { - // Note: this test is not parallel because it modifies global testFallback var. - for _, fallback := range []bool{false, true} { - if testutil.RaceEnabled && fallback { - continue // too slow under race detector - } - t.Run(fmt.Sprintf("fallback=%v", fallback), func(t *testing.T) { - oldFallback := testFallback - testFallback = fallback - defer func() { testFallback = oldFallback }() - target, err := prog.GetTarget(runtime.GOOS, runtime.GOARCH) - if err != nil { - t.Fatal(err) - } - enabled := make(map[*prog.Syscall]bool) - for _, c := range target.Syscalls { - enabled[c] = true - } - // Dump for manual inspection. - supp, disabled, err := DetectSupportedSyscalls(target, "none", enabled) - if err != nil { - t.Fatal(err) - } - for c, ok := range supp { - if !ok { - t.Fatalf("map contains false value for %v", c.Name) - } - } - t.Logf("unsupported:") - for c, reason := range disabled { - t.Logf("%v: %v", c.Name, reason) - } - _, disabled = target.TransitivelyEnabledCalls(supp) - t.Logf("\n\ntransitively unsupported:") - for c, reason := range disabled { - t.Logf("%v: %v", c.Name, reason) - } - }) - } -} - -func TestCheck(t *testing.T) { - t.Parallel() - target, err := prog.GetTarget(runtime.GOOS, runtime.GOARCH) - if err != nil { - t.Fatal(err) - } - features, err := Check(target) - if err != nil { - t.Fatal(err) - } - for _, feat := range features.Supported() { - t.Logf("%-24v: %v", feat.Name, feat.Reason) - } -} diff --git a/pkg/host/host_windows.go b/pkg/host/host_windows.go deleted file mode 100644 index 13f9b5eb1..000000000 --- a/pkg/host/host_windows.go +++ /dev/null @@ -1,12 +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 host - -import ( - "github.com/google/syzkaller/prog" -) - -func isSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - return true, "" -} diff --git a/pkg/host/syscalls.go b/pkg/host/syscalls.go deleted file mode 100644 index 3068cec2b..000000000 --- a/pkg/host/syscalls.go +++ /dev/null @@ -1,45 +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 ( - "github.com/google/syzkaller/pkg/log" - "github.com/google/syzkaller/prog" -) - -// 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, enabled map[*prog.Syscall]bool) ( - 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 noHostChecks(target) { - for _, c := range target.Syscalls { - if c.Attrs.Disabled || !enabled[c] { - continue - } - supported[c] = true - } - } else { - for _, c := range target.Syscalls { - if c.Attrs.Disabled || !enabled[c] { - continue - } - 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 deleted file mode 100644 index b690f1de7..000000000 --- a/pkg/host/syscalls_linux.go +++ /dev/null @@ -1,568 +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" - "os" - "regexp" - "runtime" - "strconv" - "strings" - "sync" - "syscall" - "time" - - "github.com/google/syzkaller/pkg/osutil" - "github.com/google/syzkaller/prog" - "github.com/google/syzkaller/sys/targets" - "golang.org/x/sys/unix" -) - -func isSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - if strings.HasPrefix(c.CallName, "syz_") { - return isSupportedSyzkall(c, target, sandbox) - } - if reason := isSupportedLSM(c); reason != "" { - return false, reason - } - 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.CallName == "pkey_alloc" { - return isSyzPkeySetSupported(c, target, sandbox) - } - return isSupportedSyscall(c, target) -} - -func isSupportedSyscall(c *prog.Syscall, target *prog.Target) (bool, string) { - // 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, _ := os.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 isSupportedSyscallName(name string, target *prog.Target) (bool, string) { - syscall := target.SyscallMap[name] - if syscall == nil { - return false, fmt.Sprintf("sys_%v is not present in the target", name) - } - return isSupportedSyscall(syscall, target) -} - -func parseKallsyms(kallsyms []byte, arch string) map[string]bool { - set := make(map[string]bool) - var re *regexp.Regexp - switch arch { - case targets.I386, targets.AMD64: - re = regexp.MustCompile(` T (__ia32_|__x64_)?sys_([^\n]+)\n`) - case targets.ARM, targets.ARM64: - re = regexp.MustCompile(` T (__arm64_)?sys_([^\n]+)\n`) - case targets.PPC64LE: - re = regexp.MustCompile(` T ()?sys_([^\n]+)\n`) - case targets.MIPS64LE: - re = regexp.MustCompile(` T sys_(mips_)?([^\n]+)\n`) - case targets.S390x: - re = regexp.MustCompile(` T (__s390_|__s390x_)?sys_([^\n]+)\n`) - case targets.RiscV64: - re = regexp.MustCompile(` T sys_(riscv_)?([^\n]+)\n`) - default: - panic("unsupported arch for kallsyms parsing") - } - matches := re.FindAllSubmatch(kallsyms, -1) - for _, m := range matches { - name := string(m[2]) - 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 enabled in the kernel (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, "" - } - reason := fmt.Sprintf("sys_%v is not enabled in the kernel (returns ENOSYS)", c.CallName) - trialMu.Lock() - defer trialMu.Unlock() - if res, ok := trialSupported[c.NR]; ok { - return res, reason - } - 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, reason -} - -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 - lsmOnce sync.Once - lsmError error - lsmDisabled map[string]bool -) - -func isSyzUsbSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - reason := checkUSBEmulation() - return reason == "", reason -} - -func alwaysSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - return true, "" -} - -func isNetInjectionSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - reason := checkNetInjection() - return reason == "", reason -} - -func isVhciInjectionSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - reason := checkVhciInjection() - return reason == "", reason -} - -func isWifiEmulationSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - reason := checkWifiEmulation() - return reason == "", reason -} - -func isSyzKvmSetupCPUSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - switch c.Name { - case "syz_kvm_setup_cpu$x86": - if runtime.GOARCH == targets.AMD64 || runtime.GOARCH == targets.I386 { - return true, "" - } - case "syz_kvm_setup_cpu$arm64": - if runtime.GOARCH == targets.ARM64 { - return true, "" - } - case "syz_kvm_setup_cpu$ppc64": - if runtime.GOARCH == targets.PPC64LE { - return true, "" - } - } - return false, "unsupported arch" -} - -func isSyzOpenDevSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - return isSupportedSyzOpenDev(sandbox, c) -} - -func isSyzInitNetSocketSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - if ok, reason := onlySandboxNone(sandbox); !ok { - return false, reason - } - return isSupportedSocket(c) -} - -func isSyzSocketConnectNvmeTCPSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - return onlySandboxNone(sandbox) -} - -func isSyzGenetlinkGetFamilyIDSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - 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) - } - // TODO: try to obtain actual family ID here. It will disable whole sets of sendmsg syscalls. - syscall.Close(fd) - return true, "" -} - -func isSyzMountImageSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - if ok, reason := onlySandboxNone(sandbox); !ok { - return ok, reason - } - fstype, ok := extractStringConst(c.Args[0].Type) - if !ok { - panic("syz_mount_image arg is not string") - } - return isSupportedFilesystem(fstype) -} - -func isSyzReadPartTableSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - return onlySandboxNone(sandbox) -} - -func isSyzIoUringSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - return isSupportedSyscallName("io_uring_setup", target) -} - -func isSyzMemcpySupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - ret, msg := isSyzIoUringSupported(c, target, sandbox) - if ret { - return ret, msg - } - return isSyzKvmSetupCPUSupported(c, target, sandbox) -} - -func isBtfVmlinuxSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - if err := osutil.IsAccessible("/sys/kernel/btf/vmlinux"); err != nil { - return false, err.Error() - } - return onlySandboxNone(sandbox) -} - -func isSyzFuseSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - if ok, reason := isSupportedFilesystem("fuse"); !ok { - return ok, reason - } - if ok, reason := onlySandboxNoneOrNamespace(sandbox); !ok { - return false, reason - } - return true, "" -} - -func isSyzUsbIPSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - if err := osutil.IsWritable("/sys/devices/platform/vhci_hcd.0/attach"); err != nil { - return false, err.Error() - } - return onlySandboxNoneOrNamespace(sandbox) -} - -var syzkallSupport = map[string]func(*prog.Syscall, *prog.Target, string) (bool, string){ - "syz_open_dev": isSyzOpenDevSupported, - "syz_open_procfs": isSyzOpenProcfsSupported, - "syz_open_pts": alwaysSupported, - "syz_execute_func": alwaysSupported, - "syz_emit_ethernet": isNetInjectionSupported, - "syz_extract_tcp_res": isNetInjectionSupported, - "syz_usb_connect": isSyzUsbSupported, - "syz_usb_connect_ath9k": isSyzUsbSupported, - "syz_usb_disconnect": isSyzUsbSupported, - "syz_usb_control_io": isSyzUsbSupported, - "syz_usb_ep_write": isSyzUsbSupported, - "syz_usb_ep_read": isSyzUsbSupported, - "syz_kvm_setup_cpu": isSyzKvmSetupCPUSupported, - "syz_emit_vhci": isVhciInjectionSupported, - "syz_init_net_socket": isSyzInitNetSocketSupported, - "syz_genetlink_get_family_id": isSyzGenetlinkGetFamilyIDSupported, - "syz_mount_image": isSyzMountImageSupported, - "syz_read_part_table": isSyzReadPartTableSupported, - "syz_io_uring_submit": isSyzIoUringSupported, - "syz_io_uring_complete": isSyzIoUringSupported, - "syz_io_uring_setup": isSyzIoUringSupported, - "syz_memcpy_off": isSyzMemcpySupported, - "syz_btf_id_by_name": isBtfVmlinuxSupported, - "syz_fuse_handle_req": isSyzFuseSupported, - "syz_80211_inject_frame": isWifiEmulationSupported, - "syz_80211_join_ibss": isWifiEmulationSupported, - "syz_usbip_server_init": isSyzUsbIPSupported, - "syz_clone": alwaysSupported, - "syz_clone3": alwaysSupported, - "syz_pkey_set": isSyzPkeySetSupported, - "syz_socket_connect_nvme_tcp": isSyzSocketConnectNvmeTCPSupported, - "syz_pidfd_open": alwaysSupported, -} - -func isSupportedSyzkall(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - sysTarget := targets.Get(target.OS, target.Arch) - for _, depCall := range sysTarget.PseudoSyscallDeps[c.CallName] { - if ok, reason := isSupportedSyscallName(depCall, target); !ok { - return ok, reason - } - } - if strings.HasPrefix(c.CallName, "syz_ext_") { - // Non-mainline pseudo-syscalls in executor/common_ext.h can't have the checking function - // and are assumed to be unconditionally supported. - if syzkallSupport[c.CallName] != nil { - panic("syz_ext_ prefix is reserved for non-mainline pseudo-syscalls") - } - return true, "" - } - if isSupported, ok := syzkallSupport[c.CallName]; ok { - return isSupported(c, target, sandbox) - } - panic("unknown syzkall: " + c.Name) -} - -func isSupportedSyzOpenDev(sandbox string, c *prog.Syscall) (bool, string) { - if _, ok := c.Args[0].Type.(*prog.ConstType); ok { - // This is for syz_open_dev$char/block. - return true, "" - } - fname, ok := extractStringConst(c.Args[0].Type) - if !ok { - panic("first open arg is not a pointer to string const") - } - if strings.Contains(fname, "/dev/raw/raw#") { - // For syz_open_dev$char_raw, these files don't exist initially. - return true, "" - } - if !strings.Contains(fname, "#") { - panic(fmt.Sprintf("%v does not contain # in the file name (should be openat)", c.Name)) - } - if checkUSBEmulation() == "" { - // 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) -} - -func isSupportedLSM(c *prog.Syscall) string { - lsmOnce.Do(func() { - data, err := os.ReadFile("/sys/kernel/security/lsm") - if err != nil { - // securityfs may not be mounted, but it does not mean - // that no LSMs are enabled. - if !os.IsNotExist(err) { - lsmError = err - } - return - } - lsmDisabled = make(map[string]bool) - for _, lsm := range []string{"selinux", "apparmor", "smack"} { - if !strings.Contains(string(data), lsm) { - lsmDisabled[lsm] = true - } - } - }) - if lsmError != nil { - return lsmError.Error() - } - for lsm := range lsmDisabled { - if strings.Contains(strings.ToLower(c.Name), lsm) { - return fmt.Sprintf("LSM %v is not enabled", lsm) - } - } - return "" -} - -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].Type.(*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].Type.(*prog.ConstType) - if !ok { - return true, "" - } - var typ uint64 - if arg, ok := c.Args[1].Type.(*prog.ConstType); ok { - typ = arg.Val - } else if arg, ok := c.Args[1].Type.(*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 isSyzOpenProcfsSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - return isSupportedOpenFile(c, 1, nil) -} - -func isSupportedOpenAt(c *prog.Syscall) (bool, string) { - // Attempt to extract flags from the syscall description. - var modes []int - if mode, ok := c.Args[2].Type.(*prog.ConstType); ok { - modes = []int{int(mode.Val)} - } - return isSupportedOpenFile(c, 1, modes) -} - -func isSupportedOpenFile(c *prog.Syscall, filenameArg int, modes []int) (bool, string) { - fname, ok := extractStringConst(c.Args[filenameArg].Type) - if !ok || fname == "" || fname[0] != '/' { - return true, "" - } - if len(modes) == 0 { - modes = []int{syscall.O_RDONLY, syscall.O_WRONLY, syscall.O_RDWR, syscall.O_RDONLY | syscall.O_NONBLOCK} - } - var err error - for _, mode := range modes { - var fd int - 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].Type) - 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, _ = os.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.Elem.(*prog.BufferType) - if !ok || str.Kind != prog.BufferString || len(str.Values) == 0 { - return "", false - } - v := str.Values[0] - for v != "" && v[len(v)-1] == 0 { - v = v[:len(v)-1] // string terminating \x00 - } - return v, true -} - -func isSyzPkeySetSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) { - if target.Arch != targets.AMD64 && target.Arch != targets.I386 { - return false, "unsupported arch" - } - key, _, err := syscall.Syscall6(unix.SYS_PKEY_ALLOC, 0, 0, 0, 0, 0, 0) - if err != 0 { - return false, fmt.Sprintf("pkey_alloc failed: %v", err) - } - _, _, err = syscall.Syscall6(unix.SYS_PKEY_FREE, key, 0, 0, 0, 0, 0) - if err != 0 { - return false, fmt.Sprintf("pkey_free failed: %v", err) - } - return true, "" -} diff --git a/pkg/host/syscalls_linux_test.go b/pkg/host/syscalls_linux_test.go deleted file mode 100644 index fc5d13dd5..000000000 --- a/pkg/host/syscalls_linux_test.go +++ /dev/null @@ -1,307 +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. - -//go:build linux - -package host - -import ( - "fmt" - "runtime" - "syscall" - "testing" - - "github.com/google/syzkaller/prog" - "github.com/google/syzkaller/sys/targets" -) - -func TestSupportedSyscalls(t *testing.T) { - t.Parallel() - target, err := prog.GetTarget(targets.Linux, runtime.GOARCH) - if err != nil { - t.Fatal(err) - } - // These are safe to execute with invalid arguments. - safe := []string{ - "memfd_create", - "sendfile", - "bpf$MAP_CREATE", - "openat", - "read", - "write", - } - enabled := make(map[*prog.Syscall]bool) - for _, name := range safe { - c := target.SyscallMap[name] - if c == nil { - t.Fatalf("can't find syscall '%v'", name) - } - enabled[c] = true - } - supp, _, err := DetectSupportedSyscalls(target, "none", enabled) - if err != nil { - t.Skipf("skipping: %v", err) - } - for c := range enabled { - 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", c.Name) - } - if ok := err != syscall.ENOSYS; ok != supp[c] { - t.Fatalf("syscall %v: perse=%v kallsyms=%v", c.Name, ok, supp[c]) - } - } -} - -func TestKallsymsParse(t *testing.T) { - tests := []struct { - Arch string - Kallsyms []byte - ParsedSyscalls []string - SupportedSyscalls []string - }{ - { - targets.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"}, - }, - { - targets.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"}, - }, - { - targets.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"}, - }, - { - targets.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. - { - targets.PPC64LE, - []byte(` -c00000000037eb00 T sys_newstat - `), - []string{"newstat"}, - []string{"stat"}, - }, - { - targets.S390x, - []byte(` -0000000000e4f760 T __sys_bind -0000000000e4f8e8 T __s390_sys_bind -0000000000e4f938 T __s390x_sys_bind -0000000000e4f938 T __se_sys_bind -0000000000e4f988 T __sys_listen -0000000000e4fab0 T __s390_sys_listen -0000000000e4faf8 T __s390x_sys_listen -0000000000e4faf8 T __se_sys_listen -0000000000e4fb40 T __sys_accept4 -0000000000e4fe58 T __s390_sys_accept4 -0000000000e4feb0 T __s390x_sys_accept4 -0000000000e4feb0 T __se_sys_accept4 - `), - []string{"bind", "listen", "accept4"}, - []string{"bind", "listen", "accept4"}, - }, - { - targets.RiscV64, - []byte(` -ffffffe0005c9b02 T __sys_bind -ffffffe0005c9ba0 T sys_bind -ffffffe0005c9ba0 T __se_sys_bind -ffffffe0005c9e72 T __sys_accept4 -ffffffe0005c9f00 T sys_accept4 -ffffffe0005c9f00 T __se_sys_accept4 -ffffffe0005c9bd8 T __sys_listen -ffffffe0005c9c76 T sys_listen -ffffffe0005c9c76 T __se_sys_listen - `), - []string{"bind", "listen", "accept4"}, - []string{"bind", "listen", "accept4"}, - }, - } - - 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) - } - } - } -} - -func TestMatchKernelVersion(t *testing.T) { - tests := []struct { - version string - major int - minor int - res bool - bad bool - }{ - { - version: "5.9.0-rc5-next-20200918", - major: 5, - minor: 8, - res: true, - }, - { - version: "5.9.0-rc5-next-20200918", - major: 4, - minor: 10, - res: true, - }, - { - version: "5.9.0-rc5-next-20200918", - major: 5, - minor: 9, - res: true, - }, - { - version: "5.9.0-rc5-next-20200918", - major: 5, - minor: 10, - res: false, - }, - { - version: "5.9.0-rc5-next-20200918", - major: 6, - minor: 0, - res: false, - }, - { - version: "4.4.246-19567-g2c01e3dada31", - major: 4, - minor: 3, - res: true, - }, - { - version: "4.4.246-19567-g2c01e3dada31", - major: 4, - minor: 4, - res: true, - }, - { - version: "5.17.17-1debian-amd64", - major: 5, - minor: 16, - res: true, - }, - { - version: "5.17.17-1debian-amd64", - major: 5, - minor: 17, - res: true, - }, - { - version: "3.5", - major: 3, - minor: 4, - res: true, - }, - { - version: "3.5", - major: 3, - minor: 5, - res: true, - }, - { - version: "3.5", - major: 3, - minor: 6, - res: false, - }, - { - version: "", - bad: true, - }, - { - version: "something unparsable", - bad: true, - }, - { - version: "mykernel 4.17-5", - bad: true, - }, - { - version: "999999.999999", - bad: true, - }, - { - version: "4.abc.def", - bad: true, - }, - } - for i, test := range tests { - t.Run(fmt.Sprint(i), func(t *testing.T) { - ok, bad := matchKernelVersion(test.version, test.major, test.minor) - if test.bad && !bad { - t.Fatal("want error, but got no error") - } - if !test.bad && bad { - t.Fatalf("want no error, but got error") - } - if test.res != ok { - t.Fatalf("want match %v, but got %v", test.res, ok) - } - }) - } -} diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go index 79b2acc18..c83fabe84 100644 --- a/pkg/ipc/ipc.go +++ b/pkg/ipc/ipc.go @@ -159,6 +159,12 @@ func FlagsToSandbox(flags EnvFlags) string { // nolint: gocyclo func FeaturesToFlags(features flatrpc.Feature, manual csource.Features) EnvFlags { + for feat := range flatrpc.EnumNamesFeature { + opt := FlatRPCFeaturesToCSource[feat] + if opt != "" && manual != nil && !manual[opt].Enabled { + features &= ^feat + } + } var flags EnvFlags if manual == nil || manual["net_reset"].Enabled { flags |= FlagEnableNetReset @@ -175,27 +181,40 @@ func FeaturesToFlags(features flatrpc.Feature, manual csource.Features) EnvFlags if features&flatrpc.FeatureDelayKcovMmap != 0 { flags |= FlagDelayKcovMmap } - if features&flatrpc.FeatureNetInjection != 0 && (manual == nil || manual["tun"].Enabled) { + if features&flatrpc.FeatureNetInjection != 0 { flags |= FlagEnableTun } - if features&flatrpc.FeatureNetDevices != 0 && (manual == nil || manual["net_dev"].Enabled) { + if features&flatrpc.FeatureNetDevices != 0 { flags |= FlagEnableNetDev } - if features&flatrpc.FeatureDevlinkPCI != 0 && (manual == nil || manual["devlink_pci"].Enabled) { + if features&flatrpc.FeatureDevlinkPCI != 0 { flags |= FlagEnableDevlinkPCI } - if features&flatrpc.FeatureNicVF != 0 && (manual == nil || manual["nic_vf"].Enabled) { + if features&flatrpc.FeatureNicVF != 0 { flags |= FlagEnableNicVF } - if features&flatrpc.FeatureVhciInjection != 0 && (manual == nil || manual["vhci"].Enabled) { + if features&flatrpc.FeatureVhciInjection != 0 { flags |= FlagEnableVhciInjection } - if features&flatrpc.FeatureWifiEmulation != 0 && (manual == nil || manual["wifi"].Enabled) { + if features&flatrpc.FeatureWifiEmulation != 0 { flags |= FlagEnableWifi } return flags } +var FlatRPCFeaturesToCSource = map[flatrpc.Feature]string{ + flatrpc.FeatureNetInjection: "tun", + flatrpc.FeatureNetDevices: "net_dev", + flatrpc.FeatureDevlinkPCI: "devlink_pci", + flatrpc.FeatureNicVF: "nic_vf", + flatrpc.FeatureVhciInjection: "vhci", + flatrpc.FeatureWifiEmulation: "wifi", + flatrpc.FeatureUSBEmulation: "usb", + flatrpc.FeatureBinFmtMisc: "binfmt_misc", + flatrpc.FeatureLRWPANEmulation: "ieee802154", + flatrpc.FeatureSwap: "swap", +} + func MakeEnv(config *Config, pid int) (*Env, error) { if config.Timeouts.Slowdown == 0 || config.Timeouts.Scale == 0 || config.Timeouts.Syscall == 0 || config.Timeouts.Program == 0 { diff --git a/pkg/ipc/ipc_test.go b/pkg/ipc/ipc_test.go index 8a8cdfac8..af4eaf24a 100644 --- a/pkg/ipc/ipc_test.go +++ b/pkg/ipc/ipc_test.go @@ -226,7 +226,7 @@ func TestExecutorCommonExt(t *testing.T) { t.Skipf("skipping, broken cross-compiler: %v", sysTarget.BrokenCompiler) } bin := csource.BuildExecutor(t, target, "../..", "-DSYZ_TEST_COMMON_EXT_EXAMPLE=1") - out, err := osutil.RunCmd(time.Minute, "", bin, "setup") + out, err := osutil.RunCmd(time.Minute, "", bin, "setup", "0") if err != nil { t.Fatal(err) } diff --git a/pkg/rpctype/rpctype.go b/pkg/rpctype/rpctype.go index ffc089271..5fa128249 100644 --- a/pkg/rpctype/rpctype.go +++ b/pkg/rpctype/rpctype.go @@ -10,7 +10,6 @@ import ( "time" "github.com/google/syzkaller/pkg/flatrpc" - "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/signal" ) @@ -92,8 +91,8 @@ type ConnectArgs struct { type ConnectRes struct { MemoryLeakFrames []string DataRaceFrames []string - // This is forwarded from CheckArgs, if checking was already done. - Features *host.Features + // Bitmask of features to try to setup. + Features flatrpc.Feature // Fuzzer reads these files inside of the VM and returns contents in CheckArgs.Files. ReadFiles []string ReadGlobs []string @@ -102,7 +101,7 @@ type ConnectRes struct { type CheckArgs struct { Name string Error string - Features *host.Features + Features []flatrpc.FeatureInfo Globs map[string][]string Files []flatrpc.FileInfo } diff --git a/pkg/runtest/run_test.go b/pkg/runtest/run_test.go index c06136078..f5798a9b4 100644 --- a/pkg/runtest/run_test.go +++ b/pkg/runtest/run_test.go @@ -14,7 +14,6 @@ import ( "time" "github.com/google/syzkaller/pkg/csource" - "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/pkg/testutil" @@ -55,29 +54,14 @@ func test(t *testing.T, sysTarget *targets.Target) { t.Fatal(err) } executor := csource.BuildExecutor(t, target, "../../", "-fsanitize-coverage=trace-pc") - features, err := host.Check(target) - if err != nil { - t.Fatalf("failed to detect host features: %v", err) - } - enabled := make(map[*prog.Syscall]bool) - for _, c := range target.Syscalls { - enabled[c] = true - } - calls, _, err := host.DetectSupportedSyscalls(target, "none", enabled) - if err != nil { - t.Fatalf("failed to detect supported syscalls: %v", err) + calls := make(map[*prog.Syscall]bool) + for _, call := range target.Syscalls { + calls[call] = true } enabledCalls := map[string]map[*prog.Syscall]bool{ "": calls, "none": calls, } - featureFlags, err := csource.ParseFeaturesFlags("none", "none", true) - if err != nil { - t.Fatal(err) - } - if err := host.Setup(target, features, featureFlags, executor); err != nil { - t.Fatal(err) - } requests := make(chan *RunRequest, 2*runtime.GOMAXPROCS(0)) go func() { for req := range requests { @@ -93,7 +77,7 @@ func test(t *testing.T, sysTarget *targets.Target) { Dir: filepath.Join("..", "..", "sys", target.OS, targets.TestOS), Target: target, Tests: *flagFilter, - Features: features.ToFlatRPC(), + Features: 0, EnabledCalls: enabledCalls, Requests: requests, LogFunc: func(text string) { diff --git a/pkg/vminfo/features.go b/pkg/vminfo/features.go new file mode 100644 index 000000000..2ba76a255 --- /dev/null +++ b/pkg/vminfo/features.go @@ -0,0 +1,192 @@ +// Copyright 2024 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 vminfo + +import ( + "fmt" + "strings" + + "github.com/google/syzkaller/pkg/flatrpc" + "github.com/google/syzkaller/pkg/ipc" + "github.com/google/syzkaller/pkg/rpctype" + "github.com/google/syzkaller/prog" +) + +type Feature struct { + Enabled bool + NeedSetup bool + Reason string +} + +type Features map[flatrpc.Feature]Feature + +func (features Features) Enabled() flatrpc.Feature { + var mask flatrpc.Feature + for feat, info := range features { + if info.Enabled { + mask |= feat + } + } + return mask +} + +func (features Features) NeedSetup() flatrpc.Feature { + var mask flatrpc.Feature + for feat, info := range features { + if info.Enabled && info.NeedSetup { + mask |= feat + } + } + return mask +} + +type featureResult struct { + id flatrpc.Feature + reason string +} + +func (ctx *checkContext) checkFeatures() { + testProg := ctx.target.DataMmapProg() + for feat := range flatrpc.EnumNamesFeature { + feat := feat + ctx.pendingRequests++ + go func() { + envFlags, execFlags := ctx.featureToFlags(feat) + res := ctx.execProg(testProg, envFlags, execFlags) + reason := ctx.featureSucceeded(feat, testProg, res) + ctx.features <- featureResult{feat, reason} + }() + } +} + +func (ctx *checkContext) finishFeatures(featureInfos []flatrpc.FeatureInfo) (Features, error) { + // Feature checking consists of 2 parts: + // - we ask executor to try to setup each feature (results are returned in featureInfos) + // - we also try to run a simple program with feature-specific flags + // Here we combine both results. + features := make(Features) + for _, info := range featureInfos { + features[info.Id] = Feature{ + Reason: info.Reason, + NeedSetup: info.NeedSetup, + } + } + outputReplacer := strings.NewReplacer( + "SYZFAIL:", "", + "\n", ". ", + ) + for range flatrpc.EnumNamesFeature { + res := <-ctx.features + feat := features[res.id] + if feat.Reason == "" { + feat.Reason = res.reason + } + if feat.Reason == "" { + feat.Reason = "enabled" + feat.Enabled = true + } + if pos := strings.Index(feat.Reason, "loop exited with status"); pos != -1 { + feat.Reason = feat.Reason[:pos] + } + // If executor exited the output is prefixed with "executor 4: EOF". + const executorPrefix = ": EOF\n" + if pos := strings.Index(feat.Reason, executorPrefix); pos != -1 { + feat.Reason = feat.Reason[pos+len(executorPrefix):] + } + feat.Reason = strings.TrimSpace(outputReplacer.Replace(feat.Reason)) + features[res.id] = feat + } + if feat := features[flatrpc.FeatureSandboxSetuid]; !feat.Enabled { + return features, fmt.Errorf("execution of simple program fails: %v", feat.Reason) + } + if feat := features[flatrpc.FeatureCoverage]; ctx.cfg.Cover && !feat.Enabled { + return features, fmt.Errorf("coverage is not supported: %v", feat.Reason) + } + return features, nil +} + +// featureToFlags creates ipc flags required to test the feature on a simple program. +// For features that has setup procedure in the executor, we just execute with the default flags. +func (ctx *checkContext) featureToFlags(feat flatrpc.Feature) (ipc.EnvFlags, ipc.ExecFlags) { + envFlags := ctx.sandbox + // These don't have a corresponding feature and are always enabled. + envFlags |= ipc.FlagEnableCloseFds | ipc.FlagEnableCgroups | ipc.FlagEnableNetReset + execFlags := ipc.FlagThreaded + switch feat { + case flatrpc.FeatureCoverage: + envFlags |= ipc.FlagSignal + execFlags |= ipc.FlagCollectSignal | ipc.FlagCollectCover + case flatrpc.FeatureComparisons: + envFlags |= ipc.FlagSignal + execFlags |= ipc.FlagCollectComps + case flatrpc.FeatureExtraCoverage: + envFlags |= ipc.FlagSignal | ipc.FlagExtraCover + execFlags |= ipc.FlagCollectSignal | ipc.FlagCollectCover + case flatrpc.FeatureDelayKcovMmap: + envFlags |= ipc.FlagSignal | ipc.FlagDelayKcovMmap + execFlags |= ipc.FlagCollectSignal | ipc.FlagCollectCover + case flatrpc.FeatureSandboxSetuid: + // We use setuid sandbox feature to test that the simple program + // succeeds under the actual sandbox (not necessary setuid). + // We do this because we don't have a feature for sandbox 'none'. + case flatrpc.FeatureSandboxNamespace: + case flatrpc.FeatureSandboxAndroid: + case flatrpc.FeatureFault: + case flatrpc.FeatureLeak: + case flatrpc.FeatureNetInjection: + envFlags |= ipc.FlagEnableTun + case flatrpc.FeatureNetDevices: + envFlags |= ipc.FlagEnableNetDev + case flatrpc.FeatureKCSAN: + case flatrpc.FeatureDevlinkPCI: + envFlags |= ipc.FlagEnableDevlinkPCI + case flatrpc.FeatureNicVF: + envFlags |= ipc.FlagEnableNicVF + case flatrpc.FeatureUSBEmulation: + case flatrpc.FeatureVhciInjection: + envFlags |= ipc.FlagEnableVhciInjection + case flatrpc.FeatureWifiEmulation: + envFlags |= ipc.FlagEnableWifi + case flatrpc.FeatureLRWPANEmulation: + case flatrpc.FeatureBinFmtMisc: + case flatrpc.FeatureSwap: + default: + panic(fmt.Sprintf("unknown feature %v", flatrpc.EnumNamesFeature[feat])) + } + return envFlags, execFlags +} + +// featureSucceeded checks if execution of a simple program with feature-specific flags succeed. +// This generally checks that just all syscalls were executed and succeed, +// for coverage features we also check that we got actual coverage. +func (ctx *checkContext) featureSucceeded(feat flatrpc.Feature, testProg *prog.Prog, + res rpctype.ExecutionResult) string { + if res.Error != "" { + if len(res.Output) != 0 { + return string(res.Output) + } + return res.Error + } + if len(res.Info.Calls) != len(testProg.Calls) { + return fmt.Sprintf("only %v calls are executed out of %v", + len(res.Info.Calls), len(testProg.Calls)) + } + for i, call := range res.Info.Calls { + if call.Errno != 0 { + return fmt.Sprintf("call %v failed with errno %v", i, call.Errno) + } + } + call := res.Info.Calls[0] + switch feat { + case flatrpc.FeatureCoverage: + if len(call.Cover) == 0 || len(call.Signal) == 0 { + return "got no coverage" + } + case flatrpc.FeatureComparisons: + if len(call.Comps) == 0 { + return "got no coverage" + } + } + return "" +} diff --git a/pkg/vminfo/linux_syscalls.go b/pkg/vminfo/linux_syscalls.go index 529fed3bd..e96ce79c1 100644 --- a/pkg/vminfo/linux_syscalls.go +++ b/pkg/vminfo/linux_syscalls.go @@ -62,7 +62,7 @@ func linuxSupportedLSM(ctx *checkContext, call *prog.Syscall) string { } var linuxSyscallChecks = map[string]func(*checkContext, *prog.Syscall) string{ - "openat": linuxSupportedOpenat, + "openat": supportedOpenat, "mount": linuxSupportedMount, "socket": linuxSupportedSocket, "socketpair": linuxSupportedSocket, @@ -185,24 +185,6 @@ func linuxSyzKvmSetupCPUSupported(ctx *checkContext, call *prog.Syscall) string return "unsupported arch" } -func linuxSupportedOpenat(ctx *checkContext, call *prog.Syscall) string { - fname, ok := extractStringConst(call.Args[1].Type) - if !ok || fname[0] != '/' { - return "" - } - modes := ctx.allOpenModes() - // Attempt to extract flags from the syscall description. - if mode, ok := call.Args[2].Type.(*prog.ConstType); ok { - modes = []uint64{mode.Val} - } - var calls []string - for _, mode := range modes { - call := fmt.Sprintf("openat(0x%0x, &AUTO='%v', 0x%x, 0x0)", ctx.val("AT_FDCWD"), fname, mode) - calls = append(calls, call) - } - return ctx.anyCallSucceeds(calls, fmt.Sprintf("failed to open %v", fname)) -} - func linuxSupportedMount(ctx *checkContext, call *prog.Syscall) string { return linuxSupportedFilesystem(ctx, call, 2) } diff --git a/pkg/vminfo/linux_test.go b/pkg/vminfo/linux_test.go index 217740f6d..e9be133c1 100644 --- a/pkg/vminfo/linux_test.go +++ b/pkg/vminfo/linux_test.go @@ -45,8 +45,8 @@ func TestLinuxSyscalls(t *testing.T) { Data: []byte(strings.Join(filesystems, "\nnodev\t")), }, } - results := createSuccessfulResults(t, cfg.Target, checkProgs) - enabled, disabled, err := checker.FinishCheck(files, results) + results, featureInfos := createSuccessfulResults(t, cfg.Target, checkProgs) + enabled, disabled, features, err := checker.FinishCheck(files, results, featureInfos) if err != nil { t.Fatal(err) } @@ -74,6 +74,14 @@ func TestLinuxSyscalls(t *testing.T) { if len(enabled) != expectEnabled { t.Errorf("enabled only %v calls out of %v", len(enabled), expectEnabled) } + if len(features) != len(flatrpc.EnumNamesFeature) { + t.Errorf("enabled only %v features out of %v", len(features), len(flatrpc.EnumNamesFeature)) + } + for feat, info := range features { + if !info.Enabled { + t.Errorf("feature %v is not enabled: %v", flatrpc.EnumNamesFeature[feat], info.Reason) + } + } } func TestReadKVMInfo(t *testing.T) { diff --git a/pkg/vminfo/netbsd.go b/pkg/vminfo/netbsd.go new file mode 100644 index 000000000..cb6c1b33a --- /dev/null +++ b/pkg/vminfo/netbsd.go @@ -0,0 +1,38 @@ +// Copyright 2024 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 vminfo + +import ( + "github.com/google/syzkaller/pkg/cover" + "github.com/google/syzkaller/prog" +) + +type netbsd int + +func (netbsd) RequiredFiles() []string { + return nil +} + +func (netbsd) checkFiles() []string { + return nil +} + +func (netbsd) parseModules(files filesystem) ([]cover.KernelModule, error) { + return nil, nil +} + +func (netbsd) machineInfos() []machineInfoFunc { + return nil +} + +func (netbsd) syscallCheck(ctx *checkContext, call *prog.Syscall) string { + switch call.CallName { + case "openat": + return supportedOpenat(ctx, call) + case "syz_usb_connect", "syz_usb_disconnect": + return ctx.rootCanOpen("/dev/vhci0") + default: + return "" + } +} diff --git a/pkg/vminfo/openbsd.go b/pkg/vminfo/openbsd.go new file mode 100644 index 000000000..07ada0a98 --- /dev/null +++ b/pkg/vminfo/openbsd.go @@ -0,0 +1,36 @@ +// Copyright 2024 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 vminfo + +import ( + "github.com/google/syzkaller/pkg/cover" + "github.com/google/syzkaller/prog" +) + +type openbsd int + +func (openbsd) RequiredFiles() []string { + return nil +} + +func (openbsd) checkFiles() []string { + return nil +} + +func (openbsd) parseModules(files filesystem) ([]cover.KernelModule, error) { + return nil, nil +} + +func (openbsd) machineInfos() []machineInfoFunc { + return nil +} + +func (openbsd) syscallCheck(ctx *checkContext, call *prog.Syscall) string { + switch call.CallName { + case "openat": + return supportedOpenat(ctx, call) + default: + return "" + } +} diff --git a/pkg/vminfo/syscalls.go b/pkg/vminfo/syscalls.go index 60112ddac..78cc973c8 100644 --- a/pkg/vminfo/syscalls.go +++ b/pkg/vminfo/syscalls.go @@ -62,12 +62,13 @@ type checkContext struct { // Ready channel is closed after we've recevied results of execution of test // programs and file contents. After this results maps and fs are populated. ready chan bool - results map[int64]*ipc.ProgInfo + results map[int64]rpctype.ExecutionResult fs filesystem // Once checking of a syscall is finished, the result is sent to syscalls. // The main goroutine will wait for exactly pendingSyscalls messages. syscalls chan syscallResult pendingSyscalls int + features chan featureResult } type syscallResult struct { @@ -86,8 +87,9 @@ func newCheckContext(cfg *mgrconfig.Config, impl checker) *checkContext { target: cfg.Target, sandbox: sandbox, requests: make(chan []*rpctype.ExecutionRequest), - results: make(map[int64]*ipc.ProgInfo), + results: make(map[int64]rpctype.ExecutionResult), syscalls: make(chan syscallResult), + features: make(chan featureResult, 100), ready: make(chan bool), } } @@ -130,6 +132,7 @@ func (ctx *checkContext) startCheck() []rpctype.ExecutionRequest { ctx.syscalls <- syscallResult{call, reason} }() } + ctx.checkFeatures() var progs []rpctype.ExecutionRequest dedup := make(map[hash.Sig]int64) for i := 0; i < ctx.pendingRequests; i++ { @@ -148,12 +151,11 @@ func (ctx *checkContext) startCheck() []rpctype.ExecutionRequest { return progs } -func (ctx *checkContext) finishCheck(fileInfos []flatrpc.FileInfo, progs []rpctype.ExecutionResult) ( - map[*prog.Syscall]bool, map[*prog.Syscall]string, error) { +func (ctx *checkContext) finishCheck(fileInfos []flatrpc.FileInfo, progs []rpctype.ExecutionResult, + featureInfos []flatrpc.FeatureInfo) (map[*prog.Syscall]bool, map[*prog.Syscall]string, Features, error) { ctx.fs = createVirtualFilesystem(fileInfos) - for i := range progs { - res := &progs[i] - ctx.results[res.ID] = &res.Info + for _, res := range progs { + ctx.results[res.ID] = res } close(ctx.ready) enabled := make(map[*prog.Syscall]bool) @@ -166,7 +168,8 @@ func (ctx *checkContext) finishCheck(fileInfos []flatrpc.FileInfo, progs []rpcty disabled[res.call] = res.reason } } - return enabled, disabled, nil + features, err := ctx.finishFeatures(featureInfos) + return enabled, disabled, features, err } func (ctx *checkContext) rootCanOpen(file string) string { @@ -220,6 +223,24 @@ func (ctx *checkContext) supportedSyscalls(names []string) string { return "" } +func supportedOpenat(ctx *checkContext, call *prog.Syscall) string { + fname, ok := extractStringConst(call.Args[1].Type) + if !ok || fname[0] != '/' { + return "" + } + modes := ctx.allOpenModes() + // Attempt to extract flags from the syscall description. + if mode, ok := call.Args[2].Type.(*prog.ConstType); ok { + modes = []uint64{mode.Val} + } + var calls []string + for _, mode := range modes { + call := fmt.Sprintf("openat(0x%0x, &AUTO='%v', 0x%x, 0x0)", ctx.val("AT_FDCWD"), fname, mode) + calls = append(calls, call) + } + return ctx.anyCallSucceeds(calls, fmt.Sprintf("failed to open %v", fname)) +} + func (ctx *checkContext) allOpenModes() []uint64 { // Various open modes we need to try if we don't have a concrete mode. // Some files can be opened only for reading, some only for writing, @@ -307,14 +328,14 @@ func (ctx *checkContext) execRaw(calls []string, mode prog.DeserializeMode, root <-ctx.ready info := &ipc.ProgInfo{} for _, req := range requests { - res := ctx.results[req.ID] - if res == nil { + res, ok := ctx.results[req.ID] + if !ok { panic(fmt.Sprintf("no result for request %v", req.ID)) } - if len(res.Calls) == 0 { + if len(res.Info.Calls) == 0 { panic(fmt.Sprintf("result for request %v has no calls", req.ID)) } - info.Calls = append(info.Calls, res.Calls...) + info.Calls = append(info.Calls, res.Info.Calls...) } if len(info.Calls) != len(calls) { panic(fmt.Sprintf("got only %v results for program %v with %v calls:\n%s", @@ -323,6 +344,34 @@ func (ctx *checkContext) execRaw(calls []string, mode prog.DeserializeMode, root return info } +func (ctx *checkContext) execProg(p *prog.Prog, envFlags ipc.EnvFlags, + execFlags ipc.ExecFlags) rpctype.ExecutionResult { + if ctx.requests == nil { + panic("only one test execution per checker is supported") + } + data, err := p.SerializeForExec() + if err != nil { + panic(fmt.Sprintf("failed to serialize test program: %v\n%s", err, p.Serialize())) + } + req := &rpctype.ExecutionRequest{ + ProgData: data, + ReturnOutput: true, + ReturnError: true, + ExecOpts: ipc.ExecOpts{ + EnvFlags: envFlags, + ExecFlags: execFlags, + SandboxArg: ctx.cfg.SandboxArg, + }, + } + ctx.requests <- []*rpctype.ExecutionRequest{req} + <-ctx.ready + res, ok := ctx.results[req.ID] + if !ok { + panic(fmt.Sprintf("no result for request %v", req.ID)) + } + return res +} + func (ctx *checkContext) readFile(name string) ([]byte, error) { ctx.waitForResults() return ctx.fs.ReadFile(name) diff --git a/pkg/vminfo/vminfo.go b/pkg/vminfo/vminfo.go index efc471b7d..8c5af606e 100644 --- a/pkg/vminfo/vminfo.go +++ b/pkg/vminfo/vminfo.go @@ -36,9 +36,13 @@ type Checker struct { func New(cfg *mgrconfig.Config) *Checker { var impl checker - switch { - case cfg.TargetOS == targets.Linux: + switch cfg.TargetOS { + case targets.Linux: impl = new(linux) + case targets.NetBSD: + impl = new(netbsd) + case targets.OpenBSD: + impl = new(openbsd) default: impl = new(stub) } @@ -77,11 +81,11 @@ func (checker *Checker) StartCheck() ([]string, []rpctype.ExecutionRequest) { return checker.checkFiles(), checker.checkContext.startCheck() } -func (checker *Checker) FinishCheck(files []flatrpc.FileInfo, progs []rpctype.ExecutionResult) ( - map[*prog.Syscall]bool, map[*prog.Syscall]string, error) { +func (checker *Checker) FinishCheck(files []flatrpc.FileInfo, progs []rpctype.ExecutionResult, + featureInfos []flatrpc.FeatureInfo) (map[*prog.Syscall]bool, map[*prog.Syscall]string, Features, error) { ctx := checker.checkContext checker.checkContext = nil - return ctx.finishCheck(files, progs) + return ctx.finishCheck(files, progs, featureInfos) } type machineInfoFunc func(files filesystem, w io.Writer) (string, error) diff --git a/pkg/vminfo/vminfo_test.go b/pkg/vminfo/vminfo_test.go index de41b48fe..2f72131a5 100644 --- a/pkg/vminfo/vminfo_test.go +++ b/pkg/vminfo/vminfo_test.go @@ -56,8 +56,8 @@ func TestSyscalls(t *testing.T) { cfg := testConfig(t, target.OS, target.Arch) checker := New(cfg) _, checkProgs := checker.StartCheck() - results := createSuccessfulResults(t, cfg.Target, checkProgs) - enabled, disabled, err := checker.FinishCheck(nil, results) + results, featureInfos := createSuccessfulResults(t, cfg.Target, checkProgs) + enabled, disabled, _, err := checker.FinishCheck(nil, results, featureInfos) if err != nil { t.Fatal(err) } @@ -73,7 +73,7 @@ func TestSyscalls(t *testing.T) { } func createSuccessfulResults(t *testing.T, target *prog.Target, - progs []rpctype.ExecutionRequest) []rpctype.ExecutionResult { + progs []rpctype.ExecutionRequest) ([]rpctype.ExecutionResult, []flatrpc.FeatureInfo) { var results []rpctype.ExecutionResult for _, req := range progs { p, err := target.DeserializeExec(req.ProgData, nil) @@ -82,13 +82,25 @@ func createSuccessfulResults(t *testing.T, target *prog.Target, } res := rpctype.ExecutionResult{ ID: req.ID, - Info: ipc.ProgInfo{ - Calls: make([]ipc.CallInfo, len(p.Calls)), - }, + } + for range p.Calls { + res.Info.Calls = append(res.Info.Calls, ipc.CallInfo{ + Cover: []uint32{1}, + Signal: []uint32{1}, + Comps: map[uint64]map[uint64]bool{ + 1: {2: true}, + }, + }) } results = append(results, res) } - return results + var features []flatrpc.FeatureInfo + for feat := range flatrpc.EnumNamesFeature { + features = append(features, flatrpc.FeatureInfo{ + Id: feat, + }) + } + return results, features } func hostChecker(t *testing.T) (*Checker, []flatrpc.FileInfo) { |
