From 4daf8570eba286299489fc3ebc7d788c458bb47a Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Fri, 6 Apr 2018 18:46:49 +0200 Subject: [PATCH] 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 +-- syz-fuzzer/fuzzer.go | 8 +-- syz-fuzzer/testing.go | 2 +- tools/syz-stress/stress.go | 8 +-- 12 files changed, 170 insertions(+), 129 deletions(-) create mode 100644 pkg/host/host.go create mode 100644 pkg/host/host_test.go diff --git a/pkg/host/host.go b/pkg/host/host.go new file mode 100644 index 00000000..f3acd05d --- /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 91ee49a7..9478c7ca 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 39e8655c..bada10ec 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 b145961d..87143d76 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 d5ab3a49..02392a4b 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" } - return osutil.IsExist("/dev/fuse") + if !osutil.IsExist("/dev/fuse") { + return false, "/dev/fuse does not exist" + } + return true, "" case "syz_fuseblk_mount": if syscall.Getuid() != 0 || sandbox == "setuid" { - return false + return false, "only supported under root with sandbox=none/namespace" } - return osutil.IsExist("/dev/fuse") + if !osutil.IsExist("/dev/fuse") { + return false, "/dev/fuse does not exist" + } + 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 9deda69b..084428ab 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 39e8655c..bada10ec 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 00000000..470a6e5e --- /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 39e8655c..bada10ec 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 { diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index 64d214cf..1701f749 100644 --- a/syz-fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go @@ -393,12 +393,12 @@ func buildCallList(target *prog.Target, enabledCalls, sandbox string) map[*prog. } } - if supp, err := host.DetectSupportedSyscalls(target, sandbox); err != nil { + if _, disabled, err := host.DetectSupportedSyscalls(target, sandbox); err != nil { Logf(0, "failed to detect host supported syscalls: %v", err) } else { for c := range calls { - if !supp[c] { - Logf(1, "disabling unsupported syscall: %v", c.Name) + if reason, ok := disabled[c]; ok { + Logf(1, "unsupported syscall: %v: %v", c.Name, reason) delete(calls, c) } } @@ -407,7 +407,7 @@ func buildCallList(target *prog.Target, enabledCalls, sandbox string) map[*prog. trans := target.TransitivelyEnabledCalls(calls) for c := range calls { if !trans[c] { - Logf(1, "disabling transitively unsupported syscall: %v", c.Name) + Logf(1, "transitively unsupported: %v", c.Name) delete(calls, c) } } diff --git a/syz-fuzzer/testing.go b/syz-fuzzer/testing.go index bf515490..257d6385 100644 --- a/syz-fuzzer/testing.go +++ b/syz-fuzzer/testing.go @@ -32,7 +32,7 @@ func testImage(hostAddr string, target *prog.Target, sandbox string) { if config.Flags&ipc.FlagSandboxNamespace != 0 && !osutil.IsExist("/proc/self/ns/user") { Fatalf("/proc/self/ns/user is not present for namespace sandbox") } - calls, err := host.DetectSupportedSyscalls(target, sandbox) + calls, _, err := host.DetectSupportedSyscalls(target, sandbox) if err != nil { Fatalf("failed to detect supported syscalls: %v", err) } diff --git a/tools/syz-stress/stress.go b/tools/syz-stress/stress.go index f09b08ed..73073c39 100644 --- a/tools/syz-stress/stress.go +++ b/tools/syz-stress/stress.go @@ -139,7 +139,7 @@ func buildCallList(target *prog.Target) map[*prog.Syscall]bool { } return calls } - calls, err := host.DetectSupportedSyscalls(target, "none") + calls, disabled, err := host.DetectSupportedSyscalls(target, "none") if err != nil { Logf(0, "failed to detect host supported syscalls: %v", err) calls = make(map[*prog.Syscall]bool) @@ -147,10 +147,8 @@ func buildCallList(target *prog.Target) map[*prog.Syscall]bool { calls[c] = true } } - for _, c := range target.Syscalls { - if !calls[c] { - Logf(0, "disabling unsupported syscall: %v", c.Name) - } + for c, reason := range disabled { + Logf(0, "disabling unsupported syscall: %v: %v", c.Name, reason) } trans := target.TransitivelyEnabledCalls(calls) for c := range calls {