diff options
Diffstat (limited to 'pkg/host/syscalls_linux.go')
| -rw-r--r-- | pkg/host/syscalls_linux.go | 568 |
1 files changed, 0 insertions, 568 deletions
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, "" -} |
