From 4daf8570eba286299489fc3ebc7d788c458bb47a Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Fri, 6 Apr 2018 18:46:49 +0200 Subject: pkg/host: explain why syscalls are disabled --- pkg/host/host.go | 27 ++++++++++ pkg/host/host_akaros.go | 9 +--- pkg/host/host_freebsd.go | 9 +--- pkg/host/host_fuchsia.go | 9 +--- pkg/host/host_linux.go | 127 +++++++++++++++++++++++++++----------------- pkg/host/host_linux_test.go | 37 +------------ pkg/host/host_netbsd.go | 9 +--- pkg/host/host_test.go | 45 ++++++++++++++++ pkg/host/host_windows.go | 9 +--- 9 files changed, 162 insertions(+), 119 deletions(-) create mode 100644 pkg/host/host.go create mode 100644 pkg/host/host_test.go (limited to 'pkg') diff --git a/pkg/host/host.go b/pkg/host/host.go new file mode 100644 index 000000000..f3acd05d9 --- /dev/null +++ b/pkg/host/host.go @@ -0,0 +1,27 @@ +// Copyright 2018 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 ( + "github.com/google/syzkaller/prog" +) + +// DetectSupportedSyscalls returns list on supported and unsupported syscalls on the host. +// For unsupported syscalls it also returns reason as to why it is unsupported. +func DetectSupportedSyscalls(target *prog.Target, sandbox string) (map[*prog.Syscall]bool, map[*prog.Syscall]string, error) { + supported := make(map[*prog.Syscall]bool) + unsupported := make(map[*prog.Syscall]string) + for _, c := range target.Syscalls { + ok, reason := isSupported(c, sandbox) + if ok { + supported[c] = true + } else { + if reason == "" { + reason = "unknown" + } + unsupported[c] = reason + } + } + return supported, unsupported, nil +} diff --git a/pkg/host/host_akaros.go b/pkg/host/host_akaros.go index 91ee49a7d..9478c7cac 100644 --- a/pkg/host/host_akaros.go +++ b/pkg/host/host_akaros.go @@ -9,13 +9,8 @@ import ( "github.com/google/syzkaller/prog" ) -// DetectSupportedSyscalls returns list on supported syscalls on host. -func DetectSupportedSyscalls(target *prog.Target, sandbox string) (map[*prog.Syscall]bool, error) { - supported := make(map[*prog.Syscall]bool) - for _, c := range target.Syscalls { - supported[c] = true - } - return supported, nil +func isSupported(c *prog.Syscall, sandbox string) (bool, string) { + return true, "" } func EnableFaultInjection() error { diff --git a/pkg/host/host_freebsd.go b/pkg/host/host_freebsd.go index 39e8655c4..bada10ec7 100644 --- a/pkg/host/host_freebsd.go +++ b/pkg/host/host_freebsd.go @@ -7,13 +7,8 @@ import ( "github.com/google/syzkaller/prog" ) -// DetectSupportedSyscalls returns list on supported syscalls on host. -func DetectSupportedSyscalls(target *prog.Target, sandbox string) (map[*prog.Syscall]bool, error) { - supported := make(map[*prog.Syscall]bool) - for _, c := range target.Syscalls { - supported[c] = true - } - return supported, nil +func isSupported(c *prog.Syscall, sandbox string) (bool, string) { + return true, "" } func EnableFaultInjection() error { diff --git a/pkg/host/host_fuchsia.go b/pkg/host/host_fuchsia.go index b145961df..87143d768 100644 --- a/pkg/host/host_fuchsia.go +++ b/pkg/host/host_fuchsia.go @@ -9,13 +9,8 @@ import ( "github.com/google/syzkaller/prog" ) -// DetectSupportedSyscalls returns list on supported syscalls on host. -func DetectSupportedSyscalls(target *prog.Target, sandbox string) (map[*prog.Syscall]bool, error) { - supported := make(map[*prog.Syscall]bool) - for _, c := range target.Syscalls { - supported[c] = true - } - return supported, nil +func isSupported(c *prog.Syscall, sandbox string) (bool, string) { + return true, "" } func EnableFaultInjection() error { diff --git a/pkg/host/host_linux.go b/pkg/host/host_linux.go index d5ab3a495..02392a4b0 100644 --- a/pkg/host/host_linux.go +++ b/pkg/host/host_linux.go @@ -10,15 +10,15 @@ import ( "runtime" "strconv" "strings" + "sync" "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, sandbox string) (map[*prog.Syscall]bool, error) { - // There are 3 possible strategies: +func isSupported(c *prog.Syscall, sandbox string) (bool, string) { + // 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. @@ -27,18 +27,9 @@ func DetectSupportedSyscalls(target *prog.Target, sandbox string) (map[*prog.Sys // 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(sandbox, kallsyms, c) { - supported[c] = true - } - } - return supported, nil -} - -func isSupported(sandbox string, kallsyms []byte, c *prog.Syscall) bool { + kallsymsOnce.Do(func() { + kallsyms, _ = ioutil.ReadFile("/proc/kallsyms") + }) if strings.HasPrefix(c.CallName, "syz_") { return isSupportedSyzkall(sandbox, c) } @@ -49,42 +40,52 @@ func isSupported(sandbox string, kallsyms []byte, c *prog.Syscall) bool { return isSupportedOpenAt(c) } if len(kallsyms) == 0 { - return true + return true, "" } name := c.CallName if newname := kallsymsMap[name]; newname != "" { name = newname } - return bytes.Contains(kallsyms, []byte(" T sys_"+name+"\n")) + if !bytes.Contains(kallsyms, []byte(" T sys_"+name+"\n")) { + return false, fmt.Sprintf("sys_%v is not present in /proc/kallsyms", name) + } + return true, "" } // 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", -} +var ( + kallsyms []byte + kallsymsOnce sync.Once + kallsymsMap = map[string]string{ + "umount": "oldumount", + "umount2": "umount", + } +) -func isSupportedSyzkall(sandbox string, c *prog.Syscall) bool { +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 + 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 || sandbox == "setuid" { - return false + return false, "only supported under root with sandbox=none/namespace" } var check func(dev string) bool check = func(dev string) bool { if !strings.Contains(dev, "#") { - return osutil.IsExist(dev) + if !osutil.IsExist(dev) { + return false + } + return true } for i := 0; i < 10; i++ { if check(strings.Replace(dev, "#", strconv.Itoa(i), 1)) { @@ -93,59 +94,80 @@ func isSupportedSyzkall(sandbox string, c *prog.Syscall) bool { } return false } - return check(fname) + if !check(fname) { + return false, fmt.Sprintf("file %v does not exist", fname) + } + return true, "" case "syz_open_procfs": - return true + return true, "" case "syz_open_pts": - return true + return true, "" case "syz_fuse_mount": if syscall.Getuid() != 0 || sandbox == "setuid" { - return false + return false, "only supported under root with sandbox=none/namespace" + } + if !osutil.IsExist("/dev/fuse") { + return false, "/dev/fuse does not exist" } - return osutil.IsExist("/dev/fuse") + return true, "" case "syz_fuseblk_mount": if syscall.Getuid() != 0 || sandbox == "setuid" { - return false + return false, "only supported under root with sandbox=none/namespace" + } + if !osutil.IsExist("/dev/fuse") { + return false, "/dev/fuse does not exist" } - return osutil.IsExist("/dev/fuse") + return true, "" 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) + if err != nil { + return false, fmt.Sprintf("open(/dev/net/tun) failed: %v", err) } - return err == nil + syscall.Close(fd) + return true, "" case "syz_kvm_setup_cpu": switch c.Name { case "syz_kvm_setup_cpu$x86": - return runtime.GOARCH == "amd64" || runtime.GOARCH == "386" + if runtime.GOARCH == "amd64" || runtime.GOARCH == "386" { + return true, "" + } case "syz_kvm_setup_cpu$arm64": - return runtime.GOARCH == "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 syscall.Getuid() != 0 || sandbox != "none" { - return false + return false, "only supported under root with sandbox=none" } return isSupportedSocket(c) case "syz_genetlink_get_family_id": - fd, _ := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_GENERIC) + fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_GENERIC) if fd == -1 { - return false + return false, fmt.Sprintf("socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC) failed: %v", err) } syscall.Close(fd) - return true + return true, "" case "syz_mount_image": - return sandbox != "setuid" + if syscall.Getuid() != 0 || sandbox == "setuid" { + return false, "only supported under root with sandbox=none/namespace" + } + return true, "" case "syz_read_part_table": - return sandbox == "none" && syscall.Getuid() == 0 + if syscall.Getuid() != 0 || sandbox != "none" { + return false, "only supported under root with sandbox=none" + } + return true, "" } panic("unknown syzkall: " + c.Name) } -func isSupportedSocket(c *prog.Syscall) bool { +func isSupportedSocket(c *prog.Syscall) (bool, string) { af, ok := c.Args[0].(*prog.ConstType) if !ok { panic("socket family is not const") @@ -154,19 +176,28 @@ func isSupportedSocket(c *prog.Syscall) bool { if fd != -1 { syscall.Close(fd) } - return err != syscall.ENOSYS && err != syscall.EAFNOSUPPORT + if err == syscall.ENOSYS { + return false, "socket syscall returns ENOSYS" + } + if err == syscall.EAFNOSUPPORT { + return false, "socket family is not supported (EAFNOSUPPORT)" + } + return true, "" } -func isSupportedOpenAt(c *prog.Syscall) bool { +func isSupportedOpenAt(c *prog.Syscall) (bool, string) { fname, ok := extractStringConst(c.Args[1]) if !ok || len(fname) == 0 || fname[0] != '/' { - return true + return true, "" } fd, err := syscall.Open(fname, syscall.O_RDONLY, 0) if fd != -1 { syscall.Close(fd) } - return err == nil + if err != nil { + return false, fmt.Sprintf("open(%v) failed: %v", fname, err) + } + return true, "" } func extractStringConst(typ prog.Type) (string, bool) { diff --git a/pkg/host/host_linux_test.go b/pkg/host/host_linux_test.go index 9deda69bc..084428ab2 100644 --- a/pkg/host/host_linux_test.go +++ b/pkg/host/host_linux_test.go @@ -11,50 +11,15 @@ import ( "testing" "github.com/google/syzkaller/prog" - _ "github.com/google/syzkaller/sys" ) -func TestLog(t *testing.T) { - t.Parallel() - target, err := prog.GetTarget("linux", runtime.GOARCH) - if err != nil { - t.Fatal(err) - } - // Dump for manual inspection. - supp, err := DetectSupportedSyscalls(target, "none") - if err != nil { - t.Skipf("skipping: %v", err) - } - t.Logf("unsupported:") - for _, c := range target.Syscalls { - s, ok := supp[c] - if ok && !s { - t.Fatalf("map contains false value") - } - if !s { - t.Logf("\t%v", c.Name) - } - } - trans := target.TransitivelyEnabledCalls(supp) - t.Logf("transitively unsupported:") - for _, c := range target.Syscalls { - s, ok := trans[c] - if ok && !s { - t.Fatalf("map contains false value") - } - if !s && supp[c] { - t.Logf("\t%v", c.Name) - } - } -} - func TestSupportedSyscalls(t *testing.T) { t.Parallel() target, err := prog.GetTarget("linux", runtime.GOARCH) if err != nil { t.Fatal(err) } - supp, err := DetectSupportedSyscalls(target, "none") + supp, _, err := DetectSupportedSyscalls(target, "none") if err != nil { t.Skipf("skipping: %v", err) } diff --git a/pkg/host/host_netbsd.go b/pkg/host/host_netbsd.go index 39e8655c4..bada10ec7 100644 --- a/pkg/host/host_netbsd.go +++ b/pkg/host/host_netbsd.go @@ -7,13 +7,8 @@ import ( "github.com/google/syzkaller/prog" ) -// DetectSupportedSyscalls returns list on supported syscalls on host. -func DetectSupportedSyscalls(target *prog.Target, sandbox string) (map[*prog.Syscall]bool, error) { - supported := make(map[*prog.Syscall]bool) - for _, c := range target.Syscalls { - supported[c] = true - } - return supported, nil +func isSupported(c *prog.Syscall, sandbox string) (bool, string) { + return true, "" } func EnableFaultInjection() error { diff --git a/pkg/host/host_test.go b/pkg/host/host_test.go new file mode 100644 index 000000000..470a6e5e1 --- /dev/null +++ b/pkg/host/host_test.go @@ -0,0 +1,45 @@ +// 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 ( + "runtime" + "testing" + + "github.com/google/syzkaller/prog" + _ "github.com/google/syzkaller/sys" +) + +func TestLog(t *testing.T) { + t.Parallel() + target, err := prog.GetTarget(runtime.GOOS, runtime.GOARCH) + if err != nil { + t.Fatal(err) + } + // Dump for manual inspection. + supp, disabled, err := DetectSupportedSyscalls(target, "none") + if err != nil { + t.Skipf("skipping: %v", err) + } + for c, ok := range supp { + if !ok { + t.Fatalf("map contains false value for %v", c.Name) + } + } + t.Logf("unsupported:") + for c, reason := range disabled { + t.Logf("%v: %v", c.Name, reason) + } + trans := target.TransitivelyEnabledCalls(supp) + t.Logf("\n\ntransitively unsupported:") + for _, c := range target.Syscalls { + s, ok := trans[c] + if ok && !s { + t.Fatalf("map contains false value") + } + if !s && supp[c] { + t.Logf("%v", c.Name) + } + } +} diff --git a/pkg/host/host_windows.go b/pkg/host/host_windows.go index 39e8655c4..bada10ec7 100644 --- a/pkg/host/host_windows.go +++ b/pkg/host/host_windows.go @@ -7,13 +7,8 @@ import ( "github.com/google/syzkaller/prog" ) -// DetectSupportedSyscalls returns list on supported syscalls on host. -func DetectSupportedSyscalls(target *prog.Target, sandbox string) (map[*prog.Syscall]bool, error) { - supported := make(map[*prog.Syscall]bool) - for _, c := range target.Syscalls { - supported[c] = true - } - return supported, nil +func isSupported(c *prog.Syscall, sandbox string) (bool, string) { + return true, "" } func EnableFaultInjection() error { -- cgit mrf-deployment