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
|
- 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
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
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
|
// 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 {
|
||||||
|
|||||||
@ -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