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 }