// 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 ( "bytes" "fmt" "os" "regexp" "strconv" "strings" "syscall" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/sys/targets" ) func (linux) syscallCheck(ctx *checkContext, call *prog.Syscall) string { check := linuxSyscallChecks[call.CallName] if check == nil { check = func(ctx *checkContext, call *prog.Syscall) string { // Execute plain syscall (rather than a variation with $) to make test program // deduplication effective. However, if the plain syscall does not exist take // the first variant for this syscall, this still allows to dedup all variants. // This works b/c in syscall test we only check for ENOSYS result. name := call.CallName if ctx.target.SyscallMap[name] == nil { for _, call1 := range ctx.target.Syscalls { if name == call1.CallName { name = call1.Name } } } return ctx.supportedSyscalls([]string{name}) } } if reason := check(ctx, call); reason != "" { return reason } return linuxSupportedLSM(ctx, call) } func linuxSupportedLSM(ctx *checkContext, call *prog.Syscall) string { for _, lsm := range []string{"selinux", "apparmor", "smack"} { if !strings.Contains(strings.ToLower(call.Name), lsm) { continue } data, err := ctx.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) { break } return err.Error() } if !bytes.Contains(data, []byte(lsm)) { return fmt.Sprintf("%v is not enabled", lsm) } } return "" } var linuxSyscallChecks = map[string]func(*checkContext, *prog.Syscall) string{ "openat": supportedOpenat, "mount": linuxSupportedMount, "socket": linuxSupportedSocket, "socketpair": linuxSupportedSocket, "pkey_alloc": linuxPkeysSupported, "syz_open_dev": linuxSyzOpenDevSupported, "syz_open_procfs": linuxSyzOpenProcfsSupported, "syz_open_pts": alwaysSupported, "syz_execute_func": alwaysSupported, "syz_emit_ethernet": linuxNetInjectionSupported, "syz_extract_tcp_res": linuxNetInjectionSupported, "syz_usb_connect": linuxCheckUSBEmulation, "syz_usb_connect_ath9k": linuxCheckUSBEmulation, "syz_usb_disconnect": linuxCheckUSBEmulation, "syz_usb_control_io": linuxCheckUSBEmulation, "syz_usb_ep_write": linuxCheckUSBEmulation, "syz_usb_ep_read": linuxCheckUSBEmulation, "syz_kvm_setup_cpu": linuxSyzKvmSupported, "syz_kvm_vgic_v3_setup": linuxSyzSupportedOnArm64, "syz_kvm_setup_syzos_vm": linuxSyzKvmSupported, "syz_kvm_add_vcpu": linuxSyzKvmSupported, "syz_kvm_assert_syzos_uexit": linuxSyzKvmSupported, "syz_kvm_assert_syzos_kvm_exit": linuxSyzKvmSupported, "syz_kvm_assert_reg": linuxSyzSupportedOnArm64, "syz_emit_vhci": linuxVhciInjectionSupported, "syz_init_net_socket": linuxSyzInitNetSocketSupported, "syz_genetlink_get_family_id": linuxSyzGenetlinkGetFamilyIDSupported, "syz_mount_image": linuxSyzMountImageSupported, "syz_read_part_table": linuxSyzReadPartTableSupported, "syz_io_uring_setup": alwaysSupported, "syz_io_uring_submit": alwaysSupported, "syz_io_uring_complete": alwaysSupported, "syz_memcpy_off": alwaysSupported, "syz_btf_id_by_name": linuxBtfVmlinuxSupported, "syz_fuse_handle_req": alwaysSupported, "syz_80211_inject_frame": linuxWifiEmulationSupported, "syz_80211_join_ibss": linuxWifiEmulationSupported, "syz_usbip_server_init": linuxSyzUsbIPSupported, "syz_clone": alwaysSupported, "syz_clone3": alwaysSupported, "syz_pkey_set": linuxPkeysSupported, "syz_socket_connect_nvme_tcp": linuxSyzSocketConnectNvmeTCPSupported, "syz_pidfd_open": alwaysSupported, "syz_create_resource": alwaysSupported, "syz_kfuzztest_run": alwaysSupported, } func linuxSyzOpenDevSupported(ctx *checkContext, call *prog.Syscall) string { if _, ok := call.Args[0].Type.(*prog.ConstType); ok || call.Attrs.Automatic { // This is for syz_open_dev$char/block. // second operand for when we have an automatically generated description return "" } fname, ok := extractStringConst(call.Args[0].Type, call.Attrs.Automatic) if !ok { panic("first open arg is not a pointer to string const") } hashCount := strings.Count(fname, "#") if hashCount == 0 { panic(fmt.Sprintf("%v does not contain # in the file name", call.Name)) } if hashCount > 2 { // If this fails, the logic below needs an adjustment. panic(fmt.Sprintf("%v contains too many #", call.Name)) } var ids []int if _, ok := call.Args[1].Type.(*prog.ProcType); ok { ids = []int{0} } else { for i := 0; i < 5; i++ { for j := 0; j < 5; j++ { if j == 0 || hashCount > 1 { ids = append(ids, i+j*10) } } } } modes := ctx.allOpenModes() var calls []string for _, id := range ids { for _, mode := range modes { call := fmt.Sprintf("%s(&AUTO='%v', 0x%x, 0x%x)", call.Name, fname, id, mode) calls = append(calls, call) } } reason := ctx.anyCallSucceeds(calls, fmt.Sprintf("failed to open %v", fname)) if reason != "" { // These entries might not be available at boot time, // but will be created by connected USB devices. for _, prefix := range []string{"/dev/hidraw", "/dev/usb/hiddev", "/dev/input/"} { if strings.HasPrefix(fname, prefix) { if ctx.rootCanOpen("/dev/raw-gadget") == "" { reason = "" } } } } return reason } func linuxNetInjectionSupported(ctx *checkContext, call *prog.Syscall) string { return ctx.rootCanOpen("/dev/net/tun") } func linuxSyzOpenProcfsSupported(ctx *checkContext, call *prog.Syscall) string { return ctx.canOpen("/proc/cmdline") } func linuxCheckUSBEmulation(ctx *checkContext, call *prog.Syscall) string { return ctx.rootCanOpen("/dev/raw-gadget") } const unsupportedArch = "unsupported arch" func linuxSyzKvmSupported(ctx *checkContext, call *prog.Syscall) string { switch call.Name { case "syz_kvm_setup_cpu$x86": if ctx.target.Arch == targets.AMD64 || ctx.target.Arch == targets.I386 { return "" } case "syz_kvm_setup_syzos_vm$x86", "syz_kvm_add_vcpu$x86", "syz_kvm_assert_syzos_uexit$x86", "syz_kvm_assert_syzos_kvm_exit$x86": if ctx.target.Arch == targets.AMD64 { return "" } case "syz_kvm_setup_cpu$arm64", "syz_kvm_setup_syzos_vm$arm64", "syz_kvm_add_vcpu$arm64", "syz_kvm_assert_syzos_uexit$arm64", "syz_kvm_assert_syzos_kvm_exit$arm64": if ctx.target.Arch == targets.ARM64 { return "" } case "syz_kvm_setup_cpu$riscv64": if ctx.target.Arch == targets.RiscV64 { return "" } case "syz_kvm_setup_cpu$ppc64": if ctx.target.Arch == targets.PPC64LE { return "" } } return unsupportedArch } func linuxSyzSupportedOnArm64(ctx *checkContext, call *prog.Syscall) string { if ctx.target.Arch == targets.ARM64 { return "" } return unsupportedArch } func linuxSupportedMount(ctx *checkContext, call *prog.Syscall) string { return linuxSupportedFilesystem(ctx, call, 2) } func linuxSyzMountImageSupported(ctx *checkContext, call *prog.Syscall) string { return linuxSupportedFilesystem(ctx, call, 0) } func linuxSupportedFilesystem(ctx *checkContext, call *prog.Syscall, fsarg int) string { if call.Attrs.Automatic { return "" } fstype, ok := extractStringConst(call.Args[fsarg].Type, call.Attrs.Automatic) if !ok { panic(fmt.Sprintf("%v: filesystem is not string const", call.Name)) } switch fstype { case "fuse", "fuseblk": if reason := ctx.canOpen("/dev/fuse"); reason != "" { return reason } if reason := ctx.onlySandboxNoneOrNamespace(); reason != "" { return reason } default: if reason := ctx.onlySandboxNone(); reason != "" { return reason } } filesystems, err := ctx.readFile("/proc/filesystems") if err != nil { return err.Error() } if !bytes.Contains(filesystems, []byte("\t"+fstype+"\n")) { return fmt.Sprintf("/proc/filesystems does not contain %v", fstype) } return "" } func linuxSyzReadPartTableSupported(ctx *checkContext, call *prog.Syscall) string { return ctx.onlySandboxNone() } func linuxSupportedSocket(ctx *checkContext, call *prog.Syscall) string { if call.Name == "socket" || call.Name == "socketpair" || call.Attrs.Automatic { return "" // generic versions are always supported } af := uint64(0) if arg, ok := call.Args[0].Type.(*prog.ConstType); ok { af = arg.Val } else { panic(fmt.Sprintf("socket family is not const in %v", call.Name)) } typ, hasType := uint64(0), false if arg, ok := call.Args[1].Type.(*prog.ConstType); ok { typ, hasType = arg.Val, true } else if arg, ok := call.Args[1].Type.(*prog.FlagsType); ok { typ, hasType = arg.Vals[0], true } proto, hasProto := uint64(0), false if arg, ok := call.Args[2].Type.(*prog.ConstType); ok { proto, hasProto = arg.Val, true } syscallName := call.Name if call.CallName == "socketpair" { syscallName = "socket" } callStr := fmt.Sprintf("%s(0x%x, 0x%x, 0x%x)", syscallName, af, typ, proto) errno := ctx.execCall(callStr) if errno == syscall.ENOSYS || errno == syscall.EAFNOSUPPORT || hasProto && hasType && errno != 0 { return fmt.Sprintf("%v failed: %v", callStr, errno) } return "" } func linuxSyzGenetlinkGetFamilyIDSupported(ctx *checkContext, call *prog.Syscall) string { // TODO: try to obtain actual family ID here. It will disable whole sets of sendmsg syscalls. return ctx.callSucceeds(fmt.Sprintf("socket(0x%x, 0x%x, 0x%x)", ctx.val("AF_NETLINK"), ctx.val("SOCK_RAW"), ctx.val("NETLINK_GENERIC"))) } func linuxPkeysSupported(ctx *checkContext, call *prog.Syscall) string { return ctx.callSucceeds("pkey_alloc(0x0, 0x0)") } func linuxSyzSocketConnectNvmeTCPSupported(ctx *checkContext, call *prog.Syscall) string { return ctx.onlySandboxNone() } func linuxVhciInjectionSupported(ctx *checkContext, call *prog.Syscall) string { return ctx.rootCanOpen("/dev/vhci") } func linuxSyzInitNetSocketSupported(ctx *checkContext, call *prog.Syscall) string { if reason := ctx.onlySandboxNone(); reason != "" { return reason } return linuxSupportedSocket(ctx, call) } func linuxBtfVmlinuxSupported(ctx *checkContext, call *prog.Syscall) string { if reason := ctx.onlySandboxNone(); reason != "" { return reason } return ctx.canOpen("/sys/kernel/btf/vmlinux") } func linuxSyzUsbIPSupported(ctx *checkContext, call *prog.Syscall) string { return ctx.canWrite("/sys/devices/platform/vhci_hcd.0/attach") } func linuxWifiEmulationSupported(ctx *checkContext, call *prog.Syscall) string { if reason := ctx.rootCanOpen("/sys/class/mac80211_hwsim/"); reason != "" { return reason } // We use HWSIM_ATTR_PERM_ADDR which was added in 4.17. return linuxRequireKernel(ctx, 4, 17) } func linuxRequireKernel(ctx *checkContext, major, minor int) string { data, err := ctx.readFile("/proc/version") if err != nil { return err.Error() } if ok, bad := matchKernelVersion(string(data), major, minor); bad { return fmt.Sprintf("failed to parse kernel version: %s", data) } else if !ok { return fmt.Sprintf("kernel %v.%v required, have %s", major, minor, data) } return "" } var kernelVersionRe = regexp.MustCompile(` ([0-9]+)\.([0-9]+)\.`) 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 }