blob: e0572f8fa4cced4dc2351ce21d491307bc6c1097 [file] [log] [blame]
// 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
}