github.com/criyle/go-sandbox@v0.10.3/container/container_exec_linux.go (about)

     1  package container
     2  
     3  import (
     4  	"fmt"
     5  	"syscall"
     6  	"time"
     7  
     8  	"github.com/criyle/go-sandbox/pkg/forkexec"
     9  	"github.com/criyle/go-sandbox/pkg/unixsocket"
    10  	"github.com/criyle/go-sandbox/runner"
    11  )
    12  
    13  func (c *containerServer) handleExecve(cmd *execCmd, msg unixsocket.Msg) error {
    14  	var (
    15  		files    []uintptr
    16  		execFile uintptr
    17  		cred     *syscall.Credential
    18  	)
    19  	if cmd == nil {
    20  		return c.sendErrorReply("handle: no parameter provided")
    21  	}
    22  	if len(msg.Fds) > 0 {
    23  		files = intSliceToUintptr(msg.Fds)
    24  		// don't leak fds to child
    25  		closeOnExecFds(msg.Fds)
    26  		// release files after execve
    27  		defer closeFds(msg.Fds)
    28  	}
    29  
    30  	// if fexecve, then the first fd must be executable
    31  	if cmd.FdExec {
    32  		if len(files) == 0 {
    33  			return c.sendErrorReply("handle: expected fexecve fd")
    34  		}
    35  		execFile = files[0]
    36  		files = files[1:]
    37  	}
    38  
    39  	var env []string
    40  	env = append(env, c.defaultEnv...)
    41  	env = append(env, cmd.Env...)
    42  
    43  	if len(cmd.Argv) > 0 {
    44  		exePath, err := lookPath(cmd.Argv[0], env)
    45  		if err != nil {
    46  			return c.sendErrorReply("handle: %s: %v", cmd.Argv[0], err)
    47  		}
    48  		cmd.Argv[0] = exePath
    49  	}
    50  
    51  	syncFunc := func(pid int) error {
    52  		msg := unixsocket.Msg{
    53  			Cred: &syscall.Ucred{
    54  				Pid: int32(pid),
    55  				Uid: uint32(syscall.Getuid()),
    56  				Gid: uint32(syscall.Getgid()),
    57  			},
    58  		}
    59  		if err := c.sendReply(reply{}, msg); err != nil {
    60  			return fmt.Errorf("syncFunc: sendReply %v", err)
    61  		}
    62  		cmd, _, err := c.recvCmd()
    63  		if err != nil {
    64  			return fmt.Errorf("syncFunc: recvCmd %v", err)
    65  		}
    66  		if cmd.Cmd == cmdKill {
    67  			return fmt.Errorf("syncFunc: received kill")
    68  		}
    69  		return nil
    70  	}
    71  
    72  	if c.Cred {
    73  		cred = &syscall.Credential{
    74  			Uid:         uint32(c.ContainerUID),
    75  			Gid:         uint32(c.ContainerGID),
    76  			NoSetGroups: true,
    77  		}
    78  	}
    79  
    80  	var seccomp *syscall.SockFprog
    81  	if cmd.Seccomp != nil {
    82  		seccomp = cmd.Seccomp.SockFprog()
    83  	}
    84  
    85  	r := forkexec.Runner{
    86  		Args:       cmd.Argv,
    87  		Env:        env,
    88  		ExecFile:   execFile,
    89  		RLimits:    cmd.RLimits,
    90  		Files:      files,
    91  		WorkDir:    c.WorkDir,
    92  		NoNewPrivs: true,
    93  		DropCaps:   true,
    94  		SyncFunc:   syncFunc,
    95  		Credential: cred,
    96  		CTTY:       cmd.CTTY,
    97  		Seccomp:    seccomp,
    98  
    99  		UnshareCgroupAfterSync: c.UnshareCgroup,
   100  	}
   101  	// starts the runner, error is handled same as wait4 to make communication equal
   102  	pid, err := r.Start()
   103  	if err != nil {
   104  		s := "<nil>"
   105  		if len(cmd.Argv) > 0 {
   106  			s = cmd.Argv[0]
   107  		}
   108  		c.sendErrorReply("start: %s: %v", s, err)
   109  		c.recvCmd()
   110  		return c.sendReply(reply{}, unixsocket.Msg{})
   111  	}
   112  	return c.handleExecveStarted(pid)
   113  }
   114  
   115  func (c *containerServer) handleExecveStarted(pid int) error {
   116  	// At this point, either recv kill / send result would be happened
   117  	// host -> container: kill
   118  	// container -> host: result
   119  	// container -> host: done
   120  
   121  	// Let's register a wait event
   122  	c.waitPid <- pid
   123  
   124  	var ret waitPidResult
   125  	select {
   126  	case <-c.done: // socket error happened
   127  		return c.err
   128  
   129  	case <-c.recvCh: // kill cmd received
   130  		syscall.Kill(-1, syscall.SIGKILL)
   131  		ret = <-c.waitPidResult
   132  		c.waitAll <- struct{}{}
   133  
   134  		if err := c.sendReply(convertReply(ret), unixsocket.Msg{}); err != nil {
   135  			return err
   136  		}
   137  
   138  	case ret = <-c.waitPidResult: // child process returned
   139  		syscall.Kill(-1, syscall.SIGKILL)
   140  		c.waitAll <- struct{}{}
   141  
   142  		if err := c.sendReply(convertReply(ret), unixsocket.Msg{}); err != nil {
   143  			return err
   144  		}
   145  		if _, _, err := c.recvCmd(); err != nil { // kill cmd received
   146  			return err
   147  		}
   148  	}
   149  	<-c.waitAllDone
   150  	return c.sendReply(reply{}, unixsocket.Msg{})
   151  }
   152  
   153  func convertReply(ret waitPidResult) reply {
   154  	if ret.Err != nil {
   155  		return reply{
   156  			Error: &errorReply{
   157  				Msg: fmt.Sprintf("execve: wait4 %v", ret.Err),
   158  			},
   159  		}
   160  	}
   161  
   162  	waitStatus := ret.WaitStatus
   163  	rusage := ret.Rusage
   164  
   165  	status := runner.StatusNormal
   166  	userTime := time.Duration(rusage.Utime.Nano()) // ns
   167  	userMem := runner.Size(rusage.Maxrss << 10)    // bytes
   168  	switch {
   169  	case waitStatus.Exited():
   170  		exitStatus := waitStatus.ExitStatus()
   171  		if exitStatus != 0 {
   172  			status = runner.StatusNonzeroExitStatus
   173  		}
   174  		return reply{
   175  			ExecReply: &execReply{
   176  				Status:     status,
   177  				ExitStatus: exitStatus,
   178  				Time:       userTime,
   179  				Memory:     userMem,
   180  			},
   181  		}
   182  
   183  	case waitStatus.Signaled():
   184  		switch waitStatus.Signal() {
   185  		// kill signal treats as TLE
   186  		case syscall.SIGXCPU, syscall.SIGKILL:
   187  			status = runner.StatusTimeLimitExceeded
   188  		case syscall.SIGXFSZ:
   189  			status = runner.StatusOutputLimitExceeded
   190  		case syscall.SIGSYS:
   191  			status = runner.StatusDisallowedSyscall
   192  		default:
   193  			status = runner.StatusSignalled
   194  		}
   195  		return reply{
   196  			ExecReply: &execReply{
   197  				ExitStatus: int(waitStatus.Signal()),
   198  				Status:     status,
   199  				Time:       userTime,
   200  				Memory:     userMem,
   201  			},
   202  		}
   203  
   204  	default:
   205  		return reply{
   206  			Error: &errorReply{
   207  				Msg: fmt.Sprintf("execve: unknown status %v", waitStatus),
   208  			},
   209  		}
   210  	}
   211  }