github.com/criyle/go-sandbox@v0.10.3/pkg/forkexec/fork_darwin.go (about) 1 package forkexec 2 3 import ( 4 "syscall" 5 "unsafe" 6 7 "golang.org/x/sys/unix" 8 ) 9 10 // Start will fork, load seccomp and execve and being traced by ptrace 11 // Return pid and potential error 12 // The runtime OS thread must be locked before calling this function 13 // if ptrace is set to true 14 func (r *Runner) Start() (int, error) { 15 argv0, argv, env, err := prepareExec(r.Args, r.Env) 16 if err != nil { 17 return 0, err 18 } 19 20 // prepare work dir 21 workdir, err := syscallStringFromString(r.WorkDir) 22 if err != nil { 23 return 0, err 24 } 25 26 // prepare sandbox profile 27 profile, err := syscallStringFromString(r.SandboxProfile) 28 if err != nil { 29 return 0, err 30 } 31 32 // ensure the socketpair created did not leak to child 33 syscall.ForkLock.Lock() 34 35 // socketpair p is also used to sync with parent before final execve 36 // p[0] is used by parent and p[1] is used by child 37 var p [2]int 38 if err := forkExecSocketPair(&p); err != nil { 39 syscall.ForkLock.Unlock() 40 return 0, err 41 } 42 43 // fork in child 44 pid, err1 := forkAndExecInChild(r, argv0, argv, env, workdir, profile, p) 45 46 // restore all signals 47 afterFork() 48 49 syscall.ForkLock.Unlock() 50 51 return syncWithChild(r, p, int(pid), err1) 52 } 53 54 func forkExecSocketPair(p *[2]int) error { 55 var err error 56 *p, err = syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0) 57 if err != nil { 58 return err 59 } 60 _, err = fcntl(p[0], syscall.F_SETFD, syscall.FD_CLOEXEC) 61 if err != nil { 62 return err 63 } 64 _, err = fcntl(p[1], syscall.F_SETFD, syscall.FD_CLOEXEC) 65 if err != nil { 66 return err 67 } 68 return nil 69 } 70 71 func syncWithChild(r *Runner, p [2]int, pid int, err1 syscall.Errno) (int, error) { 72 var ( 73 r1 uintptr 74 err2 syscall.Errno 75 err error 76 ) 77 78 // sync with child 79 unix.Close(p[1]) 80 81 // clone syscall failed 82 if err1 != 0 { 83 unix.Close(p[0]) 84 return 0, syscall.Errno(err1) 85 } 86 r1, _, err1 = syscall3(libc_read_trampoline_addr, uintptr(p[0]), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2))) 87 // child returned error code 88 if r1 != unsafe.Sizeof(err2) || err2 != 0 || err1 != 0 { 89 err = handlePipeError(r1, err2) 90 goto fail 91 } 92 93 // if syncfunc return error, then fail child immediately 94 if r.SyncFunc != nil { 95 if err = r.SyncFunc(int(pid)); err != nil { 96 goto fail 97 } 98 } 99 // otherwise, ack child (err1 == 0) 100 r1, _, err1 = syscall3(libc_write_trampoline_addr, uintptr(p[0]), uintptr(unsafe.Pointer(&err1)), uintptr(unsafe.Sizeof(err1))) 101 if err1 != 0 { 102 goto fail 103 } 104 105 // if read anything mean child failed after sync (close_on_exec so it should not block) 106 r1, _, err1 = syscall3(libc_read_trampoline_addr, uintptr(p[0]), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2))) 107 unix.Close(p[0]) 108 if r1 != 0 || err1 != 0 { 109 err = handlePipeError(r1, err2) 110 goto failAfterClose 111 } 112 return int(pid), nil 113 114 fail: 115 unix.Close(p[0]) 116 117 failAfterClose: 118 handleChildFailed(int(pid)) 119 return 0, err 120 } 121 122 // check pipe error 123 func handlePipeError(r1 uintptr, errno syscall.Errno) error { 124 if r1 == unsafe.Sizeof(errno) { 125 return syscall.Errno(errno) 126 } 127 return syscall.EPIPE 128 } 129 130 func handleChildFailed(pid int) { 131 var wstatus syscall.WaitStatus 132 // make sure not blocked 133 syscall.Kill(pid, syscall.SIGKILL) 134 // child failed; wait for it to exit, to make sure the zombies don't accumulate 135 _, err := syscall.Wait4(pid, &wstatus, 0, nil) 136 for err == syscall.EINTR { 137 _, err = syscall.Wait4(pid, &wstatus, 0, nil) 138 } 139 }