syzkaller/host/host.go
Dmitry Vyukov ff8c0180ab sys, executor: more kvm improvements
1. Basic support for arm64 kvm testing.
2. Fix compiler warnings in x86 kvm code.
3. Test all pseudo syz calls in csource.
4. Fix handling of real code in x86.
2017-01-12 11:57:17 +01:00

160 lines
4.1 KiB
Go

// 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"
"io/ioutil"
"os"
"runtime"
"strconv"
"strings"
"syscall"
"github.com/google/syzkaller/sys"
)
// DetectSupportedSyscalls returns list on supported syscalls on host.
func DetectSupportedSyscalls() (map[*sys.Call]bool, error) {
// There are 3 possible strategies:
// 1. Executes all syscalls with presumably invalid arguments and check for ENOSYS.
// 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[*sys.Call]bool)
for _, c := range sys.Calls {
if isSupported(kallsyms, c) {
supported[c] = true
}
}
return supported, nil
}
func isSupported(kallsyms []byte, c *sys.Call) bool {
if c.NR == -1 {
return false // don't even have a syscall number
}
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
}
return bytes.Index(kallsyms, []byte(" T sys_"+c.CallName+"\n")) != -1
}
func isSupportedSyzkall(c *sys.Call) bool {
switch c.CallName {
case "syz_test":
return false
case "syz_open_dev":
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, "#") {
_, err := os.Stat(dev)
return err == nil
}
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_pts":
return true
case "syz_fuse_mount":
_, err := os.Stat("/dev/fuse")
return err == nil
case "syz_fuseblk_mount":
_, err := os.Stat("/dev/fuse")
return err == nil && syscall.Getuid() == 0
case "syz_emit_ethernet":
_, err := os.Stat("/dev/net/tun")
return err == nil && syscall.Getuid() == 0
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 *sys.Call) bool {
af, ok := c.Args[0].(*sys.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 *sys.Call) 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 *sys.Call) 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 sys.Type) (string, bool) {
ptr, ok := typ.(*sys.PtrType)
if !ok {
panic("first open arg is not a pointer to string const")
}
str, ok := ptr.Type.(*sys.BufferType)
if !ok || str.Kind != sys.BufferString || len(str.Values) != 1 {
return "", false
}
v := str.Values[0]
v = v[:len(v)-1] // string terminating \x00
return v, true
}