From 874c5754bb22dbf77d6b600ff91f0f4f1fc5073a Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Mon, 12 Oct 2015 10:16:57 +0200 Subject: initial commit --- ipc/ipc.go | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++++ ipc/ipc_test.go | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 457 insertions(+) create mode 100644 ipc/ipc.go create mode 100644 ipc/ipc_test.go (limited to 'ipc') diff --git a/ipc/ipc.go b/ipc/ipc.go new file mode 100644 index 000000000..81aee4970 --- /dev/null +++ b/ipc/ipc.go @@ -0,0 +1,222 @@ +// 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 ipc + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + "time" +) + +type Env struct { + In []byte + Out []byte + + inFile *os.File + outFile *os.File + bin []string + timeout time.Duration + flags uint64 +} + +const ( + FlagDebug = uint64(1) << iota // debug output from executor + FlagCover // collect coverage + FlagThreaded // use multiple threads to mitigate blocked syscalls + FlagStrace // run executor under strace +) + +func MakeEnv(bin string, timeout time.Duration, flags uint64) (*Env, error) { + inf, inmem, err := createMapping(1 << 20) + if err != nil { + return nil, err + } + outf, outmem, err := createMapping(16 << 20) + if err != nil { + closeMapping(inf, inmem) + return nil, err + } + for i := 0; i < 8; i++ { + inmem[i] = byte(flags >> (8 * uint(i))) + } + inmem = inmem[8:] + env := &Env{ + In: inmem, + Out: outmem, + inFile: inf, + outFile: outf, + bin: strings.Split(bin, " "), + timeout: timeout, + flags: flags, + } + if len(env.bin) == 0 { + return nil, fmt.Errorf("binary is empty string") + } + return env, nil +} + +func (env *Env) Close() error { + err1 := closeMapping(env.inFile, env.In) + err2 := closeMapping(env.outFile, env.Out) + switch { + case err1 != nil: + return err1 + case err2 != nil: + return err2 + default: + return nil + } +} + +func (env *Env) Exec() (output, strace []byte, failed, hanged bool, err0 error) { + dir, err := ioutil.TempDir("./", "syzkaller-testdir") + if err != nil { + err0 = fmt.Errorf("failed to create temp dir: %v", err) + return + } + defer os.RemoveAll(dir) + rp, wp, err := os.Pipe() + if err != nil { + err0 = fmt.Errorf("failed to create pipe: %v", err) + return + } + defer rp.Close() + defer wp.Close() + cmd := exec.Command(env.bin[0], env.bin[1:]...) + traceFile := "" + if env.flags&FlagStrace != 0 { + f, err := ioutil.TempFile("./", "syzkaller-strace") + if err != nil { + err0 = fmt.Errorf("failed to create temp file: %v", err) + return + } + f.Close() + defer os.Remove(f.Name()) + traceFile, _ = filepath.Abs(f.Name()) + args := []string{"-s", "8", "-o", traceFile} + args = append(args, env.bin...) + if env.flags&FlagThreaded != 0 { + args = append([]string{"-f"}, args...) + } + cmd = exec.Command("strace", args...) + } + cmd.ExtraFiles = append(cmd.ExtraFiles, env.inFile, env.outFile) + cmd.Env = []string{} + cmd.Dir = dir + cmd.Stdout = wp + cmd.Stderr = wp + if syscall.Getuid() == 0 { + // Running under root, more isolation is possible. + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true, Cloneflags: syscall.CLONE_NEWNS} + } else { + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + } + if err := cmd.Start(); err != nil { + err0 = fmt.Errorf("failed to start executor binary: %v", err) + return + } + wp.Close() + done := make(chan bool) + hang := make(chan bool) + go func() { + t := time.NewTimer(env.timeout) + select { + case <-t.C: + // We started the process in its own process group and now kill the whole group. + // This solves a potential problem with strace: + // if we kill just strace, executor still runs and ReadAll below hangs. + syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) + syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) + syscall.Kill(cmd.Process.Pid, syscall.SIGKILL) + syscall.Kill(cmd.Process.Pid, syscall.SIGKILL) + hang <- true + case <-done: + t.Stop() + hang <- false + } + }() + output, err = ioutil.ReadAll(rp) + readErr := err + close(done) + if err = cmd.Wait(); <-hang && err != nil { + hanged = true + failed = true + } + if err != nil { + output = append(output, []byte(err.Error())...) + output = append(output, '\n') + } + if cmd.ProcessState != nil { + sys := cmd.ProcessState.Sys() + if ws, ok := sys.(syscall.WaitStatus); ok { + // Magic values returned by executor. + if ws.ExitStatus() == 67 { + err0 = fmt.Errorf("executor failed: %s", output) + return + } + if ws.ExitStatus() == 68 { + failed = true + } + } + } + if readErr != nil { + err0 = fmt.Errorf("failed to read executor output: %v", err) + return + } + if traceFile != "" { + strace, err = ioutil.ReadFile(traceFile) + if err != nil { + err0 = fmt.Errorf("failed to read strace output: %v", err) + return + } + } + return +} + +func createMapping(size int) (f *os.File, mem []byte, err error) { + f, err = ioutil.TempFile("./", "syzkaller-shm") + if err != nil { + return + } + if _, err = f.Write(make([]byte, size)); err != nil { + // if err = f.Truncate(int64(size)); err != nil { + f.Close() + os.Remove(f.Name()) + return + } + f.Close() + f, err = os.OpenFile(f.Name(), os.O_RDWR, 0) + if err != nil { + os.Remove(f.Name()) + return + } + mem, err = syscall.Mmap(int(f.Fd()), 0, size, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) + if err != nil { + f.Close() + os.Remove(f.Name()) + return + } + return +} + +func closeMapping(f *os.File, mem []byte) error { + err1 := syscall.Munmap(mem) + err2 := f.Close() + err3 := os.Remove(f.Name()) + switch { + case err1 != nil: + return err1 + case err2 != nil: + return err2 + case err3 != nil: + return err3 + default: + return nil + } +} diff --git a/ipc/ipc_test.go b/ipc/ipc_test.go new file mode 100644 index 000000000..d017b7038 --- /dev/null +++ b/ipc/ipc_test.go @@ -0,0 +1,235 @@ +// 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 ipc + +import ( + "bufio" + "bytes" + "io/ioutil" + "math/rand" + "os" + "os/exec" + "strings" + "testing" + "time" + + "github.com/google/syzkaller/prog" +) + +func buildExecutor(t *testing.T) string { + return buildProgram(t, "../executor/executor.cc") +} + +func buildSource(t *testing.T, src []byte) string { + srcf, err := ioutil.TempFile("", "syzkaller") + if err != nil { + t.Fatalf("failed to create temp file: %v", err) + } + srcf.Close() + os.Remove(srcf.Name()) + name := srcf.Name() + ".c" + if err := ioutil.WriteFile(name, src, 0600); err != nil { + t.Fatalf("failed to write temp file: %v", err) + } + defer os.Remove(name) + return buildProgram(t, name) +} + +func buildProgram(t *testing.T, src string) string { + bin, err := ioutil.TempFile("", "syzkaller") + if err != nil { + t.Fatalf("failed to create temp file: %v", err) + } + bin.Close() + out, err := exec.Command("gcc", src, "-o", bin.Name(), "-lpthread", "-static", "-O1", "-g").CombinedOutput() + if err != nil { + os.Remove(bin.Name()) + data, _ := ioutil.ReadFile(src) + t.Fatalf("failed to build program:\n%s\n%s", data, out) + } + return bin.Name() +} + +func initTest(t *testing.T) (rand.Source, int) { + iters := 100 + if testing.Short() { + iters = 10 + } + seed := int64(time.Now().UnixNano()) + rs := rand.NewSource(seed) + t.Logf("seed=%v", seed) + return rs, iters +} + +func TestEmptyProg(t *testing.T) { + bin := buildExecutor(t) + defer os.Remove(bin) + + env, err := MakeEnv(bin, time.Second, 0) + if err != nil { + t.Fatalf("failed to create env: %v", err) + } + defer env.Close() + + p := new(prog.Prog) + data := p.SerializeForExec() + copy(env.In, data) + + output, strace, failed, hanged, err := env.Exec() + if err != nil { + t.Fatalf("failed to run executor: %v", err) + } + if len(output) != 0 { + t.Fatalf("output on empty program") + } + if len(strace) != 0 { + t.Fatalf("strace output when not stracing") + } + if failed || hanged { + t.Fatalf("empty program failed") + } +} + +func TestStrace(t *testing.T) { + bin := buildExecutor(t) + defer os.Remove(bin) + + env, err := MakeEnv(bin, time.Second, FlagStrace) + if err != nil { + t.Fatalf("failed to create env: %v", err) + } + defer env.Close() + + p := new(prog.Prog) + data := p.SerializeForExec() + copy(env.In, data) + + _, strace, failed, hanged, err := env.Exec() + if err != nil { + t.Fatalf("failed to run executor: %v", err) + } + if len(strace) == 0 { + t.Fatalf("no strace output") + } + if failed || hanged { + t.Fatalf("empty program failed") + } +} + +func TestExecute(t *testing.T) { + bin := buildExecutor(t) + defer os.Remove(bin) + + rs, iters := initTest(t) + flags := []uint64{0, FlagStrace, FlagThreaded, FlagStrace | FlagThreaded} + for _, flag := range flags { + env, err := MakeEnv(bin, time.Second, flag) + if err != nil { + t.Fatalf("failed to create env: %v", err) + } + defer env.Close() + + for i := 0; i < iters/len(flags); i++ { + p := prog.Generate(rs, 10, nil) + data := p.SerializeForExec() + copy(env.In, data) + + _, _, _, _, err := env.Exec() + if err != nil { + t.Fatalf("failed to run executor: %v", err) + } + } + } +} + +func TestCompare(t *testing.T) { + t.Skip("flaky") + + bin := buildExecutor(t) + defer os.Remove(bin) + + // Sequence of syscalls that statically linked libc produces on startup. + rawTracePrefix := []string{"execve", "uname", "brk", "brk", "arch_prctl", + "readlink", "brk", "brk", "access"} + executorTracePrefix := []string{"execve", "uname", "brk", "brk", "arch_prctl", + "set_tid_address", "set_robust_list", "futex", "rt_sigaction", "rt_sigaction", + "rt_sigprocmask", "getrlimit", "readlink", "brk", "brk", "access", "mmap", "mmap"} + // These calls produce non-deterministic results, ignore them. + nondet := []string{"getrusage", "msgget", "msgrcv", "msgsnd", "shmget", "semat", "io_setup", "getpgrp", + "getpid", "getpgid", "getppid", "setsid", "ppoll", "keyctl", "ioprio_get", + "move_pages", "kcmp"} + + env1, err := MakeEnv(bin, time.Second, FlagStrace) + if err != nil { + t.Fatalf("failed to create env: %v", err) + } + defer env1.Close() + + rs, iters := initTest(t) + for i := 0; i < iters; i++ { + p := prog.Generate(rs, 10, nil) + data := p.SerializeForExec() + copy(env1.In, data) + + _, strace1, _, _, err := env1.Exec() + if err != nil { + t.Fatalf("failed to run executor: %v", err) + } + + src := p.WriteCSource() + cprog := buildSource(t, src) + defer os.Remove(cprog) + + env2, err := MakeEnv(cprog, time.Second, FlagStrace) + if err != nil { + t.Fatalf("failed to create env: %v", err) + } + defer env2.Close() // yes, that's defer in a loop + + _, strace2, _, _, err := env2.Exec() + if err != nil { + t.Fatalf("failed to run c binary: %v", err) + } + stripPrefix := func(data []byte, prefix []string) string { + prefix0 := prefix + buf := new(bytes.Buffer) + s := bufio.NewScanner(bytes.NewReader(data)) + for s.Scan() { + if strings.HasPrefix(s.Text(), "--- SIG") { + // Signal parameters can contain pid and pc. + continue + } + if len(prefix) == 0 { + skip := false + for _, c := range nondet { + if strings.HasPrefix(s.Text(), c) { + skip = true + break + } + } + if skip { + continue + } + buf.WriteString(s.Text()) + buf.Write([]byte{'\n'}) + continue + } + if !strings.HasPrefix(s.Text(), prefix[0]) { + t.Fatalf("strace output does not start with expected prefix\ngot:\n%s\nexpect prefix: %+v\ncurrent call: %v", data, prefix0, prefix[0]) + } + prefix = prefix[1:] + } + if err := s.Err(); err != nil { + t.Fatalf("failed to scan strace output: %v", err) + } + return buf.String() + } + s1 := stripPrefix(strace1, executorTracePrefix) + s2 := stripPrefix(strace2, rawTracePrefix) + if s1 == "" || s1 != s2 { + t.Logf("program:\n%s\n", p.Serialize()) + t.Fatalf("strace output differs:\n%s\n\n\n%s\n", s1, s2) + } + } +} -- cgit mrf-deployment