aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/host/host_linux.go
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2019-11-14 17:09:07 +0100
committerDmitry Vyukov <dvyukov@google.com>2019-11-16 09:58:54 +0100
commit8d85129b3cff7398f5aba861f0049afffb566865 (patch)
tree0ba0f3668ca0c0a367276ca164ec321a386614d5 /pkg/host/host_linux.go
parentb5c36524a29838b0ec9345fc1a07daeebf50c833 (diff)
pkg/host: split files into syscalls/features
pkg/host does 2 things: detects supported syscalls and supported features. There is enough code for each for a separate file.
Diffstat (limited to 'pkg/host/host_linux.go')
-rw-r--r--pkg/host/host_linux.go574
1 files changed, 0 insertions, 574 deletions
diff --git a/pkg/host/host_linux.go b/pkg/host/host_linux.go
deleted file mode 100644
index d9a8c24f9..000000000
--- a/pkg/host/host_linux.go
+++ /dev/null
@@ -1,574 +0,0 @@
-// 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"
- "os"
- "regexp"
- "runtime"
- "strconv"
- "strings"
- "sync"
- "syscall"
- "time"
- "unsafe"
-
- "github.com/google/syzkaller/pkg/log"
- "github.com/google/syzkaller/pkg/osutil"
- "github.com/google/syzkaller/prog"
- "github.com/google/syzkaller/sys/linux"
-)
-
-type KcovRemoteArg struct {
- TraceMode uint32
- AreaSize uint32
- NumHandles uint32
- CommonHandle uint64
- // Handles []uint64 goes here.
-}
-
-func isSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) {
- log.Logf(2, "checking support for %v", c.Name)
- if strings.HasPrefix(c.CallName, "syz_") {
- return isSupportedSyzkall(sandbox, c)
- }
- if strings.HasPrefix(c.Name, "socket$") ||
- strings.HasPrefix(c.Name, "socketpair$") {
- return isSupportedSocket(c)
- }
- if strings.HasPrefix(c.Name, "openat$") {
- return isSupportedOpenAt(c)
- }
- if strings.HasPrefix(c.Name, "mount$") {
- return isSupportedMount(c, sandbox)
- }
- if c.Name == "ioctl$EXT4_IOC_SHUTDOWN" && sandbox == "none" {
- // Don't shutdown root filesystem.
- return false, "unsafe with sandbox=none"
- }
- // There are 3 possible strategies for detecting supported syscalls:
- // 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.
- // Kallsyms seems to be the most reliable and fast. That's what we use first.
- // If kallsyms is not present, we fallback to execution of syscalls.
- kallsymsOnce.Do(func() {
- kallsyms, _ := ioutil.ReadFile("/proc/kallsyms")
- if len(kallsyms) == 0 {
- return
- }
- kallsymsSyscallSet = parseKallsyms(kallsyms, target.Arch)
- })
- if !testFallback && len(kallsymsSyscallSet) != 0 {
- r, v := isSupportedKallsyms(c)
- return r, v
- }
- return isSupportedTrial(c)
-}
-
-func parseKallsyms(kallsyms []byte, arch string) map[string]bool {
- set := make(map[string]bool)
- var re *regexp.Regexp
- switch arch {
- case "386", "amd64":
- re = regexp.MustCompile(` T (__ia32_|__x64_)?sys_([^\n]+)\n`)
- case "arm", "arm64":
- re = regexp.MustCompile(` T (__arm64_)?sys_([^\n]+)\n`)
- case "ppc64le":
- re = regexp.MustCompile(` T ()?sys_([^\n]+)\n`)
- default:
- panic("unsupported arch for kallsyms parsing")
- }
- matches := re.FindAllSubmatch(kallsyms, -1)
- for _, m := range matches {
- name := string(m[2])
- log.Logf(2, "found in kallsyms: %v", name)
- set[name] = true
- }
- return set
-}
-
-func isSupportedKallsyms(c *prog.Syscall) (bool, string) {
- name := c.CallName
- if newname := kallsymsRenameMap[name]; newname != "" {
- name = newname
- }
- if !kallsymsSyscallSet[name] {
- return false, fmt.Sprintf("sys_%v is not present in /proc/kallsyms", name)
- }
- return true, ""
-}
-
-func isSupportedTrial(c *prog.Syscall) (bool, string) {
- switch c.CallName {
- // These known to cause hangs.
- case "exit", "pause":
- return true, ""
- }
- trialMu.Lock()
- defer trialMu.Unlock()
- if res, ok := trialSupported[c.NR]; ok {
- return res, "ENOSYS"
- }
- cmd := osutil.Command(os.Args[0])
- cmd.Env = []string{fmt.Sprintf("SYZ_TRIAL_TEST=%v", c.NR)}
- _, err := osutil.Run(10*time.Second, cmd)
- res := err != nil
- trialSupported[c.NR] = res
- return res, "ENOSYS"
-}
-
-func init() {
- str := os.Getenv("SYZ_TRIAL_TEST")
- if str == "" {
- return
- }
- nr, err := strconv.Atoi(str)
- if err != nil {
- panic(err)
- }
- arg := ^uintptr(0) - 1e4 // something as invalid as possible
- _, _, err = syscall.Syscall6(uintptr(nr), arg, arg, arg, arg, arg, arg)
- if err == syscall.ENOSYS {
- os.Exit(0)
- }
- os.Exit(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 (
- kallsymsOnce sync.Once
- kallsymsSyscallSet map[string]bool
- kallsymsRenameMap = map[string]string{
- "umount": "oldumount",
- "umount2": "umount",
- "stat": "newstat",
- }
- trialMu sync.Mutex
- trialSupported = make(map[uint64]bool)
- filesystems []byte
- filesystemsOnce sync.Once
-)
-
-// The function is lengthy as it handles all pseudo-syscalls,
-// but it does not seem to cause comprehension problems as there is no shared state.
-// Splitting this per-syscall will only increase code size.
-// nolint: gocyclo
-func isSupportedSyzkall(sandbox string, c *prog.Syscall) (bool, string) {
- switch c.CallName {
- 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 checkUSBInjection() == "" {
- // These entries might not be available at boot time,
- // but will be created by connected USB devices.
- USBDevicePrefixes := []string{
- "/dev/hidraw", "/dev/usb/hiddev", "/dev/input/",
- }
- for _, prefix := range USBDevicePrefixes {
- if strings.HasPrefix(fname, prefix) {
- return true, ""
- }
- }
- }
- var check func(dev string) bool
- check = func(dev string) bool {
- if !strings.Contains(dev, "#") {
- // Note: don't try to open them all, some can hang (e.g. /dev/snd/pcmC#D#p).
- return osutil.IsExist(dev)
- }
- for i := 0; i < 10; i++ {
- if check(strings.Replace(dev, "#", strconv.Itoa(i), 1)) {
- return true
- }
- }
- return false
- }
- if !check(fname) {
- return false, fmt.Sprintf("file %v does not exist", fname)
- }
- return onlySandboxNoneOrNamespace(sandbox)
- case "syz_open_procfs":
- return true, ""
- case "syz_open_pts":
- return true, ""
- case "syz_emit_ethernet", "syz_extract_tcp_res":
- reason := checkNetworkInjection()
- return reason == "", reason
- case "syz_usb_connect", "syz_usb_disconnect", "syz_usb_control_io", "syz_usb_ep_write", "syz_usb_ep_read":
- reason := checkUSBInjection()
- return reason == "", reason
- case "syz_kvm_setup_cpu":
- switch c.Name {
- case "syz_kvm_setup_cpu$x86":
- if runtime.GOARCH == "amd64" || runtime.GOARCH == "386" {
- return true, ""
- }
- case "syz_kvm_setup_cpu$arm64":
- if runtime.GOARCH == "arm64" {
- return true, ""
- }
- }
- return false, "unsupported arch"
- case "syz_init_net_socket":
- // Unfortunately this only works with sandbox none at the moment.
- // The problem is that setns of a network namespace requires CAP_SYS_ADMIN
- // in the target namespace, and we've lost all privs in the init namespace
- // during creation of a user namespace.
- if ok, reason := onlySandboxNone(sandbox); !ok {
- return false, reason
- }
- return isSupportedSocket(c)
- case "syz_genetlink_get_family_id":
- fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_GENERIC)
- if fd == -1 {
- return false, fmt.Sprintf("socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC) failed: %v", err)
- }
- syscall.Close(fd)
- return true, ""
- case "syz_mount_image":
- if ok, reason := onlySandboxNone(sandbox); !ok {
- return ok, reason
- }
- fstype, ok := extractStringConst(c.Args[0])
- if !ok {
- panic("syz_mount_image arg is not string")
- }
- return isSupportedFilesystem(fstype)
- case "syz_read_part_table":
- return onlySandboxNone(sandbox)
- case "syz_execute_func":
- return true, ""
- }
- panic("unknown syzkall: " + c.Name)
-}
-
-func onlySandboxNone(sandbox string) (bool, string) {
- if syscall.Getuid() != 0 || sandbox != "none" {
- return false, "only supported under root with sandbox=none"
- }
- return true, ""
-}
-
-func onlySandboxNoneOrNamespace(sandbox string) (bool, string) {
- if syscall.Getuid() != 0 || sandbox == "setuid" {
- return false, "only supported under root with sandbox=none/namespace"
- }
- return true, ""
-}
-
-func isSupportedSocket(c *prog.Syscall) (bool, string) {
- af, ok := c.Args[0].(*prog.ConstType)
- if !ok {
- panic("socket family is not const")
- }
- fd, err := syscall.Socket(int(af.Val), 0, 0)
- if fd != -1 {
- syscall.Close(fd)
- }
- if err == syscall.ENOSYS {
- return false, "socket syscall returns ENOSYS"
- }
- if err == syscall.EAFNOSUPPORT {
- return false, "socket family is not supported (EAFNOSUPPORT)"
- }
- proto, ok := c.Args[2].(*prog.ConstType)
- if !ok {
- return true, ""
- }
- var typ uint64
- if arg, ok := c.Args[1].(*prog.ConstType); ok {
- typ = arg.Val
- } else if arg, ok := c.Args[1].(*prog.FlagsType); ok {
- typ = arg.Vals[0]
- } else {
- return true, ""
- }
- fd, err = syscall.Socket(int(af.Val), int(typ), int(proto.Val))
- if fd != -1 {
- syscall.Close(fd)
- return true, ""
- }
- return false, err.Error()
-}
-
-func isSupportedOpenAt(c *prog.Syscall) (bool, string) {
- var fd int
- var err error
-
- fname, ok := extractStringConst(c.Args[1])
- if !ok || len(fname) == 0 || fname[0] != '/' {
- return true, ""
- }
-
- modes := []int{syscall.O_RDONLY, syscall.O_WRONLY, syscall.O_RDWR}
-
- // Attempt to extract flags from the syscall description
- if mode, ok := c.Args[2].(*prog.ConstType); ok {
- modes = []int{int(mode.Val)}
- }
-
- for _, mode := range modes {
- fd, err = syscall.Open(fname, mode, 0)
- if fd != -1 {
- syscall.Close(fd)
- }
- if err == nil {
- return true, ""
- }
- }
-
- return false, fmt.Sprintf("open(%v) failed: %v", fname, err)
-}
-
-func isSupportedMount(c *prog.Syscall, sandbox string) (bool, string) {
- fstype, ok := extractStringConst(c.Args[2])
- if !ok {
- panic(fmt.Sprintf("%v: filesystem is not string const", c.Name))
- }
- if ok, reason := isSupportedFilesystem(fstype); !ok {
- return ok, reason
- }
- switch fstype {
- case "fuse", "fuseblk":
- if err := osutil.IsAccessible("/dev/fuse"); err != nil {
- return false, err.Error()
- }
- return onlySandboxNoneOrNamespace(sandbox)
- default:
- return onlySandboxNone(sandbox)
- }
-}
-
-func isSupportedFilesystem(fstype string) (bool, string) {
- filesystemsOnce.Do(func() {
- filesystems, _ = ioutil.ReadFile("/proc/filesystems")
- })
- if !bytes.Contains(filesystems, []byte("\t"+fstype+"\n")) {
- return false, fmt.Sprintf("/proc/filesystems does not contain %v", fstype)
- }
- return true, ""
-}
-
-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) == 0 {
- return "", false
- }
- v := str.Values[0]
- for len(v) != 0 && v[len(v)-1] == 0 {
- v = v[:len(v)-1] // string terminating \x00
- }
- return v, true
-}
-
-func init() {
- checkFeature[FeatureCoverage] = checkCoverage
- checkFeature[FeatureComparisons] = checkComparisons
- checkFeature[FeatureExtraCoverage] = checkExtraCoverage
- checkFeature[FeatureSandboxSetuid] = unconditionallyEnabled
- checkFeature[FeatureSandboxNamespace] = checkSandboxNamespace
- checkFeature[FeatureSandboxAndroidUntrustedApp] = checkSandboxAndroidUntrustedApp
- checkFeature[FeatureFaultInjection] = checkFaultInjection
- checkFeature[FeatureLeakChecking] = checkLeakChecking
- checkFeature[FeatureNetworkInjection] = checkNetworkInjection
- checkFeature[FeatureNetworkDevices] = unconditionallyEnabled
- checkFeature[FeatureKCSAN] = checkKCSAN
- checkFeature[FeatureDevlinkPCI] = checkDevlinkPCI
-}
-
-func checkCoverage() string {
- if reason := checkDebugFS(); reason != "" {
- return reason
- }
- if !osutil.IsExist("/sys/kernel/debug/kcov") {
- return "CONFIG_KCOV is not enabled"
- }
- if err := osutil.IsAccessible("/sys/kernel/debug/kcov"); err != nil {
- return err.Error()
- }
- return ""
-}
-
-func checkComparisons() (reason string) {
- return checkCoverageFeature(FeatureComparisons)
-}
-
-func checkExtraCoverage() (reason string) {
- return checkCoverageFeature(FeatureExtraCoverage)
-}
-
-func checkCoverageFeature(feature int) (reason string) {
- if reason = checkDebugFS(); reason != "" {
- return reason
- }
- // TODO(dvyukov): this should run under target arch.
- // E.g. KCOV ioctls were initially not supported on 386 (missing compat_ioctl),
- // and a 386 executor won't be able to use them, but an amd64 fuzzer will be.
- fd, err := syscall.Open("/sys/kernel/debug/kcov", syscall.O_RDWR, 0)
- if err != nil {
- return "CONFIG_KCOV is not enabled"
- }
- defer syscall.Close(fd)
- // Trigger host target lazy initialization, it will fill linux.KCOV_INIT_TRACE.
- // It's all wrong and needs to be refactored.
- if _, err := prog.GetTarget(runtime.GOOS, runtime.GOARCH); err != nil {
- return fmt.Sprintf("failed to get target: %v", err)
- }
- coverSize := uintptr(64 << 10)
- _, _, errno := syscall.Syscall(
- syscall.SYS_IOCTL, uintptr(fd), linux.KCOV_INIT_TRACE, coverSize)
- if errno != 0 {
- return fmt.Sprintf("ioctl(KCOV_INIT_TRACE) failed: %v", errno)
- }
- mem, err := syscall.Mmap(fd, 0, int(coverSize*unsafe.Sizeof(uintptr(0))),
- syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
- if err != nil {
- return fmt.Sprintf("KCOV mmap failed: %v", err)
- }
- defer func() {
- if err := syscall.Munmap(mem); err != nil {
- reason = fmt.Sprintf("munmap failed: %v", err)
- }
- }()
- switch feature {
- case FeatureComparisons:
- _, _, errno = syscall.Syscall(syscall.SYS_IOCTL,
- uintptr(fd), linux.KCOV_ENABLE, linux.KCOV_TRACE_CMP)
- if errno != 0 {
- if errno == 524 { // ENOTSUPP
- return "CONFIG_KCOV_ENABLE_COMPARISONS is not enabled"
- }
- return fmt.Sprintf("ioctl(KCOV_TRACE_CMP) failed: %v", errno)
- }
- case FeatureExtraCoverage:
- arg := KcovRemoteArg{
- TraceMode: uint32(linux.KCOV_TRACE_PC),
- AreaSize: uint32(coverSize * unsafe.Sizeof(uintptr(0))),
- NumHandles: 0,
- CommonHandle: 0,
- }
- _, _, errno = syscall.Syscall(syscall.SYS_IOCTL,
- uintptr(fd), linux.KCOV_REMOTE_ENABLE, uintptr(unsafe.Pointer(&arg)))
- if errno != 0 {
- if errno == 25 { // ENOTTY
- return "extra coverage is not supported by the kernel"
- }
- return fmt.Sprintf("ioctl(KCOV_REMOTE_ENABLE) failed: %v", errno)
- }
- default:
- panic("unknown feature in checkCoverageFeature")
- }
- defer func() {
- _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), linux.KCOV_DISABLE, 0)
- if errno != 0 {
- reason = fmt.Sprintf("ioctl(KCOV_DISABLE) failed: %v", errno)
- }
- }()
- return ""
-}
-
-func checkFaultInjection() string {
- if err := osutil.IsAccessible("/proc/self/make-it-fail"); err != nil {
- return "CONFIG_FAULT_INJECTION is not enabled"
- }
- if err := osutil.IsAccessible("/proc/thread-self/fail-nth"); err != nil {
- return "kernel does not have systematic fault injection support"
- }
- if reason := checkDebugFS(); reason != "" {
- return reason
- }
- if err := osutil.IsAccessible("/sys/kernel/debug/failslab/ignore-gfp-wait"); err != nil {
- return "CONFIG_FAULT_INJECTION_DEBUG_FS or CONFIG_FAILSLAB are not enabled"
- }
- return ""
-}
-
-func checkLeakChecking() string {
- if reason := checkDebugFS(); reason != "" {
- return reason
- }
- fd, err := syscall.Open("/sys/kernel/debug/kmemleak", syscall.O_RDWR, 0)
- if err != nil {
- return "CONFIG_DEBUG_KMEMLEAK is not enabled"
- }
- defer syscall.Close(fd)
- if _, err := syscall.Write(fd, []byte("scan=off")); err != nil {
- if err == syscall.EBUSY {
- return "KMEMLEAK disabled: increase CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE or unset CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF"
- }
- return fmt.Sprintf("/sys/kernel/debug/kmemleak write failed: %v", err)
- }
- return ""
-}
-
-func checkSandboxNamespace() string {
- if err := osutil.IsAccessible("/proc/self/ns/user"); err != nil {
- return err.Error()
- }
- return ""
-}
-
-func checkSandboxAndroidUntrustedApp() string {
- if err := osutil.IsAccessible("/sys/fs/selinux/policy"); err != nil {
- return err.Error()
- }
- return ""
-}
-
-func checkNetworkInjection() string {
- if err := osutil.IsAccessible("/dev/net/tun"); err != nil {
- return err.Error()
- }
- return ""
-}
-
-func checkUSBInjection() string {
- if err := osutil.IsAccessible("/dev/raw-gadget"); err != nil {
- return err.Error()
- }
- return ""
-}
-
-func checkDebugFS() string {
- if err := osutil.IsAccessible("/sys/kernel/debug"); err != nil {
- return "debugfs is not enabled or not mounted"
- }
- return ""
-}
-
-func checkKCSAN() string {
- if err := osutil.IsAccessible("/sys/kernel/debug/kcsan"); err != nil {
- return err.Error()
- }
- return ""
-}
-
-func checkDevlinkPCI() string {
- if err := osutil.IsAccessible("/sys/bus/pci/devices/0000:00:10.0/"); err != nil {
- return "PCI device 0000:00:10.0 is not available"
- }
- return ""
-}