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 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 ipc/ipc.go (limited to 'ipc/ipc.go') 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 + } +} -- cgit mrf-deployment