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  }