aboutsummaryrefslogtreecommitdiffstats
path: root/ipc
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2015-10-12 10:16:57 +0200
committerDmitry Vyukov <dvyukov@google.com>2015-10-12 10:16:57 +0200
commit874c5754bb22dbf77d6b600ff91f0f4f1fc5073a (patch)
tree0075fbd088046ad5c86e6e972235701d68b3ce7c /ipc
initial commit
Diffstat (limited to 'ipc')
-rw-r--r--ipc/ipc.go222
-rw-r--r--ipc/ipc_test.go235
2 files changed, 457 insertions, 0 deletions
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)
+ }
+ }
+}