go-sandbox/runner/unshare/run_linux.go

157 lines
3.3 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/runner"
)
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) (result runner.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,
UnshareCgroupAfterSync: true,
}
var (
wstatus unix.WaitStatus // wait4 wait status
rusage unix.Rusage // wait4 rusage
status = runner.StatusNormal
sTime = time.Now() // start time
fTime time.Time // finish time for setup
)
// Start the runner
pgid, err := ch.Start()
r.println("Starts: ", pgid, err)
if err != nil {
result.Status = runner.StatusRunnerError
result.Error = err.Error()
return
}
ctx, cancel := context.WithCancel(c)
defer cancel()
// handle cancel
go func() {
<-ctx.Done()
killAll(pgid)
}()
// kill all tracee upon return
defer func() {
killAll(pgid)
collectZombie(pgid)
result.SetUpTime = fTime.Sub(sTime)
result.RunningTime = time.Since(fTime)
}()
fTime = time.Now()
for {
_, err := unix.Wait4(pgid, &wstatus, 0, &rusage)
if err == unix.EINTR {
continue
}
r.println("wait4: ", wstatus)
if err != nil {
result.Status = runner.StatusRunnerError
result.Error = err.Error()
return
}
// update resource usage and check against limits
userTime := time.Duration(rusage.Utime.Nano()) // ns
userMem := runner.Size(rusage.Maxrss << 10) // bytes
// check tle / mle
if userTime > r.Limit.TimeLimit {
status = runner.StatusTimeLimitExceeded
}
if userMem > r.Limit.MemoryLimit {
status = runner.StatusMemoryLimitExceeded
}
result = runner.Result{
Status: status,
Time: userTime,
Memory: userMem,
}
if status != runner.StatusNormal {
return
}
switch {
case wstatus.Exited():
result.Status = runner.StatusNormal
result.ExitStatus = wstatus.ExitStatus()
if result.ExitStatus != 0 {
result.Status = runner.StatusNonzeroExitStatus
}
return
case wstatus.Signaled():
sig := wstatus.Signal()
switch sig {
case unix.SIGXCPU, unix.SIGKILL:
status = runner.StatusTimeLimitExceeded
case unix.SIGXFSZ:
status = runner.StatusOutputLimitExceeded
case unix.SIGSYS:
status = runner.StatusDisallowedSyscall
default:
status = runner.StatusSignalled
}
result.Status = status
result.ExitStatus = int(sig)
return
}
}
}
// 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
for {
if _, err := unix.Wait4(-pgid, &wstatus, unix.WALL|unix.WNOHANG, nil); err != unix.EINTR && err != nil {
break
}
}
}
func (r *Runner) println(v ...interface{}) {
if r.ShowDetails {
fmt.Fprintln(os.Stderr, v...)
}
}