• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 syzkaller project authors. All rights reserved.
2// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3
4package ipc
5
6import (
7	"fmt"
8	"io"
9	"io/ioutil"
10	"os"
11	"os/exec"
12	"path/filepath"
13	"strings"
14	"sync/atomic"
15	"time"
16	"unsafe"
17
18	"github.com/google/syzkaller/pkg/osutil"
19	"github.com/google/syzkaller/prog"
20)
21
22// Configuration flags for Config.Flags.
23type EnvFlags uint64
24
25const (
26	FlagDebug            EnvFlags = 1 << iota // debug output from executor
27	FlagSignal                                // collect feedback signals (coverage)
28	FlagSandboxSetuid                         // impersonate nobody user
29	FlagSandboxNamespace                      // use namespaces for sandboxing
30	FlagEnableTun                             // initialize and use tun in executor
31	FlagEnableNetDev                          // setup a bunch of various network devices for testing
32	FlagEnableFault                           // enable fault injection support
33	// Executor does not know about these:
34	FlagUseShmem      // use shared memory instead of pipes for communication
35	FlagUseForkServer // use extended protocol with handshake
36)
37
38// Per-exec flags for ExecOpts.Flags:
39type ExecFlags uint64
40
41const (
42	FlagCollectCover ExecFlags = 1 << iota // collect coverage
43	FlagDedupCover                         // deduplicate coverage in executor
44	FlagInjectFault                        // inject a fault in this execution (see ExecOpts)
45	FlagCollectComps                       // collect KCOV comparisons
46	FlagThreaded                           // use multiple threads to mitigate blocked syscalls
47	FlagCollide                            // collide syscalls to provoke data races
48)
49
50type ExecOpts struct {
51	Flags     ExecFlags
52	FaultCall int // call index for fault injection (0-based)
53	FaultNth  int // fault n-th operation in the call (0-based)
54}
55
56// ExecutorFailure is returned from MakeEnv or from env.Exec when executor terminates
57// by calling fail function. This is considered a logical error (a failed assert).
58type ExecutorFailure string
59
60func (err ExecutorFailure) Error() string {
61	return string(err)
62}
63
64// Config is the configuration for Env.
65type Config struct {
66	// Path to executor binary.
67	Executor string
68
69	// Flags are configuation flags, defined above.
70	Flags EnvFlags
71
72	// Timeout is the execution timeout for a single program.
73	Timeout time.Duration
74}
75
76type CallFlags uint32
77
78const (
79	CallExecuted      CallFlags = 1 << iota // was started at all
80	CallFinished                            // finished executing (rather than blocked forever)
81	CallBlocked                             // finished but blocked during execution
82	CallFaultInjected                       // fault was injected into this call
83)
84
85type CallInfo struct {
86	Flags  CallFlags
87	Signal []uint32 // feedback signal, filled if FlagSignal is set
88	Cover  []uint32 // per-call coverage, filled if FlagSignal is set and cover == true,
89	//if dedup == false, then cov effectively contains a trace, otherwise duplicates are removed
90	Comps prog.CompMap // per-call comparison operands
91	Errno int          // call errno (0 if the call was successful)
92}
93
94type Env struct {
95	in  []byte
96	out []byte
97
98	cmd       *command
99	inFile    *os.File
100	outFile   *os.File
101	bin       []string
102	linkedBin string
103	pid       int
104	config    *Config
105
106	StatExecs    uint64
107	StatRestarts uint64
108}
109
110const (
111	outputSize = 16 << 20
112
113	statusFail  = 67
114	statusError = 68
115	statusRetry = 69
116
117	// Comparison types masks taken from KCOV headers.
118	compSizeMask  = 6
119	compSize8     = 6
120	compConstMask = 1
121)
122
123func MakeEnv(config *Config, pid int) (*Env, error) {
124	var inf, outf *os.File
125	var inmem, outmem []byte
126	if config.Flags&FlagUseShmem != 0 {
127		var err error
128		inf, inmem, err = osutil.CreateMemMappedFile(prog.ExecBufferSize)
129		if err != nil {
130			return nil, err
131		}
132		defer func() {
133			if inf != nil {
134				osutil.CloseMemMappedFile(inf, inmem)
135			}
136		}()
137		outf, outmem, err = osutil.CreateMemMappedFile(outputSize)
138		if err != nil {
139			return nil, err
140		}
141		defer func() {
142			if outf != nil {
143				osutil.CloseMemMappedFile(outf, outmem)
144			}
145		}()
146	} else {
147		inmem = make([]byte, prog.ExecBufferSize)
148		outmem = make([]byte, outputSize)
149	}
150	env := &Env{
151		in:      inmem,
152		out:     outmem,
153		inFile:  inf,
154		outFile: outf,
155		bin:     strings.Split(config.Executor, " "),
156		pid:     pid,
157		config:  config,
158	}
159	if len(env.bin) == 0 {
160		return nil, fmt.Errorf("binary is empty string")
161	}
162	env.bin[0] = osutil.Abs(env.bin[0]) // we are going to chdir
163	// Append pid to binary name.
164	// E.g. if binary is 'syz-executor' and pid=15,
165	// we create a link from 'syz-executor15' to 'syz-executor' and use 'syz-executor15' as binary.
166	// This allows to easily identify program that lead to a crash in the log.
167	// Log contains pid in "executing program 15" and crashes usually contain "Comm: syz-executor15".
168	base := filepath.Base(env.bin[0])
169	pidStr := fmt.Sprint(pid)
170	if len(base)+len(pidStr) >= 16 {
171		// TASK_COMM_LEN is currently set to 16
172		base = base[:15-len(pidStr)]
173	}
174	binCopy := filepath.Join(filepath.Dir(env.bin[0]), base+pidStr)
175	if err := os.Link(env.bin[0], binCopy); err == nil {
176		env.bin[0] = binCopy
177		env.linkedBin = binCopy
178	}
179	inf = nil
180	outf = nil
181	return env, nil
182}
183
184func (env *Env) Close() error {
185	if env.cmd != nil {
186		env.cmd.close()
187	}
188	if env.linkedBin != "" {
189		os.Remove(env.linkedBin)
190	}
191	var err1, err2 error
192	if env.inFile != nil {
193		err1 = osutil.CloseMemMappedFile(env.inFile, env.in)
194	}
195	if env.outFile != nil {
196		err2 = osutil.CloseMemMappedFile(env.outFile, env.out)
197	}
198	switch {
199	case err1 != nil:
200		return err1
201	case err2 != nil:
202		return err2
203	default:
204		return nil
205	}
206}
207
208var rateLimit = time.NewTicker(1 * time.Second)
209
210// Exec starts executor binary to execute program p and returns information about the execution:
211// output: process output
212// info: per-call info
213// failed: true if executor has detected a kernel bug
214// hanged: program hanged and was killed
215// err0: failed to start process, or executor has detected a logical error
216func (env *Env) Exec(opts *ExecOpts, p *prog.Prog) (output []byte, info []CallInfo, failed, hanged bool, err0 error) {
217	// Copy-in serialized program.
218	progSize, err := p.SerializeForExec(env.in)
219	if err != nil {
220		err0 = fmt.Errorf("failed to serialize: %v", err)
221		return
222	}
223	var progData []byte
224	if env.config.Flags&FlagUseShmem == 0 {
225		progData = env.in[:progSize]
226	}
227	// Zero out the first two words (ncmd and nsig), so that we don't have garbage there
228	// if executor crashes before writing non-garbage there.
229	for i := 0; i < 4; i++ {
230		env.out[i] = 0
231	}
232
233	atomic.AddUint64(&env.StatExecs, 1)
234	if env.cmd == nil {
235		if p.Target.OS == "akaros" {
236			// On akaros executor is actually ssh,
237			// starting them too frequently leads to timeouts.
238			<-rateLimit.C
239		}
240		atomic.AddUint64(&env.StatRestarts, 1)
241		env.cmd, err0 = makeCommand(env.pid, env.bin, env.config, env.inFile, env.outFile, env.out)
242		if err0 != nil {
243			return
244		}
245	}
246	var restart bool
247	output, failed, hanged, restart, err0 = env.cmd.exec(opts, progData)
248	if err0 != nil {
249		env.cmd.close()
250		env.cmd = nil
251		return
252	}
253
254	info, err0 = env.parseOutput(p)
255	if info != nil && env.config.Flags&FlagSignal == 0 {
256		addFallbackSignal(p, info)
257	}
258	if restart {
259		env.cmd.close()
260		env.cmd = nil
261	}
262	return
263}
264
265// addFallbackSignal computes simple fallback signal in cases we don't have real coverage signal.
266// We use syscall number or-ed with returned errno value as signal.
267// At least this gives us all combinations of syscall+errno.
268func addFallbackSignal(p *prog.Prog, info []CallInfo) {
269	callInfos := make([]prog.CallInfo, len(info))
270	for i, inf := range info {
271		if inf.Flags&CallExecuted != 0 {
272			callInfos[i].Flags |= prog.CallExecuted
273		}
274		if inf.Flags&CallFinished != 0 {
275			callInfos[i].Flags |= prog.CallFinished
276		}
277		if inf.Flags&CallBlocked != 0 {
278			callInfos[i].Flags |= prog.CallBlocked
279		}
280		callInfos[i].Errno = inf.Errno
281	}
282	p.FallbackSignal(callInfos)
283	for i, inf := range callInfos {
284		info[i].Signal = inf.Signal
285	}
286}
287
288func (env *Env) parseOutput(p *prog.Prog) ([]CallInfo, error) {
289	out := env.out
290	ncmd, ok := readUint32(&out)
291	if !ok {
292		return nil, fmt.Errorf("failed to read number of calls")
293	}
294	info := make([]CallInfo, len(p.Calls))
295	for i := uint32(0); i < ncmd; i++ {
296		if len(out) < int(unsafe.Sizeof(callReply{})) {
297			return nil, fmt.Errorf("failed to read call %v reply", i)
298		}
299		reply := *(*callReply)(unsafe.Pointer(&out[0]))
300		out = out[unsafe.Sizeof(callReply{}):]
301		if int(reply.index) >= len(info) {
302			return nil, fmt.Errorf("bad call %v index %v/%v", i, reply.index, len(info))
303		}
304		if num := p.Calls[reply.index].Meta.ID; int(reply.num) != num {
305			return nil, fmt.Errorf("wrong call %v num %v/%v", i, reply.num, num)
306		}
307		inf := &info[reply.index]
308		if inf.Flags != 0 || inf.Signal != nil {
309			return nil, fmt.Errorf("duplicate reply for call %v/%v/%v", i, reply.index, reply.num)
310		}
311		inf.Errno = int(reply.errno)
312		inf.Flags = CallFlags(reply.flags)
313		if inf.Signal, ok = readUint32Array(&out, reply.signalSize); !ok {
314			return nil, fmt.Errorf("call %v/%v/%v: signal overflow: %v/%v",
315				i, reply.index, reply.num, reply.signalSize, len(out))
316		}
317		if inf.Cover, ok = readUint32Array(&out, reply.coverSize); !ok {
318			return nil, fmt.Errorf("call %v/%v/%v: cover overflow: %v/%v",
319				i, reply.index, reply.num, reply.coverSize, len(out))
320		}
321		comps, err := readComps(&out, reply.compsSize)
322		if err != nil {
323			return nil, err
324		}
325		inf.Comps = comps
326	}
327	return info, nil
328}
329
330func readComps(outp *[]byte, compsSize uint32) (prog.CompMap, error) {
331	if compsSize == 0 {
332		return nil, nil
333	}
334	compMap := make(prog.CompMap)
335	for i := uint32(0); i < compsSize; i++ {
336		typ, ok := readUint32(outp)
337		if !ok {
338			return nil, fmt.Errorf("failed to read comp %v", i)
339		}
340		if typ > compConstMask|compSizeMask {
341			return nil, fmt.Errorf("bad comp %v type %v", i, typ)
342		}
343		var op1, op2 uint64
344		var ok1, ok2 bool
345		if typ&compSizeMask == compSize8 {
346			op1, ok1 = readUint64(outp)
347			op2, ok2 = readUint64(outp)
348		} else {
349			var tmp1, tmp2 uint32
350			tmp1, ok1 = readUint32(outp)
351			tmp2, ok2 = readUint32(outp)
352			op1, op2 = uint64(tmp1), uint64(tmp2)
353		}
354		if !ok1 || !ok2 {
355			return nil, fmt.Errorf("failed to read comp %v op", i)
356		}
357		if op1 == op2 {
358			continue // it's useless to store such comparisons
359		}
360		compMap.AddComp(op2, op1)
361		if (typ & compConstMask) != 0 {
362			// If one of the operands was const, then this operand is always
363			// placed first in the instrumented callbacks. Such an operand
364			// could not be an argument of our syscalls (because otherwise
365			// it wouldn't be const), thus we simply ignore it.
366			continue
367		}
368		compMap.AddComp(op1, op2)
369	}
370	return compMap, nil
371}
372
373func readUint32(outp *[]byte) (uint32, bool) {
374	out := *outp
375	if len(out) < 4 {
376		return 0, false
377	}
378	v := *(*uint32)(unsafe.Pointer(&out[0]))
379	*outp = out[4:]
380	return v, true
381}
382
383func readUint64(outp *[]byte) (uint64, bool) {
384	out := *outp
385	if len(out) < 8 {
386		return 0, false
387	}
388	v := *(*uint64)(unsafe.Pointer(&out[0]))
389	*outp = out[8:]
390	return v, true
391}
392
393func readUint32Array(outp *[]byte, size uint32) ([]uint32, bool) {
394	out := *outp
395	if int(size)*4 > len(out) {
396		return nil, false
397	}
398	arr := ((*[1 << 28]uint32)(unsafe.Pointer(&out[0])))
399	res := arr[:size:size]
400	*outp = out[size*4:]
401	return res, true
402}
403
404type command struct {
405	pid      int
406	config   *Config
407	timeout  time.Duration
408	cmd      *exec.Cmd
409	dir      string
410	readDone chan []byte
411	exited   chan struct{}
412	inrp     *os.File
413	outwp    *os.File
414	outmem   []byte
415}
416
417const (
418	inMagic  = uint64(0xbadc0ffeebadface)
419	outMagic = uint32(0xbadf00d)
420)
421
422type handshakeReq struct {
423	magic uint64
424	flags uint64 // env flags
425	pid   uint64
426}
427
428type handshakeReply struct {
429	magic uint32
430}
431
432type executeReq struct {
433	magic     uint64
434	envFlags  uint64 // env flags
435	execFlags uint64 // exec flags
436	pid       uint64
437	faultCall uint64
438	faultNth  uint64
439	progSize  uint64
440	// prog follows on pipe or in shmem
441}
442
443type executeReply struct {
444	magic uint32
445	// If done is 0, then this is call completion message followed by callReply.
446	// If done is 1, then program execution is finished and status is set.
447	done   uint32
448	status uint32
449}
450
451type callReply struct {
452	index      uint32 // call index in the program
453	num        uint32 // syscall number (for cross-checking)
454	errno      uint32
455	flags      uint32 // see CallFlags
456	signalSize uint32
457	coverSize  uint32
458	compsSize  uint32
459	// signal/cover/comps follow
460}
461
462func makeCommand(pid int, bin []string, config *Config, inFile, outFile *os.File, outmem []byte) (
463	*command, error) {
464	dir, err := ioutil.TempDir("./", "syzkaller-testdir")
465	if err != nil {
466		return nil, fmt.Errorf("failed to create temp dir: %v", err)
467	}
468	dir = osutil.Abs(dir)
469
470	c := &command{
471		pid:     pid,
472		config:  config,
473		timeout: sanitizeTimeout(config),
474		dir:     dir,
475		outmem:  outmem,
476	}
477	defer func() {
478		if c != nil {
479			c.close()
480		}
481	}()
482
483	if config.Flags&(FlagSandboxSetuid|FlagSandboxNamespace) != 0 {
484		if err := os.Chmod(dir, 0777); err != nil {
485			return nil, fmt.Errorf("failed to chmod temp dir: %v", err)
486		}
487	}
488
489	// Output capture pipe.
490	rp, wp, err := os.Pipe()
491	if err != nil {
492		return nil, fmt.Errorf("failed to create pipe: %v", err)
493	}
494	defer wp.Close()
495
496	// executor->ipc command pipe.
497	inrp, inwp, err := os.Pipe()
498	if err != nil {
499		return nil, fmt.Errorf("failed to create pipe: %v", err)
500	}
501	defer inwp.Close()
502	c.inrp = inrp
503
504	// ipc->executor command pipe.
505	outrp, outwp, err := os.Pipe()
506	if err != nil {
507		return nil, fmt.Errorf("failed to create pipe: %v", err)
508	}
509	defer outrp.Close()
510	c.outwp = outwp
511
512	c.readDone = make(chan []byte, 1)
513	c.exited = make(chan struct{})
514
515	cmd := osutil.Command(bin[0], bin[1:]...)
516	if inFile != nil && outFile != nil {
517		cmd.ExtraFiles = []*os.File{inFile, outFile}
518	}
519	cmd.Env = []string{}
520	cmd.Dir = dir
521	cmd.Stdin = outrp
522	cmd.Stdout = inwp
523	if config.Flags&FlagDebug != 0 {
524		close(c.readDone)
525		cmd.Stderr = os.Stdout
526	} else if config.Flags&FlagUseForkServer == 0 {
527		close(c.readDone)
528		// TODO: read out output after execution failure.
529	} else {
530		cmd.Stderr = wp
531		go func(c *command) {
532			// Read out output in case executor constantly prints something.
533			const bufSize = 128 << 10
534			output := make([]byte, bufSize)
535			var size uint64
536			for {
537				n, err := rp.Read(output[size:])
538				if n > 0 {
539					size += uint64(n)
540					if size >= bufSize*3/4 {
541						copy(output, output[size-bufSize/2:size])
542						size = bufSize / 2
543					}
544				}
545				if err != nil {
546					rp.Close()
547					c.readDone <- output[:size]
548					close(c.readDone)
549					return
550				}
551			}
552		}(c)
553	}
554	if err := cmd.Start(); err != nil {
555		return nil, fmt.Errorf("failed to start executor binary: %v", err)
556	}
557	c.cmd = cmd
558	wp.Close()
559	inwp.Close()
560
561	if c.config.Flags&FlagUseForkServer != 0 {
562		if err := c.handshake(); err != nil {
563			return nil, err
564		}
565	}
566	tmp := c
567	c = nil // disable defer above
568	return tmp, nil
569}
570
571func (c *command) close() {
572	if c.cmd != nil {
573		c.cmd.Process.Kill()
574		c.wait()
575	}
576	osutil.RemoveAll(c.dir)
577	if c.inrp != nil {
578		c.inrp.Close()
579	}
580	if c.outwp != nil {
581		c.outwp.Close()
582	}
583}
584
585// handshake sends handshakeReq and waits for handshakeReply (sandbox setup can take significant time).
586func (c *command) handshake() error {
587	req := &handshakeReq{
588		magic: inMagic,
589		flags: uint64(c.config.Flags),
590		pid:   uint64(c.pid),
591	}
592	reqData := (*[unsafe.Sizeof(*req)]byte)(unsafe.Pointer(req))[:]
593	if _, err := c.outwp.Write(reqData); err != nil {
594		return c.handshakeError(fmt.Errorf("failed to write control pipe: %v", err))
595	}
596
597	read := make(chan error, 1)
598	go func() {
599		reply := &handshakeReply{}
600		replyData := (*[unsafe.Sizeof(*reply)]byte)(unsafe.Pointer(reply))[:]
601		if _, err := io.ReadFull(c.inrp, replyData); err != nil {
602			read <- err
603			return
604		}
605		if reply.magic != outMagic {
606			read <- fmt.Errorf("bad handshake reply magic 0x%x", reply.magic)
607			return
608		}
609		read <- nil
610	}()
611	timeout := time.NewTimer(time.Minute)
612	select {
613	case err := <-read:
614		timeout.Stop()
615		if err != nil {
616			return c.handshakeError(err)
617		}
618		return nil
619	case <-timeout.C:
620		return c.handshakeError(fmt.Errorf("not serving"))
621	}
622}
623
624func (c *command) handshakeError(err error) error {
625	c.cmd.Process.Kill()
626	output := <-c.readDone
627	err = fmt.Errorf("executor %v: %v\n%s", c.pid, err, output)
628	c.wait()
629	if c.cmd.ProcessState != nil {
630		// Magic values returned by executor.
631		if osutil.ProcessExitStatus(c.cmd.ProcessState) == statusFail {
632			err = ExecutorFailure(err.Error())
633		}
634	}
635	return err
636}
637
638func (c *command) wait() error {
639	err := c.cmd.Wait()
640	select {
641	case <-c.exited:
642		// c.exited closed by an earlier call to wait.
643	default:
644		close(c.exited)
645	}
646	return err
647}
648
649func (c *command) exec(opts *ExecOpts, progData []byte) (output []byte, failed, hanged,
650	restart bool, err0 error) {
651	req := &executeReq{
652		magic:     inMagic,
653		envFlags:  uint64(c.config.Flags),
654		execFlags: uint64(opts.Flags),
655		pid:       uint64(c.pid),
656		faultCall: uint64(opts.FaultCall),
657		faultNth:  uint64(opts.FaultNth),
658		progSize:  uint64(len(progData)),
659	}
660	reqData := (*[unsafe.Sizeof(*req)]byte)(unsafe.Pointer(req))[:]
661	if _, err := c.outwp.Write(reqData); err != nil {
662		output = <-c.readDone
663		err0 = fmt.Errorf("executor %v: failed to write control pipe: %v", c.pid, err)
664		return
665	}
666	if progData != nil {
667		if _, err := c.outwp.Write(progData); err != nil {
668			output = <-c.readDone
669			err0 = fmt.Errorf("executor %v: failed to write control pipe: %v", c.pid, err)
670			return
671		}
672	}
673	// At this point program is executing.
674
675	done := make(chan bool)
676	hang := make(chan bool)
677	go func() {
678		t := time.NewTimer(c.timeout)
679		select {
680		case <-t.C:
681			c.cmd.Process.Kill()
682			hang <- true
683		case <-done:
684			t.Stop()
685			hang <- false
686		}
687	}()
688	restart = c.config.Flags&FlagUseForkServer == 0
689	exitStatus := -1
690	completedCalls := (*uint32)(unsafe.Pointer(&c.outmem[0]))
691	outmem := c.outmem[4:]
692	for {
693		reply := &executeReply{}
694		replyData := (*[unsafe.Sizeof(*reply)]byte)(unsafe.Pointer(reply))[:]
695		if _, err := io.ReadFull(c.inrp, replyData); err != nil {
696			break
697		}
698		if reply.magic != outMagic {
699			fmt.Fprintf(os.Stderr, "executor %v: got bad reply magic 0x%x\n", c.pid, reply.magic)
700			os.Exit(1)
701		}
702		if reply.done != 0 {
703			exitStatus = int(reply.status)
704			break
705		}
706		callReply := &callReply{}
707		callReplyData := (*[unsafe.Sizeof(*callReply)]byte)(unsafe.Pointer(callReply))[:]
708		if _, err := io.ReadFull(c.inrp, callReplyData); err != nil {
709			break
710		}
711		if callReply.signalSize != 0 || callReply.coverSize != 0 || callReply.compsSize != 0 {
712			// This is unsupported yet.
713			fmt.Fprintf(os.Stderr, "executor %v: got call reply with coverage\n", c.pid)
714			os.Exit(1)
715		}
716		copy(outmem, callReplyData)
717		outmem = outmem[len(callReplyData):]
718		*completedCalls++
719	}
720	close(done)
721	if exitStatus == 0 {
722		// Program was OK.
723		<-hang
724		return
725	}
726	c.cmd.Process.Kill()
727	output = <-c.readDone
728	if err := c.wait(); <-hang {
729		hanged = true
730		output = append(output, []byte(err.Error())...)
731		output = append(output, '\n')
732		return
733	}
734	if exitStatus == -1 {
735		exitStatus = osutil.ProcessExitStatus(c.cmd.ProcessState)
736		if exitStatus == 0 {
737			exitStatus = statusRetry // fuchsia always returns wrong exit status 0
738		}
739	}
740	// Handle magic values returned by executor.
741	switch exitStatus {
742	case statusFail:
743		err0 = ExecutorFailure(fmt.Sprintf("executor %v: failed: %s", c.pid, output))
744	case statusError:
745		err0 = fmt.Errorf("executor %v: detected kernel bug", c.pid)
746		failed = true
747	case statusRetry:
748		// This is a temporal error (ENOMEM) or an unfortunate
749		// program that messes with testing setup (e.g. kills executor
750		// loop process). Pretend that nothing happened.
751		// It's better than a false crash report.
752		err0 = nil
753		hanged = false
754		restart = true
755	default:
756		err0 = fmt.Errorf("executor %v: exit status %d", c.pid, exitStatus)
757	}
758	return
759}
760
761func sanitizeTimeout(config *Config) time.Duration {
762	const (
763		executorTimeout = 5 * time.Second
764		minTimeout      = executorTimeout + 2*time.Second
765	)
766	timeout := config.Timeout
767	if timeout == 0 {
768		// Executor protects against most hangs, so we use quite large timeout here.
769		// Executor can be slow due to global locks in namespaces and other things,
770		// so let's better wait than report false misleading crashes.
771		timeout = time.Minute
772		if config.Flags&FlagUseForkServer == 0 {
773			// If there is no fork server, executor does not have internal timeout.
774			timeout = executorTimeout
775		}
776	}
777	// IPC timeout must be larger then executor timeout.
778	// Otherwise IPC will kill parent executor but leave child executor alive.
779	if config.Flags&FlagUseForkServer != 0 && timeout < minTimeout {
780		timeout = minTimeout
781	}
782	return timeout
783}
784