adapted to context

This commit is contained in:
criyle 2020-02-12 03:01:37 -05:00
parent 4ee93c1dae
commit bfedda0ba2
14 changed files with 273 additions and 160 deletions

View File

@ -56,12 +56,12 @@ Default file access syscall check:
- Unauthorized Access - Unauthorized Access
- Disallowed Syscall - Disallowed Syscall
- Runtime Error - Runtime Error
- Signaled - Signalled
- `SIGXCPU` / `SIGKILL` are treated as TimeLimitExceeded by rlimit or caller kill - `SIGXCPU` / `SIGKILL` are treated as TimeLimitExceeded by rlimit or caller kill
- `SIGXFSZ` is treated as OutputLimitExceeded by rlimit - `SIGXFSZ` is treated as OutputLimitExceeded by rlimit
- `SIGSYS` is treaded as Disallowed Syscall by seccomp - `SIGSYS` is treaded as Disallowed Syscall by seccomp
- Potential Runtime error are: `SIGSEGV` (segment fault) - Potential Runtime error are: `SIGSEGV` (segment fault)
- Nonzero Exit Code - Nonzero Exit Status
- Program Runner Error - Program Runner Error
### Result Structure ### Result Structure
@ -80,6 +80,16 @@ type Result struct {
} }
``` ```
### Runner Interface
Configured runner to run the program. `Context` is used to cancel (control time limit exceeded event; should not be nil).
``` go
type Runner interface {
Run(context.Context) <-chan types.Result
}
```
### Pre-forked Container Protocol ### Pre-forked Container Protocol
1. Pre-fork container daemons to run programs inside 1. Pre-fork container daemons to run programs inside

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"context"
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -108,7 +109,7 @@ func main() {
rt, err := start() rt, err := start()
if rt == nil { if rt == nil {
rt = &types.Result{ rt = &types.Result{
Status: types.StatusFatal, Status: types.StatusRunnerError,
} }
} }
if err == nil && rt.Status != types.StatusNormal { if err == nil && rt.Status != types.StatusNormal {
@ -120,15 +121,15 @@ func main() {
debug(err) debug(err)
c, ok := err.(types.Status) c, ok := err.(types.Status)
if !ok { if !ok {
c = types.StatusFatal c = types.StatusRunnerError
} }
// Handle fatal error from trace // Handle fatal error from trace
fmt.Fprintf(f, "%d %d %d %d\n", int(c), rt.UserTime, rt.UserMem, rt.ExitStatus) fmt.Fprintf(f, "%d %d %d %d\n", getStatus(c), int(rt.Time/time.Millisecond), uint64(rt.Memory)>>10, rt.ExitStatus)
if c == types.StatusFatal { if c == types.StatusRunnerError {
os.Exit(1) os.Exit(1)
} }
} else { } else {
fmt.Fprintf(f, "%d %d %d %d\n", 0, rt.UserTime, rt.UserMem, rt.ExitStatus) fmt.Fprintf(f, "%d %d %d %d\n", 0, int(rt.Time/time.Millisecond), uint64(rt.Memory)>>10, rt.ExitStatus)
} }
} }
@ -137,8 +138,8 @@ type daemonRunner struct {
*daemon.ExecveParam *daemon.ExecveParam
} }
func (r *daemonRunner) Start(done <-chan struct{}) (<-chan types.Result, error) { func (r *daemonRunner) Run(c context.Context) <-chan types.Result {
return r.Master.Execve(done, r.ExecveParam) return r.Master.Execve(c, r.ExecveParam)
} }
func start() (*types.Result, error) { func start() (*types.Result, error) {
@ -223,6 +224,11 @@ func start() (*types.Result, error) {
actionDefault = seccomp.ActionTrace.WithReturnCode(seccomp.MsgDisallow) actionDefault = seccomp.ActionTrace.WithReturnCode(seccomp.MsgDisallow)
} }
limit := types.Limit{
TimeLimit: time.Duration(timeLimit) * time.Second,
MemoryLimit: types.Size(memoryLimit << 20),
}
if runt == "daemon" { if runt == "daemon" {
root, err := ioutil.TempDir("", "dm") root, err := ioutil.TempDir("", "dm")
if err != nil { if err != nil {
@ -273,16 +279,13 @@ func start() (*types.Result, error) {
return nil, fmt.Errorf("cannot make rootfs mounts") return nil, fmt.Errorf("cannot make rootfs mounts")
} }
runner = &unshare.Runner{ runner = &unshare.Runner{
Args: args, Args: args,
Env: []string{pathEnv}, Env: []string{pathEnv},
ExecFile: execFile, ExecFile: execFile,
WorkDir: "/w", WorkDir: "/w",
Files: fds, Files: fds,
RLimits: rlims.PrepareRLimit(), RLimits: rlims.PrepareRLimit(),
Limit: types.Limit{ Limit: limit,
TimeLimit: timeLimit * 1e3,
MemoryLimit: memoryLimit << 10,
},
Seccomp: filter, Seccomp: filter,
Root: root, Root: root,
Mounts: mounts, Mounts: mounts,
@ -302,15 +305,12 @@ func start() (*types.Result, error) {
return nil, fmt.Errorf("failed to create seccomp filter %v", err) return nil, fmt.Errorf("failed to create seccomp filter %v", err)
} }
runner = &ptrace.Runner{ runner = &ptrace.Runner{
Args: args, Args: args,
Env: []string{pathEnv}, Env: []string{pathEnv},
ExecFile: execFile, ExecFile: execFile,
WorkDir: workPath, WorkDir: workPath,
RLimits: rlims.PrepareRLimit(), RLimits: rlims.PrepareRLimit(),
Limit: types.Limit{ Limit: limit,
TimeLimit: timeLimit * 1e3,
MemoryLimit: memoryLimit << 10,
},
Files: fds, Files: fds,
Seccomp: filter, Seccomp: filter,
ShowDetails: showDetails, ShowDetails: showDetails,
@ -328,22 +328,17 @@ func start() (*types.Result, error) {
// Run tracer // Run tracer
sTime := time.Now() sTime := time.Now()
done := make(chan struct{}) c, cancel := context.WithTimeout(context.Background(), time.Duration(int64(realTimeLimit)*int64(time.Second)))
s, err := runner.Start(done) defer cancel()
s := runner.Run(c)
rTime := time.Now() rTime := time.Now()
if err != nil {
return nil, fmt.Errorf("failed to execve: %v", err)
}
tC := time.After(time.Duration(int64(realTimeLimit) * int64(time.Second)))
select { select {
case <-sig: case <-sig:
close(done) cancel()
rt = <-s
rt.Status = types.StatusFatal
case <-tC:
close(done)
rt = <-s rt = <-s
rt.Status = types.StatusRunnerError
case rt = <-s: case rt = <-s:
} }
@ -366,8 +361,9 @@ func start() (*types.Result, error) {
return nil, fmt.Errorf("cgroup memory: %v", err) return nil, fmt.Errorf("cgroup memory: %v", err)
} }
debug("cgroup: cpu: ", cpu, " memory: ", memory) debug("cgroup: cpu: ", cpu, " memory: ", memory)
rt.UserTime = cpu / uint64(time.Millisecond) rt.Time = time.Duration(cpu)
rt.UserMem = memory >> 10 rt.Memory = types.Size(memory)
debug("cgroup:", rt)
} }
return &rt, nil return &rt, nil
} }
@ -377,3 +373,38 @@ func debug(v ...interface{}) {
fmt.Fprintln(os.Stderr, v...) fmt.Fprintln(os.Stderr, v...)
} }
} }
type Status int
// UOJ run_program constants
const (
StatusNormal Status = iota // 0
StatusInvalid // 1
StatusRE // 2
StatusMLE // 3
StatusTLE // 4
StatusOLE // 5
StatusBan // 6
StatusFatal // 7
)
func getStatus(s types.Status) int {
switch s {
case types.StatusNormal:
return int(StatusNormal)
case types.StatusInvalid:
return int(StatusInvalid)
case types.StatusTimeLimitExceeded:
return int(StatusTLE)
case types.StatusMemoryLimitExceeded:
return int(StatusMLE)
case types.StatusOutputLimitExceeded:
return int(StatusOLE)
case types.StatusDisallowedSyscall:
return int(StatusBan)
case types.StatusSignalled, types.StatusNonzeroExitStatus:
return int(StatusRE)
default:
return int(StatusFatal)
}
}

