aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/ipc
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-10-16 12:18:50 +0200
committerDmitry Vyukov <dvyukov@google.com>2017-10-16 14:21:54 +0200
commit85b1f93f8dbbc767c564e494a6353aa3517d5d49 (patch)
tree702d2318a2ddcf1a576294a6a4981c58abcfcf61 /pkg/ipc
parentf78642861b4dbe396a67d5e2a750e22f83f3edd5 (diff)
executor, pkg/ipc: unify ipc protocol between linux and other OSes
We currently use more complex and functional protocol on linux, and a simple ad-hoc protocol on other OSes. This leads to code duplication in both ipc and executor. Linux supports coverage, shared memory communication and fork server, which would also be useful for most other OSes. Unify communication protocol and parametrize it by (1) use of shmem or only pipes, (2) use of fork server. This reduces duplication in ipc and executor and will allow to support the useful features for other OSes easily. Finally, this fixes akaros support as it currently uses syz-stress running on host (linux) and executor running on akaros.
Diffstat (limited to 'pkg/ipc')
-rw-r--r--pkg/ipc/ipc.go745
-rw-r--r--pkg/ipc/ipc_linux.go641
-rw-r--r--pkg/ipc/ipc_simple.go109
-rw-r--r--pkg/ipc/ipc_test.go30
4 files changed, 752 insertions, 773 deletions
diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go
index bae83806c..8abf54054 100644
--- a/pkg/ipc/ipc.go
+++ b/pkg/ipc/ipc.go
@@ -4,11 +4,25 @@
package ipc
import (
+ "bytes"
"flag"
"fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+ "sync/atomic"
"time"
+ "unsafe"
+ "github.com/google/syzkaller/pkg/host"
+ "github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/prog"
+ "github.com/google/syzkaller/sys/targets"
)
// Configuration flags for Config.Flags.
@@ -21,6 +35,8 @@ const (
FlagSandboxNamespace // use namespaces for sandboxing
FlagEnableTun // initialize and use tun in executor
FlagEnableFault // enable fault injection support
+ FlagUseShmem // use shared memory instead of pipes for communication
+ FlagUseForkServer // use extended protocol with handshake
)
// Per-exec flags for ExecOpts.Flags:
@@ -40,17 +56,15 @@ const (
)
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")
+ 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", "none", "sandbox for fuzzing (none/setuid/namespace)")
+ flagDebug = flag.Bool("debug", false, "debug output from executor")
+ flagTimeout = flag.Duration("timeout", 0, "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")
+ flagIPC = flag.String("ipc", "", "ipc scheme (pipe/shmem)")
)
type ExecOpts struct {
@@ -108,6 +122,24 @@ func DefaultConfig() (Config, error) {
c.Timeout = *flagTimeout
c.AbortSignal = *flagAbortSignal
c.BufferSize = *flagBufferSize
+
+ sysTarget := targets.List[runtime.GOOS][runtime.GOARCH]
+ if sysTarget.ExecutorUsesShmem {
+ c.Flags |= FlagUseShmem
+ }
+ if sysTarget.ExecutorUsesForkServer {
+ c.Flags |= FlagUseForkServer
+ }
+ switch *flagIPC {
+ case "":
+ case "pipe":
+ c.Flags &^= FlagUseShmem
+ case "shmem":
+ c.Flags |= FlagUseShmem
+ default:
+ return Config{}, fmt.Errorf("unknown ipc scheme: %v", *flagIPC)
+ }
+
return c, nil
}
@@ -127,3 +159,698 @@ func GetCompMaps(info []CallInfo) []prog.CompMap {
}
return compMaps
}
+
+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
+}
+
+const (
+ // Comparison types masks taken from KCOV headers.
+ compSizeMask = 6
+ compSize8 = 6
+ compConstMask = 1
+)
+
+func MakeEnv(bin string, pid int, config Config) (*Env, error) {
+ const (
+ executorTimeout = 5 * time.Second
+ minTimeout = executorTimeout + 2*time.Second
+ )
+ if config.Timeout == 0 {
+ // 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.
+ config.Timeout = time.Minute
+ if config.Flags&FlagUseForkServer == 0 {
+ // If there is no fork server, executor does not have internal timeout.
+ config.Timeout = executorTimeout
+ }
+ }
+ // IPC timeout must be larger then executor timeout.
+ // Otherwise IPC will kill parent executor but leave child executor alive.
+ if config.Flags&FlagUseForkServer != 0 && config.Timeout < minTimeout {
+ config.Timeout = minTimeout
+ }
+
+ var inf, outf *os.File
+ var inmem, outmem []byte
+ if config.Flags&FlagUseShmem != 0 {
+ var err error
+ inf, inmem, err = osutil.CreateMemMappedFile(prog.ExecBufferSize)
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if inf != nil {
+ osutil.CloseMemMappedFile(inf, inmem)
+ }
+ }()
+ outf, outmem, err = osutil.CreateMemMappedFile(outputSize)
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if outf != nil {
+ osutil.CloseMemMappedFile(outf, outmem)
+ }
+ }()
+ } else {
+ inmem = make([]byte, prog.ExecBufferSize)
+ }
+ 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] = osutil.Abs(env.bin[0]) // we are going to chdir
+ // 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()
+ }
+ var err1, err2 error
+ if env.inFile != nil {
+ err1 = osutil.CloseMemMappedFile(env.inFile, env.in)
+ }
+ if env.outFile != nil {
+ err2 = osutil.CloseMemMappedFile(env.outFile, env.out)
+ }
+ switch {
+ case err1 != nil:
+ return err1
+ case err2 != nil:
+ return err2
+ default:
+ return nil
+ }
+}
+
+var enableFaultOnce sync.Once
+
+// 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 opts.Flags&FlagInjectFault != 0 {
+ enableFaultOnce.Do(func() {
+ if err := host.EnableFaultInjection(); err != nil {
+ panic(err)
+ }
+ })
+ }
+ // Copy-in serialized program.
+ progSize, err := p.SerializeForExec(env.in, env.pid)
+ if err != nil {
+ err0 = fmt.Errorf("executor %v: failed to serialize: %v", env.pid, err)
+ return
+ }
+ var progData []byte
+ if env.config.Flags&FlagUseShmem == 0 {
+ progData = env.in[:progSize]
+ }
+ needOutput := env.config.Flags&FlagSignal != 0 || opts.Flags&FlagCollectComps != 0
+ if needOutput && env.out != nil {
+ // 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, progData)
+ if err0 != nil || restart {
+ env.cmd.close()
+ env.cmd = nil
+ return
+ }
+
+ if needOutput && env.out != nil {
+ 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
+ }
+
+ readOutAndSetErr := func(v *uint32, msg string, args ...interface{}) bool {
+ if !readOut(v) {
+ err0 = fmt.Errorf(msg, args)
+ return false
+ }
+ return true
+ }
+
+ // Reads out a 64 bits int in Little-endian as two blocks of 32 bits.
+ readOut64 := func(v *uint64, msg string, args ...interface{}) bool {
+ var a, b uint32
+ if !(readOutAndSetErr(&a, msg, args) && readOutAndSetErr(&b, msg, args)) {
+ return false
+ }
+ *v = uint64(a) + uint64(b)<<32
+ return true
+ }
+
+ var ncmd uint32
+ if !readOutAndSetErr(&ncmd,
+ "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, compsSize uint32
+ if !readOut(&callIndex) || !readOut(&callNum) || !readOut(&errno) || !readOut(&faultInjected) || !readOut(&signalSize) || !readOut(&coverSize) || !readOut(&compsSize) {
+ 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
+ }
+ // Read out signals.
+ info[callIndex].Signal = out[:signalSize:signalSize]
+ out = out[signalSize:]
+ // Read out coverage.
+ 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:]
+ // Read out comparisons.
+ compMap := make(prog.CompMap)
+ for j := uint32(0); j < compsSize; j++ {
+ var typ uint32
+ var op1, op2 uint64
+ if !readOutAndSetErr(&typ,
+ "executor %v: failed while reading type of comparison %v", env.pid, j) {
+ return
+ }
+ if typ > compConstMask|compSizeMask {
+ err0 = fmt.Errorf("executor %v: got wrong value (%v) while reading type of comparison %v",
+ env.pid, typ, j)
+ return
+ }
+
+ isSize8 := (typ & compSizeMask) == compSize8
+ isConst := (typ & compConstMask) != 0
+ arg1ErrString := "executor %v: failed while reading op1 of comparison %v"
+ arg2ErrString := "executor %v: failed while reading op2 of comparison %v"
+ if isSize8 {
+ var tmp1, tmp2 uint32
+ if !readOutAndSetErr(&tmp1, arg1ErrString, env.pid, j) ||
+ !readOutAndSetErr(&tmp2, arg2ErrString, env.pid, j) {
+ return
+ }
+ op1 = uint64(tmp1)
+ op2 = uint64(tmp2)
+ } else {
+ if !readOut64(&op1, arg1ErrString, env.pid, j) ||
+ !readOut64(&op2, arg2ErrString, env.pid, j) {
+ return
+ }
+ }
+ if op1 == op2 {
+ // It's useless to store such comparisons.
+ continue
+ }
+ compMap.AddComp(op2, op1)
+ if isConst {
+ // If one of the operands was const, then this operand is always
+ // placed first in the instrumented callbacks. Such an operand
+ // could not be an argument of our syscalls (because otherwise
+ // it wouldn't be const), thus we simply ignore it.
+ continue
+ }
+ compMap.AddComp(op1, op2)
+ }
+ info[callIndex].Comps = compMap
+ }
+ return
+}
+
+type command struct {
+ pid int
+ config Config
+ cmd *exec.Cmd
+ dir string
+ readDone chan []byte
+ exited chan struct{}
+ inrp *os.File
+ outwp *os.File
+}
+
+const (
+ inMagic = uint64(0xbadc0ffeebadface)
+ outMagic = uint32(0xbadf00d)
+)
+
+type handshakeReq struct {
+ magic uint64
+ flags uint64 // env flags
+ pid uint64
+}
+
+type handshakeReply struct {
+ magic uint32
+}
+
+type executeReq struct {
+ magic uint64
+ envFlags uint64 // env flags
+ execFlags uint64 // exec flags
+ pid uint64
+ faultCall uint64
+ faultNth uint64
+ progSize uint64
+ // prog follows on pipe or in shmem
+}
+
+type executeReply struct {
+ magic uint32
+ // If done is 0, then this is call completion message followed by callReply.
+ // If done is 1, then program execution is finished and status is set.
+ done uint32
+ status uint32
+}
+
+type callReply struct {
+ callIndex uint32
+ callNum uint32
+ errno uint32
+ blocked uint32
+ faultInjected uint32
+ signalSize uint32
+ coverSize uint32
+ compsSize uint32
+ // signal/cover/comps follow
+}
+
+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()
+
+ // executor->ipc 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
+
+ // ipc->executor 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:]...)
+ if inFile != nil && outFile != nil {
+ cmd.ExtraFiles = []*os.File{inFile, outFile}
+ }
+ cmd.Env = []string{}
+ cmd.Dir = dir
+ cmd.Stdin = outrp
+ cmd.Stdout = inwp
+ if config.Flags&FlagDebug != 0 {
+ close(c.readDone)
+ cmd.Stderr = os.Stdout
+ } else if config.Flags&FlagUseForkServer == 0 {
+ close(c.readDone)
+ // TODO: read out output after execution failure.
+ } else {
+ 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)
+ }
+ 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 c.config.Flags&FlagUseForkServer != 0 {
+ if err := c.handshake(); 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()
+ }
+ osutil.UmountAll(c.dir)
+ os.RemoveAll(c.dir)
+ if c.inrp != nil {
+ c.inrp.Close()
+ }
+ if c.outwp != nil {
+ c.outwp.Close()
+ }
+}
+
+// handshake sends handshakeReq and waits for handshakeReply (sandbox setup can take significant time).
+func (c *command) handshake() error {
+ req := &handshakeReq{
+ magic: inMagic,
+ flags: c.config.Flags,
+ pid: uint64(c.pid),
+ }
+ reqData := (*[unsafe.Sizeof(*req)]byte)(unsafe.Pointer(req))[:]
+ if _, err := c.outwp.Write(reqData); err != nil {
+ return c.handshakeError(fmt.Errorf("executor %v: failed to write control pipe: %v",
+ c.pid, err))
+ }
+
+ read := make(chan error, 1)
+ go func() {
+ reply := &handshakeReply{}
+ replyData := (*[unsafe.Sizeof(*reply)]byte)(unsafe.Pointer(reply))[:]
+ if _, err := io.ReadFull(c.inrp, replyData); err != nil {
+ read <- err
+ return
+ }
+ if reply.magic != outMagic {
+ read <- fmt.Errorf("executor %v: bad handshake reply magic 0x%x",
+ c.pid, reply.magic)
+ return
+ }
+ read <- nil
+ }()
+ timeout := time.NewTimer(time.Minute)
+ select {
+ case err := <-read:
+ timeout.Stop()
+ if err != nil {
+ return c.handshakeError(err)
+ }
+ return nil
+ case <-timeout.C:
+ return c.handshakeError(fmt.Errorf("executor %v: not serving", c.pid))
+ }
+}
+
+func (c *command) handshakeError(err error) error {
+ c.abort()
+ output := <-c.readDone
+ err = fmt.Errorf("%v\n%s", err, output)
+ c.wait()
+ if c.cmd.ProcessState != nil {
+ // Magic values returned by executor.
+ if osutil.ProcessExitStatus(c.cmd.ProcessState) == statusFail {
+ err = ExecutorFailure(err.Error())
+ }
+ }
+ return err
+}
+
+// abort sends the abort signal to the command and then SIGKILL if wait doesn't return within 5s.
+func (c *command) abort() {
+ if osutil.ProcessSignal(c.cmd.Process, c.config.AbortSignal) {
+ return
+ }
+ go func() {
+ t := time.NewTimer(5 * time.Second)
+ select {
+ case <-t.C:
+ c.cmd.Process.Kill()
+ 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, progData []byte) (output []byte, failed, hanged,
+ restart bool, err0 error) {
+ req := &executeReq{
+ magic: inMagic,
+ envFlags: c.config.Flags,
+ execFlags: opts.Flags,
+ pid: uint64(c.pid),
+ faultCall: uint64(opts.FaultCall),
+ faultNth: uint64(opts.FaultNth),
+ progSize: uint64(len(progData)),
+ }
+ reqData := (*[unsafe.Sizeof(*req)]byte)(unsafe.Pointer(req))[:]
+ if _, err := c.outwp.Write(reqData); err != nil {
+ output = <-c.readDone
+ err0 = fmt.Errorf("executor %v: failed to write control pipe: %v", c.pid, err)
+ return
+ }
+ if progData != nil {
+ if _, err := c.outwp.Write(progData); err != nil {
+ output = <-c.readDone
+ err0 = fmt.Errorf("executor %v: failed to write control pipe: %v", c.pid, err)
+ return
+ }
+ }
+ // At this point program is executing.
+
+ 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
+ }
+ }()
+ exitStatus := 0
+ if c.config.Flags&FlagUseForkServer == 0 {
+ restart = true
+ c.cmd.Wait()
+ close(done)
+ <-hang
+ exitStatus = osutil.ProcessExitStatus(c.cmd.ProcessState)
+ } else {
+ reply := &executeReply{}
+ replyData := (*[unsafe.Sizeof(*reply)]byte)(unsafe.Pointer(reply))[:]
+ _, readErr := io.ReadFull(c.inrp, replyData)
+ close(done)
+ if readErr == nil {
+ if reply.magic != outMagic {
+ panic(fmt.Sprintf("executor %v: got bad reply magic 0x%x", c.pid, reply.magic))
+ }
+ if reply.done == 0 {
+ // TODO: call completion/coverage over the control pipe is not supported yet.
+ panic(fmt.Sprintf("executor %v: got call reply", c.pid))
+ }
+ if reply.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')
+ }
+ exitStatus = int(reply.status)
+ }
+ // Handle magic values returned by executor.
+ switch exitStatus {
+ case statusFail:
+ err0 = ExecutorFailure(fmt.Sprintf("executor %v: failed: %s", c.pid, output))
+ case statusError:
+ err0 = fmt.Errorf("executor %v: detected kernel bug", c.pid)
+ 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:
+ if c.config.Flags&FlagUseForkServer == 0 {
+ return
+ }
+ // 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("executor %v: invalid status %d, exit status: %s",
+ c.pid, exitStatus, c.cmd.ProcessState)
+ }
+ return
+}
+
+func serializeUint64(buf []byte, v uint64) {
+ for i := 0; i < 8; i++ {
+ buf[i] = byte(v >> (8 * uint(i)))
+ }
+}
diff --git a/pkg/ipc/ipc_linux.go b/pkg/ipc/ipc_linux.go
deleted file mode 100644
index 0279b0ee8..000000000
--- a/pkg/ipc/ipc_linux.go
+++ /dev/null
@@ -1,641 +0,0 @@
-// 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"
- "fmt"
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
- "sync"
- "sync/atomic"
- "syscall"
- "time"
- "unsafe"
-
- "github.com/google/syzkaller/pkg/osutil"
- "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
-}
-
-const (
- // Comparison types masks taken from KCOV headers.
- compSizeMask = 6
- compSize8 = 6
- compConstMask = 1
-)
-
-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] = osutil.Abs(env.bin[0]) // we are going to chdir
- // 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
- }
-}
-
-// 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 p == nil || env.config.Flags&FlagSignal == 0 &&
- env.config.Flags&FlagCollectComps == 0 {
- 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
- }
-
- readOutAndSetErr := func(v *uint32, msg string, args ...interface{}) bool {
- if !readOut(v) {
- err0 = fmt.Errorf(msg, args)
- return false
- }
- return true
- }
-
- // Reads out a 64 bits int in Little-endian as two blocks of 32 bits.
- readOut64 := func(v *uint64, msg string, args ...interface{}) bool {
- var a, b uint32
- if !(readOutAndSetErr(&a, msg, args) && readOutAndSetErr(&b, msg, args)) {
- return false
- }
- *v = uint64(a) + uint64(b)<<32
- return true
- }
-
- var ncmd uint32
- if !readOutAndSetErr(&ncmd,
- "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, compsSize uint32
- if !readOut(&callIndex) || !readOut(&callNum) || !readOut(&errno) || !readOut(&faultInjected) || !readOut(&signalSize) || !readOut(&coverSize) || !readOut(&compsSize) {
- 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
- }
- // Read out signals.
- info[callIndex].Signal = out[:signalSize:signalSize]
- out = out[signalSize:]
- // Read out coverage.
- 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:]
- // Read out comparisons.
- compMap := make(prog.CompMap)
- for j := uint32(0); j < compsSize; j++ {
- var typ uint32
- var op1, op2 uint64
- if !readOutAndSetErr(&typ,
- "executor %v: failed while reading type of comparison %v", env.pid, j) {
- return
- }
- if typ > compConstMask|compSizeMask {
- err0 = fmt.Errorf("executor %v: got wrong value (%v) while reading type of comparison %v",
- env.pid, typ, j)
- return
- }
-
- isSize8 := (typ & compSizeMask) == compSize8
- isConst := (typ & compConstMask) != 0
- arg1ErrString := "executor %v: failed while reading op1 of comparison %v"
- arg2ErrString := "executor %v: failed while reading op2 of comparison %v"
- if isSize8 {
- var tmp1, tmp2 uint32
- if !readOutAndSetErr(&tmp1, arg1ErrString, env.pid, j) ||
- !readOutAndSetErr(&tmp2, arg2ErrString, env.pid, j) {
- return
- }
- op1 = uint64(tmp1)
- op2 = uint64(tmp2)
- } else {
- if !readOut64(&op1, arg1ErrString, env.pid, j) ||
- !readOut64(&op2, arg2ErrString, env.pid, j) {
- return
- }
- }
- if op1 == op2 {
- // It's useless to store such comparisons.
- continue
- }
- compMap.AddComp(op2, op1)
- if isConst {
- // If one of the operands was const, then this operand is always
- // placed first in the instrumented callbacks. Such an operand
- // could not be an argument of our syscalls (because otherwise
- // it wouldn't be const), thus we simply ignore it.
- continue
- }
- compMap.AddComp(op1, op2)
- }
- info[callIndex].Comps = compMap
- }
- 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, osutil.DefaultFilePerm)
- 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()
- }
- osutil.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 := syscall.Signal(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 := osutil.WriteFile("/sys/kernel/debug/failslab/ignore-gfp-wait", []byte("N")); err != nil {
- panic(fmt.Sprintf("failed to write /sys/kernel/debug/failslab/ignore-gfp-wait: %v", err))
- }
- if err := osutil.WriteFile("/sys/kernel/debug/fail_futex/ignore-private", []byte("N")); err != nil {
- panic(fmt.Sprintf("failed to write /sys/kernel/debug/fail_futex/ignore-private: %v", err))
- }
-}
diff --git a/pkg/ipc/ipc_simple.go b/pkg/ipc/ipc_simple.go
deleted file mode 100644
index 50dcce661..000000000
--- a/pkg/ipc/ipc_simple.go
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2017 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.
-
-// +build freebsd fuchsia windows
-
-package ipc
-
-import (
- "bytes"
- "encoding/binary"
- "fmt"
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
- "sync/atomic"
- "time"
-
- "github.com/google/syzkaller/pkg/osutil"
- "github.com/google/syzkaller/prog"
-)
-
-type Env struct {
- bin []string
- pid int
- config Config
-
- StatExecs uint64
- StatRestarts uint64
-}
-
-func MakeEnv(bin string, pid int, config Config) (*Env, error) {
- if config.Timeout < 7*time.Second {
- config.Timeout = 7 * time.Second
- }
- env := &Env{
- bin: strings.Split(bin, " "),
- pid: pid,
- config: config,
- }
- if len(env.bin) == 0 {
- return nil, fmt.Errorf("binary is empty string")
- }
- env.bin[0] = osutil.Abs(env.bin[0])
- 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
- }
- return env, nil
-}
-
-func (env *Env) Close() error {
- return nil
-}
-
-func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info []CallInfo, failed, hanged bool, err0 error) {
- atomic.AddUint64(&env.StatExecs, 1)
- dir, err := ioutil.TempDir("./", "syzkaller-testdir")
- if err != nil {
- err0 = fmt.Errorf("failed to create temp dir: %v", err)
- return
- }
- defer os.RemoveAll(dir)
-
- data := make([]byte, prog.ExecBufferSize)
- n, err := p.SerializeForExec(data, env.pid)
- if err != nil {
- err0 = err
- return
- }
- inbuf := new(bytes.Buffer)
- binary.Write(inbuf, binary.LittleEndian, uint64(env.config.Flags))
- binary.Write(inbuf, binary.LittleEndian, uint64(opts.Flags))
- binary.Write(inbuf, binary.LittleEndian, uint64(env.pid))
- inbuf.Write(data[:n])
-
- cmd := exec.Command(env.bin[0], env.bin[1:]...)
- cmd.Env = []string{}
- cmd.Dir = dir
- cmd.Stdin = inbuf
- if env.config.Flags&FlagDebug != 0 {
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stdout
- }
- if err := cmd.Start(); err != nil {
- err0 = fmt.Errorf("failed to start %d/%+v: %v", dir, env.bin, err)
- return
- }
- done := make(chan error)
- go func() {
- done <- cmd.Wait()
- }()
- t := time.NewTimer(env.config.Timeout)
- select {
- case <-done:
- t.Stop()
- case <-t.C:
- cmd.Process.Kill()
- <-done
- }
- return
-}
diff --git a/pkg/ipc/ipc_test.go b/pkg/ipc/ipc_test.go
index ca0af1060..51e961119 100644
--- a/pkg/ipc/ipc_test.go
+++ b/pkg/ipc/ipc_test.go
@@ -42,7 +42,7 @@ func buildProgram(t *testing.T, target *prog.Target, src string) string {
return bin
}
-func initTest(t *testing.T) (rand.Source, int) {
+func initTest(t *testing.T) (*prog.Target, rand.Source, int, uint64) {
t.Parallel()
iters := 100
if testing.Short() {
@@ -51,19 +51,26 @@ func initTest(t *testing.T) (rand.Source, int) {
seed := int64(time.Now().UnixNano())
rs := rand.NewSource(seed)
t.Logf("seed=%v", seed)
- return rs, iters
-}
-
-func TestEmptyProg(t *testing.T) {
- target, err := prog.GetTarget("linux", runtime.GOARCH)
+ target, err := prog.GetTarget(runtime.GOOS, runtime.GOARCH)
+ if err != nil {
+ t.Fatal(err)
+ }
+ cfg, err := DefaultConfig()
if err != nil {
t.Fatal(err)
}
+ flags := cfg.Flags & (FlagUseShmem | FlagUseForkServer)
+ return target, rs, iters, flags
+}
+
+func TestEmptyProg(t *testing.T) {
+ target, _, _, flags0 := initTest(t)
bin := buildExecutor(t, target)
defer os.Remove(bin)
cfg := Config{
+ Flags: flags0,
Timeout: timeout,
}
env, err := MakeEnv(bin, 0, cfg)
@@ -87,21 +94,16 @@ func TestEmptyProg(t *testing.T) {
}
func TestExecute(t *testing.T) {
- rs, iters := initTest(t)
- flags := []uint64{0, FlagThreaded, FlagThreaded | FlagCollide}
-
- target, err := prog.GetTarget("linux", runtime.GOARCH)
- if err != nil {
- t.Fatal(err)
- }
+ target, rs, iters, flags0 := initTest(t)
bin := buildExecutor(t, target)
defer os.Remove(bin)
+ flags := []uint64{0, FlagThreaded, FlagThreaded | FlagCollide}
for _, flag := range flags {
t.Logf("testing flags 0x%x\n", flag)
cfg := Config{
- Flags: flag,
+ Flags: flag | flags0,
Timeout: timeout,
}
env, err := MakeEnv(bin, 0, cfg)