go-sandbox/runner/unshare/run.go
2020-02-12 03:01:37 -05:00

178 lines
3.7 KiB
Go

package unshare
import (
"context"
"fmt"
"os"
"time"
"golang.org/x/sys/unix"
"github.com/criyle/go-sandbox/pkg/forkexec"
"github.com/criyle/go-sandbox/types"
)
const (
// UnshareFlags is flags used to create namespaces except NET and IPC
UnshareFlags = unix.CLONE_NEWNS | unix.CLONE_NEWPID | unix.CLONE_NEWUSER | unix.CLONE_NEWUTS | unix.CLONE_NEWCGROUP
)
// Run starts the unshared process
func (r *Runner) Run(c context.Context) <-chan types.Result {
ch := &forkexec.Runner{
Args: r.Args,
Env: r.Env,
ExecFile: r.ExecFile,
RLimits: r.RLimits,
Files: r.Files,
WorkDir: r.WorkDir,
Seccomp: r.Seccomp.SockFprog(),
NoNewPrivs: true,
CloneFlags: UnshareFlags,
Mounts: r.Mounts,
HostName: r.HostName,
DomainName: r.DomainName,
PivotRoot: r.Root,
DropCaps: true,
SyncFunc: r.SyncFunc,
}
result := make(chan types.Result, 1)
start := make(chan struct{})
finish := make(chan struct{})
// run
go func() {
defer close(finish)
ret, err2 := r.Trace(c.Done(), start, ch)
ret.Error = err2.Error()
result <- ret
}()
select {
case <-start:
case <-finish:
}
return result
}
// Trace tracks child processes
func (r *Runner) Trace(done <-chan struct{}, start chan<- struct{},
runner *forkexec.Runner) (result types.Result, err error) {
var (
wstatus unix.WaitStatus // wait4 wait status
rusage unix.Rusage // wait4 rusage
tle = false
status = types.StatusNormal
sTime = time.Now() // start time
fTime time.Time // finish time for setup
)
// Start the runner
pgid, err := runner.Start()
r.println("Starts: ", pgid, err)
if err != nil {
result.Status = types.StatusRunnerError
return result, err
}
close(start)
finish := make(chan struct{})
defer close(finish)
// handle cancel
go func() {
select {
case <-done:
tle = true
killAll(pgid)
case <-finish:
}
}()
defer func() {
if tle {
err = types.StatusTimeLimitExceeded
}
// kill all tracee upon return
killAll(pgid)
collectZombie(pgid)
result.SetUpTime = fTime.Sub(sTime)
result.RunningTime = time.Since(fTime)
}()
fTime = time.Now()
loop:
for {
_, err := unix.Wait4(pgid, &wstatus, 0, &rusage)
r.println("wait4: ", wstatus)
if err != nil {
return result, types.StatusRunnerError
}
// update resource usage and check against limits
userTime := time.Duration(rusage.Utime.Nano()) // ns
userMem := types.Size(rusage.Maxrss << 10) // bytes // kb
// check tle / mle
if userTime > r.Limit.TimeLimit {
status = types.StatusTimeLimitExceeded
}
if userMem > r.Limit.MemoryLimit {
status = types.StatusMemoryLimitExceeded
}
result = types.Result{
Status: status,
Time: userTime,
Memory: userMem,
}
if status != types.StatusNormal {
return result, status
}
switch {
case wstatus.Exited():
result.ExitStatus = wstatus.ExitStatus()
return result, nil
case wstatus.Signaled():
sig := wstatus.Signal()
switch sig {
case unix.SIGXCPU, unix.SIGKILL:
status = types.StatusTimeLimitExceeded
case unix.SIGXFSZ:
status = types.StatusOutputLimitExceeded
case unix.SIGSYS:
status = types.StatusDisallowedSyscall
default:
status = types.StatusSignalled
}
result.Status = status
result.ExitStatus = int(sig)
break loop
}
}
return result, status
}
// kill all tracee according to pids
func killAll(pgid int) {
unix.Kill(-pgid, unix.SIGKILL)
}
// collect died child processes
func collectZombie(pgid int) {
var wstatus unix.WaitStatus
// collect zombies
for {
if _, err := unix.Wait4(-pgid, &wstatus, unix.WALL|unix.WNOHANG, nil); err != nil {
break
}
}
}
func (r *Runner) println(v ...interface{}) {
if r.ShowDetails {
fmt.Fprintln(os.Stderr, v...)
}
}