From af9f337ea645491c9ec8db05d305887a51e419fe Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Mon, 18 Jun 2018 19:45:45 +0200 Subject: pkg/host: support trial supported syscall detection Detect supported syscall by directly executing them if kallsyms is not present. This is required for gvisor testing. --- pkg/host/host.go | 2 ++ pkg/host/host_linux.go | 70 ++++++++++++++++++++++++++++++++++++++++---------- pkg/host/host_test.go | 52 +++++++++++++++++++++---------------- 3 files changed, 89 insertions(+), 35 deletions(-) (limited to 'pkg') diff --git a/pkg/host/host.go b/pkg/host/host.go index d4f4d71a8..2c300189e 100644 --- a/pkg/host/host.go +++ b/pkg/host/host.go @@ -27,6 +27,8 @@ func DetectSupportedSyscalls(target *prog.Target, sandbox string) ( return supported, unsupported, nil } +var testFallback = false + const ( FeatureCoverage = iota FeatureComparisons diff --git a/pkg/host/host_linux.go b/pkg/host/host_linux.go index dff8bd88d..4b9f3ebe4 100644 --- a/pkg/host/host_linux.go +++ b/pkg/host/host_linux.go @@ -22,6 +22,16 @@ import ( ) func isSupported(c *prog.Syscall, sandbox string) (bool, string) { + 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) + } // 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, @@ -30,23 +40,19 @@ func isSupported(c *prog.Syscall, sandbox string) (bool, string) { // 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. + // 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 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 len(kallsyms) == 0 { - return true, "" + if !testFallback && len(kallsyms) != 0 { + return isSupportedKallsyms(c) } + return isSupportedTrial(c) +} + +func isSupportedKallsyms(c *prog.Syscall) (bool, string) { name := c.CallName if newname := kallsymsMap[name]; newname != "" { name = newname @@ -60,6 +66,42 @@ func isSupported(c *prog.Syscall, sandbox string) (bool, string) { 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. @@ -70,6 +112,8 @@ var ( "umount": "oldumount", "umount2": "umount", } + trialMu sync.Mutex + trialSupported = make(map[uint64]bool) ) func isSupportedSyzkall(sandbox string, c *prog.Syscall) (bool, string) { diff --git a/pkg/host/host_test.go b/pkg/host/host_test.go index 8a16d5e5d..c44b9bbce 100644 --- a/pkg/host/host_test.go +++ b/pkg/host/host_test.go @@ -4,6 +4,7 @@ package host import ( + "fmt" "runtime" "testing" @@ -13,28 +14,35 @@ import ( func TestDetectSupportedSyscalls(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) - } - _, disabled = target.TransitivelyEnabledCalls(supp) - t.Logf("\n\ntransitively unsupported:") - for c, reason := range disabled { - t.Logf("%v: %v", c.Name, reason) + for _, fallback := range []bool{false, true} { + t.Run(fmt.Sprintf("fallback=%v", fallback), func(t *testing.T) { + oldFallback := testFallback + testFallback = fallback + defer func() { testFallback = oldFallback }() + 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.Fatal(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) + } + _, disabled = target.TransitivelyEnabledCalls(supp) + t.Logf("\n\ntransitively unsupported:") + for c, reason := range disabled { + t.Logf("%v: %v", c.Name, reason) + } + }) } } -- cgit mrf-deployment