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 }