aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-06-17 12:37:05 +0200
committerDmitry Vyukov <dvyukov@google.com>2017-06-17 14:41:15 +0200
commit4b2a9e225c495475272e8a67f525329afa5f892a (patch)
tree49eec6c9fa0bedf654961466e9d21dbb1901a08e /pkg
parenta853b91c58c5403428499f5cdc661033ac7a91ce (diff)
pkg/host: move from host
Diffstat (limited to 'pkg')
-rw-r--r--pkg/host/host.go164
-rw-r--r--pkg/host/host_test.go74
2 files changed, 238 insertions, 0 deletions
diff --git a/pkg/host/host.go b/pkg/host/host.go
new file mode 100644
index 000000000..4e634f1aa
--- /dev/null
+++ b/pkg/host/host.go
@@ -0,0 +1,164 @@
+// 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"
+ "runtime"
+ "strconv"
+ "strings"
+ "syscall"
+
+ "github.com/google/syzkaller/pkg/osutil"
+ "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":
+ if _, ok := c.Args[0].(*sys.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_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_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
+}
diff --git a/pkg/host/host_test.go b/pkg/host/host_test.go
new file mode 100644
index 000000000..9b44d8e36
--- /dev/null
+++ b/pkg/host/host_test.go
@@ -0,0 +1,74 @@
+// 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 (
+ "syscall"
+ "testing"
+
+ "github.com/google/syzkaller/sys"
+)
+
+func TestLog(t *testing.T) {
+ t.Parallel()
+ // Dump for manual inspection.
+ supp, err := DetectSupportedSyscalls()
+ if err != nil {
+ t.Skipf("skipping: %v", err)
+ }
+ t.Logf("unsupported:")
+ for _, c := range sys.Calls {
+ s, ok := supp[c]
+ if ok && !s {
+ t.Fatalf("map contains false value")
+ }
+ if !s {
+ t.Logf("\t%v", c.Name)
+ }
+ }
+ trans := sys.TransitivelyEnabledCalls(supp)
+ t.Logf("transitively unsupported:")
+ for _, c := range sys.Calls {
+ 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()
+ supp, err := DetectSupportedSyscalls()
+ if err != nil {
+ t.Skipf("skipping: %v", err)
+ }
+ // These are safe to execute with invalid arguments.
+ safe := []string{
+ "memfd_create",
+ "sendfile",
+ "bpf$MAP_CREATE",
+ "open",
+ "openat",
+ "read",
+ "write",
+ "stat",
+ }
+ for _, name := range safe {
+ c := sys.CallMap[name]
+ if c == nil {
+ t.Fatalf("can't find syscall '%v'", name)
+ }
+ a := ^uintptr(0) - 4097 // hopefully invalid
+ _, _, err := syscall.Syscall6(uintptr(c.NR), a, a, a, a, a, a)
+ if err == 0 {
+ t.Fatalf("%v did not fail", name)
+ }
+ if ok := err != syscall.ENOSYS; ok != supp[c] {
+ t.Fatalf("syscall %v: perse=%v kallsyms=%v", name, ok, supp[c])
+ }
+ }
+}