aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-06-17 12:47:35 +0200
committerDmitry Vyukov <dvyukov@google.com>2017-06-17 14:41:15 +0200
commite8e63830a60fc048df976188bf726abc32045162 (patch)
tree9e38c6b9d3e6cbaeba29cc5b29ed36aac0706d3d /pkg
parentbaad3b4b027658f49ba318c41dfc745788ba87d1 (diff)
pkg/ipc: move from ipc
Diffstat (limited to 'pkg')
-rw-r--r--pkg/ipc/gate.go76
-rw-r--r--pkg/ipc/ipc.go678
-rw-r--r--pkg/ipc/ipc_test.go109
3 files changed, 863 insertions, 0 deletions
diff --git a/pkg/ipc/gate.go b/pkg/ipc/gate.go
new file mode 100644
index 000000000..b1b1f1fc8
--- /dev/null
+++ b/pkg/ipc/gate.go
@@ -0,0 +1,76 @@
+// 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 (
+ "sync"
+)
+
+// Gate limits concurrency level and window to the given value.
+// Limitation of concurrency window means that if a very old activity is still
+// running it will not let new activities to start even if concurrency level is low.
+type Gate struct {
+ cv *sync.Cond
+ busy []bool
+ pos int
+ running int
+ stop bool
+ f func()
+}
+
+// If f is not nil, it will be called after each batch of c activities.
+func NewGate(c int, f func()) *Gate {
+ return &Gate{
+ cv: sync.NewCond(new(sync.Mutex)),
+ busy: make([]bool, c),
+ f: f,
+ }
+}
+
+func (g *Gate) Enter() int {
+ g.cv.L.Lock()
+ for g.busy[g.pos] || g.stop {
+ g.cv.Wait()
+ }
+ idx := g.pos
+ g.pos++
+ if g.pos >= len(g.busy) {
+ g.pos = 0
+ }
+ g.busy[idx] = true
+ g.running++
+ if g.running > len(g.busy) {
+ panic("broken gate")
+ }
+ g.cv.L.Unlock()
+ return idx
+}
+
+func (g *Gate) Leave(idx int) {
+ g.cv.L.Lock()
+ if !g.busy[idx] {
+ panic("broken gate")
+ }
+ g.busy[idx] = false
+ g.running--
+ if g.running < 0 {
+ panic("broken gate")
+ }
+ if idx == 0 && g.f != nil {
+ if g.stop {
+ panic("broken gate")
+ }
+ g.stop = true
+ for g.running != 0 {
+ g.cv.Wait()
+ }
+ g.stop = false
+ g.f()
+ g.cv.Broadcast()
+ }
+ if idx == g.pos && !g.stop || g.running == 0 && g.stop {
+ g.cv.Broadcast()
+ }
+ g.cv.L.Unlock()
+}
diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go
new file mode 100644
index 000000000..6c68433b6
--- /dev/null
+++ b/pkg/ipc/ipc.go
@@ -0,0 +1,678 @@
+// 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 (
+ "bytes"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "syscall"
+ "time"
+ "unsafe"
+
+ "github.com/google/syzkaller/pkg/fileutil"
+ "github.com/google/syzkaller/prog"
+)
+
+type Env struct {
+ In []byte
+ Out []byte
+
+ cmd *command
+ inFile *os.File
+ outFile *os.File
+ bin []string
+ pid int
+ config Config
+
+ StatExecs uint64
+ StatRestarts uint64
+}
+
+// Configuration flags for Config.Flags.
+const (
+ FlagDebug = uint64(1) << iota // debug output from executor
+ FlagSignal // collect feedback signals (coverage)
+ FlagThreaded // use multiple threads to mitigate blocked syscalls
+ FlagCollide // collide syscalls to provoke data races
+ FlagSandboxSetuid // impersonate nobody user
+ FlagSandboxNamespace // use namespaces for sandboxing
+ FlagEnableTun // initialize and use tun in executor
+ FlagEnableFault // enable fault injection support
+)
+
+// Per-exec flags for ExecOpts.Flags:
+const (
+ FlagCollectCover = uint64(1) << iota // collect coverage
+ FlagDedupCover // deduplicate coverage in executor
+ FlagInjectFault // inject a fault in this execution (see ExecOpts)
+)
+
+const (
+ outputSize = 16 << 20
+ signalOffset = 15 << 20
+
+ statusFail = 67
+ statusError = 68
+ statusRetry = 69
+)
+
+var (
+ flagThreaded = flag.Bool("threaded", true, "use threaded mode in executor")
+ flagCollide = flag.Bool("collide", true, "collide syscalls to provoke data races")
+ flagSignal = flag.Bool("cover", true, "collect feedback signals (coverage)")
+ flagSandbox = flag.String("sandbox", "setuid", "sandbox for fuzzing (none/setuid/namespace)")
+ flagDebug = flag.Bool("debug", false, "debug output from executor")
+ // Executor protects against most hangs, so we use quite large timeout here.
+ // Executor can be slow due to global locks in namespaces and other things,
+ // so let's better wait than report false misleading crashes.
+ flagTimeout = flag.Duration("timeout", 1*time.Minute, "execution timeout")
+ flagAbortSignal = flag.Int("abort_signal", 0, "initial signal to send to executor in error conditions; upgrades to SIGKILL if executor does not exit")
+ flagBufferSize = flag.Uint64("buffer_size", 0, "internal buffer size (in bytes) for executor output")
+)
+
+type ExecOpts struct {
+ Flags uint64
+ FaultCall int // call index for fault injection (0-based)
+ FaultNth int // fault n-th operation in the call (0-based)
+}
+
+// ExecutorFailure is returned from MakeEnv or from env.Exec when executor terminates by calling fail function.
+// This is considered a logical error (a failed assert).
+type ExecutorFailure string
+
+func (err ExecutorFailure) Error() string {
+ return string(err)
+}
+
+// Config is the configuration for Env.
+type Config struct {
+ // Flags are configuation flags, defined above.
+ Flags uint64
+
+ // Timeout is the execution timeout for a single program.
+ Timeout time.Duration
+
+ // AbortSignal is the signal to send to the executor in error
+ // conditions.
+ AbortSignal syscall.Signal
+
+ // BufferSize is the size of the internal buffer for executor output.
+ BufferSize uint64
+}
+
+func DefaultConfig() (Config, error) {
+ var c Config
+ if *flagThreaded {
+ c.Flags |= FlagThreaded
+ }
+ if *flagCollide {
+ c.Flags |= FlagCollide
+ }
+ if *flagSignal {
+ c.Flags |= FlagSignal
+ }
+ switch *flagSandbox {
+ case "none":
+ case "setuid":
+ c.Flags |= FlagSandboxSetuid
+ case "namespace":
+ c.Flags |= FlagSandboxNamespace
+ default:
+ return Config{}, fmt.Errorf("flag sandbox must contain one of none/setuid/namespace")
+ }
+ if *flagDebug {
+ c.Flags |= FlagDebug
+ }
+ c.Timeout = *flagTimeout
+ c.AbortSignal = syscall.Signal(*flagAbortSignal)
+ c.BufferSize = *flagBufferSize
+ return c, nil
+}
+
+func MakeEnv(bin string, pid int, config Config) (*Env, error) {
+ // IPC timeout must be larger then executor timeout.
+ // Otherwise IPC will kill parent executor but leave child executor alive.
+ if config.Timeout < 7*time.Second {
+ config.Timeout = 7 * time.Second
+ }
+ inf, inmem, err := createMapping(prog.ExecBufferSize)
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if inf != nil {
+ closeMapping(inf, inmem)
+ }
+ }()
+ outf, outmem, err := createMapping(outputSize)
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if outf != nil {
+ closeMapping(outf, outmem)
+ }
+ }()
+ serializeUint64(inmem[0:], config.Flags)
+ serializeUint64(inmem[8:], uint64(pid))
+ inmem = inmem[16:]
+ env := &Env{
+ In: inmem,
+ Out: outmem,
+ inFile: inf,
+ outFile: outf,
+ bin: strings.Split(bin, " "),
+ pid: pid,
+ config: config,
+ }
+ if len(env.bin) == 0 {
+ return nil, fmt.Errorf("binary is empty string")
+ }
+ env.bin[0], err = filepath.Abs(env.bin[0]) // we are going to chdir
+ if err != nil {
+ return nil, fmt.Errorf("filepath.Abs failed: %v", err)
+ }
+ // Append pid to binary name.
+ // E.g. if binary is 'syz-executor' and pid=15,
+ // we create a link from 'syz-executor15' to 'syz-executor' and use 'syz-executor15' as binary.
+ // This allows to easily identify program that lead to a crash in the log.
+ // Log contains pid in "executing program 15" and crashes usually contain "Comm: syz-executor15".
+ base := filepath.Base(env.bin[0])
+ pidStr := fmt.Sprint(pid)
+ if len(base)+len(pidStr) >= 16 {
+ // TASK_COMM_LEN is currently set to 16
+ base = base[:15-len(pidStr)]
+ }
+ binCopy := filepath.Join(filepath.Dir(env.bin[0]), base+pidStr)
+ if err := os.Link(env.bin[0], binCopy); err == nil {
+ env.bin[0] = binCopy
+ }
+ inf = nil
+ outf = nil
+ return env, nil
+}
+
+func (env *Env) Close() error {
+ if env.cmd != nil {
+ env.cmd.close()
+ }
+ 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
+ }
+}
+
+type CallInfo struct {
+ Signal []uint32 // feedback signal, filled if FlagSignal is set
+ Cover []uint32 // per-call coverage, filled if FlagSignal is set and cover == true,
+ //if dedup == false, then cov effectively contains a trace, otherwise duplicates are removed
+ Errno int // call errno (0 if the call was successful)
+ FaultInjected bool
+}
+
+// Exec starts executor binary to execute program p and returns information about the execution:
+// output: process output
+// info: per-call info
+// failed: true if executor has detected a kernel bug
+// hanged: program hanged and was killed
+// err0: failed to start process, or executor has detected a logical error
+func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info []CallInfo, failed, hanged bool, err0 error) {
+ if p != nil {
+ // Copy-in serialized program.
+ if err := p.SerializeForExec(env.In, env.pid); err != nil {
+ err0 = fmt.Errorf("executor %v: failed to serialize: %v", env.pid, err)
+ return
+ }
+ }
+ if env.config.Flags&FlagSignal != 0 {
+ // Zero out the first two words (ncmd and nsig), so that we don't have garbage there
+ // if executor crashes before writing non-garbage there.
+ for i := 0; i < 4; i++ {
+ env.Out[i] = 0
+ }
+ }
+
+ atomic.AddUint64(&env.StatExecs, 1)
+ if env.cmd == nil {
+ atomic.AddUint64(&env.StatRestarts, 1)
+ env.cmd, err0 = makeCommand(env.pid, env.bin, env.config, env.inFile, env.outFile)
+ if err0 != nil {
+ return
+ }
+ }
+ var restart bool
+ output, failed, hanged, restart, err0 = env.cmd.exec(opts)
+ if err0 != nil || restart {
+ env.cmd.close()
+ env.cmd = nil
+ return
+ }
+
+ if env.config.Flags&FlagSignal == 0 || p == nil {
+ return
+ }
+ info, err0 = env.readOutCoverage(p)
+ return
+}
+
+func (env *Env) readOutCoverage(p *prog.Prog) (info []CallInfo, err0 error) {
+ out := ((*[1 << 28]uint32)(unsafe.Pointer(&env.Out[0])))[:len(env.Out)/int(unsafe.Sizeof(uint32(0)))]
+ readOut := func(v *uint32) bool {
+ if len(out) == 0 {
+ return false
+ }
+ *v = out[0]
+ out = out[1:]
+ return true
+ }
+
+ var ncmd uint32
+ if !readOut(&ncmd) {
+ err0 = fmt.Errorf("executor %v: failed to read output coverage", env.pid)
+ return
+ }
+ info = make([]CallInfo, len(p.Calls))
+ for i := range info {
+ info[i].Errno = -1 // not executed
+ }
+ dumpCov := func() string {
+ buf := new(bytes.Buffer)
+ for i, inf := range info {
+ str := "nil"
+ if inf.Signal != nil {
+ str = fmt.Sprint(len(inf.Signal))
+ }
+ fmt.Fprintf(buf, "%v:%v|", i, str)
+ }
+ return buf.String()
+ }
+ for i := uint32(0); i < ncmd; i++ {
+ var callIndex, callNum, errno, faultInjected, signalSize, coverSize uint32
+ if !readOut(&callIndex) || !readOut(&callNum) || !readOut(&errno) || !readOut(&faultInjected) || !readOut(&signalSize) || !readOut(&coverSize) {
+ err0 = fmt.Errorf("executor %v: failed to read output coverage", env.pid)
+ return
+ }
+ if int(callIndex) >= len(info) {
+ err0 = fmt.Errorf("executor %v: failed to read output coverage: record %v, call %v, total calls %v (cov: %v)",
+ env.pid, i, callIndex, len(info), dumpCov())
+ return
+ }
+ c := p.Calls[callIndex]
+ if num := c.Meta.ID; uint32(num) != callNum {
+ err0 = fmt.Errorf("executor %v: failed to read output coverage: record %v call %v: expect syscall %v, got %v, executed %v (cov: %v)",
+ env.pid, i, callIndex, num, callNum, ncmd, dumpCov())
+ return
+ }
+ if info[callIndex].Signal != nil {
+ err0 = fmt.Errorf("executor %v: failed to read output coverage: double coverage for call %v (cov: %v)",
+ env.pid, callIndex, dumpCov())
+ return
+ }
+ info[callIndex].Errno = int(errno)
+ info[callIndex].FaultInjected = faultInjected != 0
+ if signalSize > uint32(len(out)) {
+ err0 = fmt.Errorf("executor %v: failed to read output signal: record %v, call %v, signalsize=%v coversize=%v",
+ env.pid, i, callIndex, signalSize, coverSize)
+ return
+ }
+ info[callIndex].Signal = out[:signalSize:signalSize]
+ out = out[signalSize:]
+ if coverSize > uint32(len(out)) {
+ err0 = fmt.Errorf("executor %v: failed to read output coverage: record %v, call %v, signalsize=%v coversize=%v",
+ env.pid, i, callIndex, signalSize, coverSize)
+ return
+ }
+ info[callIndex].Cover = out[:coverSize:coverSize]
+ out = out[coverSize:]
+ }
+ return
+}
+
+func createMapping(size int) (f *os.File, mem []byte, err error) {
+ f, err = ioutil.TempFile("./", "syzkaller-shm")
+ if err != nil {
+ err = fmt.Errorf("failed to create temp file: %v", err)
+ return
+ }
+ if err = f.Truncate(int64(size)); err != nil {
+ err = fmt.Errorf("failed to truncate shm file: %v", err)
+ f.Close()
+ os.Remove(f.Name())
+ return
+ }
+ f.Close()
+ fname := f.Name()
+ f, err = os.OpenFile(f.Name(), os.O_RDWR, 0)
+ if err != nil {
+ err = fmt.Errorf("failed to open shm file: %v", err)
+ os.Remove(fname)
+ return
+ }
+ mem, err = syscall.Mmap(int(f.Fd()), 0, size, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
+ if err != nil {
+ err = fmt.Errorf("failed to mmap shm file: %v", err)
+ 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
+ }
+}
+
+type command struct {
+ pid int
+ config Config
+ cmd *exec.Cmd
+ dir string
+ readDone chan []byte
+ exited chan struct{}
+ inrp *os.File
+ outwp *os.File
+}
+
+func makeCommand(pid int, bin []string, config Config, inFile *os.File, outFile *os.File) (*command, error) {
+ dir, err := ioutil.TempDir("./", "syzkaller-testdir")
+ if err != nil {
+ return nil, fmt.Errorf("failed to create temp dir: %v", err)
+ }
+
+ c := &command{
+ pid: pid,
+ config: config,
+ dir: dir,
+ }
+ defer func() {
+ if c != nil {
+ c.close()
+ }
+ }()
+
+ if config.Flags&(FlagSandboxSetuid|FlagSandboxNamespace) != 0 {
+ if err := os.Chmod(dir, 0777); err != nil {
+ return nil, fmt.Errorf("failed to chmod temp dir: %v", err)
+ }
+ }
+
+ // Output capture pipe.
+ rp, wp, err := os.Pipe()
+ if err != nil {
+ return nil, fmt.Errorf("failed to create pipe: %v", err)
+ }
+ defer wp.Close()
+
+ // Input command pipe.
+ inrp, inwp, err := os.Pipe()
+ if err != nil {
+ return nil, fmt.Errorf("failed to create pipe: %v", err)
+ }
+ defer inwp.Close()
+ c.inrp = inrp
+
+ // Output command pipe.
+ outrp, outwp, err := os.Pipe()
+ if err != nil {
+ return nil, fmt.Errorf("failed to create pipe: %v", err)
+ }
+ defer outrp.Close()
+ c.outwp = outwp
+
+ c.readDone = make(chan []byte, 1)
+ c.exited = make(chan struct{})
+
+ cmd := exec.Command(bin[0], bin[1:]...)
+ cmd.ExtraFiles = []*os.File{inFile, outFile, outrp, inwp}
+ cmd.Env = []string{}
+ cmd.Dir = dir
+ if config.Flags&FlagDebug == 0 {
+ cmd.Stdout = wp
+ cmd.Stderr = wp
+ go func(c *command) {
+ // Read out output in case executor constantly prints something.
+ bufSize := c.config.BufferSize
+ if bufSize == 0 {
+ bufSize = 128 << 10
+ }
+ output := make([]byte, bufSize)
+ var size uint64
+ for {
+ n, err := rp.Read(output[size:])
+ if n > 0 {
+ size += uint64(n)
+ if size >= bufSize*3/4 {
+ copy(output, output[size-bufSize/2:size])
+ size = bufSize / 2
+ }
+ }
+ if err != nil {
+ rp.Close()
+ c.readDone <- output[:size]
+ close(c.readDone)
+ return
+ }
+ }
+ }(c)
+ } else {
+ close(c.readDone)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stdout
+ }
+ if err := cmd.Start(); err != nil {
+ return nil, fmt.Errorf("failed to start executor binary: %v", err)
+ }
+ c.cmd = cmd
+ wp.Close()
+ inwp.Close()
+ if err := c.waitServing(); err != nil {
+ return nil, err
+ }
+
+ tmp := c
+ c = nil // disable defer above
+ return tmp, nil
+}
+
+func (c *command) close() {
+ if c.cmd != nil {
+ c.abort()
+ c.wait()
+ }
+ fileutil.UmountAll(c.dir)
+ os.RemoveAll(c.dir)
+ if c.inrp != nil {
+ c.inrp.Close()
+ }
+ if c.outwp != nil {
+ c.outwp.Close()
+ }
+}
+
+// Wait for executor to start serving (sandbox setup can take significant time).
+func (c *command) waitServing() error {
+ read := make(chan error, 1)
+ go func() {
+ var buf [1]byte
+ _, err := c.inrp.Read(buf[:])
+ read <- err
+ }()
+ timeout := time.NewTimer(time.Minute)
+ select {
+ case err := <-read:
+ timeout.Stop()
+ if err != nil {
+ c.abort()
+ output := <-c.readDone
+ err = fmt.Errorf("executor is not serving: %v\n%s", err, output)
+ c.wait()
+ if c.cmd.ProcessState != nil {
+ sys := c.cmd.ProcessState.Sys()
+ if ws, ok := sys.(syscall.WaitStatus); ok {
+ // Magic values returned by executor.
+ if ws.ExitStatus() == statusFail {
+ err = ExecutorFailure(fmt.Sprintf("executor is not serving:\n%s", output))
+ }
+ }
+ }
+ }
+ return err
+ case <-timeout.C:
+ return fmt.Errorf("executor is not serving")
+ }
+}
+
+// abort sends the abort signal to the command and then SIGKILL if wait doesn't
+// return within 5s.
+func (c *command) abort() {
+ sig := c.config.AbortSignal
+ if sig <= 0 || sig >= 32 {
+ sig = syscall.SIGKILL
+ }
+ syscall.Kill(c.cmd.Process.Pid, sig)
+ if sig != syscall.SIGKILL {
+ go func() {
+ t := time.NewTimer(5 * time.Second)
+ select {
+ case <-t.C:
+ syscall.Kill(c.cmd.Process.Pid, syscall.SIGKILL)
+ case <-c.exited:
+ t.Stop()
+ }
+ }()
+ }
+}
+
+func (c *command) wait() error {
+ err := c.cmd.Wait()
+ select {
+ case <-c.exited:
+ // c.exited closed by an earlier call to wait.
+ default:
+ close(c.exited)
+ }
+ return err
+}
+
+func (c *command) exec(opts *ExecOpts) (output []byte, failed, hanged, restart bool, err0 error) {
+ if opts.Flags&FlagInjectFault != 0 {
+ enableFaultOnce.Do(enableFaultInjection)
+ }
+ var inCmd [24]byte
+ serializeUint64(inCmd[0:], opts.Flags)
+ serializeUint64(inCmd[8:], uint64(opts.FaultCall))
+ serializeUint64(inCmd[16:], uint64(opts.FaultNth))
+ if _, err := c.outwp.Write(inCmd[:]); err != nil {
+ output = <-c.readDone
+ err0 = fmt.Errorf("failed to write control pipe: %v", err)
+ return
+ }
+ done := make(chan bool)
+ hang := make(chan bool)
+ go func() {
+ t := time.NewTimer(c.config.Timeout)
+ select {
+ case <-t.C:
+ c.abort()
+ hang <- true
+ case <-done:
+ t.Stop()
+ hang <- false
+ }
+ }()
+ var reply [1]byte
+ readN, readErr := c.inrp.Read(reply[:])
+ close(done)
+ status := 0
+ if readErr == nil {
+ if readN != len(reply) {
+ panic(fmt.Sprintf("executor %v: read only %v bytes", c.pid, readN))
+ }
+ status = int(reply[0])
+ if status == 0 {
+ // Program was OK.
+ <-hang
+ return
+ }
+ // Executor writes magic values into the pipe before exiting,
+ // so proceed with killing and joining it.
+ }
+ c.abort()
+ output = <-c.readDone
+ if err := c.wait(); <-hang {
+ hanged = true
+ // In all likelihood, this will be duplicated by the default
+ // case below, but that's fine.
+ output = append(output, []byte(err.Error())...)
+ output = append(output, '\n')
+ }
+ // Handle magic values returned by executor.
+ switch status {
+ case statusFail:
+ err0 = ExecutorFailure(fmt.Sprintf("executor failed: %s", output))
+ case statusError:
+ err0 = fmt.Errorf("executor detected kernel bug")
+ failed = true
+ case statusRetry:
+ // This is a temporal error (ENOMEM) or an unfortunate
+ // program that messes with testing setup (e.g. kills executor
+ // loop process). Pretend that nothing happened.
+ // It's better than a false crash report.
+ err0 = nil
+ hanged = false
+ restart = true
+ default:
+ // Failed to get a valid (or perhaps any) status from the
+ // executor.
+ //
+ // Once the executor is serving the status is always written to
+ // the pipe, so we don't bother to check the specific exit
+ // codes from wait.
+ err0 = fmt.Errorf("invalid (or no) executor status received: %d, executor exit: %s", status, c.cmd.ProcessState)
+ }
+ return
+}
+
+func serializeUint64(buf []byte, v uint64) {
+ for i := 0; i < 8; i++ {
+ buf[i] = byte(v >> (8 * uint(i)))
+ }
+}
+
+var enableFaultOnce sync.Once
+
+func enableFaultInjection() {
+ if err := ioutil.WriteFile("/sys/kernel/debug/failslab/ignore-gfp-wait", []byte("N"), 0600); err != nil {
+ panic(fmt.Sprintf("failed to write /sys/kernel/debug/failslab/ignore-gfp-wait: %v", err))
+ }
+ if err := ioutil.WriteFile("/sys/kernel/debug/fail_futex/ignore-private", []byte("N"), 0600); err != nil {
+ panic(fmt.Sprintf("failed to write /sys/kernel/debug/fail_futex/ignore-private: %v", err))
+ }
+}
diff --git a/pkg/ipc/ipc_test.go b/pkg/ipc/ipc_test.go
new file mode 100644
index 000000000..b7d591d5d
--- /dev/null
+++ b/pkg/ipc/ipc_test.go
@@ -0,0 +1,109 @@
+// 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 (
+ "math/rand"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/google/syzkaller/pkg/csource"
+ "github.com/google/syzkaller/pkg/fileutil"
+ "github.com/google/syzkaller/prog"
+)
+
+const timeout = 10 * time.Second
+
+func buildExecutor(t *testing.T) string {
+ return buildProgram(t, filepath.FromSlash("../../executor/executor.cc"))
+}
+
+func buildSource(t *testing.T, src []byte) string {
+ tmp, err := fileutil.WriteTempFile(src)
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+ defer os.Remove(tmp)
+ return buildProgram(t, tmp)
+}
+
+func buildProgram(t *testing.T, src string) string {
+ bin, err := csource.Build("c++", src)
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+ return bin
+}
+
+func initTest(t *testing.T) (rand.Source, int) {
+ t.Parallel()
+ 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)
+
+ cfg := Config{
+ Timeout: timeout,
+ }
+ env, err := MakeEnv(bin, 0, cfg)
+ if err != nil {
+ t.Fatalf("failed to create env: %v", err)
+ }
+ defer env.Close()
+
+ p := new(prog.Prog)
+ opts := &ExecOpts{}
+ output, _, failed, hanged, err := env.Exec(opts, p)
+ if err != nil {
+ t.Fatalf("failed to run executor: %v", err)
+ }
+ if len(output) != 0 {
+ t.Fatalf("output on empty program")
+ }
+ if failed || hanged {
+ t.Fatalf("empty program failed")
+ }
+}
+
+func TestExecute(t *testing.T) {
+ rs, iters := initTest(t)
+ flags := []uint64{0, FlagThreaded, FlagThreaded | FlagCollide}
+
+ bin := buildExecutor(t)
+ defer os.Remove(bin)
+
+ for _, flag := range flags {
+ t.Logf("testing flags 0x%x\n", flag)
+ cfg := Config{
+ Flags: flag,
+ Timeout: timeout,
+ }
+ env, err := MakeEnv(bin, 0, cfg)
+ 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)
+ opts := &ExecOpts{}
+ output, _, _, _, err := env.Exec(opts, p)
+ if err != nil {
+ t.Logf("program:\n%s\n", p.Serialize())
+ t.Fatalf("failed to run executor: %v\n%s", err, output)
+ }
+ }
+ }
+}