diff options
37 files changed, 709 insertions, 2083 deletions
diff --git a/docs/pseudo_syscalls.md b/docs/pseudo_syscalls.md index cb899e6fc..fc36f85f9 100644 --- a/docs/pseudo_syscalls.md +++ b/docs/pseudo_syscalls.md @@ -56,17 +56,10 @@ are violated (e.g. passing `NULL` to a `non-NULL` argument, or passing that. Now, to handle the pseudo-syscall properly we have to update the -`isSupportedSyzkall` in -[syscalls_linux.go](../pkg/host/syscalls_linux.go) and add a particular +`linuxSyscallChecks` in +[linux_syscalls.go](../pkg/vminfo/linux_syscalls.go) and add a particular case for this syscall, enabling it when necessary. If we want to enable -it unconditionally we can simply make `isSupportedSyzkall` return `true, -""` for it: - - func isSupportedSyzkall(sandbox string, c *prog.Syscall) (bool, string) { - switch c.CallName { - ... - case "syz_mycall": - return true, "" +it unconditionally we can simply use `alwaysSupported` for it. Finally, run `make generate`. Now you can use it in a syscall description file as if it was a regular system call: diff --git a/executor/common_bsd.h b/executor/common_bsd.h index 4ed45d0bd..e0beac33f 100644 --- a/executor/common_bsd.h +++ b/executor/common_bsd.h @@ -24,6 +24,7 @@ static void setup_usb(void) if (dir == NULL) fail("failed to open /dev"); + bool have_vhci = false; struct dirent* ent = NULL; while ((ent = readdir(dir)) != NULL) { if (ent->d_type != DT_CHR) @@ -34,7 +35,10 @@ static void setup_usb(void) snprintf(path, sizeof(path), "/dev/%s", ent->d_name); if (chmod(path, 0666)) failmsg("failed to chmod vhci", "path=%s", path); + have_vhci = true; } + if (!have_vhci) + fail("don't have any /dev/vhci devices"); closedir(dir); } diff --git a/executor/executor.cc b/executor/executor.cc index 6ac777ad6..2e4e01da8 100644 --- a/executor/executor.cc +++ b/executor/executor.cc @@ -369,7 +369,7 @@ struct kcov_comparison_t { typedef char kcov_comparison_size[sizeof(kcov_comparison_t) == 4 * sizeof(uint64) ? 1 : -1]; struct feature_t { - const char* name; + rpc::Feature id; void (*setup)(); }; @@ -1658,33 +1658,41 @@ bool kcov_comparison_t::operator<(const struct kcov_comparison_t& other) const } #endif // if SYZ_EXECUTOR_USES_SHMEM +#if !SYZ_HAVE_FEATURES +static feature_t features[] = {}; +#endif + void setup_features(char** enable, int n) { // This does any one-time setup for the requested features on the machine. // Note: this can be called multiple times and must be idempotent. flag_debug = true; + if (n != 1) + fail("setup: more than one feature"); + char* endptr = nullptr; + auto feature = static_cast<rpc::Feature>(strtoull(enable[0], &endptr, 10)); + if (endptr == enable[0] || (feature > rpc::Feature::ANY) || + __builtin_popcountll(static_cast<uint64>(feature)) > 1) + failmsg("setup: failed to parse feature", "feature='%s'", enable[0]); + if (feature == rpc::Feature::NONE) { #if SYZ_HAVE_FEATURES - setup_sysctl(); - setup_cgroups(); + setup_sysctl(); + setup_cgroups(); #endif #if SYZ_HAVE_SETUP_EXT - // This can be defined in common_ext.h. - setup_ext(); + // This can be defined in common_ext.h. + setup_ext(); #endif - for (int i = 0; i < n; i++) { - bool found = false; -#if SYZ_HAVE_FEATURES - for (unsigned f = 0; f < sizeof(features) / sizeof(features[0]); f++) { - if (strcmp(enable[i], features[f].name) == 0) { - features[f].setup(); - found = true; - break; - } + return; + } + for (size_t i = 0; i < sizeof(features) / sizeof(features[0]); i++) { + if (features[i].id == feature) { + features[i].setup(); + return; } -#endif - if (!found) - failmsg("setup features: unknown feature", "feature=%s", enable[i]); } + // Note: pkg/host knows about this error message. + fail("feature setup is not needed"); } void failmsg(const char* err, const char* msg, ...) @@ -1728,7 +1736,7 @@ void exitf(const char* msg, ...) vfprintf(stderr, msg, args); va_end(args); fprintf(stderr, " (errno %d)\n", e); - doexit(0); + doexit(1); } void debug(const char* msg, ...) diff --git a/executor/executor_bsd.h b/executor/executor_bsd.h index d88922417..6c2174360 100644 --- a/executor/executor_bsd.h +++ b/executor/executor_bsd.h @@ -182,8 +182,8 @@ static bool use_cover_edges(uint64 pc) #if GOOS_netbsd #define SYZ_HAVE_FEATURES 1 static feature_t features[] = { - {"usb", setup_usb}, - {"fault", setup_fault}, + {rpc::Feature::USBEmulation, setup_usb}, + {rpc::Feature::Fault, setup_fault}, }; static void setup_sysctl(void) diff --git a/executor/executor_linux.h b/executor/executor_linux.h index 578c65673..b8c98d3f7 100644 --- a/executor/executor_linux.h +++ b/executor/executor_linux.h @@ -256,13 +256,51 @@ NORETURN void doexit_thread(int status) } } +static void setup_nicvf() +{ + // This feature has custom checking precedure rather than just rely on running + // a simple program with this feature enabled b/c find_vf_interface cannot be made + // failing. It searches for the nic in init namespace, but then the nic is moved + // to one of testing namespace, so if number of procs is more than the number of devices, + // then some of them won't fine a nic (the code is also racy, more than one proc + // can find the same device and then moving it will fail for all but one). + // So we have to make find_vf_interface non-failing in case of failures, + // which means we cannot use it for feature checking. + if (open("/sys/bus/pci/devices/0000:00:11.0/", O_RDONLY | O_NONBLOCK) == -1) + fail("PCI device 0000:00:11.0 is not available"); +} + +static void setup_devlink_pci() +{ + // See comment in setup_nicvf. + if (open("/sys/bus/pci/devices/0000:00:10.0/", O_RDONLY | O_NONBLOCK) == -1) + fail("PCI device 0000:00:10.0 is not available"); +} + +static void setup_delay_kcov() +{ + is_kernel_64_bit = detect_kernel_bitness(); + cover_t cov = {}; + cov.fd = kCoverFd; + cover_open(&cov, false); + cover_mmap(&cov); + cov.data = nullptr; + cover_mmap(&cov); + // If delayed kcov mmap is not supported by the kernel, + // accesses to the second mapping will crash. + const_cast<volatile char*>(cov.data)[0] = 1; +} + #define SYZ_HAVE_FEATURES 1 static feature_t features[] = { - {"leak", setup_leak}, - {"fault", setup_fault}, - {"binfmt_misc", setup_binfmt_misc}, - {"kcsan", setup_kcsan}, - {"usb", setup_usb}, - {"802154", setup_802154}, - {"swap", setup_swap}, + {rpc::Feature::DelayKcovMmap, setup_delay_kcov}, + {rpc::Feature::Fault, setup_fault}, + {rpc::Feature::Leak, setup_leak}, + {rpc::Feature::KCSAN, setup_kcsan}, + {rpc::Feature::USBEmulation, setup_usb}, + {rpc::Feature::LRWPANEmulation, setup_802154}, + {rpc::Feature::BinFmtMisc, setup_binfmt_misc}, + {rpc::Feature::Swap, setup_swap}, + {rpc::Feature::NicVF, setup_nicvf}, + {rpc::Feature::DevlinkPCI, setup_devlink_pci}, }; 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) { diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index bdccf9d82..6870583df 100644 --- a/syz-fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go @@ -16,7 +16,7 @@ import ( "sync/atomic" "time" - "github.com/google/syzkaller/pkg/csource" + "github.com/google/syzkaller/pkg/flatrpc" "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/ipc/ipcconfig" @@ -38,6 +38,7 @@ type FuzzerTool struct { // TODO: repair triagedCandidates logic, it's broken now. triagedCandidates uint32 timeouts targets.Timeouts + leakFrames []string noExecRequests atomic.Uint64 noExecDuration atomic.Uint64 @@ -93,7 +94,6 @@ func main() { log.SyzFatalf("failed to create default ipc config: %v", err) } timeouts := config.Timeouts - sandbox := ipc.FlagsToSandbox(execOpts.EnvFlags) executor := config.Executor shutdown := make(chan struct{}) osutil.HandleInterrupts(shutdown) @@ -108,15 +108,15 @@ func main() { setupPprofHandler(*flagPprofPort) } - checkArgs := &checkArgs{ - target: target, - sandbox: sandbox, - ipcConfig: config, - ipcExecOpts: execOpts, - gitRevision: prog.GitRevision, - targetRevision: target.Revision, - } if *flagTest { + checkArgs := &checkArgs{ + target: target, + sandbox: ipc.FlagsToSandbox(execOpts.EnvFlags), + ipcConfig: config, + ipcExecOpts: execOpts, + gitRevision: prog.GitRevision, + targetRevision: target.Revision, + } testImage(*flagManager, checkArgs) return } @@ -141,53 +141,16 @@ func main() { if err := manager.Call("Manager.Connect", a, r); err != nil { log.SyzFatalf("failed to call Manager.Connect(): %v ", err) } - featureFlags, err := csource.ParseFeaturesFlags("none", "none", true) - if err != nil { - log.SyzFatal(err) - } - var checkReq *rpctype.CheckArgs - if r.Features == nil { - checkArgs.featureFlags = featureFlags - checkReq, err = checkMachine(checkArgs) - if err != nil { - if checkReq == nil { - checkReq = new(rpctype.CheckArgs) - } - checkReq.Error = err.Error() - } - r.Features = checkReq.Features - } else { - if err = host.Setup(target, r.Features, featureFlags, executor); err != nil { - log.SyzFatal(err) - } - checkReq = new(rpctype.CheckArgs) - } - - for _, feat := range r.Features.Supported() { - log.Logf(0, "%v: %v", feat.Name, feat.Reason) + checkReq := &rpctype.CheckArgs{ + Name: *flagName, + Files: host.ReadFiles(r.ReadFiles), + Globs: make(map[string][]string), } - - inputsCount := *flagProcs * 2 - fuzzerTool := &FuzzerTool{ - name: *flagName, - executor: executor, - manager: manager, - timeouts: timeouts, - - requests: make(chan rpctype.ExecutionRequest, 2*inputsCount), - results: make(chan executionResult, 2*inputsCount), - } - gateCb := fuzzerTool.useBugFrames(r.Features, r.MemoryLeakFrames, r.DataRaceFrames) - fuzzerTool.gate = ipc.NewGate(gateSize, gateCb) - - log.Logf(0, "starting %v executor processes", *flagProcs) - for pid := 0; pid < *flagProcs; pid++ { - startProc(fuzzerTool, pid, config) + features, err := host.SetupFeatures(target, executor, r.Features, nil) + if err != nil { + log.SyzFatalf("failed to setup features: %v ", err) } - - checkReq.Name = *flagName - checkReq.Files = host.ReadFiles(r.ReadFiles) - checkReq.Globs = make(map[string][]string) + checkReq.Features = features for _, glob := range r.ReadGlobs { files, err := filepath.Glob(filepath.FromSlash(glob)) if err != nil && checkReq.Error == "" { @@ -208,28 +171,39 @@ func main() { log.SyzFatalf("failed to write syz-cover-bitmap: %v", err) } } - // Query enough inputs at the beginning. - fuzzerTool.exchangeDataCall(nil, 0) - go fuzzerTool.exchangeDataWorker() - fuzzerTool.exchangeDataWorker() -} -// Returns gateCallback for leak checking if enabled. -func (tool *FuzzerTool) useBugFrames(featues *host.Features, leakFrames, raceFrames []string) func() { - var gateCallback func() + inputsCount := *flagProcs * 2 + fuzzerTool := &FuzzerTool{ + name: *flagName, + executor: executor, + manager: manager, + timeouts: timeouts, + leakFrames: r.MemoryLeakFrames, - if featues[host.FeatureLeak].Enabled { - gateCallback = func() { tool.gateCallback(leakFrames) } + requests: make(chan rpctype.ExecutionRequest, 2*inputsCount), + results: make(chan executionResult, 2*inputsCount), } + fuzzerTool.filterDataRaceFrames(r.DataRaceFrames) + var gateCallback func() + for _, feat := range features { + if feat.Id == flatrpc.FeatureLeak && feat.Reason == "" { + gateCallback = fuzzerTool.leakGateCallback + } + } + fuzzerTool.gate = ipc.NewGate(gateSize, gateCallback) - if featues[host.FeatureKCSAN].Enabled && len(raceFrames) != 0 { - tool.filterDataRaceFrames(raceFrames) + log.Logf(0, "starting %v executor processes", *flagProcs) + for pid := 0; pid < *flagProcs; pid++ { + startProc(fuzzerTool, pid, config) } - return gateCallback + // Query enough inputs at the beginning. + fuzzerTool.exchangeDataCall(nil, 0) + go fuzzerTool.exchangeDataWorker() + fuzzerTool.exchangeDataWorker() } -func (tool *FuzzerTool) gateCallback(leakFrames []string) { +func (tool *FuzzerTool) leakGateCallback() { // Leak checking is very slow so we don't do it while triaging the corpus // (otherwise it takes infinity). When we have presumably triaged the corpus // (triagedCandidates == 1), we run leak checking bug ignore the result @@ -239,7 +213,7 @@ func (tool *FuzzerTool) gateCallback(leakFrames []string) { if triagedCandidates == 0 { return } - args := append([]string{"leak"}, leakFrames...) + args := append([]string{"leak"}, tool.leakFrames...) timeout := tool.timeouts.NoOutput * 9 / 10 output, err := osutil.RunCmd(timeout, "", tool.executor, args...) if err != nil && triagedCandidates == 2 { @@ -255,6 +229,9 @@ func (tool *FuzzerTool) gateCallback(leakFrames []string) { } func (tool *FuzzerTool) filterDataRaceFrames(frames []string) { + if len(frames) == 0 { + return + } args := append([]string{"setup_kcsan_filterlist"}, frames...) timeout := time.Minute * tool.timeouts.Scale output, err := osutil.RunCmd(timeout, "", tool.executor, args...) diff --git a/syz-fuzzer/proc.go b/syz-fuzzer/proc.go index c80de0518..6978b2591 100644 --- a/syz-fuzzer/proc.go +++ b/syz-fuzzer/proc.go @@ -121,7 +121,10 @@ func (proc *Proc) executeProgram(req rpctype.ExecutionRequest) (*ipc.ProgInfo, [ proc.tool.startExecutingCall(req.ID, proc.pid, try) output, info, hanged, err = proc.env.ExecProg(&req.ExecOpts, req.ProgData) proc.tool.gate.Leave(ticket) - log.Logf(2, "result hanged=%v err=%v: %s", hanged, err, output) + // Don't print output if returning error b/c it may contain SYZFAIL. + if !req.ReturnError { + log.Logf(2, "result hanged=%v err=%v: %s", hanged, err, output) + } if hanged && err == nil && req.ReturnError { err = errors.New("hanged") } diff --git a/syz-fuzzer/testing.go b/syz-fuzzer/testing.go index ce21aaa11..5f5b6dc98 100644 --- a/syz-fuzzer/testing.go +++ b/syz-fuzzer/testing.go @@ -10,12 +10,9 @@ import ( "strings" "time" - "github.com/google/syzkaller/pkg/csource" - "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/osutil" - "github.com/google/syzkaller/pkg/rpctype" "github.com/google/syzkaller/prog" ) @@ -26,7 +23,6 @@ type checkArgs struct { targetRevision string ipcConfig *ipc.Config ipcExecOpts *ipc.ExecOpts - featureFlags map[string]csource.Feature } func testImage(hostAddr string, args *checkArgs) { @@ -43,62 +39,9 @@ func testImage(hostAddr string, args *checkArgs) { if err := checkRevisions(args); err != nil { log.SyzFatal(err) } - if _, err := checkMachine(args); err != nil { + if err := checkSimpleProgram(args); err != nil { log.SyzFatal(err) } - if err := buildCallList(args.target, args.sandbox); err != nil { - log.SyzFatal(err) - } -} - -func checkMachineHeartbeats(done chan bool) { - ticker := time.NewTicker(3 * time.Second) - defer ticker.Stop() - for { - select { - case <-done: - return - case <-ticker.C: - fmt.Printf("executing program\n") - } - } -} - -func checkMachine(args *checkArgs) (*rpctype.CheckArgs, error) { - log.Logf(0, "checking machine...") - // Machine checking can be very slow on some machines (qemu without kvm, KMEMLEAK linux, etc), - // so print periodic heartbeats for vm.MonitorExecution so that it does not decide that we are dead. - done := make(chan bool) - defer close(done) - go checkMachineHeartbeats(done) - features, err := host.Check(args.target) - if err != nil { - return nil, err - } - if feat := features[host.FeatureCoverage]; !feat.Enabled && - args.ipcExecOpts.EnvFlags&ipc.FlagSignal != 0 { - return nil, fmt.Errorf("coverage is not supported (%v)", feat.Reason) - } - if feat := features[host.FeatureSandboxSetuid]; !feat.Enabled && - args.ipcExecOpts.EnvFlags&ipc.FlagSandboxSetuid != 0 { - return nil, fmt.Errorf("sandbox=setuid is not supported (%v)", feat.Reason) - } - if feat := features[host.FeatureSandboxNamespace]; !feat.Enabled && - args.ipcExecOpts.EnvFlags&ipc.FlagSandboxNamespace != 0 { - return nil, fmt.Errorf("sandbox=namespace is not supported (%v)", feat.Reason) - } - if feat := features[host.FeatureSandboxAndroid]; !feat.Enabled && - args.ipcExecOpts.EnvFlags&ipc.FlagSandboxAndroid != 0 { - return nil, fmt.Errorf("sandbox=android is not supported (%v)", feat.Reason) - } - args.ipcExecOpts.EnvFlags |= ipc.FeaturesToFlags(features.ToFlatRPC(), nil) - if err := checkSimpleProgram(args, features); err != nil { - return nil, err - } - res := &rpctype.CheckArgs{ - Features: features, - } - return res, nil } func checkRevisions(args *checkArgs) error { @@ -149,11 +92,8 @@ func executorVersion(bin string) (string, string, string, error) { return vers[1], vers[2], vers[3], nil } -func checkSimpleProgram(args *checkArgs, features *host.Features) error { +func checkSimpleProgram(args *checkArgs) error { log.Logf(0, "testing simple program...") - if err := host.Setup(args.target, features, args.featureFlags, args.ipcConfig.Executor); err != nil { - return fmt.Errorf("host setup failed: %w", err) - } env, err := ipc.MakeEnv(args.ipcConfig, 0) if err != nil { return fmt.Errorf("failed to create ipc env: %w", err) @@ -178,35 +118,3 @@ func checkSimpleProgram(args *checkArgs, features *host.Features) error { } return nil } - -func buildCallList(target *prog.Target, sandbox string) error { - log.Logf(0, "building call list...") - calls := make(map[*prog.Syscall]bool) - for _, c := range target.Syscalls { - calls[c] = true - } - - _, unsupported, err := host.DetectSupportedSyscalls(target, sandbox, calls) - if err != nil { - return fmt.Errorf("failed to detect host supported syscalls: %w", err) - } - for c := range calls { - if reason, ok := unsupported[c]; ok { - // Note: if we print call name followed by ':', it may be detected - // as a kernel crash if the call ends with "BUG" or "INFO". - log.Logf(1, "unsupported syscall: %v(): %v", c.Name, reason) - delete(calls, c) - } - } - _, unsupported = target.TransitivelyEnabledCalls(calls) - for c := range calls { - if reason, ok := unsupported[c]; ok { - log.Logf(1, "transitively unsupported: %v(): %v", c.Name, reason) - delete(calls, c) - } - } - if len(calls) == 0 { - return fmt.Errorf("all system calls are disabled") - } - return nil -} diff --git a/syz-manager/rpc.go b/syz-manager/rpc.go index ef4b81361..5a9074b15 100644 --- a/syz-manager/rpc.go +++ b/syz-manager/rpc.go @@ -16,7 +16,6 @@ import ( "github.com/google/syzkaller/pkg/cover" "github.com/google/syzkaller/pkg/flatrpc" "github.com/google/syzkaller/pkg/fuzzer" - "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/mgrconfig" @@ -35,15 +34,17 @@ type RPCServer struct { checker *vminfo.Checker port int + infoDone bool checkDone atomic.Bool checkFiles []string checkFilesInfo []flatrpc.FileInfo + checkFeatureInfo []flatrpc.FeatureInfo checkProgs []rpctype.ExecutionRequest checkResults []rpctype.ExecutionResult needCheckResults int checkFailures int - checkFeatures *host.Features enabledFeatures flatrpc.Feature + setupFeatures flatrpc.Feature modules []cover.KernelModule canonicalModules *cover.Canonicalizer execCoverFilter map[uint32]uint32 @@ -154,11 +155,13 @@ func (serv *RPCServer) Connect(a *rpctype.ConnectArgs, r *rpctype.ConnectRes) er serv.mu.Lock() defer serv.mu.Unlock() - r.Features = serv.checkFeatures r.ReadFiles = serv.checker.RequiredFiles() - if serv.checkFeatures == nil { + if serv.checkDone.Load() { + r.Features = serv.setupFeatures + } else { r.ReadFiles = append(r.ReadFiles, serv.checkFiles...) r.ReadGlobs = serv.target.RequiredGlobs() + r.Features = flatrpc.AllFeatures } return nil } @@ -206,9 +209,9 @@ func (serv *RPCServer) Check(a *rpctype.CheckArgs, r *rpctype.CheckRes) error { return fmt.Errorf("machine check failed: %v", a.Error) } - if serv.checkFeatures == nil { - serv.checkFeatures = a.Features - serv.enabledFeatures = a.Features.ToFlatRPC() + if !serv.infoDone { + serv.infoDone = true + serv.checkFeatureInfo = a.Features serv.checkFilesInfo = a.Files serv.modules = modules serv.target.UpdateGlobs(a.Globs) @@ -241,10 +244,8 @@ func (serv *RPCServer) Check(a *rpctype.CheckArgs, r *rpctype.CheckRes) error { func (serv *RPCServer) finishCheck() error { // Note: need to print disbled syscalls before failing due to an error. // This helps to debug "all system calls are disabled". - enabledCalls, disabledCalls, err := serv.checker.FinishCheck(serv.checkFilesInfo, serv.checkResults) - if err != nil { - return fmt.Errorf("failed to detect enabled syscalls: %w", err) - } + enabledCalls, disabledCalls, features, checkErr := serv.checker.FinishCheck( + serv.checkFilesInfo, serv.checkResults, serv.checkFeatureInfo) enabledCalls, transitivelyDisabled := serv.target.TransitivelyEnabledCalls(enabledCalls) buf := new(bytes.Buffer) if len(serv.cfg.EnabledSyscalls) != 0 || log.V(1) { @@ -281,15 +282,25 @@ func (serv *RPCServer) finishCheck() error { if hasFileErrors { fmt.Fprintf(buf, "\n") } - fmt.Fprintf(buf, "%-24v: %v/%v\n", "syscalls", len(enabledCalls), len(serv.cfg.Target.Syscalls)) - for _, feat := range serv.checkFeatures.Supported() { - fmt.Fprintf(buf, "%-24v: %v\n", feat.Name, feat.Reason) + var lines []string + lines = append(lines, fmt.Sprintf("%-24v: %v/%v\n", "syscalls", + len(enabledCalls), len(serv.cfg.Target.Syscalls))) + for feat, info := range features { + lines = append(lines, fmt.Sprintf("%-24v: %v\n", + flatrpc.EnumNamesFeature[feat], info.Reason)) } + sort.Strings(lines) + buf.WriteString(strings.Join(lines, "")) fmt.Fprintf(buf, "\n") log.Logf(0, "machine check:\n%s", buf.Bytes()) + if checkErr != nil { + return checkErr + } if len(enabledCalls) == 0 { - log.Fatalf("all system calls are disabled") + return fmt.Errorf("all system calls are disabled") } + serv.enabledFeatures = features.Enabled() + serv.setupFeatures = features.NeedSetup() serv.mgr.machineChecked(serv.enabledFeatures, enabledCalls) return nil } @@ -344,6 +355,7 @@ func (serv *RPCServer) ExchangeInfo(a *rpctype.ExchangeInfoRequest, r *rpctype.E serv.checkResults = nil serv.checkFiles = nil serv.checkFilesInfo = nil + serv.checkFeatureInfo = nil serv.checkDone.Store(true) } } diff --git a/syz-runner/runner.go b/syz-runner/runner.go index 329b340a3..8c9f3a5c3 100644 --- a/syz-runner/runner.go +++ b/syz-runner/runner.go @@ -4,11 +4,9 @@ package main import ( "flag" - "fmt" "log" "runtime" - "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/ipc/ipcconfig" "github.com/google/syzkaller/pkg/rpctype" @@ -74,19 +72,7 @@ func main() { enabled[c] = true } if r.CheckUnsupportedCalls { - sandbox := ipc.FlagsToSandbox(opts.EnvFlags) - _, unsupported, err := host.DetectSupportedSyscalls(target, sandbox, enabled) - if err != nil { - log.Fatalf("failed to get unsupported system calls: %v", err) - } - - calls := make([]rpctype.SyscallReason, 0) - for c, reason := range unsupported { - calls = append(calls, rpctype.SyscallReason{ - ID: c.ID, - Reason: fmt.Sprintf("%s (not supported on kernel %d)", reason, rn.pool)}) - } - a := &rpctype.UpdateUnsupportedArgs{Pool: rn.pool, UnsupportedCalls: calls} + a := &rpctype.UpdateUnsupportedArgs{Pool: rn.pool, UnsupportedCalls: nil} if err := vrf.Call("Verifier.UpdateUnsupported", a, nil); err != nil { log.Fatalf("failed to send unsupported system calls: %v", err) } diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go index 7f328e31a..518e7511f 100644 --- a/tools/syz-execprog/execprog.go +++ b/tools/syz-execprog/execprog.go @@ -21,13 +21,16 @@ import ( "github.com/google/syzkaller/pkg/cover/backend" "github.com/google/syzkaller/pkg/csource" "github.com/google/syzkaller/pkg/db" + "github.com/google/syzkaller/pkg/flatrpc" "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/ipc" "github.com/google/syzkaller/pkg/ipc/ipcconfig" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/mgrconfig" "github.com/google/syzkaller/pkg/osutil" + "github.com/google/syzkaller/pkg/rpctype" "github.com/google/syzkaller/pkg/tool" + "github.com/google/syzkaller/pkg/vminfo" "github.com/google/syzkaller/prog" _ "github.com/google/syzkaller/sys" "github.com/google/syzkaller/sys/targets" @@ -91,24 +94,23 @@ func main() { flag.Usage() os.Exit(1) } - features, err := host.Check(target) - if err != nil { - log.Fatalf("%v", err) - } - if *flagOutput { - for _, feat := range features.Supported() { - log.Logf(0, "%-24v: %v", feat.Name, feat.Reason) - } - } if *flagCollide { log.Logf(0, "note: setting -collide to true is deprecated now and has no effect") } - config, execOpts := createConfig(target, features, featuresFlags) - if err = host.Setup(target, features, featuresFlags, config.Executor); err != nil { - log.Fatal(err) + var requestedSyscalls []int + if *flagStress { + syscallList := strings.Split(*flagSyscalls, ",") + if *flagSyscalls == "" { + syscallList = nil + } + requestedSyscalls, err = mgrconfig.ParseEnabledSyscalls(target, syscallList, nil) + if err != nil { + log.Fatalf("failed to parse enabled syscalls: %v", err) + } } + config, execOpts, syscalls, features := createConfig(target, featuresFlags, requestedSyscalls) var gateCallback func() - if features[host.FeatureLeak].Enabled { + if features&flatrpc.FeatureLeak != 0 { gateCallback = func() { output, err := osutil.RunCmd(10*time.Minute, "", config.Executor, "leak") if err != nil { @@ -119,12 +121,7 @@ func main() { } var choiceTable *prog.ChoiceTable if *flagStress { - var syscalls []string - if *flagSyscalls != "" { - syscalls = strings.Split(*flagSyscalls, ",") - } - calls := buildCallList(target, syscalls) - choiceTable = target.BuildChoiceTable(progs, calls) + choiceTable = target.BuildChoiceTable(progs, syscalls) } sysTarget := targets.Get(*flagOS, *flagArch) upperBase := getKernelUpperBase(sysTarget) @@ -398,8 +395,8 @@ func loadPrograms(target *prog.Target, files []string) []*prog.Prog { return progs } -func createConfig(target *prog.Target, features *host.Features, featuresFlags csource.Features) ( - *ipc.Config, *ipc.ExecOpts) { +func createConfig(target *prog.Target, featuresFlags csource.Features, syscalls []int) ( + *ipc.Config, *ipc.ExecOpts, map[*prog.Syscall]bool, flatrpc.Feature) { config, execOpts, err := ipcconfig.Default(target) if err != nil { log.Fatalf("%v", err) @@ -418,29 +415,64 @@ func createConfig(target *prog.Target, features *host.Features, featuresFlags cs } execOpts.ExecFlags |= ipc.FlagCollectComps } - execOpts.EnvFlags |= ipc.FeaturesToFlags(features.ToFlatRPC(), featuresFlags) - return config, execOpts -} - -func buildCallList(target *prog.Target, enabled []string) map[*prog.Syscall]bool { - syscallsIDs, err := mgrconfig.ParseEnabledSyscalls(target, enabled, nil) + cfg := &mgrconfig.Config{ + Sandbox: ipc.FlagsToSandbox(execOpts.EnvFlags), + SandboxArg: execOpts.SandboxArg, + Derived: mgrconfig.Derived{ + TargetOS: target.OS, + TargetArch: target.Arch, + TargetVMArch: target.Arch, + Target: target, + SysTarget: targets.Get(target.OS, target.Arch), + Syscalls: syscalls, + }, + } + checker := vminfo.New(cfg) + files, requests := checker.StartCheck() + fileInfos := host.ReadFiles(files) + featureInfos, err := host.SetupFeatures(target, config.Executor, flatrpc.AllFeatures, featuresFlags) if err != nil { - log.Fatalf("failed to parse enabled syscalls: %v", err) - } - enabledSyscalls := make(map[*prog.Syscall]bool) - for _, id := range syscallsIDs { - enabledSyscalls[target.Syscalls[id]] = true + log.Fatal(err) } - calls, disabled, err := host.DetectSupportedSyscalls(target, "none", enabledSyscalls) + env, err := ipc.MakeEnv(config, 0) if err != nil { - log.Fatalf("failed to detect host supported syscalls: %v", err) + log.Fatalf("failed to create ipc env: %v", err) } - for c, reason := range disabled { - log.Logf(0, "unsupported syscall: %v: %v", c.Name, reason) + defer env.Close() + var results []rpctype.ExecutionResult + for _, req := range requests { + output, info, hanged, err := env.ExecProg(&req.ExecOpts, req.ProgData) + res := rpctype.ExecutionResult{ + ID: req.ID, + Output: output, + } + if info != nil { + res.Info = *info + } + if err != nil { + res.Error = err.Error() + } + if hanged && err == nil { + res.Error = "hanged" + } + results = append(results, res) } - calls, disabled = target.TransitivelyEnabledCalls(calls) - for c, reason := range disabled { - log.Logf(0, "transitively unsupported: %v: %v", c.Name, reason) + enabledSyscalls, disabledSyscalls, features, err := checker.FinishCheck(fileInfos, results, featureInfos) + if err != nil { + log.Fatal(err) + } + if *flagOutput { + for feat, info := range features { + log.Logf(0, "%-24v: %v", flatrpc.EnumNamesFeature[feat], info.Reason) + } + for c, reason := range disabledSyscalls { + log.Logf(0, "unsupported syscall: %v: %v", c.Name, reason) + } + enabledSyscalls, disabledSyscalls = target.TransitivelyEnabledCalls(enabledSyscalls) + for c, reason := range disabledSyscalls { + log.Logf(0, "transitively unsupported: %v: %v", c.Name, reason) + } } - return calls + execOpts.EnvFlags |= ipc.FeaturesToFlags(features.Enabled(), featuresFlags) + return config, execOpts, enabledSyscalls, features.Enabled() } diff --git a/tools/syz-runtest/runtest.go b/tools/syz-runtest/runtest.go index 1203abbb5..6f71519c7 100644 --- a/tools/syz-runtest/runtest.go +++ b/tools/syz-runtest/runtest.go @@ -19,7 +19,6 @@ import ( "time" "github.com/google/syzkaller/pkg/flatrpc" - "github.com/google/syzkaller/pkg/host" "github.com/google/syzkaller/pkg/instance" "github.com/google/syzkaller/pkg/mgrconfig" "github.com/google/syzkaller/pkg/osutil" @@ -54,18 +53,17 @@ func main() { } osutil.MkdirAll(cfg.Workdir) mgr := &Manager{ - cfg: cfg, - vmPool: vmPool, - checker: vminfo.New(cfg), - reporter: reporter, - debug: *flagDebug, - requests: make(chan *runtest.RunRequest, 4*vmPool.Count()*cfg.Procs), - checkResultC: make(chan *rpctype.CheckArgs, 1), - checkProgsDone: make(chan bool), - checkFeaturesReady: make(chan bool), - vmStop: make(chan bool), - reqMap: make(map[int64]*runtest.RunRequest), - pending: make(map[string]map[int64]bool), + cfg: cfg, + vmPool: vmPool, + checker: vminfo.New(cfg), + reporter: reporter, + debug: *flagDebug, + requests: make(chan *runtest.RunRequest, 4*vmPool.Count()*cfg.Procs), + checkResultC: make(chan *rpctype.CheckArgs, 1), + checkProgsDone: make(chan bool), + vmStop: make(chan bool), + reqMap: make(map[int64]*runtest.RunRequest), + pending: make(map[string]map[int64]bool), } mgr.checkFiles, mgr.checkProgs = mgr.checker.StartCheck() mgr.needCheckResults = len(mgr.checkProgs) @@ -99,11 +97,8 @@ func main() { }() } checkResult := <-mgr.checkResultC - mgr.checkFeatures = checkResult.Features - mgr.checkFilesInfo = checkResult.Files - close(mgr.checkFeaturesReady) <-mgr.checkProgsDone - calls, _, err := mgr.checker.FinishCheck(mgr.checkFilesInfo, mgr.checkResults) + calls, _, features, err := mgr.checker.FinishCheck(checkResult.Files, mgr.checkResults, checkResult.Features) if err != nil { log.Fatalf("failed to detect enabled syscalls: %v", err) } @@ -113,8 +108,8 @@ func main() { // Note: syz_emit_ethernet/syz_extract_tcp_res were manually disabled for "" ("no") sandbox, // b/c tun is not setup without sandbox. enabledCalls[mgr.cfg.Sandbox] = calls - for _, feat := range checkResult.Features.Supported() { - fmt.Printf("%-24v: %v\n", feat.Name, feat.Reason) + for feat, info := range features { + fmt.Printf("%-24v: %v\n", flatrpc.EnumNamesFeature[feat], info.Reason) } for sandbox, calls := range enabledCalls { if sandbox == "" { @@ -125,7 +120,7 @@ func main() { ctx := &runtest.Context{ Dir: filepath.Join(cfg.Syzkaller, "sys", cfg.Target.OS, "test"), Target: cfg.Target, - Features: checkResult.Features.ToFlatRPC(), + Features: features.Enabled(), EnabledCalls: enabledCalls, Requests: mgr.requests, LogFunc: func(text string) { fmt.Println(text) }, @@ -143,23 +138,20 @@ func main() { } type Manager struct { - cfg *mgrconfig.Config - vmPool *vm.Pool - checker *vminfo.Checker - checkFiles []string - checkFilesInfo []flatrpc.FileInfo - checkProgs []rpctype.ExecutionRequest - checkResults []rpctype.ExecutionResult - needCheckResults int - checkProgsDone chan bool - reporter *report.Reporter - requests chan *runtest.RunRequest - checkFeatures *host.Features - checkFeaturesReady chan bool - checkResultC chan *rpctype.CheckArgs - vmStop chan bool - port int - debug bool + cfg *mgrconfig.Config + vmPool *vm.Pool + checker *vminfo.Checker + checkFiles []string + checkProgs []rpctype.ExecutionRequest + checkResults []rpctype.ExecutionResult + needCheckResults int + checkProgsDone chan bool + reporter *report.Reporter + requests chan *runtest.RunRequest + checkResultC chan *rpctype.CheckArgs + vmStop chan bool + port int + debug bool reqMu sync.Mutex reqSeq int64 @@ -240,12 +232,10 @@ func (mgr *Manager) finishRequests(name string, rep *report.Report) error { } func (mgr *Manager) Connect(a *rpctype.ConnectArgs, r *rpctype.ConnectRes) error { - select { - case <-mgr.checkFeaturesReady: - r.Features = mgr.checkFeatures - default: - r.ReadFiles = append(mgr.checker.RequiredFiles(), mgr.checkFiles...) - r.ReadGlobs = mgr.cfg.Target.RequiredGlobs() + r.ReadFiles = append(mgr.checker.RequiredFiles(), mgr.checkFiles...) + r.ReadGlobs = mgr.cfg.Target.RequiredGlobs() + for feat := range flatrpc.EnumNamesFeature { + r.Features |= feat } return nil } |