View File

@ -3,6 +3,7 @@ package daemon
import ( import (
"fmt" "fmt"
"syscall" "syscall"
"time"
"github.com/criyle/go-sandbox/pkg/forkexec" "github.com/criyle/go-sandbox/pkg/forkexec"
"github.com/criyle/go-sandbox/pkg/unixsocket" "github.com/criyle/go-sandbox/pkg/unixsocket"
@ -114,9 +115,9 @@ func (c *containerServer) handleExecve(cmd *ExecCmd, msg *unixsocket.Msg) error
if err != nil { if err != nil {
c.sendErrorReply("execve: wait4 %v", err) c.sendErrorReply("execve: wait4 %v", err)
} else { } else {
var status types.Status status := types.StatusNormal
userTime := uint64(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms userTime := time.Duration(rusage.Utime.Nano()) // ns
userMem := uint64(rusage.Maxrss) // kb userMem := types.Size(rusage.Maxrss << 10) // bytes
switch { switch {
case wstatus.Exited(): case wstatus.Exited():
exitStatus := wstatus.ExitStatus() exitStatus := wstatus.ExitStatus()
@ -124,8 +125,8 @@ func (c *containerServer) handleExecve(cmd *ExecCmd, msg *unixsocket.Msg) error
ExecReply: &ExecReply{ ExecReply: &ExecReply{
Status: status, Status: status,
ExitStatus: exitStatus, ExitStatus: exitStatus,
UserTime: userTime, Time: userTime,
UserMem: userMem, Memory: userMem,
}, },
}, nil) }, nil)
@ -133,19 +134,20 @@ func (c *containerServer) handleExecve(cmd *ExecCmd, msg *unixsocket.Msg) error
switch wstatus.Signal() { switch wstatus.Signal() {
// kill signal treats as TLE // kill signal treats as TLE
case syscall.SIGXCPU, syscall.SIGKILL: case syscall.SIGXCPU, syscall.SIGKILL:
status = types.StatusTLE status = types.StatusTimeLimitExceeded
case syscall.SIGXFSZ: case syscall.SIGXFSZ:
status = types.StatusOLE status = types.StatusOutputLimitExceeded
case syscall.SIGSYS: case syscall.SIGSYS:
status = types.StatusBan status = types.StatusDisallowedSyscall
default: default:
status = types.StatusRE status = types.StatusSignalled
} }
c.sendReply(&Reply{ c.sendReply(&Reply{
ExecReply: &ExecReply{ ExecReply: &ExecReply{
Status: status, ExitStatus: int(wstatus.Signal()),
UserTime: userTime, Status: status,
UserMem: userMem, Time: userTime,
Memory: userMem,
}, },
}, nil) }, nil)

View File

@ -36,6 +36,7 @@ Any socket related error will cause the daemon exit (with all process inside con
import ( import (
"os" "os"
"syscall" "syscall"
"time"
"github.com/criyle/go-sandbox/pkg/rlimit" "github.com/criyle/go-sandbox/pkg/rlimit"
"github.com/criyle/go-sandbox/types" "github.com/criyle/go-sandbox/types"
@ -90,10 +91,10 @@ type ErrorReply struct {
// ExecReply stores execve result // ExecReply stores execve result
type ExecReply struct { type ExecReply struct {
ExitStatus int // waitpid exit status ExitStatus int // waitpid exit status
Status types.Status // return status Status types.Status // return status
UserTime uint64 // waitpid user CPU (ms) Time time.Duration // waitpid user CPU (ns)
UserMem uint64 // waitpid user memory (kb) Memory types.Size // waitpid user memory (byte)
} }
func (e *ErrorReply) Error() string { func (e *ErrorReply) Error() string {

View File

@ -1,6 +1,7 @@
package daemon package daemon
import ( import (
"context"
"fmt" "fmt"
"time" "time"
@ -26,12 +27,23 @@ type ExecveParam struct {
} }
// Execve runs process inside container // Execve runs process inside container
// accepts done for cancelation // accepts context for cancelation
func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types.Result, error) { func (m *Master) Execve(c context.Context, param *ExecveParam) <-chan types.Result {
m.mu.Lock() m.mu.Lock()
sTime := time.Now() sTime := time.Now()
// make sure goroutine not leaked (blocked) even if result is not consumed
result := make(chan types.Result, 1)
errResult := func(f string, v ...interface{}) <-chan types.Result {
result <- types.Result{
Status: types.StatusRunnerError,
Error: fmt.Sprintf(f, v...),
}
return result
}
// if execve with fd, put fd at the first parameter // if execve with fd, put fd at the first parameter
var files []int var files []int
if param.ExecFile > 0 { if param.ExecFile > 0 {
@ -53,20 +65,20 @@ func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types.
} }
if err := m.sendCmd(&cmd, msg); err != nil { if err := m.sendCmd(&cmd, msg); err != nil {
m.mu.Unlock() m.mu.Unlock()
return nil, fmt.Errorf("execve: sendCmd %v", err) return errResult("execve: sendCmd %v", err)
} }
// sync function // sync function
reply, msg, err := m.recvReply() reply, msg, err := m.recvReply()
if err != nil { if err != nil {
m.mu.Unlock() m.mu.Unlock()
return nil, fmt.Errorf("execve: recvReply %v", err) return errResult("execve: recvReply %v", err)
} }
// if sync function did not involved // if sync function did not involved
if reply.Error != nil || msg == nil || msg.Cred == nil { if reply.Error != nil || msg == nil || msg.Cred == nil {
// tell kill function to exit and sync // tell kill function to exit and sync
m.execveSyncKill() m.execveSyncKill()
m.mu.Unlock() m.mu.Unlock()
return nil, fmt.Errorf("execve: no pid received or error %v", reply.Error) return errResult("execve: no pid received or error %v", reply.Error)
} }
if param.SyncFunc != nil { if param.SyncFunc != nil {
if err := param.SyncFunc(int(msg.Cred.Pid)); err != nil { if err := param.SyncFunc(int(msg.Cred.Pid)); err != nil {
@ -75,19 +87,17 @@ func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types.
// tell kill function to exit and sync // tell kill function to exit and sync
m.execveSyncKill() m.execveSyncKill()
m.mu.Unlock() m.mu.Unlock()
return nil, fmt.Errorf("execve: syncfunc failed %v", err) return errResult("execve: syncfunc failed %v", err)
} }
} }
// send to syncFunc ack ok // send to syncFunc ack ok
if err := m.sendCmd(&Cmd{Cmd: cmdOk}, nil); err != nil { if err := m.sendCmd(&Cmd{Cmd: cmdOk}, nil); err != nil {
m.mu.Unlock() m.mu.Unlock()
return nil, fmt.Errorf("execve: ack failed %v", err) return errResult("execve: ack failed %v", err)
} }
mTime := time.Now() mTime := time.Now()
// make sure goroutine not leaked (blocked) even if result is not consumed
result := make(chan types.Result, 1)
waitDone := make(chan struct{}) waitDone := make(chan struct{})
// Wait // Wait
@ -102,21 +112,21 @@ func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types.
// handle potential error // handle potential error
if err != nil { if err != nil {
result <- types.Result{ result <- types.Result{
Status: types.StatusFatal, Status: types.StatusRunnerError,
Error: err.Error(), Error: err.Error(),
} }
return return
} }
if reply2.Error != nil { if reply2.Error != nil {
result <- types.Result{ result <- types.Result{
Status: types.StatusFatal, Status: types.StatusRunnerError,
Error: reply2.Error.Error(), Error: reply2.Error.Error(),
} }
return return
} }
if reply2.ExecReply == nil { if reply2.ExecReply == nil {
result <- types.Result{ result <- types.Result{
Status: types.StatusFatal, Status: types.StatusRunnerError,
Error: "execve: no reply received", Error: "execve: no reply received",
} }
return return
@ -125,8 +135,8 @@ func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types.
result <- types.Result{ result <- types.Result{
Status: reply2.ExecReply.Status, Status: reply2.ExecReply.Status,
ExitStatus: reply2.ExecReply.ExitStatus, ExitStatus: reply2.ExecReply.ExitStatus,
UserTime: reply2.ExecReply.UserTime, Time: reply2.ExecReply.Time,
UserMem: reply2.ExecReply.UserMem, Memory: reply2.ExecReply.Memory,
SetUpTime: mTime.Sub(sTime), SetUpTime: mTime.Sub(sTime),
RunningTime: time.Since(mTime), RunningTime: time.Since(mTime),
} }
@ -135,13 +145,13 @@ func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types.
// Kill (if wait is done, a kill message need to be send to collect zombies) // Kill (if wait is done, a kill message need to be send to collect zombies)
go func() { go func() {
select { select {
case <-done: case <-c.Done():
case <-waitDone: case <-waitDone:
} }
m.sendCmd(&Cmd{Cmd: cmdKill}, nil) m.sendCmd(&Cmd{Cmd: cmdKill}, nil)
}() }()
return result, nil return result
} }
// execveSyncKill will send kill and recv reply // execveSyncKill will send kill and recv reply

View File

@ -55,7 +55,7 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
t.Handler.Debug("tracer started: ", pgid, err) t.Handler.Debug("tracer started: ", pgid, err)
if err != nil { if err != nil {
t.Handler.Debug("start tracee failed: ", err) t.Handler.Debug("start tracee failed: ", err)
result.Status = types.StatusRE result.Status = types.StatusRunnerError
return result, err return result, err
} }
@ -77,11 +77,11 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
// also ensure processes was well terminated // also ensure processes was well terminated
defer func() { defer func() {
if tle { if tle {
err = types.StatusTLE err = types.StatusTimeLimitExceeded
} }
if err2 := recover(); err2 != nil { if err2 := recover(); err2 != nil {
t.Handler.Debug(err2) t.Handler.Debug(err2)
err = types.StatusFatal err = types.StatusRunnerError
} }
// kill all tracee upon return // kill all tracee upon return
killAll(pgid) killAll(pgid)
@ -101,27 +101,27 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
} }
if err != nil { if err != nil {
t.Handler.Debug("wait4 failed: ", err) t.Handler.Debug("wait4 failed: ", err)
return result, types.StatusFatal return result, types.StatusRunnerError
} }
t.Handler.Debug("------ ", pid, " ------") t.Handler.Debug("------ ", pid, " ------")
status := types.StatusNormal status := types.StatusNormal
if pid == pgid { if pid == pgid {
// update resource usage and check against limits // update resource usage and check against limits
userTime := uint64(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms userTime := time.Duration(rusage.Utime.Nano()) // ns
userMem := uint64(rusage.Maxrss) // kb userMem := types.Size(rusage.Maxrss << 10) // bytes
// check tle / mle // check tle / mle
if userTime > t.Limit.TimeLimit { if userTime > t.Limit.TimeLimit {
status = types.StatusTLE status = types.StatusTimeLimitExceeded
} }
if userMem > t.Limit.MemoryLimit { if userMem > t.Limit.MemoryLimit {
status = types.StatusMLE status = types.StatusMemoryLimitExceeded
} }
result = types.Result{ result = types.Result{
Status: status, Status: status,
UserTime: userTime, Time: userTime,
UserMem: userMem, Memory: userMem,
} }
if status != types.StatusNormal { if status != types.StatusNormal {
return result, status return result, status
@ -138,8 +138,8 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
result.ExitStatus = wstatus.ExitStatus() result.ExitStatus = wstatus.ExitStatus()
return result, nil return result, nil
} }
result.Status = types.StatusFatal result.Status = types.StatusRunnerError
return result, types.StatusFatal return result, types.StatusRunnerError
} }
case wstatus.Signaled(): case wstatus.Signaled():
@ -149,15 +149,16 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
delete(traced, pid) delete(traced, pid)
switch sig { switch sig {
case unix.SIGXCPU, unix.SIGKILL: case unix.SIGXCPU, unix.SIGKILL:
status = types.StatusTLE status = types.StatusTimeLimitExceeded
case unix.SIGXFSZ: case unix.SIGXFSZ:
status = types.StatusOLE status = types.StatusOutputLimitExceeded
case unix.SIGSYS: case unix.SIGSYS:
status = types.StatusBan status = types.StatusDisallowedSyscall
default: default:
status = types.StatusRE status = types.StatusSignalled
} }
result.Status = status result.Status = status
result.ExitStatus = int(sig)
return result, status return result, status
} }
unix.PtraceCont(pid, int(sig)) unix.PtraceCont(pid, int(sig))
@ -170,7 +171,7 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
// Ptrace set option valid if the tracee is stopped // Ptrace set option valid if the tracee is stopped
err = setPtraceOption(pid) err = setPtraceOption(pid)
if err != nil { if err != nil {
result.Status = types.StatusFatal result.Status = types.StatusRunnerError
return result, err return result, err
} }
} }
@ -183,7 +184,7 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
// give the customized handle for syscall // give the customized handle for syscall
err := t.handleTrap(pid) err := t.handleTrap(pid)
if err != nil { if err != nil {
result.Status = types.StatusBan result.Status = types.StatusDisallowedSyscall
return result, err return result, err
} }
} else { } else {
@ -212,9 +213,9 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
// check if cpu rlimit hit // check if cpu rlimit hit
switch stopSig { switch stopSig {
case unix.SIGXCPU: case unix.SIGXCPU:
status = types.StatusTLE status = types.StatusTimeLimitExceeded
case unix.SIGXFSZ: case unix.SIGXFSZ:
status = types.StatusOLE status = types.StatusOutputLimitExceeded
} }
if status != types.StatusNormal { if status != types.StatusNormal {
result.Status = status result.Status = status
@ -266,7 +267,7 @@ func (t *Tracer) handleTrap(pid int) error {
return ctx.skipSyscall() return ctx.skipSyscall()
case TraceKill: case TraceKill:
return types.StatusBan return types.StatusDisallowedSyscall
} }
} }

View File

@ -127,7 +127,7 @@ func (h *tracerHandler) GetSyscallName(ctx *ptracer.Context) (string, error) {
func (h *tracerHandler) HandlerDisallow(name string) error { func (h *tracerHandler) HandlerDisallow(name string) error {
if !h.Unsafe { if !h.Unsafe {
return types.StatusBan return types.StatusDisallowedSyscall
} }
return nil return nil
} }

View File

@ -1,13 +1,15 @@
package ptrace package ptrace
import ( import (
"context"
"github.com/criyle/go-sandbox/pkg/forkexec" "github.com/criyle/go-sandbox/pkg/forkexec"
"github.com/criyle/go-sandbox/ptracer" "github.com/criyle/go-sandbox/ptracer"
"github.com/criyle/go-sandbox/types" "github.com/criyle/go-sandbox/types"
) )
// Start starts the tracing process // Run starts the tracing process
func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) { func (r *Runner) Run(c context.Context) <-chan types.Result {
ch := &forkexec.Runner{ ch := &forkexec.Runner{
Args: r.Args, Args: r.Args,
Env: r.Env, Env: r.Env,
@ -31,5 +33,14 @@ func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) {
Runner: ch, Runner: ch,
Limit: r.Limit, Limit: r.Limit,
} }
return tracer.Trace(done) rt, err := tracer.Trace(c.Done())
if err != nil {
ch := make(chan types.Result, 1)
ch <- types.Result{
Status: types.StatusRunnerError,
Error: err.Error(),
}
rt = ch
}
return rt
} }

View File

@ -1,8 +1,12 @@
package runner package runner
import "github.com/criyle/go-sandbox/types" import (
"context"
"github.com/criyle/go-sandbox/types"
)
// Runner interface defines method to start running // Runner interface defines method to start running
type Runner interface { type Runner interface {
Start(<-chan struct{}) (<-chan types.Result, error) Run(context.Context) <-chan types.Result
} }

View File

@ -1,6 +1,7 @@
package unshare package unshare
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"time" "time"
@ -16,9 +17,8 @@ const (
UnshareFlags = unix.CLONE_NEWNS | unix.CLONE_NEWPID | unix.CLONE_NEWUSER | unix.CLONE_NEWUTS | unix.CLONE_NEWCGROUP UnshareFlags = unix.CLONE_NEWNS | unix.CLONE_NEWPID | unix.CLONE_NEWUSER | unix.CLONE_NEWUTS | unix.CLONE_NEWCGROUP
) )
// Start starts the unshared process // Run starts the unshared process
func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) { func (r *Runner) Run(c context.Context) <-chan types.Result {
var err error
ch := &forkexec.Runner{ ch := &forkexec.Runner{
Args: r.Args, Args: r.Args,
Env: r.Env, Env: r.Env,
@ -44,8 +44,8 @@ func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) {
// run // run
go func() { go func() {
defer close(finish) defer close(finish)
ret, err2 := r.Trace(done, start, ch) ret, err2 := r.Trace(c.Done(), start, ch)
err = err2 ret.Error = err2.Error()
result <- ret result <- ret
}() }()
@ -53,7 +53,7 @@ func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) {
case <-start: case <-start:
case <-finish: case <-finish:
} }
return result, err return result
} }
// Trace tracks child processes // Trace tracks child processes
@ -72,7 +72,7 @@ func (r *Runner) Trace(done <-chan struct{}, start chan<- struct{},
pgid, err := runner.Start() pgid, err := runner.Start()
r.println("Starts: ", pgid, err) r.println("Starts: ", pgid, err)
if err != nil { if err != nil {
result.Status = types.StatusRE result.Status = types.StatusRunnerError
return result, err return result, err
} }
@ -92,7 +92,7 @@ func (r *Runner) Trace(done <-chan struct{}, start chan<- struct{},
defer func() { defer func() {
if tle { if tle {
err = types.StatusTLE err = types.StatusTimeLimitExceeded
} }
// kill all tracee upon return // kill all tracee upon return
killAll(pgid) killAll(pgid)
@ -107,24 +107,24 @@ loop:
_, err := unix.Wait4(pgid, &wstatus, 0, &rusage) _, err := unix.Wait4(pgid, &wstatus, 0, &rusage)
r.println("wait4: ", wstatus) r.println("wait4: ", wstatus)
if err != nil { if err != nil {
return result, types.StatusFatal return result, types.StatusRunnerError
} }
// update resource usage and check against limits // update resource usage and check against limits
userTime := uint64(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms userTime := time.Duration(rusage.Utime.Nano()) // ns
userMem := uint64(rusage.Maxrss) // kb userMem := types.Size(rusage.Maxrss << 10) // bytes // kb
// check tle / mle // check tle / mle
if userTime > r.Limit.TimeLimit { if userTime > r.Limit.TimeLimit {
status = types.StatusTLE status = types.StatusTimeLimitExceeded
} }
if userMem > r.Limit.MemoryLimit { if userMem > r.Limit.MemoryLimit {
status = types.StatusMLE status = types.StatusMemoryLimitExceeded
} }
result = types.Result{ result = types.Result{
Status: status, Status: status,
UserTime: userTime, Time: userTime,
UserMem: userMem, Memory: userMem,
} }
if status != types.StatusNormal { if status != types.StatusNormal {
return result, status return result, status
@ -138,15 +138,16 @@ loop:
sig := wstatus.Signal() sig := wstatus.Signal()
switch sig { switch sig {
case unix.SIGXCPU, unix.SIGKILL: case unix.SIGXCPU, unix.SIGKILL:
status = types.StatusTLE status = types.StatusTimeLimitExceeded
case unix.SIGXFSZ: case unix.SIGXFSZ:
status = types.StatusOLE status = types.StatusOutputLimitExceeded
case unix.SIGSYS: case unix.SIGSYS:
status = types.StatusBan status = types.StatusDisallowedSyscall
default: default:
status = types.StatusRE status = types.StatusSignalled
} }
result.Status = status result.Status = status
result.ExitStatus = int(sig)
break loop break loop
} }
} }

16
types/limit.go Normal file
View File

@ -0,0 +1,16 @@
package types
import (
"fmt"
"time"
)
// Limit represents the resource limit for traced process
type Limit struct {
TimeLimit time.Duration // user CPU time limit (in ns)
MemoryLimit Size // user memory limit (in bytes)
}
func (l Limit) String() string {
return fmt.Sprintf("Limit[Time=%v, Memory=%v]", l.TimeLimit, l.MemoryLimit)
}

36
types/result.go Normal file
View File

@ -0,0 +1,36 @@
package types
import (
"fmt"
"time"
)
// Result is the program runner result
type Result struct {
Status // result status
ExitStatus int // exit status (signal number if signalled)
Error string // potential detailed error message (for program runner error)
Time time.Duration // used user CPU time (underlying type int64 in ns)
Memory Size // used user memory (underlying type uint64 in bytes)
// metrics for the program runner
SetUpTime time.Duration
RunningTime time.Duration
}
func (r Result) String() string {
switch r.Status {
case StatusNormal:
return fmt.Sprintf("Result[%v %v][%v %v]", r.Time, r.Memory, r.SetUpTime, r.RunningTime)
case StatusSignalled:
return fmt.Sprintf("Result[Signalled(%d)][%v %v][%v %v]", r.ExitStatus, r.Time, r.Memory, r.SetUpTime, r.RunningTime)
case StatusRunnerError:
return fmt.Sprintf("Result[RunnerFailed(%s)][%v %v][%v %v]", r.Error, r.Time, r.Memory, r.SetUpTime, r.RunningTime)
default:
return fmt.Sprintf("Result[%v(%s)][%v %v][%v %v]", r.Status, r.Error, r.Time, r.Memory, r.SetUpTime, r.RunningTime)
}
}

View File

@ -3,28 +3,39 @@ package types
// Status is the result Status // Status is the result Status
type Status int type Status int
// Different end condition // Result Status for program runner
const ( const (
StatusNormal Status = iota // 0 StatusInvalid Status = iota // 0 not initialized
StatusInvalid // 1 // Normal
StatusRE // 2 StatusNormal // 1 normal
StatusMLE // 3
StatusTLE // 4 // Resource Limit Exceeded
StatusOLE // 5 StatusTimeLimitExceeded // 2 tle
StatusBan // 6 StatusMemoryLimitExceeded // 3 mle
StatusFatal // 7 StatusOutputLimitExceeded // 4 ole
// Unauthorized Access
StatusDisallowedSyscall // 5 ban
// Runtime Error
StatusSignalled // 6 signalled
StatusNonzeroExitStatus // 7 nonzero exit status
// Programmer Runner Error
StatusRunnerError // 8 runner error
) )
var ( var (
statusString = []string{ statusString = []string{
"Invalid",
"", "",
"invalid", "Time Limit Exceeded",
"runtime error", "Memory Limit Exceeded",
"memory limit exceeded", "Output Limit Exceeded",
"time limit exceeded", "Disallowed Syscall",
"output limit exceeded", "Signalled",
"syscall banned", "Nonzero Exit Status",
"runner failed", "Runner Error",
} }
) )
@ -33,7 +44,7 @@ func (t Status) String() string {
if i >= 0 && i < len(statusString) { if i >= 0 && i < len(statusString) {
return statusString[i] return statusString[i]
} }
return "invalid" return statusString[0]
} }
func (t Status) Error() string { func (t Status) Error() string {

View File

@ -1,21 +0,0 @@
package types
import "time"
// Result is the result returned by strat trace
type Result struct {
Status // the final status for the process
ExitStatus int // exit Status
Error string // potential detailed error message
UserTime uint64 // used user CPU time (in ms)
UserMem uint64 // used user memory (in kb)
// collects time usage for the runner
SetUpTime time.Duration
RunningTime time.Duration
}
// Limit represents the resource limit for traced process
type Limit struct {
TimeLimit uint64 // user CPU time limit (in ms)
MemoryLimit uint64 // user memory limit (in kB)
}