github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/vminfo/linux_syscalls.go (about)

     1  // Copyright 2024 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package vminfo
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"os"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  	"syscall"
    14  
    15  	"github.com/google/syzkaller/prog"
    16  	"github.com/google/syzkaller/sys/targets"
    17  )
    18  
    19  func (linux) syscallCheck(ctx *checkContext, call *prog.Syscall) string {
    20  	check := linuxSyscallChecks[call.CallName]
    21  	if check == nil {
    22  		check = func(ctx *checkContext, call *prog.Syscall) string {
    23  			// Execute plain syscall (rather than a variation with $) to make test program
    24  			// deduplication effective. However, if the plain syscall does not exist take
    25  			// the first variant for this syscall, this still allows to dedup all variants.
    26  			// This works b/c in syscall test we only check for ENOSYS result.
    27  			name := call.CallName
    28  			if ctx.target.SyscallMap[name] == nil {
    29  				for _, call1 := range ctx.target.Syscalls {
    30  					if name == call1.CallName {
    31  						name = call1.Name
    32  					}
    33  				}
    34  			}
    35  			return ctx.supportedSyscalls([]string{name})
    36  		}
    37  	}
    38  	if reason := check(ctx, call); reason != "" {
    39  		return reason
    40  	}
    41  	return linuxSupportedLSM(ctx, call)
    42  }
    43  
    44  func linuxSupportedLSM(ctx *checkContext, call *prog.Syscall) string {
    45  	for _, lsm := range []string{"selinux", "apparmor", "smack"} {
    46  		if !strings.Contains(strings.ToLower(call.Name), lsm) {
    47  			continue
    48  		}
    49  		data, err := ctx.readFile("/sys/kernel/security/lsm")
    50  		if err != nil {
    51  			// Securityfs may not be mounted, but it does not mean that no LSMs are enabled.
    52  			if os.IsNotExist(err) {
    53  				break
    54  			}
    55  			return err.Error()
    56  		}
    57  		if !bytes.Contains(data, []byte(lsm)) {
    58  			return fmt.Sprintf("%v is not enabled", lsm)
    59  		}
    60  	}
    61  	return ""
    62  }
    63  
    64  var linuxSyscallChecks = map[string]func(*checkContext, *prog.Syscall) string{
    65  	"openat":                        supportedOpenat,
    66  	"mount":                         linuxSupportedMount,
    67  	"socket":                        linuxSupportedSocket,
    68  	"socketpair":                    linuxSupportedSocket,
    69  	"pkey_alloc":                    linuxPkeysSupported,
    70  	"syz_open_dev":                  linuxSyzOpenDevSupported,
    71  	"syz_open_procfs":               linuxSyzOpenProcfsSupported,
    72  	"syz_open_pts":                  alwaysSupported,
    73  	"syz_execute_func":              alwaysSupported,
    74  	"syz_emit_ethernet":             linuxNetInjectionSupported,
    75  	"syz_extract_tcp_res":           linuxNetInjectionSupported,
    76  	"syz_usb_connect":               linuxCheckUSBEmulation,
    77  	"syz_usb_connect_ath9k":         linuxCheckUSBEmulation,
    78  	"syz_usb_disconnect":            linuxCheckUSBEmulation,
    79  	"syz_usb_control_io":            linuxCheckUSBEmulation,
    80  	"syz_usb_ep_write":              linuxCheckUSBEmulation,
    81  	"syz_usb_ep_read":               linuxCheckUSBEmulation,
    82  	"syz_kvm_setup_cpu":             linuxSyzKvmSupported,
    83  	"syz_kvm_vgic_v3_setup":         linuxSyzSupportedOnArm64,
    84  	"syz_kvm_setup_syzos_vm":        linuxSyzKvmSupported,
    85  	"syz_kvm_add_vcpu":              linuxSyzKvmSupported,
    86  	"syz_kvm_assert_syzos_uexit":    linuxSyzKvmSupported,
    87  	"syz_kvm_assert_syzos_kvm_exit": linuxSyzKvmSupported,
    88  	"syz_kvm_assert_reg":            linuxSyzSupportedOnArm64,
    89  	"syz_emit_vhci":                 linuxVhciInjectionSupported,
    90  	"syz_init_net_socket":           linuxSyzInitNetSocketSupported,
    91  	"syz_genetlink_get_family_id":   linuxSyzGenetlinkGetFamilyIDSupported,
    92  	"syz_mount_image":               linuxSyzMountImageSupported,
    93  	"syz_read_part_table":           linuxSyzReadPartTableSupported,
    94  	"syz_io_uring_setup":            alwaysSupported,
    95  	"syz_io_uring_submit":           alwaysSupported,
    96  	"syz_io_uring_complete":         alwaysSupported,
    97  	"syz_memcpy_off":                alwaysSupported,
    98  	"syz_btf_id_by_name":            linuxBtfVmlinuxSupported,
    99  	"syz_fuse_handle_req":           alwaysSupported,
   100  	"syz_80211_inject_frame":        linuxWifiEmulationSupported,
   101  	"syz_80211_join_ibss":           linuxWifiEmulationSupported,
   102  	"syz_usbip_server_init":         linuxSyzUsbIPSupported,
   103  	"syz_clone":                     alwaysSupported,
   104  	"syz_clone3":                    alwaysSupported,
   105  	"syz_pkey_set":                  linuxPkeysSupported,
   106  	"syz_socket_connect_nvme_tcp":   linuxSyzSocketConnectNvmeTCPSupported,
   107  	"syz_pidfd_open":                alwaysSupported,
   108  	"syz_create_resource":           alwaysSupported,
   109  	"syz_kfuzztest_run":             alwaysSupported,
   110  }
   111  
   112  func linuxSyzOpenDevSupported(ctx *checkContext, call *prog.Syscall) string {
   113  	if _, ok := call.Args[0].Type.(*prog.ConstType); ok || call.Attrs.Automatic {
   114  		// This is for syz_open_dev$char/block.
   115  		// second operand for when we have an automatically generated description
   116  		return ""
   117  	}
   118  	fname, ok := extractStringConst(call.Args[0].Type, call.Attrs.Automatic)
   119  	if !ok {
   120  		panic("first open arg is not a pointer to string const")
   121  	}
   122  	hashCount := strings.Count(fname, "#")
   123  	if hashCount == 0 {
   124  		panic(fmt.Sprintf("%v does not contain # in the file name", call.Name))
   125  	}
   126  	if hashCount > 2 {
   127  		// If this fails, the logic below needs an adjustment.
   128  		panic(fmt.Sprintf("%v contains too many #", call.Name))
   129  	}
   130  	var ids []int
   131  	if _, ok := call.Args[1].Type.(*prog.ProcType); ok {
   132  		ids = []int{0}
   133  	} else {
   134  		for i := 0; i < 5; i++ {
   135  			for j := 0; j < 5; j++ {
   136  				if j == 0 || hashCount > 1 {
   137  					ids = append(ids, i+j*10)
   138  				}
   139  			}
   140  		}
   141  	}
   142  	modes := ctx.allOpenModes()
   143  	var calls []string
   144  	for _, id := range ids {
   145  		for _, mode := range modes {
   146  			call := fmt.Sprintf("%s(&AUTO='%v', 0x%x, 0x%x)", call.Name, fname, id, mode)
   147  			calls = append(calls, call)
   148  		}
   149  	}
   150  	reason := ctx.anyCallSucceeds(calls, fmt.Sprintf("failed to open %v", fname))
   151  	if reason != "" {
   152  		// These entries might not be available at boot time,
   153  		// but will be created by connected USB devices.
   154  		for _, prefix := range []string{"/dev/hidraw", "/dev/usb/hiddev", "/dev/input/"} {
   155  			if strings.HasPrefix(fname, prefix) {
   156  				if ctx.rootCanOpen("/dev/raw-gadget") == "" {
   157  					reason = ""
   158  				}
   159  			}
   160  		}
   161  	}
   162  	return reason
   163  }
   164  
   165  func linuxNetInjectionSupported(ctx *checkContext, call *prog.Syscall) string {
   166  	return ctx.rootCanOpen("/dev/net/tun")
   167  }
   168  
   169  func linuxSyzOpenProcfsSupported(ctx *checkContext, call *prog.Syscall) string {
   170  	return ctx.canOpen("/proc/cmdline")
   171  }
   172  
   173  func linuxCheckUSBEmulation(ctx *checkContext, call *prog.Syscall) string {
   174  	return ctx.rootCanOpen("/dev/raw-gadget")
   175  }
   176  
   177  const unsupportedArch = "unsupported arch"
   178  
   179  func linuxSyzKvmSupported(ctx *checkContext, call *prog.Syscall) string {
   180  	switch call.Name {
   181  	case "syz_kvm_setup_cpu$x86":
   182  		if ctx.target.Arch == targets.AMD64 || ctx.target.Arch == targets.I386 {
   183  			return ""
   184  		}
   185  	case "syz_kvm_setup_syzos_vm$x86", "syz_kvm_add_vcpu$x86", "syz_kvm_assert_syzos_uexit$x86",
   186  		"syz_kvm_assert_syzos_kvm_exit$x86":
   187  		if ctx.target.Arch == targets.AMD64 {
   188  			return ""
   189  		}
   190  	case "syz_kvm_setup_cpu$arm64", "syz_kvm_setup_syzos_vm$arm64", "syz_kvm_add_vcpu$arm64",
   191  		"syz_kvm_assert_syzos_uexit$arm64", "syz_kvm_assert_syzos_kvm_exit$arm64":
   192  		if ctx.target.Arch == targets.ARM64 {
   193  			return ""
   194  		}
   195  	case "syz_kvm_setup_cpu$ppc64":
   196  		if ctx.target.Arch == targets.PPC64LE {
   197  			return ""
   198  		}
   199  	}
   200  	return unsupportedArch
   201  }
   202  
   203  func linuxSyzSupportedOnArm64(ctx *checkContext, call *prog.Syscall) string {
   204  	if ctx.target.Arch == targets.ARM64 {
   205  		return ""
   206  	}
   207  	return unsupportedArch
   208  }
   209  
   210  func linuxSupportedMount(ctx *checkContext, call *prog.Syscall) string {
   211  	return linuxSupportedFilesystem(ctx, call, 2)
   212  }
   213  
   214  func linuxSyzMountImageSupported(ctx *checkContext, call *prog.Syscall) string {
   215  	return linuxSupportedFilesystem(ctx, call, 0)
   216  }
   217  
   218  func linuxSupportedFilesystem(ctx *checkContext, call *prog.Syscall, fsarg int) string {
   219  	if call.Attrs.Automatic {
   220  		return ""
   221  	}
   222  	fstype, ok := extractStringConst(call.Args[fsarg].Type, call.Attrs.Automatic)
   223  	if !ok {
   224  		panic(fmt.Sprintf("%v: filesystem is not string const", call.Name))
   225  	}
   226  	switch fstype {
   227  	case "fuse", "fuseblk":
   228  		if reason := ctx.canOpen("/dev/fuse"); reason != "" {
   229  			return reason
   230  		}
   231  		if reason := ctx.onlySandboxNoneOrNamespace(); reason != "" {
   232  			return reason
   233  		}
   234  	default:
   235  		if reason := ctx.onlySandboxNone(); reason != "" {
   236  			return reason
   237  		}
   238  	}
   239  	filesystems, err := ctx.readFile("/proc/filesystems")
   240  	if err != nil {
   241  		return err.Error()
   242  	}
   243  	if !bytes.Contains(filesystems, []byte("\t"+fstype+"\n")) {
   244  		return fmt.Sprintf("/proc/filesystems does not contain %v", fstype)
   245  	}
   246  	return ""
   247  }
   248  
   249  func linuxSyzReadPartTableSupported(ctx *checkContext, call *prog.Syscall) string {
   250  	return ctx.onlySandboxNone()
   251  }
   252  
   253  func linuxSupportedSocket(ctx *checkContext, call *prog.Syscall) string {
   254  	if call.Name == "socket" || call.Name == "socketpair" || call.Attrs.Automatic {
   255  		return "" // generic versions are always supported
   256  	}
   257  	af := uint64(0)
   258  	if arg, ok := call.Args[0].Type.(*prog.ConstType); ok {
   259  		af = arg.Val
   260  	} else {
   261  		panic(fmt.Sprintf("socket family is not const in %v", call.Name))
   262  	}
   263  	typ, hasType := uint64(0), false
   264  	if arg, ok := call.Args[1].Type.(*prog.ConstType); ok {
   265  		typ, hasType = arg.Val, true
   266  	} else if arg, ok := call.Args[1].Type.(*prog.FlagsType); ok {
   267  		typ, hasType = arg.Vals[0], true
   268  	}
   269  	proto, hasProto := uint64(0), false
   270  	if arg, ok := call.Args[2].Type.(*prog.ConstType); ok {
   271  		proto, hasProto = arg.Val, true
   272  	}
   273  	syscallName := call.Name
   274  	if call.CallName == "socketpair" {
   275  		syscallName = "socket"
   276  	}
   277  	callStr := fmt.Sprintf("%s(0x%x, 0x%x, 0x%x)", syscallName, af, typ, proto)
   278  	errno := ctx.execCall(callStr)
   279  	if errno == syscall.ENOSYS || errno == syscall.EAFNOSUPPORT || hasProto && hasType && errno != 0 {
   280  		return fmt.Sprintf("%v failed: %v", callStr, errno)
   281  	}
   282  	return ""
   283  }
   284  
   285  func linuxSyzGenetlinkGetFamilyIDSupported(ctx *checkContext, call *prog.Syscall) string {
   286  	// TODO: try to obtain actual family ID here. It will disable whole sets of sendmsg syscalls.
   287  	return ctx.callSucceeds(fmt.Sprintf("socket(0x%x, 0x%x, 0x%x)",
   288  		ctx.val("AF_NETLINK"), ctx.val("SOCK_RAW"), ctx.val("NETLINK_GENERIC")))
   289  }
   290  
   291  func linuxPkeysSupported(ctx *checkContext, call *prog.Syscall) string {
   292  	return ctx.callSucceeds("pkey_alloc(0x0, 0x0)")
   293  }
   294  
   295  func linuxSyzSocketConnectNvmeTCPSupported(ctx *checkContext, call *prog.Syscall) string {
   296  	return ctx.onlySandboxNone()
   297  }
   298  
   299  func linuxVhciInjectionSupported(ctx *checkContext, call *prog.Syscall) string {
   300  	return ctx.rootCanOpen("/dev/vhci")
   301  }
   302  
   303  func linuxSyzInitNetSocketSupported(ctx *checkContext, call *prog.Syscall) string {
   304  	if reason := ctx.onlySandboxNone(); reason != "" {
   305  		return reason
   306  	}
   307  	return linuxSupportedSocket(ctx, call)
   308  }
   309  
   310  func linuxBtfVmlinuxSupported(ctx *checkContext, call *prog.Syscall) string {
   311  	if reason := ctx.onlySandboxNone(); reason != "" {
   312  		return reason
   313  	}
   314  	return ctx.canOpen("/sys/kernel/btf/vmlinux")
   315  }
   316  
   317  func linuxSyzUsbIPSupported(ctx *checkContext, call *prog.Syscall) string {
   318  	return ctx.canWrite("/sys/devices/platform/vhci_hcd.0/attach")
   319  }
   320  
   321  func linuxWifiEmulationSupported(ctx *checkContext, call *prog.Syscall) string {
   322  	if reason := ctx.rootCanOpen("/sys/class/mac80211_hwsim/"); reason != "" {
   323  		return reason
   324  	}
   325  	// We use HWSIM_ATTR_PERM_ADDR which was added in 4.17.
   326  	return linuxRequireKernel(ctx, 4, 17)
   327  }
   328  
   329  func linuxRequireKernel(ctx *checkContext, major, minor int) string {
   330  	data, err := ctx.readFile("/proc/version")
   331  	if err != nil {
   332  		return err.Error()
   333  	}
   334  	if ok, bad := matchKernelVersion(string(data), major, minor); bad {
   335  		return fmt.Sprintf("failed to parse kernel version: %s", data)
   336  	} else if !ok {
   337  		return fmt.Sprintf("kernel %v.%v required, have %s", major, minor, data)
   338  	}
   339  	return ""
   340  }
   341  
   342  var kernelVersionRe = regexp.MustCompile(` ([0-9]+)\.([0-9]+)\.`)
   343  
   344  func matchKernelVersion(ver string, x, y int) (bool, bool) {
   345  	match := kernelVersionRe.FindStringSubmatch(ver)
   346  	if match == nil {
   347  		return false, true
   348  	}
   349  	major, err := strconv.Atoi(match[1])
   350  	if err != nil {
   351  		return false, true
   352  	}
   353  	if major <= 0 || major > 999 {
   354  		return false, true
   355  	}
   356  	minor, err := strconv.Atoi(match[2])
   357  	if err != nil {
   358  		return false, true
   359  	}
   360  	if minor <= 0 || minor > 999 {
   361  		return false, true
   362  	}
   363  	return major*1000+minor >= x*1000+y, false
   364  }