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 }