mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
adapted to context
This commit is contained in:
parent
4ee93c1dae
commit
bfedda0ba2
14
README.md
14
README.md
@ -56,12 +56,12 @@ Default file access syscall check:
|
||||
- Unauthorized Access
|
||||
- Disallowed Syscall
|
||||
- Runtime Error
|
||||
- Signaled
|
||||
- Signalled
|
||||
- `SIGXCPU` / `SIGKILL` are treated as TimeLimitExceeded by rlimit or caller kill
|
||||
- `SIGXFSZ` is treated as OutputLimitExceeded by rlimit
|
||||
- `SIGSYS` is treaded as Disallowed Syscall by seccomp
|
||||
- Potential Runtime error are: `SIGSEGV` (segment fault)
|
||||
- Nonzero Exit Code
|
||||
- Nonzero Exit Status
|
||||
- Program Runner Error
|
||||
|
||||
### 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
|
||||
|
||||
1. Pre-fork container daemons to run programs inside
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@ -108,7 +109,7 @@ func main() {
|
||||
rt, err := start()
|
||||
if rt == nil {
|
||||
rt = &types.Result{
|
||||
Status: types.StatusFatal,
|
||||
Status: types.StatusRunnerError,
|
||||
}
|
||||
}
|
||||
if err == nil && rt.Status != types.StatusNormal {
|
||||
@ -120,15 +121,15 @@ func main() {
|
||||
debug(err)
|
||||
c, ok := err.(types.Status)
|
||||
if !ok {
|
||||
c = types.StatusFatal
|
||||
c = types.StatusRunnerError
|
||||
}
|
||||
// Handle fatal error from trace
|
||||
fmt.Fprintf(f, "%d %d %d %d\n", int(c), rt.UserTime, rt.UserMem, rt.ExitStatus)
|
||||
if c == types.StatusFatal {
|
||||
fmt.Fprintf(f, "%d %d %d %d\n", getStatus(c), int(rt.Time/time.Millisecond), uint64(rt.Memory)>>10, rt.ExitStatus)
|
||||
if c == types.StatusRunnerError {
|
||||
os.Exit(1)
|
||||
}
|
||||
} 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
|
||||
}
|
||||
|
||||
func (r *daemonRunner) Start(done <-chan struct{}) (<-chan types.Result, error) {
|
||||
return r.Master.Execve(done, r.ExecveParam)
|
||||
func (r *daemonRunner) Run(c context.Context) <-chan types.Result {
|
||||
return r.Master.Execve(c, r.ExecveParam)
|
||||
}
|
||||
|
||||
func start() (*types.Result, error) {
|
||||
@ -223,6 +224,11 @@ func start() (*types.Result, error) {
|
||||
actionDefault = seccomp.ActionTrace.WithReturnCode(seccomp.MsgDisallow)
|
||||
}
|
||||
|
||||
limit := types.Limit{
|
||||
TimeLimit: time.Duration(timeLimit) * time.Second,
|
||||
MemoryLimit: types.Size(memoryLimit << 20),
|
||||
}
|
||||
|
||||
if runt == "daemon" {
|
||||
root, err := ioutil.TempDir("", "dm")
|
||||
if err != nil {
|
||||
@ -279,10 +285,7 @@ func start() (*types.Result, error) {
|
||||
WorkDir: "/w",
|
||||
Files: fds,
|
||||
RLimits: rlims.PrepareRLimit(),
|
||||
Limit: types.Limit{
|
||||
TimeLimit: timeLimit * 1e3,
|
||||
MemoryLimit: memoryLimit << 10,
|
||||
},
|
||||
Limit: limit,
|
||||
Seccomp: filter,
|
||||
Root: root,
|
||||
Mounts: mounts,
|
||||
@ -307,10 +310,7 @@ func start() (*types.Result, error) {
|
||||
ExecFile: execFile,
|
||||
WorkDir: workPath,
|
||||
RLimits: rlims.PrepareRLimit(),
|
||||
Limit: types.Limit{
|
||||
TimeLimit: timeLimit * 1e3,
|
||||
MemoryLimit: memoryLimit << 10,
|
||||
},
|
||||
Limit: limit,
|
||||
Files: fds,
|
||||
Seccomp: filter,
|
||||
ShowDetails: showDetails,
|
||||
@ -328,22 +328,17 @@ func start() (*types.Result, error) {
|
||||
|
||||
// Run tracer
|
||||
sTime := time.Now()
|
||||
done := make(chan struct{})
|
||||
s, err := runner.Start(done)
|
||||
c, cancel := context.WithTimeout(context.Background(), time.Duration(int64(realTimeLimit)*int64(time.Second)))
|
||||
defer cancel()
|
||||
|
||||
s := runner.Run(c)
|
||||
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 {
|
||||
case <-sig:
|
||||
close(done)
|
||||
rt = <-s
|
||||
rt.Status = types.StatusFatal
|
||||
|
||||
case <-tC:
|
||||
close(done)
|
||||
cancel()
|
||||
rt = <-s
|
||||
rt.Status = types.StatusRunnerError
|
||||
|
||||
case rt = <-s:
|
||||
}
|
||||
@ -366,8 +361,9 @@ func start() (*types.Result, error) {
|
||||
return nil, fmt.Errorf("cgroup memory: %v", err)
|
||||
}
|
||||
debug("cgroup: cpu: ", cpu, " memory: ", memory)
|
||||
rt.UserTime = cpu / uint64(time.Millisecond)
|
||||
rt.UserMem = memory >> 10
|
||||
rt.Time = time.Duration(cpu)
|
||||
rt.Memory = types.Size(memory)
|
||||
debug("cgroup:", rt)
|
||||
}
|
||||
return &rt, nil
|
||||
}
|
||||
@ -377,3 +373,38 @@ func debug(v ...interface{}) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package daemon
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/criyle/go-sandbox/pkg/forkexec"
|
||||
"github.com/criyle/go-sandbox/pkg/unixsocket"
|
||||
@ -114,9 +115,9 @@ func (c *containerServer) handleExecve(cmd *ExecCmd, msg *unixsocket.Msg) error
|
||||
if err != nil {
|
||||
c.sendErrorReply("execve: wait4 %v", err)
|
||||
} else {
|
||||
var status types.Status
|
||||
userTime := uint64(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms
|
||||
userMem := uint64(rusage.Maxrss) // kb
|
||||
status := types.StatusNormal
|
||||
userTime := time.Duration(rusage.Utime.Nano()) // ns
|
||||
userMem := types.Size(rusage.Maxrss << 10) // bytes
|
||||
switch {
|
||||
case wstatus.Exited():
|
||||
exitStatus := wstatus.ExitStatus()
|
||||
@ -124,8 +125,8 @@ func (c *containerServer) handleExecve(cmd *ExecCmd, msg *unixsocket.Msg) error
|
||||
ExecReply: &ExecReply{
|
||||
Status: status,
|
||||
ExitStatus: exitStatus,
|
||||
UserTime: userTime,
|
||||
UserMem: userMem,
|
||||
Time: userTime,
|
||||
Memory: userMem,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
@ -133,19 +134,20 @@ func (c *containerServer) handleExecve(cmd *ExecCmd, msg *unixsocket.Msg) error
|
||||
switch wstatus.Signal() {
|
||||
// kill signal treats as TLE
|
||||
case syscall.SIGXCPU, syscall.SIGKILL:
|
||||
status = types.StatusTLE
|
||||
status = types.StatusTimeLimitExceeded
|
||||
case syscall.SIGXFSZ:
|
||||
status = types.StatusOLE
|
||||
status = types.StatusOutputLimitExceeded
|
||||
case syscall.SIGSYS:
|
||||
status = types.StatusBan
|
||||
status = types.StatusDisallowedSyscall
|
||||
default:
|
||||
status = types.StatusRE
|
||||
status = types.StatusSignalled
|
||||
}
|
||||
c.sendReply(&Reply{
|
||||
ExecReply: &ExecReply{
|
||||
ExitStatus: int(wstatus.Signal()),
|
||||
Status: status,
|
||||
UserTime: userTime,
|
||||
UserMem: userMem,
|
||||
Time: userTime,
|
||||
Memory: userMem,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
|
||||
@ -36,6 +36,7 @@ Any socket related error will cause the daemon exit (with all process inside con
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/criyle/go-sandbox/pkg/rlimit"
|
||||
"github.com/criyle/go-sandbox/types"
|
||||
@ -92,8 +93,8 @@ type ErrorReply struct {
|
||||
type ExecReply struct {
|
||||
ExitStatus int // waitpid exit status
|
||||
Status types.Status // return status
|
||||
UserTime uint64 // waitpid user CPU (ms)
|
||||
UserMem uint64 // waitpid user memory (kb)
|
||||
Time time.Duration // waitpid user CPU (ns)
|
||||
Memory types.Size // waitpid user memory (byte)
|
||||
}
|
||||
|
||||
func (e *ErrorReply) Error() string {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@ -26,12 +27,23 @@ type ExecveParam struct {
|
||||
}
|
||||
|
||||
// Execve runs process inside container
|
||||
// accepts done for cancelation
|
||||
func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types.Result, error) {
|
||||
// accepts context for cancelation
|
||||
func (m *Master) Execve(c context.Context, param *ExecveParam) <-chan types.Result {
|
||||
m.mu.Lock()
|
||||
|
||||
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
|
||||
var files []int
|
||||
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 {
|
||||
m.mu.Unlock()
|
||||
return nil, fmt.Errorf("execve: sendCmd %v", err)
|
||||
return errResult("execve: sendCmd %v", err)
|
||||
}
|
||||
// sync function
|
||||
reply, msg, err := m.recvReply()
|
||||
if err != nil {
|
||||
m.mu.Unlock()
|
||||
return nil, fmt.Errorf("execve: recvReply %v", err)
|
||||
return errResult("execve: recvReply %v", err)
|
||||
}
|
||||
// if sync function did not involved
|
||||
if reply.Error != nil || msg == nil || msg.Cred == nil {
|
||||
// tell kill function to exit and sync
|
||||
m.execveSyncKill()
|
||||
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 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
|
||||
m.execveSyncKill()
|
||||
m.mu.Unlock()
|
||||
return nil, fmt.Errorf("execve: syncfunc failed %v", err)
|
||||
return errResult("execve: syncfunc failed %v", err)
|
||||
}
|
||||
}
|
||||
// send to syncFunc ack ok
|
||||
if err := m.sendCmd(&Cmd{Cmd: cmdOk}, nil); err != nil {
|
||||
m.mu.Unlock()
|
||||
return nil, fmt.Errorf("execve: ack failed %v", err)
|
||||
return errResult("execve: ack failed %v", err)
|
||||
}
|
||||
|
||||
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{})
|
||||
|
||||
// Wait
|
||||
@ -102,21 +112,21 @@ func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types.
|
||||
// handle potential error
|
||||
if err != nil {
|
||||
result <- types.Result{
|
||||
Status: types.StatusFatal,
|
||||
Status: types.StatusRunnerError,
|
||||
Error: err.Error(),
|
||||
}
|
||||
return
|
||||
}
|
||||
if reply2.Error != nil {
|
||||
result <- types.Result{
|
||||
Status: types.StatusFatal,
|
||||
Status: types.StatusRunnerError,
|
||||
Error: reply2.Error.Error(),
|
||||
}
|
||||
return
|
||||
}
|
||||
if reply2.ExecReply == nil {
|
||||
result <- types.Result{
|
||||
Status: types.StatusFatal,
|
||||
Status: types.StatusRunnerError,
|
||||
Error: "execve: no reply received",
|
||||
}
|
||||
return
|
||||
@ -125,8 +135,8 @@ func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types.
|
||||
result <- types.Result{
|
||||
Status: reply2.ExecReply.Status,
|
||||
ExitStatus: reply2.ExecReply.ExitStatus,
|
||||
UserTime: reply2.ExecReply.UserTime,
|
||||
UserMem: reply2.ExecReply.UserMem,
|
||||
Time: reply2.ExecReply.Time,
|
||||
Memory: reply2.ExecReply.Memory,
|
||||
SetUpTime: mTime.Sub(sTime),
|
||||
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)
|
||||
go func() {
|
||||
select {
|
||||
case <-done:
|
||||
case <-c.Done():
|
||||
case <-waitDone:
|
||||
}
|
||||
m.sendCmd(&Cmd{Cmd: cmdKill}, nil)
|
||||
}()
|
||||
|
||||
return result, nil
|
||||
return result
|
||||
}
|
||||
|
||||
// execveSyncKill will send kill and recv reply
|
||||
|
||||
@ -55,7 +55,7 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
|
||||
t.Handler.Debug("tracer started: ", pgid, err)
|
||||
if err != nil {
|
||||
t.Handler.Debug("start tracee failed: ", err)
|
||||
result.Status = types.StatusRE
|
||||
result.Status = types.StatusRunnerError
|
||||
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
|
||||
defer func() {
|
||||
if tle {
|
||||
err = types.StatusTLE
|
||||
err = types.StatusTimeLimitExceeded
|
||||
}
|
||||
if err2 := recover(); err2 != nil {
|
||||
t.Handler.Debug(err2)
|
||||
err = types.StatusFatal
|
||||
err = types.StatusRunnerError
|
||||
}
|
||||
// kill all tracee upon return
|
||||
killAll(pgid)
|
||||
@ -101,27 +101,27 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
|
||||
}
|
||||
if err != nil {
|
||||
t.Handler.Debug("wait4 failed: ", err)
|
||||
return result, types.StatusFatal
|
||||
return result, types.StatusRunnerError
|
||||
}
|
||||
t.Handler.Debug("------ ", pid, " ------")
|
||||
|
||||
status := types.StatusNormal
|
||||
if pid == pgid {
|
||||
// update resource usage and check against limits
|
||||
userTime := uint64(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms
|
||||
userMem := uint64(rusage.Maxrss) // kb
|
||||
userTime := time.Duration(rusage.Utime.Nano()) // ns
|
||||
userMem := types.Size(rusage.Maxrss << 10) // bytes
|
||||
|
||||
// check tle / mle
|
||||
if userTime > t.Limit.TimeLimit {
|
||||
status = types.StatusTLE
|
||||
status = types.StatusTimeLimitExceeded
|
||||
}
|
||||
if userMem > t.Limit.MemoryLimit {
|
||||
status = types.StatusMLE
|
||||
status = types.StatusMemoryLimitExceeded
|
||||
}
|
||||
result = types.Result{
|
||||
Status: status,
|
||||
UserTime: userTime,
|
||||
UserMem: userMem,
|
||||
Time: userTime,
|
||||
Memory: userMem,
|
||||
}
|
||||
if status != types.StatusNormal {
|
||||
return result, status
|
||||
@ -138,8 +138,8 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
|
||||
result.ExitStatus = wstatus.ExitStatus()
|
||||
return result, nil
|
||||
}
|
||||
result.Status = types.StatusFatal
|
||||
return result, types.StatusFatal
|
||||
result.Status = types.StatusRunnerError
|
||||
return result, types.StatusRunnerError
|
||||
}
|
||||
|
||||
case wstatus.Signaled():
|
||||
@ -149,15 +149,16 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
|
||||
delete(traced, pid)
|
||||
switch sig {
|
||||
case unix.SIGXCPU, unix.SIGKILL:
|
||||
status = types.StatusTLE
|
||||
status = types.StatusTimeLimitExceeded
|
||||
case unix.SIGXFSZ:
|
||||
status = types.StatusOLE
|
||||
status = types.StatusOutputLimitExceeded
|
||||
case unix.SIGSYS:
|
||||
status = types.StatusBan
|
||||
status = types.StatusDisallowedSyscall
|
||||
default:
|
||||
status = types.StatusRE
|
||||
status = types.StatusSignalled
|
||||
}
|
||||
result.Status = status
|
||||
result.ExitStatus = int(sig)
|
||||
return result, status
|
||||
}
|
||||
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
|
||||
err = setPtraceOption(pid)
|
||||
if err != nil {
|
||||
result.Status = types.StatusFatal
|
||||
result.Status = types.StatusRunnerError
|
||||
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
|
||||
err := t.handleTrap(pid)
|
||||
if err != nil {
|
||||
result.Status = types.StatusBan
|
||||
result.Status = types.StatusDisallowedSyscall
|
||||
return result, err
|
||||
}
|
||||
} else {
|
||||
@ -212,9 +213,9 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
|
||||
// check if cpu rlimit hit
|
||||
switch stopSig {
|
||||
case unix.SIGXCPU:
|
||||
status = types.StatusTLE
|
||||
status = types.StatusTimeLimitExceeded
|
||||
case unix.SIGXFSZ:
|
||||
status = types.StatusOLE
|
||||
status = types.StatusOutputLimitExceeded
|
||||
}
|
||||
if status != types.StatusNormal {
|
||||
result.Status = status
|
||||
@ -266,7 +267,7 @@ func (t *Tracer) handleTrap(pid int) error {
|
||||
return ctx.skipSyscall()
|
||||
|
||||
case TraceKill:
|
||||
return types.StatusBan
|
||||
return types.StatusDisallowedSyscall
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -127,7 +127,7 @@ func (h *tracerHandler) GetSyscallName(ctx *ptracer.Context) (string, error) {
|
||||
|
||||
func (h *tracerHandler) HandlerDisallow(name string) error {
|
||||
if !h.Unsafe {
|
||||
return types.StatusBan
|
||||
return types.StatusDisallowedSyscall
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
package ptrace
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/criyle/go-sandbox/pkg/forkexec"
|
||||
"github.com/criyle/go-sandbox/ptracer"
|
||||
"github.com/criyle/go-sandbox/types"
|
||||
)
|
||||
|
||||
// Start starts the tracing process
|
||||
func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) {
|
||||
// Run starts the tracing process
|
||||
func (r *Runner) Run(c context.Context) <-chan types.Result {
|
||||
ch := &forkexec.Runner{
|
||||
Args: r.Args,
|
||||
Env: r.Env,
|
||||
@ -31,5 +33,14 @@ func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) {
|
||||
Runner: ch,
|
||||
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
|
||||
}
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
package runner
|
||||
|
||||
import "github.com/criyle/go-sandbox/types"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/criyle/go-sandbox/types"
|
||||
)
|
||||
|
||||
// Runner interface defines method to start running
|
||||
type Runner interface {
|
||||
Start(<-chan struct{}) (<-chan types.Result, error)
|
||||
Run(context.Context) <-chan types.Result
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package unshare
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
@ -16,9 +17,8 @@ const (
|
||||
UnshareFlags = unix.CLONE_NEWNS | unix.CLONE_NEWPID | unix.CLONE_NEWUSER | unix.CLONE_NEWUTS | unix.CLONE_NEWCGROUP
|
||||
)
|
||||
|
||||
// Start starts the unshared process
|
||||
func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) {
|
||||
var err error
|
||||
// Run starts the unshared process
|
||||
func (r *Runner) Run(c context.Context) <-chan types.Result {
|
||||
ch := &forkexec.Runner{
|
||||
Args: r.Args,
|
||||
Env: r.Env,
|
||||
@ -44,8 +44,8 @@ func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) {
|
||||
// run
|
||||
go func() {
|
||||
defer close(finish)
|
||||
ret, err2 := r.Trace(done, start, ch)
|
||||
err = err2
|
||||
ret, err2 := r.Trace(c.Done(), start, ch)
|
||||
ret.Error = err2.Error()
|
||||
result <- ret
|
||||
}()
|
||||
|
||||
@ -53,7 +53,7 @@ func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) {
|
||||
case <-start:
|
||||
case <-finish:
|
||||
}
|
||||
return result, err
|
||||
return result
|
||||
}
|
||||
|
||||
// Trace tracks child processes
|
||||
@ -72,7 +72,7 @@ func (r *Runner) Trace(done <-chan struct{}, start chan<- struct{},
|
||||
pgid, err := runner.Start()
|
||||
r.println("Starts: ", pgid, err)
|
||||
if err != nil {
|
||||
result.Status = types.StatusRE
|
||||
result.Status = types.StatusRunnerError
|
||||
return result, err
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ func (r *Runner) Trace(done <-chan struct{}, start chan<- struct{},
|
||||
|
||||
defer func() {
|
||||
if tle {
|
||||
err = types.StatusTLE
|
||||
err = types.StatusTimeLimitExceeded
|
||||
}
|
||||
// kill all tracee upon return
|
||||
killAll(pgid)
|
||||
@ -107,24 +107,24 @@ loop:
|
||||
_, err := unix.Wait4(pgid, &wstatus, 0, &rusage)
|
||||
r.println("wait4: ", wstatus)
|
||||
if err != nil {
|
||||
return result, types.StatusFatal
|
||||
return result, types.StatusRunnerError
|
||||
}
|
||||
|
||||
// update resource usage and check against limits
|
||||
userTime := uint64(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms
|
||||
userMem := uint64(rusage.Maxrss) // kb
|
||||
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.StatusTLE
|
||||
status = types.StatusTimeLimitExceeded
|
||||
}
|
||||
if userMem > r.Limit.MemoryLimit {
|
||||
status = types.StatusMLE
|
||||
status = types.StatusMemoryLimitExceeded
|
||||
}
|
||||
result = types.Result{
|
||||
Status: status,
|
||||
UserTime: userTime,
|
||||
UserMem: userMem,
|
||||
Time: userTime,
|
||||
Memory: userMem,
|
||||
}
|
||||
if status != types.StatusNormal {
|
||||
return result, status
|
||||
@ -138,15 +138,16 @@ loop:
|
||||
sig := wstatus.Signal()
|
||||
switch sig {
|
||||
case unix.SIGXCPU, unix.SIGKILL:
|
||||
status = types.StatusTLE
|
||||
status = types.StatusTimeLimitExceeded
|
||||
case unix.SIGXFSZ:
|
||||
status = types.StatusOLE
|
||||
status = types.StatusOutputLimitExceeded
|
||||
case unix.SIGSYS:
|
||||
status = types.StatusBan
|
||||
status = types.StatusDisallowedSyscall
|
||||
default:
|
||||
status = types.StatusRE
|
||||
status = types.StatusSignalled
|
||||
}
|
||||
result.Status = status
|
||||
result.ExitStatus = int(sig)
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
16
types/limit.go
Normal file
16
types/limit.go
Normal 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
36
types/result.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -3,28 +3,39 @@ package types
|
||||
// Status is the result Status
|
||||
type Status int
|
||||
|
||||
// Different end condition
|
||||
// Result Status for program runner
|
||||
const (
|
||||
StatusNormal Status = iota // 0
|
||||
StatusInvalid // 1
|
||||
StatusRE // 2
|
||||
StatusMLE // 3
|
||||
StatusTLE // 4
|
||||
StatusOLE // 5
|
||||
StatusBan // 6
|
||||
StatusFatal // 7
|
||||
StatusInvalid Status = iota // 0 not initialized
|
||||
// Normal
|
||||
StatusNormal // 1 normal
|
||||
|
||||
// Resource Limit Exceeded
|
||||
StatusTimeLimitExceeded // 2 tle
|
||||
StatusMemoryLimitExceeded // 3 mle
|
||||
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 (
|
||||
statusString = []string{
|
||||
"Invalid",
|
||||
"",
|
||||
"invalid",
|
||||
"runtime error",
|
||||
"memory limit exceeded",
|
||||
"time limit exceeded",
|
||||
"output limit exceeded",
|
||||
"syscall banned",
|
||||
"runner failed",
|
||||
"Time Limit Exceeded",
|
||||
"Memory Limit Exceeded",
|
||||
"Output Limit Exceeded",
|
||||
"Disallowed Syscall",
|
||||
"Signalled",
|
||||
"Nonzero Exit Status",
|
||||
"Runner Error",
|
||||
}
|
||||
)
|
||||
|
||||
@ -33,7 +44,7 @@ func (t Status) String() string {
|
||||
if i >= 0 && i < len(statusString) {
|
||||
return statusString[i]
|
||||
}
|
||||
return "invalid"
|
||||
return statusString[0]
|
||||
}
|
||||
|
||||
func (t Status) Error() string {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user