mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
178 lines
3.7 KiB
Go
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...)
|
|
}
|
|
}
|