mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-09-26 23:19:11 +08:00
157 lines
3.3 KiB
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...)
|
|
}
|
|
}
|