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