| // Copyright 2015 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 host |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io/ioutil" |
| "runtime" |
| "strconv" |
| "strings" |
| "syscall" |
| |
| "github.com/google/syzkaller/pkg/osutil" |
| "github.com/google/syzkaller/prog" |
| ) |
| |
| // DetectSupportedSyscalls returns list on supported syscalls on host. |
| func DetectSupportedSyscalls(target *prog.Target) (map[*prog.Syscall]bool, error) { |
| // There are 3 possible strategies: |
| // 1. Executes all syscalls with presumably invalid arguments and check for ENOprog. |
| // But not all syscalls are safe to execute. For example, pause will hang, |
| // while setpgrp will push the process into own process group. |
| // 2. Check presence of /sys/kernel/debug/tracing/events/syscalls/sys_enter_* files. |
| // This requires root and CONFIG_FTRACE_SYSCALLS. Also it lies for some syscalls. |
| // For example, on x86_64 it says that sendfile is not present (only sendfile64). |
| // 3. Check sys_syscallname in /proc/kallsyms. |
| // Requires CONFIG_KALLSYMS. Seems to be the most reliable. That's what we use here. |
| |
| kallsyms, _ := ioutil.ReadFile("/proc/kallsyms") |
| supported := make(map[*prog.Syscall]bool) |
| for _, c := range target.Syscalls { |
| if isSupported(kallsyms, c) { |
| supported[c] = true |
| } |
| } |
| return supported, nil |
| } |
| |
| func isSupported(kallsyms []byte, c *prog.Syscall) bool { |
| if strings.HasPrefix(c.CallName, "syz_") { |
| return isSupportedSyzkall(c) |
| } |
| if strings.HasPrefix(c.Name, "socket$") { |
| return isSupportedSocket(c) |
| } |
| if strings.HasPrefix(c.Name, "open$") { |
| return isSupportedOpen(c) |
| } |
| if strings.HasPrefix(c.Name, "openat$") { |
| return isSupportedOpenAt(c) |
| } |
| if len(kallsyms) == 0 { |
| return true |
| } |
| name := c.CallName |
| if newname := kallsymsMap[name]; newname != "" { |
| name = newname |
| } |
| return bytes.Index(kallsyms, []byte(" T sys_"+name+"\n")) != -1 |
| } |
| |
| // Some syscall names diverge in __NR_* consts and kallsyms. |
| // umount2 is renamed to umount in arch/x86/entry/syscalls/syscall_64.tbl. |
| // Where umount is renamed to oldumount is unclear. |
| var kallsymsMap = map[string]string{ |
| "umount": "oldumount", |
| "umount2": "umount", |
| } |
| |
| func isSupportedSyzkall(c *prog.Syscall) bool { |
| switch c.CallName { |
| case "syz_test": |
| return false |
| case "syz_open_dev": |
| if _, ok := c.Args[0].(*prog.ConstType); ok { |
| // This is for syz_open_dev$char/block. |
| // They are currently commented out, but in case one enables them. |
| return true |
| } |
| fname, ok := extractStringConst(c.Args[0]) |
| if !ok { |
| panic("first open arg is not a pointer to string const") |
| } |
| if syscall.Getuid() != 0 { |
| return false |
| } |
| var check func(dev string) bool |
| check = func(dev string) bool { |
| if !strings.Contains(dev, "#") { |
| return osutil.IsExist(dev) |
| } |
| for i := 0; i < 10; i++ { |
| if check(strings.Replace(dev, "#", strconv.Itoa(i), 1)) { |
| return true |
| } |
| } |
| return false |
| } |
| return check(fname) |
| case "syz_open_procfs": |
| return true |
| case "syz_open_pts": |
| return true |
| case "syz_fuse_mount": |
| return osutil.IsExist("/dev/fuse") |
| case "syz_fuseblk_mount": |
| return osutil.IsExist("/dev/fuse") && syscall.Getuid() == 0 |
| case "syz_emit_ethernet", "syz_extract_tcp_res": |
| fd, err := syscall.Open("/dev/net/tun", syscall.O_RDWR, 0) |
| if err == nil { |
| syscall.Close(fd) |
| } |
| return err == nil && syscall.Getuid() == 0 |
| case "syz_usb_connect", "syz_usb_disconnect": |
| fd, err := syscall.Open("/sys/kernel/debug/usb-fuzzer", syscall.O_RDWR, 0) |
| if err == nil { |
| syscall.Close(fd) |
| } |
| return err == nil |
| case "syz_kvm_setup_cpu": |
| switch c.Name { |
| case "syz_kvm_setup_cpu$x86": |
| return runtime.GOARCH == "amd64" || runtime.GOARCH == "386" |
| case "syz_kvm_setup_cpu$arm64": |
| return runtime.GOARCH == "arm64" |
| } |
| } |
| panic("unknown syzkall: " + c.Name) |
| } |
| |
| func isSupportedSocket(c *prog.Syscall) bool { |
| af, ok := c.Args[0].(*prog.ConstType) |
| if !ok { |
| println(c.Name) |
| panic("socket family is not const") |
| } |
| fd, err := syscall.Socket(int(af.Val), 0, 0) |
| if fd != -1 { |
| syscall.Close(fd) |
| } |
| return err != syscall.ENOSYS && err != syscall.EAFNOSUPPORT |
| } |
| |
| func isSupportedOpen(c *prog.Syscall) bool { |
| fname, ok := extractStringConst(c.Args[0]) |
| if !ok { |
| return true |
| } |
| fd, err := syscall.Open(fname, syscall.O_RDONLY, 0) |
| if fd != -1 { |
| syscall.Close(fd) |
| } |
| return err == nil |
| } |
| |
| func isSupportedOpenAt(c *prog.Syscall) bool { |
| fname, ok := extractStringConst(c.Args[1]) |
| if !ok { |
| return true |
| } |
| fd, err := syscall.Open(fname, syscall.O_RDONLY, 0) |
| if fd != -1 { |
| syscall.Close(fd) |
| } |
| return err == nil |
| } |
| |
| func extractStringConst(typ prog.Type) (string, bool) { |
| ptr, ok := typ.(*prog.PtrType) |
| if !ok { |
| panic("first open arg is not a pointer to string const") |
| } |
| str, ok := ptr.Type.(*prog.BufferType) |
| if !ok || str.Kind != prog.BufferString || len(str.Values) != 1 { |
| return "", false |
| } |
| v := str.Values[0] |
| v = v[:len(v)-1] // string terminating \x00 |
| return v, true |
| } |
| |
| func EnableFaultInjection() error { |
| if err := osutil.WriteFile("/sys/kernel/debug/failslab/ignore-gfp-wait", []byte("N")); err != nil { |
| return fmt.Errorf("failed to write /sys/kernel/debug/failslab/ignore-gfp-wait: %v", err) |
| } |
| if err := osutil.WriteFile("/sys/kernel/debug/fail_futex/ignore-private", []byte("N")); err != nil { |
| return fmt.Errorf("failed to write /sys/kernel/debug/fail_futex/ignore-private: %v", err) |
| } |
| return nil |
| } |