github.com/criyle/go-sandbox@v0.10.3/container/host_exec_linux.go (about) 1 package container 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/criyle/go-sandbox/pkg/rlimit" 9 "github.com/criyle/go-sandbox/pkg/seccomp" 10 "github.com/criyle/go-sandbox/pkg/unixsocket" 11 "github.com/criyle/go-sandbox/runner" 12 ) 13 14 // ExecveParam is parameters to run process inside container 15 type ExecveParam struct { 16 // Args holds command line arguments 17 Args []string 18 19 // Env specifies the environment of the process 20 Env []string 21 22 // Files specifies file descriptors for the child process 23 Files []uintptr 24 25 // ExecFile specifies file descriptor for executable file using fexecve 26 ExecFile uintptr 27 28 // RLimits specifies POSIX Resource limit through setrlimit 29 RLimits []rlimit.RLimit 30 31 // Seccomp specifies seccomp filter 32 Seccomp seccomp.Filter 33 34 // CTTY specifies whether to set controlling TTY 35 CTTY bool 36 37 // SyncFunc calls with pid just before execve (for attach the process to cgroups) 38 SyncFunc func(pid int) error 39 } 40 41 // Execve runs process inside container. It accepts context cancelation as time limit exceeded. 42 func (c *container) Execve(ctx context.Context, param ExecveParam) runner.Result { 43 c.mu.Lock() 44 defer c.mu.Unlock() 45 46 sTime := time.Now() 47 48 // if execve with fd, put fd at the first parameter 49 var files []int 50 if param.ExecFile > 0 { 51 files = append(files, int(param.ExecFile)) 52 } 53 files = append(files, uintptrSliceToInt(param.Files)...) 54 msg := unixsocket.Msg{ 55 Fds: files, 56 } 57 execCmd := &execCmd{ 58 Argv: param.Args, 59 Env: param.Env, 60 RLimits: param.RLimits, 61 Seccomp: param.Seccomp, 62 FdExec: param.ExecFile > 0, 63 CTTY: param.CTTY, 64 } 65 cm := cmd{ 66 Cmd: cmdExecve, 67 ExecCmd: execCmd, 68 } 69 if err := c.sendCmd(cm, msg); err != nil { 70 return errResult("execve: sendCmd %v", err) 71 } 72 // sync function 73 rep, msg, err := c.recvReply() 74 if err != nil { 75 return errResult("execve: recvReply %v", err) 76 } 77 // if sync function did not involved 78 if rep.Error != nil { 79 return errResult("execve: %v", rep.Error) 80 } 81 // if pid not received 82 if msg.Cred == nil { 83 // tell kill function to exit and sync 84 c.execveSyncKill() 85 // tell err exec function to exit and sync 86 c.execveSyncKill() 87 return errResult("execve: no pid received") 88 } 89 if param.SyncFunc != nil { 90 if err := param.SyncFunc(int(msg.Cred.Pid)); err != nil { 91 // tell sync function to exit and recv error 92 c.execveSyncKill() 93 // tell kill function to exit and sync 94 c.execveSyncKill() 95 return errResult("execve: syncfunc failed %v", err) 96 } 97 } 98 // send to syncFunc ack ok 99 if err := c.sendCmd(cmd{Cmd: cmdOk}, unixsocket.Msg{}); err != nil { 100 return errResult("execve: ack failed %v", err) 101 } 102 103 // wait for done 104 return c.waitForDone(ctx, sTime) 105 } 106 107 func (c *container) waitForDone(ctx context.Context, sTime time.Time) runner.Result { 108 mTime := time.Now() 109 select { 110 case <-c.done: // socket error 111 return convertReplyResult(reply{}, sTime, mTime, c.err) 112 113 case <-ctx.Done(): // cancel 114 c.sendCmd(cmd{Cmd: cmdKill}, unixsocket.Msg{}) // kill 115 reply, _, _ := c.recvReply() 116 _, _, err := c.recvReply() 117 return convertReplyResult(reply, sTime, mTime, err) 118 119 case ret := <-c.recvCh: // result 120 c.sendCmd(cmd{Cmd: cmdKill}, unixsocket.Msg{}) // kill 121 _, _, err := c.recvReply() 122 return convertReplyResult(ret.Reply, sTime, mTime, err) 123 } 124 } 125 126 func convertReplyResult(reply reply, sTime, mTime time.Time, err error) runner.Result { 127 // handle potential error 128 if err != nil { 129 return runner.Result{ 130 Status: runner.StatusRunnerError, 131 Error: err.Error(), 132 } 133 } 134 if reply.Error != nil { 135 return runner.Result{ 136 Status: runner.StatusRunnerError, 137 Error: reply.Error.Error(), 138 } 139 } 140 if reply.ExecReply == nil { 141 return runner.Result{ 142 Status: runner.StatusRunnerError, 143 Error: "execve: no reply received", 144 } 145 } 146 // emit result after all communication finish 147 return runner.Result{ 148 Status: reply.ExecReply.Status, 149 ExitStatus: reply.ExecReply.ExitStatus, 150 Time: reply.ExecReply.Time, 151 Memory: reply.ExecReply.Memory, 152 SetUpTime: mTime.Sub(sTime), 153 RunningTime: time.Since(mTime), 154 } 155 } 156 157 // execveSyncKill will send kill and recv reply 158 func (c *container) execveSyncKill() { 159 c.sendCmd(cmd{Cmd: cmdKill}, unixsocket.Msg{}) 160 c.recvReply() 161 } 162 163 func errResult(f string, v ...interface{}) runner.Result { 164 return runner.Result{ 165 Status: runner.StatusRunnerError, 166 Error: fmt.Sprintf(f, v...), 167 } 168 }