github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/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":           linuxSyzKvmSetupCPUSupported,
    83  	"syz_emit_vhci":               linuxVhciInjectionSupported,
    84  	"syz_init_net_socket":         linuxSyzInitNetSocketSupported,
    85  	"syz_genetlink_get_family_id": linuxSyzGenetlinkGetFamilyIDSupported,
    86  	"syz_mount_image":             linuxSyzMountImageSupported,
    87  	"syz_read_part_table":         linuxSyzReadPartTableSupported,
    88  	"syz_io_uring_setup":          alwaysSupported,
    89  	"syz_io_uring_submit":         alwaysSupported,
    90  	"syz_io_uring_complete":       alwaysSupported,
    91  	"syz_memcpy_off":              alwaysSupported,
    92  	"syz_btf_id_by_name":          linuxBtfVmlinuxSupported,
    93  	"syz_fuse_handle_req":         alwaysSupported,
    94  	"syz_80211_inject_frame":      linuxWifiEmulationSupported,
    95  	"syz_80211_join_ibss":         linuxWifiEmulationSupported,
    96  	"syz_usbip_server_init":       linuxSyzUsbIPSupported,
    97  	"syz_clone":                   alwaysSupported,
    98  	"syz_clone3":                  alwaysSupported,
    99  	"syz_pkey_set":                linuxPkeysSupported,
   100  	"syz_socket_connect_nvme_tcp": linuxSyzSocketConnectNvmeTCPSupported,
   101  	"syz_pidfd_open":              alwaysSupported,
   102  }
   103  
   104  func linuxSyzOpenDevSupported(ctx *checkContext, call *prog.Syscall) string {
   105  	if _, ok := call.Args[0].Type.(*prog.ConstType); ok {
   106  		// This is for syz_open_dev$char/block.
   107  		return ""
   108  	}
   109  	fname, ok := extractStringConst(call.Args[0].Type)
   110  	if !ok {
   111  		panic("first open arg is not a pointer to string const")
   112  	}
   113  	hashCount := strings.Count(fname, "#")
   114  	if hashCount == 0 {
   115  		panic(fmt.Sprintf("%v does not contain # in the file name", call.Name))
   116  	}
   117  	if hashCount > 2 {
   118  		// If this fails, the logic below needs an adjustment.
   119  		panic(fmt.Sprintf("%v contains too many #", call.Name))
   120  	}
   121  	var ids []int
   122  	if _, ok := call.Args[1].Type.(*prog.ProcType); ok {
   123  		ids = []int{0}
   124  	} else {
   125  		for i := 0; i < 5; i++ {
   126  			for j := 0; j < 5; j++ {
   127  				if j == 0 || hashCount > 1 {
   128  					ids = append(ids, i+j*10)
   129  				}
   130  			}
   131  		}
   132  	}
   133  	modes := ctx.allOpenModes()
   134  	var calls []string
   135  	for _, id := range ids {
   136  		for _, mode := range modes {
   137  			call := fmt.Sprintf("%s(&AUTO='%v', 0x%x, 0x%x)", call.Name, fname, id, mode)
   138  			calls = append(calls, call)
   139  		}
   140  	}
   141  	reason := ctx.anyCallSucceeds(calls, fmt.Sprintf("failed to open %v", fname))
   142  	if reason != "" {
   143  		// These entries might not be available at boot time,
   144  		// but will be created by connected USB devices.
   145  		for _, prefix := range []string{"/dev/hidraw", "/dev/usb/hiddev", "/dev/input/"} {
   146  			if strings.HasPrefix(fname, prefix) {
   147  				// Note: ideally we use linuxSyzOpenDevSupported here,
   148  				// since we already issued test syscalls, we can't.
   149  				if _, err := ctx.readFile("/dev/raw-gadget"); !os.IsNotExist(err) {
   150  					reason = ""
   151  				}
   152  			}
   153  		}
   154  	}
   155  	return reason
   156  }
   157  
   158  func linuxNetInjectionSupported(ctx *checkContext, call *prog.Syscall) string {
   159  	return ctx.rootCanOpen("/dev/net/tun")
   160  }
   161  
   162  func linuxSyzOpenProcfsSupported(ctx *checkContext, call *prog.Syscall) string {
   163  	return ctx.canOpen("/proc/cmdline")
   164  }
   165  
   166  func linuxCheckUSBEmulation(ctx *checkContext, call *prog.Syscall) string {
   167  	return ctx.rootCanOpen("/dev/raw-gadget")
   168  }
   169  
   170  func linuxSyzKvmSetupCPUSupported(ctx *checkContext, call *prog.Syscall) string {
   171  	switch call.Name {
   172  	case "syz_kvm_setup_cpu$x86":
   173  		if ctx.target.Arch == targets.AMD64 || ctx.target.Arch == targets.I386 {
   174  			return ""
   175  		}
   176  	case "syz_kvm_setup_cpu$arm64":
   177  		if ctx.target.Arch == targets.ARM64 {
   178  			return ""
   179  		}
   180  	case "syz_kvm_setup_cpu$ppc64":
   181  		if ctx.target.Arch == targets.PPC64LE {
   182  			return ""
   183  		}
   184  	}
   185  	return "unsupported arch"
   186  }
   187  
   188  func linuxSupportedMount(ctx *checkContext, call *prog.Syscall) string {
   189  	return linuxSupportedFilesystem(ctx, call, 2)
   190  }
   191  
   192  func linuxSyzMountImageSupported(ctx *checkContext, call *prog.Syscall) string {
   193  	return linuxSupportedFilesystem(ctx, call, 0)
   194  }
   195  
   196  func linuxSupportedFilesystem(ctx *checkContext, call *prog.Syscall, fsarg int) string {
   197  	fstype, ok := extractStringConst(call.Args[fsarg].Type)
   198  	if !ok {
   199  		panic(fmt.Sprintf("%v: filesystem is not string const", call.Name))
   200  	}
   201  	switch fstype {
   202  	case "fuse", "fuseblk":
   203  		if reason := ctx.canOpen("/dev/fuse"); reason != "" {
   204  			return reason
   205  		}
   206  		if reason := ctx.onlySandboxNoneOrNamespace(); reason != "" {
   207  			return reason
   208  		}
   209  	default:
   210  		if reason := ctx.onlySandboxNone(); reason != "" {
   211  			return reason
   212  		}
   213  	}
   214  	filesystems, err := ctx.readFile("/proc/filesystems")
   215  	if err != nil {
   216  		return err.Error()
   217  	}
   218  	if !bytes.Contains(filesystems, []byte("\t"+fstype+"\n")) {
   219  		return fmt.Sprintf("/proc/filesystems does not contain %v", fstype)
   220  	}
   221  	return ""
   222  }
   223  
   224  func linuxSyzReadPartTableSupported(ctx *checkContext, call *prog.Syscall) string {
   225  	return ctx.onlySandboxNone()
   226  }
   227  
   228  func linuxSupportedSocket(ctx *checkContext, call *prog.Syscall) string {
   229  	if call.Name == "socket" || call.Name == "socketpair" {
   230  		return "" // generic versions are always supported
   231  	}
   232  	af := uint64(0)
   233  	if arg, ok := call.Args[0].Type.(*prog.ConstType); ok {
   234  		af = arg.Val
   235  	} else {
   236  		panic(fmt.Sprintf("socket family is not const in %v", call.Name))
   237  	}
   238  	typ, hasType := uint64(0), false
   239  	if arg, ok := call.Args[1].Type.(*prog.ConstType); ok {
   240  		typ, hasType = arg.Val, true
   241  	} else if arg, ok := call.Args[1].Type.(*prog.FlagsType); ok {
   242  		typ, hasType = arg.Vals[0], true
   243  	}
   244  	proto, hasProto := uint64(0), false
   245  	if arg, ok := call.Args[2].Type.(*prog.ConstType); ok {
   246  		proto, hasProto = arg.Val, true
   247  	}
   248  	syscallName := call.Name
   249  	if call.CallName == "socketpair" {
   250  		syscallName = "socket"
   251  	}
   252  	callStr := fmt.Sprintf("%s(0x%x, 0x%x, 0x%x)", syscallName, af, typ, proto)
   253  	errno := ctx.execCall(callStr)
   254  	if errno == syscall.ENOSYS || errno == syscall.EAFNOSUPPORT || hasProto && hasType && errno != 0 {
   255  		return fmt.Sprintf("%v failed: %v", callStr, errno)
   256  	}
   257  	return ""
   258  }
   259  
   260  func linuxSyzGenetlinkGetFamilyIDSupported(ctx *checkContext, call *prog.Syscall) string {
   261  	// TODO: try to obtain actual family ID here. It will disable whole sets of sendmsg syscalls.
   262  	return ctx.callSucceeds(fmt.Sprintf("socket(0x%x, 0x%x, 0x%x)",
   263  		ctx.val("AF_NETLINK"), ctx.val("SOCK_RAW"), ctx.val("NETLINK_GENERIC")))
   264  }
   265  
   266  func linuxPkeysSupported(ctx *checkContext, call *prog.Syscall) string {
   267  	return ctx.callSucceeds("pkey_alloc(0x0, 0x0)")
   268  }
   269  
   270  func linuxSyzSocketConnectNvmeTCPSupported(ctx *checkContext, call *prog.Syscall) string {
   271  	return ctx.onlySandboxNone()
   272  }
   273  
   274  func linuxVhciInjectionSupported(ctx *checkContext, call *prog.Syscall) string {
   275  	return ctx.rootCanOpen("/dev/vhci")
   276  }
   277  
   278  func linuxSyzInitNetSocketSupported(ctx *checkContext, call *prog.Syscall) string {
   279  	if reason := ctx.onlySandboxNone(); reason != "" {
   280  		return reason
   281  	}
   282  	return linuxSupportedSocket(ctx, call)
   283  }
   284  
   285  func linuxBtfVmlinuxSupported(ctx *checkContext, call *prog.Syscall) string {
   286  	if reason := ctx.onlySandboxNone(); reason != "" {
   287  		return reason
   288  	}
   289  	return ctx.canOpen("/sys/kernel/btf/vmlinux")
   290  }
   291  
   292  func linuxSyzUsbIPSupported(ctx *checkContext, call *prog.Syscall) string {
   293  	return ctx.canWrite("/sys/devices/platform/vhci_hcd.0/attach")
   294  }
   295  
   296  func linuxWifiEmulationSupported(ctx *checkContext, call *prog.Syscall) string {
   297  	if reason := ctx.rootCanOpen("/sys/class/mac80211_hwsim/"); reason != "" {
   298  		return reason
   299  	}
   300  	// We use HWSIM_ATTR_PERM_ADDR which was added in 4.17.
   301  	return linuxRequireKernel(ctx, 4, 17)
   302  }
   303  
   304  func linuxRequireKernel(ctx *checkContext, major, minor int) string {
   305  	data, err := ctx.readFile("/proc/version")
   306  	if err != nil {
   307  		return err.Error()
   308  	}
   309  	if ok, bad := matchKernelVersion(string(data), major, minor); bad {
   310  		return fmt.Sprintf("failed to parse kernel version: %s", data)
   311  	} else if !ok {
   312  		return fmt.Sprintf("kernel %v.%v required, have %s", major, minor, data)
   313  	}
   314  	return ""
   315  }
   316  
   317  var kernelVersionRe = regexp.MustCompile(` ([0-9]+)\.([0-9]+)\.`)
   318  
   319  func matchKernelVersion(ver string, x, y int) (bool, bool) {
   320  	match := kernelVersionRe.FindStringSubmatch(ver)
   321  	if match == nil {
   322  		return false, true
   323  	}
   324  	major, err := strconv.Atoi(match[1])
   325  	if err != nil {
   326  		return false, true
   327  	}
   328  	if major <= 0 || major > 999 {
   329  		return false, true
   330  	}
   331  	minor, err := strconv.Atoi(match[2])
   332  	if err != nil {
   333  		return false, true
   334  	}
   335  	if minor <= 0 || minor > 999 {
   336  		return false, true
   337  	}
   338  	return major*1000+minor >= x*1000+y, false
   339  }