mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-09-26 23:19:11 +08:00
Remove types pkg & update documents
- move types pkg into runner - add documentations for cgroup & container - add benchmakr for cgroup & container
This commit is contained in:
parent
e8260dde37
commit
4f2257a187
68
README.md
68
README.md
@ -43,7 +43,7 @@ Default file access syscall check:
|
|||||||
2. Use Linux Control Groups to limit & acct CPU & memory (elimilate wait4.rusage)
|
2. Use Linux Control Groups to limit & acct CPU & memory (elimilate wait4.rusage)
|
||||||
3. Container tech with execveat memfd, sethostname, setdomainname
|
3. Container tech with execveat memfd, sethostname, setdomainname
|
||||||
|
|
||||||
## Design (in progress)
|
## Design
|
||||||
|
|
||||||
### Result Status
|
### Result Status
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ Configured runner to run the program. `Context` is used to cancel (control time
|
|||||||
|
|
||||||
``` go
|
``` go
|
||||||
type Runner interface {
|
type Runner interface {
|
||||||
Run(context.Context) <-chan types.Result
|
Run(context.Context) <-chan runner.Result
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ type Environment interface {
|
|||||||
Open([]OpenCmd) ([]*os.File, error)
|
Open([]OpenCmd) ([]*os.File, error)
|
||||||
Delete(p string) error
|
Delete(p string) error
|
||||||
Reset() error
|
Reset() error
|
||||||
Execve(context.Context, ExecveParam) <-chan types.Result
|
Execve(context.Context, ExecveParam) <-chan runner.Result
|
||||||
Destroy() error
|
Destroy() error
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -170,7 +170,6 @@ type Environment interface {
|
|||||||
- filehandler: an example implementation of UOJ file set
|
- filehandler: an example implementation of UOJ file set
|
||||||
- unshare: wrapper to call forkexec and unshared namespaces
|
- unshare: wrapper to call forkexec and unshared namespaces
|
||||||
- ptracer: ptrace tracer and provides syscall trap filter context
|
- ptracer: ptrace tracer and provides syscall trap filter context
|
||||||
- types: provides general res / result data structures
|
|
||||||
|
|
||||||
## Executable
|
## Executable
|
||||||
|
|
||||||
@ -180,35 +179,50 @@ type Environment interface {
|
|||||||
|
|
||||||
- config/config.go: all configs toward running specs (similar to UOJ)
|
- config/config.go: all configs toward running specs (similar to UOJ)
|
||||||
|
|
||||||
## Benchmarks (MacOS docker amd64 / native arm64)
|
## Benchmarks
|
||||||
|
|
||||||
- 1ms / 2ms: fork, unshare pid / user / cgroup
|
### ForkExec
|
||||||
- 4ms / 8ms: run inside pre-forked container
|
|
||||||
- 50ms / 25ms: unshare ipc / mount
|
|
||||||
- 100ms / 44ms: unshare pid & user & cgroup & mount & pivot root
|
|
||||||
- 400ms / 63ms: unshare net
|
|
||||||
- 800ms / 170ms: unshare all
|
|
||||||
- 880ms / 170ms: unshare all & pivot root
|
|
||||||
|
|
||||||
It seems unshare net or ipc takes time, maybe limits action by seccomp instead.
|
|
||||||
Pre-forked container also saves time for container creation / cleanup.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ go test -bench . -benchtime 10s
|
$ go test -bench . -benchtime 10s
|
||||||
goos: linux
|
goos: linux
|
||||||
goarch: amd64
|
goarch: amd64
|
||||||
pkg: github.com/criyle/go-sandbox/pkg/forkexec
|
pkg: github.com/criyle/go-sandbox/pkg/forkexec
|
||||||
BenchmarkSimpleFork-4 12789 870486 ns/op
|
BenchmarkSimpleFork-4 12409 996096 ns/op
|
||||||
BenchmarkUnsharePid-4 13172 917304 ns/op
|
BenchmarkUnsharePid-4 10000 1065168 ns/op
|
||||||
BenchmarkUnshareUser-4 13148 927952 ns/op
|
BenchmarkUnshareUser-4 10000 1061770 ns/op
|
||||||
BenchmarkUnshareUts-4 13170 884606 ns/op
|
BenchmarkUnshareUts-4 10000 1056558 ns/op
|
||||||
BenchmarkUnshareCgroup-4 13650 895186 ns/op
|
BenchmarkUnshareCgroup-4 10000 1049446 ns/op
|
||||||
BenchmarkUnshareIpc-4 196 66418708 ns/op
|
BenchmarkUnshareIpc-4 709 16114052 ns/op
|
||||||
BenchmarkUnshareMount-4 243 46957682 ns/op
|
BenchmarkUnshareMount-4 745 16207754 ns/op
|
||||||
BenchmarkUnshareNet-4 100 411869776 ns/op
|
BenchmarkUnshareNet-4 3643 3492924 ns/op
|
||||||
BenchmarkFastUnshareMountPivot-4 120 107310917 ns/op
|
BenchmarkFastUnshareMountPivot-4 612 20967318 ns/op
|
||||||
BenchmarkUnshareAll-4 100 837352275 ns/op
|
BenchmarkUnshareAll-4 837 14047995 ns/op
|
||||||
BenchmarkUnshareMountPivot-4 12 913099234 ns/op
|
BenchmarkUnshareMountPivot-4 488 24198331 ns/op
|
||||||
PASS
|
PASS
|
||||||
ok github.com/criyle/go-sandbox/pkg/forkexec 300.744s
|
ok github.com/criyle/go-sandbox/pkg/forkexec 147.186s
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go test -bench . -benchtime 10s
|
||||||
|
goos: linux
|
||||||
|
goarch: amd64
|
||||||
|
pkg: github.com/criyle/go-sandbox/container
|
||||||
|
BenchmarkContainer-4 5907 2062070 ns/op
|
||||||
|
PASS
|
||||||
|
ok github.com/criyle/go-sandbox/container 21.763s
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cgroup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go test -bench . -benchtime 10s
|
||||||
|
goos: linux
|
||||||
|
goarch: amd64
|
||||||
|
pkg: github.com/criyle/go-sandbox/pkg/cgroup
|
||||||
|
BenchmarkCgroup-4 50283 245094 ns/op
|
||||||
|
PASS
|
||||||
|
ok github.com/criyle/go-sandbox/pkg/cgroup 14.744s
|
||||||
```
|
```
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// Command runprog executes program defined restricted environment including seccomp-ptraced, namespaced and containerized.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -21,7 +22,6 @@ import (
|
|||||||
"github.com/criyle/go-sandbox/runner/ptrace"
|
"github.com/criyle/go-sandbox/runner/ptrace"
|
||||||
"github.com/criyle/go-sandbox/runner/ptrace/filehandler"
|
"github.com/criyle/go-sandbox/runner/ptrace/filehandler"
|
||||||
"github.com/criyle/go-sandbox/runner/unshare"
|
"github.com/criyle/go-sandbox/runner/unshare"
|
||||||
"github.com/criyle/go-sandbox/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -108,24 +108,24 @@ func main() {
|
|||||||
|
|
||||||
rt, err := start()
|
rt, err := start()
|
||||||
if rt == nil {
|
if rt == nil {
|
||||||
rt = &types.Result{
|
rt = &runner.Result{
|
||||||
Status: types.StatusRunnerError,
|
Status: runner.StatusRunnerError,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil && rt.Status != types.StatusNormal {
|
if err == nil && rt.Status != runner.StatusNormal {
|
||||||
err = rt.Status
|
err = rt.Status
|
||||||
}
|
}
|
||||||
debug("setupTime: ", rt.SetUpTime)
|
debug("setupTime: ", rt.SetUpTime)
|
||||||
debug("runningTime: ", rt.RunningTime)
|
debug("runningTime: ", rt.RunningTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug(err)
|
debug(err)
|
||||||
c, ok := err.(types.Status)
|
c, ok := err.(runner.Status)
|
||||||
if !ok {
|
if !ok {
|
||||||
c = types.StatusRunnerError
|
c = runner.StatusRunnerError
|
||||||
}
|
}
|
||||||
// Handle fatal error from trace
|
// Handle fatal error from trace
|
||||||
fmt.Fprintf(f, "%d %d %d %d\n", getStatus(c), int(rt.Time/time.Millisecond), uint64(rt.Memory)>>10, 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.StatusRunnerError {
|
if c == runner.StatusRunnerError {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -138,17 +138,17 @@ type containerRunner struct {
|
|||||||
container.ExecveParam
|
container.ExecveParam
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *containerRunner) Run(c context.Context) <-chan types.Result {
|
func (r *containerRunner) Run(c context.Context) <-chan runner.Result {
|
||||||
return r.Environment.Execve(c, r.ExecveParam)
|
return r.Environment.Execve(c, r.ExecveParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() (*types.Result, error) {
|
func start() (*runner.Result, error) {
|
||||||
var (
|
var (
|
||||||
runner runner.Runner
|
r runner.Runner
|
||||||
cg *cgroup.CGroup
|
cg *cgroup.Cgroup
|
||||||
err error
|
err error
|
||||||
execFile uintptr
|
execFile uintptr
|
||||||
rt types.Result
|
rt runner.Result
|
||||||
)
|
)
|
||||||
|
|
||||||
addRead := filehandler.GetExtraSet(addReadable, addRawReadable)
|
addRead := filehandler.GetExtraSet(addReadable, addRawReadable)
|
||||||
@ -225,9 +225,9 @@ func start() (*types.Result, error) {
|
|||||||
actionDefault = seccomp.ActionTrace.WithReturnCode(seccomp.MsgDisallow)
|
actionDefault = seccomp.ActionTrace.WithReturnCode(seccomp.MsgDisallow)
|
||||||
}
|
}
|
||||||
|
|
||||||
limit := types.Limit{
|
limit := runner.Limit{
|
||||||
TimeLimit: time.Duration(timeLimit) * time.Second,
|
TimeLimit: time.Duration(timeLimit) * time.Second,
|
||||||
MemoryLimit: types.Size(memoryLimit << 20),
|
MemoryLimit: runner.Size(memoryLimit << 20),
|
||||||
}
|
}
|
||||||
|
|
||||||
if runt == "container" {
|
if runt == "container" {
|
||||||
@ -250,12 +250,12 @@ func start() (*types.Result, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to ping container: %v", err)
|
return nil, fmt.Errorf("failed to ping container: %v", err)
|
||||||
}
|
}
|
||||||
runner = &containerRunner{
|
r = &containerRunner{
|
||||||
Environment: m,
|
Environment: m,
|
||||||
ExecveParam: container.ExecveParam{
|
ExecveParam: container.ExecveParam{
|
||||||
Args: args,
|
Args: args,
|
||||||
Env: []string{pathEnv},
|
Env: []string{pathEnv},
|
||||||
Fds: fds,
|
Files: fds,
|
||||||
ExecFile: execFile,
|
ExecFile: execFile,
|
||||||
RLimits: rlims.PrepareRLimit(),
|
RLimits: rlims.PrepareRLimit(),
|
||||||
SyncFunc: syncFunc,
|
SyncFunc: syncFunc,
|
||||||
@ -279,7 +279,7 @@ func start() (*types.Result, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot make rootfs mounts")
|
return nil, fmt.Errorf("cannot make rootfs mounts")
|
||||||
}
|
}
|
||||||
runner = &unshare.Runner{
|
r = &unshare.Runner{
|
||||||
Args: args,
|
Args: args,
|
||||||
Env: []string{pathEnv},
|
Env: []string{pathEnv},
|
||||||
ExecFile: execFile,
|
ExecFile: execFile,
|
||||||
@ -305,7 +305,7 @@ func start() (*types.Result, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
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{
|
r = &ptrace.Runner{
|
||||||
Args: args,
|
Args: args,
|
||||||
Env: []string{pathEnv},
|
Env: []string{pathEnv},
|
||||||
ExecFile: execFile,
|
ExecFile: execFile,
|
||||||
@ -332,14 +332,14 @@ func start() (*types.Result, error) {
|
|||||||
c, cancel := context.WithTimeout(context.Background(), time.Duration(int64(realTimeLimit)*int64(time.Second)))
|
c, cancel := context.WithTimeout(context.Background(), time.Duration(int64(realTimeLimit)*int64(time.Second)))
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
s := runner.Run(c)
|
s := r.Run(c)
|
||||||
rTime := time.Now()
|
rTime := time.Now()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-sig:
|
case <-sig:
|
||||||
cancel()
|
cancel()
|
||||||
rt = <-s
|
rt = <-s
|
||||||
rt.Status = types.StatusRunnerError
|
rt.Status = runner.StatusRunnerError
|
||||||
|
|
||||||
case rt = <-s:
|
case rt = <-s:
|
||||||
}
|
}
|
||||||
@ -367,7 +367,7 @@ func start() (*types.Result, error) {
|
|||||||
}
|
}
|
||||||
debug("cgroup: cpu: ", cpu, " memory: ", memory, "cache: ", cache)
|
debug("cgroup: cpu: ", cpu, " memory: ", memory, "cache: ", cache)
|
||||||
rt.Time = time.Duration(cpu)
|
rt.Time = time.Duration(cpu)
|
||||||
rt.Memory = types.Size(memory - cache)
|
rt.Memory = runner.Size(memory - cache)
|
||||||
debug("cgroup:", rt)
|
debug("cgroup:", rt)
|
||||||
}
|
}
|
||||||
return &rt, nil
|
return &rt, nil
|
||||||
@ -394,21 +394,21 @@ const (
|
|||||||
StatusFatal // 7
|
StatusFatal // 7
|
||||||
)
|
)
|
||||||
|
|
||||||
func getStatus(s types.Status) int {
|
func getStatus(s runner.Status) int {
|
||||||
switch s {
|
switch s {
|
||||||
case types.StatusNormal:
|
case runner.StatusNormal:
|
||||||
return int(StatusNormal)
|
return int(StatusNormal)
|
||||||
case types.StatusInvalid:
|
case runner.StatusInvalid:
|
||||||
return int(StatusInvalid)
|
return int(StatusInvalid)
|
||||||
case types.StatusTimeLimitExceeded:
|
case runner.StatusTimeLimitExceeded:
|
||||||
return int(StatusTLE)
|
return int(StatusTLE)
|
||||||
case types.StatusMemoryLimitExceeded:
|
case runner.StatusMemoryLimitExceeded:
|
||||||
return int(StatusMLE)
|
return int(StatusMLE)
|
||||||
case types.StatusOutputLimitExceeded:
|
case runner.StatusOutputLimitExceeded:
|
||||||
return int(StatusOLE)
|
return int(StatusOLE)
|
||||||
case types.StatusDisallowedSyscall:
|
case runner.StatusDisallowedSyscall:
|
||||||
return int(StatusBan)
|
return int(StatusBan)
|
||||||
case types.StatusSignalled, types.StatusNonzeroExitStatus:
|
case runner.StatusSignalled, runner.StatusNonzeroExitStatus:
|
||||||
return int(StatusRE)
|
return int(StatusRE)
|
||||||
default:
|
default:
|
||||||
return int(StatusFatal)
|
return int(StatusFatal)
|
||||||
|
115
container/benchmark_test.go
Normal file
115
container/benchmark_test.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/criyle/go-sandbox/runner"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkContainer(b *testing.B) {
|
||||||
|
tmpDir, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
b.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
builder := &Builder{
|
||||||
|
Root: tmpDir,
|
||||||
|
}
|
||||||
|
m, err := builder.Build()
|
||||||
|
if err != nil {
|
||||||
|
b.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.Cleanup(func() {
|
||||||
|
m.Destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
rt := m.Execve(context.TODO(), ExecveParam{
|
||||||
|
Args: []string{"/bin/echo"},
|
||||||
|
Env: []string{"PATH=/bin"},
|
||||||
|
})
|
||||||
|
r := <-rt
|
||||||
|
if r.Status != runner.StatusNormal {
|
||||||
|
b.Error(r.Status, r.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerSuccess(t *testing.T) {
|
||||||
|
m := getEnv(t)
|
||||||
|
if m == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rt := m.Execve(context.TODO(), ExecveParam{
|
||||||
|
Args: []string{"/bin/echo"},
|
||||||
|
Env: []string{"PATH=/bin"},
|
||||||
|
})
|
||||||
|
r := <-rt
|
||||||
|
if r.Status != runner.StatusNormal {
|
||||||
|
t.Error(r.Status, r.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerNotExists(t *testing.T) {
|
||||||
|
m := getEnv(t)
|
||||||
|
if m == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rt := m.Execve(context.TODO(), ExecveParam{
|
||||||
|
Args: []string{"not_exists"},
|
||||||
|
Env: []string{"PATH=/bin"},
|
||||||
|
})
|
||||||
|
r := <-rt
|
||||||
|
if r.Status != runner.StatusRunnerError {
|
||||||
|
t.Error(r.Status, r.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerSyncFuncFail(t *testing.T) {
|
||||||
|
m := getEnv(t)
|
||||||
|
if m == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := errors.New("test error")
|
||||||
|
rt := m.Execve(context.TODO(), ExecveParam{
|
||||||
|
Args: []string{"/bin/echo"},
|
||||||
|
Env: []string{"PATH=/bin"},
|
||||||
|
SyncFunc: func(pid int) error {
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
})
|
||||||
|
r := <-rt
|
||||||
|
if r.Status != runner.StatusRunnerError {
|
||||||
|
t.Error(r.Status, r.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnv(t *testing.T) Environment {
|
||||||
|
tmpDir, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
builder := &Builder{
|
||||||
|
Root: tmpDir,
|
||||||
|
}
|
||||||
|
m, err := builder.Build()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"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"
|
||||||
"github.com/criyle/go-sandbox/types"
|
"github.com/criyle/go-sandbox/runner"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *containerServer) handleExecve(cmd *execCmd, msg *unixsocket.Msg) error {
|
func (c *containerServer) handleExecve(cmd *execCmd, msg *unixsocket.Msg) error {
|
||||||
@ -118,14 +118,14 @@ 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 {
|
||||||
status := types.StatusNormal
|
status := runner.StatusNormal
|
||||||
userTime := time.Duration(rusage.Utime.Nano()) // ns
|
userTime := time.Duration(rusage.Utime.Nano()) // ns
|
||||||
userMem := types.Size(rusage.Maxrss << 10) // bytes
|
userMem := runner.Size(rusage.Maxrss << 10) // bytes
|
||||||
switch {
|
switch {
|
||||||
case wstatus.Exited():
|
case wstatus.Exited():
|
||||||
exitStatus := wstatus.ExitStatus()
|
exitStatus := wstatus.ExitStatus()
|
||||||
if exitStatus != 0 {
|
if exitStatus != 0 {
|
||||||
status = types.StatusNonzeroExitStatus
|
status = runner.StatusNonzeroExitStatus
|
||||||
}
|
}
|
||||||
c.sendReply(&reply{
|
c.sendReply(&reply{
|
||||||
ExecReply: &execReply{
|
ExecReply: &execReply{
|
||||||
@ -140,13 +140,13 @@ 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.StatusTimeLimitExceeded
|
status = runner.StatusTimeLimitExceeded
|
||||||
case syscall.SIGXFSZ:
|
case syscall.SIGXFSZ:
|
||||||
status = types.StatusOutputLimitExceeded
|
status = runner.StatusOutputLimitExceeded
|
||||||
case syscall.SIGSYS:
|
case syscall.SIGSYS:
|
||||||
status = types.StatusDisallowedSyscall
|
status = runner.StatusDisallowedSyscall
|
||||||
default:
|
default:
|
||||||
status = types.StatusSignalled
|
status = runner.StatusSignalled
|
||||||
}
|
}
|
||||||
c.sendReply(&reply{
|
c.sendReply(&reply{
|
||||||
ExecReply: &execReply{
|
ExecReply: &execReply{
|
||||||
|
@ -12,33 +12,33 @@
|
|||||||
// Host to container communication protocol is single threaded and always initiated by
|
// Host to container communication protocol is single threaded and always initiated by
|
||||||
// the host:
|
// the host:
|
||||||
//
|
//
|
||||||
// - ping (alive check):
|
// - ping (alive check):
|
||||||
// - reply: pong
|
// - reply: pong
|
||||||
//
|
//
|
||||||
// - conf (set configuration):
|
// - conf (set configuration):
|
||||||
// - reply pong
|
// - reply pong
|
||||||
//
|
//
|
||||||
// - open (open files in given mode inside container):
|
// - open (open files in given mode inside container):
|
||||||
// - send: []OpenCmd
|
// - send: []OpenCmd
|
||||||
// - reply: "success", file fds / "error"
|
// - reply: "success", file fds / "error"
|
||||||
//
|
//
|
||||||
// - delete (unlink file / rmdir dir inside container):
|
// - delete (unlink file / rmdir dir inside container):
|
||||||
// - send: path
|
// - send: path
|
||||||
// - reply: "finished" / "error"
|
// - reply: "finished" / "error"
|
||||||
//
|
//
|
||||||
// - reset (clean up container for later use (clear workdir / tmp)):
|
// - reset (clean up container for later use (clear workdir / tmp)):
|
||||||
// - send:
|
// - send:
|
||||||
// - reply: "success"
|
// - reply: "success"
|
||||||
//
|
//
|
||||||
// - execve: (execute file inside container):
|
// - execve: (execute file inside container):
|
||||||
// - send: argv, env, rLimits, fds
|
// - send: argv, env, rLimits, fds
|
||||||
// - reply:
|
// - reply:
|
||||||
// - success: "success", pid
|
// - success: "success", pid
|
||||||
// - failed: "failed"
|
// - failed: "failed"
|
||||||
// - send (success): "init_finished" (as cmd)
|
// - send (success): "init_finished" (as cmd)
|
||||||
// - reply: "finished" / send: "kill" (as cmd)
|
// - reply: "finished" / send: "kill" (as cmd)
|
||||||
// - send: "kill" (as cmd) / reply: "finished"
|
// - send: "kill" (as cmd) / reply: "finished"
|
||||||
// - reply:
|
// - reply:
|
||||||
//
|
//
|
||||||
// Any socket related error will cause the container exit with all process inside container
|
// Any socket related error will cause the container exit with all process inside container
|
||||||
package container
|
package container
|
||||||
|
@ -8,10 +8,9 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/criyle/go-sandbox/pkg/forkexec"
|
"github.com/criyle/go-sandbox/pkg/forkexec"
|
||||||
"github.com/criyle/go-sandbox/pkg/memfd"
|
|
||||||
"github.com/criyle/go-sandbox/pkg/mount"
|
"github.com/criyle/go-sandbox/pkg/mount"
|
||||||
"github.com/criyle/go-sandbox/pkg/unixsocket"
|
"github.com/criyle/go-sandbox/pkg/unixsocket"
|
||||||
"github.com/criyle/go-sandbox/types"
|
"github.com/criyle/go-sandbox/runner"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -49,7 +48,7 @@ type Environment interface {
|
|||||||
Open([]OpenCmd) ([]*os.File, error)
|
Open([]OpenCmd) ([]*os.File, error)
|
||||||
Delete(p string) error
|
Delete(p string) error
|
||||||
Reset() error
|
Reset() error
|
||||||
Execve(context.Context, ExecveParam) <-chan types.Result
|
Execve(context.Context, ExecveParam) <-chan runner.Result
|
||||||
Destroy() error
|
Destroy() error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,21 +195,12 @@ func (b *Builder) exec() (*os.File, error) {
|
|||||||
return OpenCurrentExec()
|
return OpenCurrentExec()
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenCurrentExec opens current executable and dup and seal it to memfd
|
// OpenCurrentExec opens current executable (/proc/self/exe)
|
||||||
func OpenCurrentExec() (*os.File, error) {
|
func OpenCurrentExec() (*os.File, error) {
|
||||||
self, err := os.Open(currentExec)
|
return os.Open(currentExec)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to open %v: %v", currentExec, err)
|
|
||||||
}
|
|
||||||
defer self.Close()
|
|
||||||
|
|
||||||
execFile, err := memfd.DupToMemfd("init", self)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create memfd: %v", err)
|
|
||||||
}
|
|
||||||
return execFile, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newPassCredSocketPair creates socket pair and let the first socket to receive credential information
|
||||||
func newPassCredSocketPair() (*unixsocket.Socket, *unixsocket.Socket, error) {
|
func newPassCredSocketPair() (*unixsocket.Socket, *unixsocket.Socket, error) {
|
||||||
ins, outs, err := unixsocket.NewSocketPair()
|
ins, outs, err := unixsocket.NewSocketPair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -221,11 +211,6 @@ func newPassCredSocketPair() (*unixsocket.Socket, *unixsocket.Socket, error) {
|
|||||||
outs.Close()
|
outs.Close()
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if err = outs.SetPassCred(1); err != nil {
|
|
||||||
ins.Close()
|
|
||||||
outs.Close()
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return ins, outs, nil
|
return ins, outs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,37 +7,42 @@ import (
|
|||||||
|
|
||||||
"github.com/criyle/go-sandbox/pkg/rlimit"
|
"github.com/criyle/go-sandbox/pkg/rlimit"
|
||||||
"github.com/criyle/go-sandbox/pkg/unixsocket"
|
"github.com/criyle/go-sandbox/pkg/unixsocket"
|
||||||
"github.com/criyle/go-sandbox/types"
|
"github.com/criyle/go-sandbox/runner"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecveParam is parameters to run process inside container
|
// ExecveParam is parameters to run process inside container
|
||||||
type ExecveParam struct {
|
type ExecveParam struct {
|
||||||
|
// Args holds command line arguments
|
||||||
Args []string
|
Args []string
|
||||||
Env []string
|
|
||||||
Fds []uintptr
|
|
||||||
|
|
||||||
// fd parameter of fexecve
|
// Env specifies the environment of the process
|
||||||
|
Env []string
|
||||||
|
|
||||||
|
// Files specifies file descriptors for the child process
|
||||||
|
Files []uintptr
|
||||||
|
|
||||||
|
// ExecFile specifies file descriptor for executable file using fexecve
|
||||||
ExecFile uintptr
|
ExecFile uintptr
|
||||||
|
|
||||||
// POSIX Resource limit set by set rlimit
|
// RLimits specifies POSIX Resource limit through setrlimit
|
||||||
RLimits []rlimit.RLimit
|
RLimits []rlimit.RLimit
|
||||||
|
|
||||||
// SyncFunc called with pid before execve (for adding the process to cgroups)
|
// SyncFunc calls with pid just before execve (for attach the process to cgroups)
|
||||||
SyncFunc func(pid int) error
|
SyncFunc func(pid int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execve runs process inside container. It accepts context cancelation as time limit exceeded.
|
// Execve runs process inside container. It accepts context cancelation as time limit exceeded.
|
||||||
func (c *container) Execve(ctx context.Context, param ExecveParam) <-chan types.Result {
|
func (c *container) Execve(ctx context.Context, param ExecveParam) <-chan runner.Result {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
|
|
||||||
sTime := time.Now()
|
sTime := time.Now()
|
||||||
|
|
||||||
// make sure goroutine not leaked (blocked) even if result is not consumed
|
// make sure goroutine not leaked (blocked) even if result is not consumed
|
||||||
result := make(chan types.Result, 1)
|
result := make(chan runner.Result, 1)
|
||||||
|
|
||||||
errResult := func(f string, v ...interface{}) <-chan types.Result {
|
errResult := func(f string, v ...interface{}) <-chan runner.Result {
|
||||||
result <- types.Result{
|
result <- runner.Result{
|
||||||
Status: types.StatusRunnerError,
|
Status: runner.StatusRunnerError,
|
||||||
Error: fmt.Sprintf(f, v...),
|
Error: fmt.Sprintf(f, v...),
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@ -48,7 +53,7 @@ func (c *container) Execve(ctx context.Context, param ExecveParam) <-chan types.
|
|||||||
if param.ExecFile > 0 {
|
if param.ExecFile > 0 {
|
||||||
files = append(files, int(param.ExecFile))
|
files = append(files, int(param.ExecFile))
|
||||||
}
|
}
|
||||||
files = append(files, uintptrSliceToInt(param.Fds)...)
|
files = append(files, uintptrSliceToInt(param.Files)...)
|
||||||
msg := &unixsocket.Msg{
|
msg := &unixsocket.Msg{
|
||||||
Fds: files,
|
Fds: files,
|
||||||
}
|
}
|
||||||
@ -110,28 +115,28 @@ func (c *container) Execve(ctx context.Context, param ExecveParam) <-chan types.
|
|||||||
|
|
||||||
// handle potential error
|
// handle potential error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result <- types.Result{
|
result <- runner.Result{
|
||||||
Status: types.StatusRunnerError,
|
Status: runner.StatusRunnerError,
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if reply2.Error != nil {
|
if reply2.Error != nil {
|
||||||
result <- types.Result{
|
result <- runner.Result{
|
||||||
Status: types.StatusRunnerError,
|
Status: runner.StatusRunnerError,
|
||||||
Error: reply2.Error.Error(),
|
Error: reply2.Error.Error(),
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if reply2.ExecReply == nil {
|
if reply2.ExecReply == nil {
|
||||||
result <- types.Result{
|
result <- runner.Result{
|
||||||
Status: types.StatusRunnerError,
|
Status: runner.StatusRunnerError,
|
||||||
Error: "execve: no reply received",
|
Error: "execve: no reply received",
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// emit result after all communication finish
|
// emit result after all communication finish
|
||||||
result <- types.Result{
|
result <- runner.Result{
|
||||||
Status: reply2.ExecReply.Status,
|
Status: reply2.ExecReply.Status,
|
||||||
ExitStatus: reply2.ExecReply.ExitStatus,
|
ExitStatus: reply2.ExecReply.ExitStatus,
|
||||||
Time: reply2.ExecReply.Time,
|
Time: reply2.ExecReply.Time,
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"time"
|
"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/runner"
|
||||||
)
|
)
|
||||||
|
|
||||||
// cmd is the control message send into container
|
// cmd is the control message send into container
|
||||||
@ -64,9 +64,9 @@ 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 runner.Status // return status
|
||||||
Time time.Duration // waitpid user CPU (ns)
|
Time time.Duration // waitpid user CPU (ns)
|
||||||
Memory types.Size // waitpid user memory (byte)
|
Memory runner.Size // waitpid user memory (byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorReply) Error() string {
|
func (e *errorReply) Error() string {
|
||||||
|
66
pkg/cgroup/benchmark_test.go
Normal file
66
pkg/cgroup/benchmark_test.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package cgroup
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func BenchmarkCgroup(b *testing.B) {
|
||||||
|
builder, err := NewBuilder("benchmark").WithCPUAcct().WithMemory().WithPids().FilterByEnv()
|
||||||
|
if err != nil {
|
||||||
|
b.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
cg, err := builder.Build()
|
||||||
|
if err != nil {
|
||||||
|
b.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := cg.SetMemoryLimitInBytes(4096); err != nil {
|
||||||
|
b.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := cg.SetPidsMax(1); err != nil {
|
||||||
|
b.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := cg.CpuacctUsage(); err != nil {
|
||||||
|
b.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := cg.MemoryMaxUsageInBytes(); err != nil {
|
||||||
|
b.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cg.Destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCgroup(t *testing.T) {
|
||||||
|
builder, err := NewBuilder("test").WithCPUAcct().WithMemory().WithPids().FilterByEnv()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cg, err := builder.Build()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := cg.SetMemoryLimitInBytes(4096); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := cg.SetPidsMax(1); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := cg.CpuacctUsage(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := cg.MemoryMaxUsageInBytes(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cg.Destroy()
|
||||||
|
}
|
@ -6,20 +6,14 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// additional ideas:
|
// Cgroup is the combination of sub-cgroups
|
||||||
// cpu share(not used): cpu.share
|
type Cgroup struct {
|
||||||
// reclaim pages from old process: memory.force_empty
|
|
||||||
// (tasks kill are managed out of cgroup as freeze takes some time)
|
|
||||||
// freeze: freezer.state
|
|
||||||
|
|
||||||
// CGroup is the combination of sub-cgroups
|
|
||||||
type CGroup struct {
|
|
||||||
prefix string
|
prefix string
|
||||||
cpuacct, memory, pids *SubCGroup
|
cpuacct, memory, pids *SubCgroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build creates new cgrouup directories
|
// Build creates new cgrouup directories
|
||||||
func (b *Builder) Build() (cg *CGroup, err error) {
|
func (b *Builder) Build() (cg *Cgroup, err error) {
|
||||||
var (
|
var (
|
||||||
cpuacctPath, memoryPath, pidsPath string
|
cpuacctPath, memoryPath, pidsPath string
|
||||||
)
|
)
|
||||||
@ -32,31 +26,31 @@ func (b *Builder) Build() (cg *CGroup, err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
if b.CPUAcct {
|
if b.CPUAcct {
|
||||||
if cpuacctPath, err = CreateSubCGroupPath("cpuacct", b.Prefix); err != nil {
|
if cpuacctPath, err = CreateSubCgroupPath("cpuacct", b.Prefix); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if b.Memory {
|
if b.Memory {
|
||||||
if memoryPath, err = CreateSubCGroupPath("memory", b.Prefix); err != nil {
|
if memoryPath, err = CreateSubCgroupPath("memory", b.Prefix); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if b.Pids {
|
if b.Pids {
|
||||||
if pidsPath, err = CreateSubCGroupPath("pids", b.Prefix); err != nil {
|
if pidsPath, err = CreateSubCgroupPath("pids", b.Prefix); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CGroup{
|
return &Cgroup{
|
||||||
prefix: b.Prefix,
|
prefix: b.Prefix,
|
||||||
cpuacct: NewSubCGroup(cpuacctPath),
|
cpuacct: NewSubCgroup(cpuacctPath),
|
||||||
memory: NewSubCGroup(memoryPath),
|
memory: NewSubCgroup(memoryPath),
|
||||||
pids: NewSubCGroup(pidsPath),
|
pids: NewSubCgroup(pidsPath),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddProc writes cgroup.procs to all sub-cgroup
|
// AddProc writes cgroup.procs to all sub-cgroup
|
||||||
func (c *CGroup) AddProc(pid int) error {
|
func (c *Cgroup) AddProc(pid int) error {
|
||||||
if err := c.cpuacct.WriteUint(cgroupProcs, uint64(pid)); err != nil {
|
if err := c.cpuacct.WriteUint(cgroupProcs, uint64(pid)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -69,8 +63,8 @@ func (c *CGroup) AddProc(pid int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy removes dir for sub-cggroup, errors are ignored if remove one failed
|
// Destroy removes dir for sub-cgroup, errors are ignored if remove one failed
|
||||||
func (c *CGroup) Destroy() error {
|
func (c *Cgroup) Destroy() error {
|
||||||
var err1 error
|
var err1 error
|
||||||
if err := remove(c.cpuacct.path); err != nil {
|
if err := remove(c.cpuacct.path); err != nil {
|
||||||
err1 = err
|
err1 = err
|
||||||
@ -85,37 +79,37 @@ func (c *CGroup) Destroy() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CpuacctUsage read cpuacct.usage in ns
|
// CpuacctUsage read cpuacct.usage in ns
|
||||||
func (c *CGroup) CpuacctUsage() (uint64, error) {
|
func (c *Cgroup) CpuacctUsage() (uint64, error) {
|
||||||
return c.cpuacct.ReadUint("cpuacct.usage")
|
return c.cpuacct.ReadUint("cpuacct.usage")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemoryMaxUsageInBytes read memory.max_usage_in_bytes
|
// MemoryMaxUsageInBytes read memory.max_usage_in_bytes
|
||||||
func (c *CGroup) MemoryMaxUsageInBytes() (uint64, error) {
|
func (c *Cgroup) MemoryMaxUsageInBytes() (uint64, error) {
|
||||||
return c.memory.ReadUint("memory.max_usage_in_bytes")
|
return c.memory.ReadUint("memory.max_usage_in_bytes")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMemoryLimitInBytes write memory.limit_in_bytes
|
// SetMemoryLimitInBytes write memory.limit_in_bytes
|
||||||
func (c *CGroup) SetMemoryLimitInBytes(i uint64) error {
|
func (c *Cgroup) SetMemoryLimitInBytes(i uint64) error {
|
||||||
return c.memory.WriteUint("memory.limit_in_bytes", i)
|
return c.memory.WriteUint("memory.limit_in_bytes", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPidsMax write pids.max
|
// SetPidsMax write pids.max
|
||||||
func (c *CGroup) SetPidsMax(i uint64) error {
|
func (c *Cgroup) SetPidsMax(i uint64) error {
|
||||||
return c.pids.WriteUint("pids.max", i)
|
return c.pids.WriteUint("pids.max", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCpuacctUsage write cpuacct.usage in ns
|
// SetCpuacctUsage write cpuacct.usage in ns
|
||||||
func (c *CGroup) SetCpuacctUsage(i uint64) error {
|
func (c *Cgroup) SetCpuacctUsage(i uint64) error {
|
||||||
return c.cpuacct.WriteUint("cpuacct.usage", i)
|
return c.cpuacct.WriteUint("cpuacct.usage", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMemoryMaxUsageInBytes write cpuacct.usage in ns
|
// SetMemoryMaxUsageInBytes write cpuacct.usage in ns
|
||||||
func (c *CGroup) SetMemoryMaxUsageInBytes(i uint64) error {
|
func (c *Cgroup) SetMemoryMaxUsageInBytes(i uint64) error {
|
||||||
return c.memory.WriteUint("memory.max_usage_in_bytes", i)
|
return c.memory.WriteUint("memory.max_usage_in_bytes", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindMemoryStatProperty find certain property from memory.stat
|
// FindMemoryStatProperty find certain property from memory.stat
|
||||||
func (c *CGroup) FindMemoryStatProperty(prop string) (uint64, error) {
|
func (c *Cgroup) FindMemoryStatProperty(prop string) (uint64, error) {
|
||||||
content, err := c.memory.ReadFile("memory.stat")
|
content, err := c.memory.ReadFile("memory.stat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -1,6 +1,17 @@
|
|||||||
// Package cgroup provices builder to create multiple different cgroup-v1 sub groups
|
// Package cgroup provices builder to create multiple different cgroup-v1 sub groups
|
||||||
// under systemd defined path (i.e. /sys/fs/cgroup).
|
// under systemd defined mount path (i.e.,sys/fs/cgroup).
|
||||||
//
|
//
|
||||||
// current available cgroups are cpuacct, memory, pids
|
// Current available:
|
||||||
// not available: cpu, cpuset, devices, freezer, net_cls, blkio, perf_event, net_prio, huge_tlb, rdma
|
// cpuacct
|
||||||
|
// memory
|
||||||
|
// pids
|
||||||
|
//
|
||||||
|
// Current not avaliable: cpu, cpuset, devices, freezer, net_cls, blkio, perf_event, net_prio, huge_tlb, rdma
|
||||||
|
//
|
||||||
|
// Additional ideas:
|
||||||
|
//
|
||||||
|
// cpu share(not used): cpu.share
|
||||||
|
// reclaim pages from old process: memory.force_empty
|
||||||
|
// (tasks kill are managed out of cgroup as freeze takes some time)
|
||||||
|
// freeze: freezer.state
|
||||||
package cgroup
|
package cgroup
|
||||||
|
@ -9,31 +9,29 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SubCGroup is the sub-cgroup
|
// SubCgroup is the accessor for single cgroup resource with given path
|
||||||
type SubCGroup struct {
|
type SubCgroup struct {
|
||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrNotInitialized returned when trying to read from not initialized cgroup
|
// ErrNotInitialized returned when trying to read from not initialized cgroup
|
||||||
var ErrNotInitialized = errors.New("cgroup was not initialized")
|
var ErrNotInitialized = errors.New("cgroup was not initialized")
|
||||||
|
|
||||||
// NewSubCGroup creates a sug CGroup
|
// NewSubCgroup creates a cgroup accessor with given path (path needs to be created in advance)
|
||||||
func NewSubCGroup(p string) *SubCGroup {
|
func NewSubCgroup(p string) *SubCgroup {
|
||||||
return &SubCGroup{
|
return &SubCgroup{path: p}
|
||||||
path: p,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteUint writes uint64 into given file
|
// WriteUint writes uint64 into given file
|
||||||
func (c *SubCGroup) WriteUint(filename string, i uint64) error {
|
func (c *SubCgroup) WriteUint(filename string, i uint64) error {
|
||||||
if c.path == "" {
|
if c.path == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return c.WriteFile(filename, []byte(strconv.FormatUint(i, 10)))
|
return c.WriteFile(filename, []byte(strconv.FormatUint(i, 10)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadUint read uint64 into given file
|
// ReadUint read uint64 from given file
|
||||||
func (c *SubCGroup) ReadUint(filename string) (uint64, error) {
|
func (c *SubCgroup) ReadUint(filename string) (uint64, error) {
|
||||||
if c.path == "" {
|
if c.path == "" {
|
||||||
return 0, ErrNotInitialized
|
return 0, ErrNotInitialized
|
||||||
}
|
}
|
||||||
@ -50,7 +48,7 @@ func (c *SubCGroup) ReadUint(filename string) (uint64, error) {
|
|||||||
|
|
||||||
// WriteFile writes cgroup file and handles potential EINTR error while writes to
|
// WriteFile writes cgroup file and handles potential EINTR error while writes to
|
||||||
// the slow device (cgroup)
|
// the slow device (cgroup)
|
||||||
func (c *SubCGroup) WriteFile(name string, content []byte) error {
|
func (c *SubCgroup) WriteFile(name string, content []byte) error {
|
||||||
p := path.Join(c.path, name)
|
p := path.Join(c.path, name)
|
||||||
err := ioutil.WriteFile(p, content, 0664)
|
err := ioutil.WriteFile(p, content, 0664)
|
||||||
for err != nil && errors.Is(err, syscall.EINTR) {
|
for err != nil && errors.Is(err, syscall.EINTR) {
|
||||||
@ -61,7 +59,7 @@ func (c *SubCGroup) WriteFile(name string, content []byte) error {
|
|||||||
|
|
||||||
// ReadFile reads cgroup file and handles potential EINTR error while read to
|
// ReadFile reads cgroup file and handles potential EINTR error while read to
|
||||||
// the slow device (cgroup)
|
// the slow device (cgroup)
|
||||||
func (c *SubCGroup) ReadFile(name string) ([]byte, error) {
|
func (c *SubCgroup) ReadFile(name string) ([]byte, error) {
|
||||||
p := path.Join(c.path, name)
|
p := path.Join(c.path, name)
|
||||||
data, err := ioutil.ReadFile(p)
|
data, err := ioutil.ReadFile(p)
|
||||||
for err != nil && errors.Is(err, syscall.EINTR) {
|
for err != nil && errors.Is(err, syscall.EINTR) {
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnsureDirExists creates dir if not exists
|
// EnsureDirExists creates directories if the path not exists
|
||||||
func EnsureDirExists(path string) error {
|
func EnsureDirExists(path string) error {
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
return os.Mkdir(path, os.ModePerm)
|
return os.Mkdir(path, os.ModePerm)
|
||||||
@ -16,8 +16,8 @@ func EnsureDirExists(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateSubCGroupPath creates path for sub-cgroup
|
// CreateSubCgroupPath creates path for sub-cgroup with given group and prefix
|
||||||
func CreateSubCGroupPath(group, prefix string) (string, error) {
|
func CreateSubCgroupPath(group, prefix string) (string, error) {
|
||||||
base := path.Join(basePath, group, prefix)
|
base := path.Join(basePath, group, prefix)
|
||||||
EnsureDirExists(base)
|
EnsureDirExists(base)
|
||||||
return ioutil.TempDir(base, "")
|
return ioutil.TempDir(base, "")
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/criyle/go-sandbox/types"
|
"github.com/criyle/go-sandbox/runner"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RLimits defines the rlimit applied by setrlimit syscall to traced process
|
// RLimits defines the rlimit applied by setrlimit syscall to traced process
|
||||||
@ -88,7 +88,7 @@ func (r RLimit) String() string {
|
|||||||
case syscall.RLIMIT_AS:
|
case syscall.RLIMIT_AS:
|
||||||
t = "AddressSpace"
|
t = "AddressSpace"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s[%v:%v]", t, types.Size(r.Rlim.Cur), types.Size(r.Rlim.Max))
|
return fmt.Sprintf("%s[%v:%v]", t, runner.Size(r.Rlim.Cur), runner.Size(r.Rlim.Max))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r RLimits) String() string {
|
func (r RLimits) String() string {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package ptracer
|
package ptracer
|
||||||
|
|
||||||
import "github.com/criyle/go-sandbox/types"
|
import "github.com/criyle/go-sandbox/runner"
|
||||||
|
|
||||||
// TraceAction defines the action returned by TraceHandle
|
// TraceAction defines the action returned by TraceHandle
|
||||||
type TraceAction int
|
type TraceAction int
|
||||||
@ -18,7 +18,7 @@ const (
|
|||||||
type Tracer struct {
|
type Tracer struct {
|
||||||
Handler
|
Handler
|
||||||
Runner
|
Runner
|
||||||
types.Limit
|
runner.Limit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runner represents the process runner
|
// Runner represents the process runner
|
||||||
|
@ -9,12 +9,12 @@ import (
|
|||||||
unix "golang.org/x/sys/unix"
|
unix "golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/criyle/go-sandbox/pkg/seccomp"
|
"github.com/criyle/go-sandbox/pkg/seccomp"
|
||||||
"github.com/criyle/go-sandbox/types"
|
"github.com/criyle/go-sandbox/runner"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Trace starts new goroutine and trace runner with ptrace
|
// Trace starts new goroutine and trace runner with ptrace
|
||||||
func (t *Tracer) Trace(c context.Context) <-chan types.Result {
|
func (t *Tracer) Trace(c context.Context) <-chan runner.Result {
|
||||||
result := make(chan types.Result, 1)
|
result := make(chan runner.Result, 1)
|
||||||
go func() {
|
go func() {
|
||||||
result <- t.TraceRun(c)
|
result <- t.TraceRun(c)
|
||||||
}()
|
}()
|
||||||
@ -23,9 +23,9 @@ func (t *Tracer) Trace(c context.Context) <-chan types.Result {
|
|||||||
|
|
||||||
// TraceRun start and traces all child process by runner in the calling goroutine
|
// TraceRun start and traces all child process by runner in the calling goroutine
|
||||||
// parameter done used to cancel work, start is used notify child starts
|
// parameter done used to cancel work, start is used notify child starts
|
||||||
func (t *Tracer) TraceRun(c context.Context) (result types.Result) {
|
func (t *Tracer) TraceRun(c context.Context) (result runner.Result) {
|
||||||
var (
|
var (
|
||||||
status = types.StatusNormal
|
status = runner.StatusNormal
|
||||||
wstatus unix.WaitStatus // wait4 wait status
|
wstatus unix.WaitStatus // wait4 wait status
|
||||||
rusage unix.Rusage // wait4 rusage
|
rusage unix.Rusage // wait4 rusage
|
||||||
traced = make(map[int]bool) // store all process that have set ptrace options
|
traced = make(map[int]bool) // store all process that have set ptrace options
|
||||||
@ -44,7 +44,7 @@ func (t *Tracer) TraceRun(c context.Context) (result types.Result) {
|
|||||||
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.StatusRunnerError
|
result.Status = runner.StatusRunnerError
|
||||||
result.Error = err.Error()
|
result.Error = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ func (t *Tracer) TraceRun(c context.Context) (result types.Result) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
t.Handler.Debug("panic: ", err)
|
t.Handler.Debug("panic: ", err)
|
||||||
result.Status = types.StatusRunnerError
|
result.Status = runner.StatusRunnerError
|
||||||
result.Error = fmt.Sprintf("%v", err)
|
result.Error = fmt.Sprintf("%v", err)
|
||||||
}
|
}
|
||||||
// kill all tracee upon return
|
// kill all tracee upon return
|
||||||
@ -88,7 +88,7 @@ func (t *Tracer) TraceRun(c context.Context) (result types.Result) {
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Handler.Debug("wait4 failed: ", err)
|
t.Handler.Debug("wait4 failed: ", err)
|
||||||
result.Status = types.StatusRunnerError
|
result.Status = runner.StatusRunnerError
|
||||||
result.Error = err.Error()
|
result.Error = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -97,21 +97,21 @@ func (t *Tracer) TraceRun(c context.Context) (result types.Result) {
|
|||||||
if pid == pgid {
|
if pid == pgid {
|
||||||
// update resource usage and check against limits
|
// update resource usage and check against limits
|
||||||
userTime := time.Duration(rusage.Utime.Nano()) // ns
|
userTime := time.Duration(rusage.Utime.Nano()) // ns
|
||||||
userMem := types.Size(rusage.Maxrss << 10) // bytes
|
userMem := runner.Size(rusage.Maxrss << 10) // bytes
|
||||||
|
|
||||||
// check tle / mle
|
// check tle / mle
|
||||||
if userTime > t.Limit.TimeLimit {
|
if userTime > t.Limit.TimeLimit {
|
||||||
status = types.StatusTimeLimitExceeded
|
status = runner.StatusTimeLimitExceeded
|
||||||
}
|
}
|
||||||
if userMem > t.Limit.MemoryLimit {
|
if userMem > t.Limit.MemoryLimit {
|
||||||
status = types.StatusMemoryLimitExceeded
|
status = runner.StatusMemoryLimitExceeded
|
||||||
}
|
}
|
||||||
result = types.Result{
|
result = runner.Result{
|
||||||
Status: status,
|
Status: status,
|
||||||
Time: userTime,
|
Time: userTime,
|
||||||
Memory: userMem,
|
Memory: userMem,
|
||||||
}
|
}
|
||||||
if status != types.StatusNormal {
|
if status != runner.StatusNormal {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,13 +125,13 @@ func (t *Tracer) TraceRun(c context.Context) (result types.Result) {
|
|||||||
if execved {
|
if execved {
|
||||||
result.ExitStatus = wstatus.ExitStatus()
|
result.ExitStatus = wstatus.ExitStatus()
|
||||||
if result.ExitStatus == 0 {
|
if result.ExitStatus == 0 {
|
||||||
result.Status = types.StatusNormal
|
result.Status = runner.StatusNormal
|
||||||
} else {
|
} else {
|
||||||
result.Status = types.StatusNonzeroExitStatus
|
result.Status = runner.StatusNonzeroExitStatus
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result.Status = types.StatusRunnerError
|
result.Status = runner.StatusRunnerError
|
||||||
result.Error = "child process exit before execve"
|
result.Error = "child process exit before execve"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -143,13 +143,13 @@ func (t *Tracer) TraceRun(c context.Context) (result types.Result) {
|
|||||||
delete(traced, pid)
|
delete(traced, pid)
|
||||||
switch sig {
|
switch sig {
|
||||||
case unix.SIGXCPU, unix.SIGKILL:
|
case unix.SIGXCPU, unix.SIGKILL:
|
||||||
status = types.StatusTimeLimitExceeded
|
status = runner.StatusTimeLimitExceeded
|
||||||
case unix.SIGXFSZ:
|
case unix.SIGXFSZ:
|
||||||
status = types.StatusOutputLimitExceeded
|
status = runner.StatusOutputLimitExceeded
|
||||||
case unix.SIGSYS:
|
case unix.SIGSYS:
|
||||||
status = types.StatusDisallowedSyscall
|
status = runner.StatusDisallowedSyscall
|
||||||
default:
|
default:
|
||||||
status = types.StatusSignalled
|
status = runner.StatusSignalled
|
||||||
}
|
}
|
||||||
result.Status = status
|
result.Status = status
|
||||||
result.ExitStatus = int(sig)
|
result.ExitStatus = int(sig)
|
||||||
@ -165,7 +165,7 @@ func (t *Tracer) TraceRun(c context.Context) (result types.Result) {
|
|||||||
// 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.StatusRunnerError
|
result.Status = runner.StatusRunnerError
|
||||||
result.Error = err.Error()
|
result.Error = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -179,7 +179,7 @@ func (t *Tracer) TraceRun(c context.Context) (result types.Result) {
|
|||||||
// 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.StatusDisallowedSyscall
|
result.Status = runner.StatusDisallowedSyscall
|
||||||
result.Error = err.Error()
|
result.Error = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -209,11 +209,11 @@ func (t *Tracer) TraceRun(c context.Context) (result types.Result) {
|
|||||||
// check if cpu rlimit hit
|
// check if cpu rlimit hit
|
||||||
switch stopSig {
|
switch stopSig {
|
||||||
case unix.SIGXCPU:
|
case unix.SIGXCPU:
|
||||||
status = types.StatusTimeLimitExceeded
|
status = runner.StatusTimeLimitExceeded
|
||||||
case unix.SIGXFSZ:
|
case unix.SIGXFSZ:
|
||||||
status = types.StatusOutputLimitExceeded
|
status = runner.StatusOutputLimitExceeded
|
||||||
}
|
}
|
||||||
if status != types.StatusNormal {
|
if status != runner.StatusNormal {
|
||||||
result.Status = status
|
result.Status = status
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -263,7 +263,7 @@ func (t *Tracer) handleTrap(pid int) error {
|
|||||||
return ctx.skipSyscall()
|
return ctx.skipSyscall()
|
||||||
|
|
||||||
case TraceKill:
|
case TraceKill:
|
||||||
return types.StatusDisallowedSyscall
|
return runner.StatusDisallowedSyscall
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
33
runner/doc.go
Normal file
33
runner/doc.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Package runner provides common interface for program runner together with
|
||||||
|
// common types including Result, Limit, Size and Status.
|
||||||
|
//
|
||||||
|
// Status
|
||||||
|
//
|
||||||
|
// Status defines the program running result status including
|
||||||
|
// Normal
|
||||||
|
// Program Error
|
||||||
|
// Resource Limit Exceeded (Time / Memory / Output)
|
||||||
|
// Unauthorized Access (Disallowed Syscall)
|
||||||
|
// Runtime Error (Signaled / Nonzero Exit Status)
|
||||||
|
// Program Runner Error
|
||||||
|
//
|
||||||
|
// Size
|
||||||
|
//
|
||||||
|
// Size defines size in bytes, underlying type is uint64 so it
|
||||||
|
// is effective to store up to EiB of size
|
||||||
|
//
|
||||||
|
// Limit
|
||||||
|
//
|
||||||
|
// Limit defines Time & Memory restriction on program runner
|
||||||
|
//
|
||||||
|
// Result
|
||||||
|
//
|
||||||
|
// Result defines program running result including
|
||||||
|
// Status, ExitStatus, Detailed Error, Time, Memory,
|
||||||
|
// SetupTime and RunningTime (in real clock)
|
||||||
|
//
|
||||||
|
// Runner
|
||||||
|
//
|
||||||
|
// General interface to run a program, including a context
|
||||||
|
// for canclation
|
||||||
|
package runner
|
@ -1,4 +1,4 @@
|
|||||||
package types
|
package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/criyle/go-sandbox/pkg/seccomp/libseccomp"
|
"github.com/criyle/go-sandbox/pkg/seccomp/libseccomp"
|
||||||
"github.com/criyle/go-sandbox/ptracer"
|
"github.com/criyle/go-sandbox/ptracer"
|
||||||
"github.com/criyle/go-sandbox/types"
|
"github.com/criyle/go-sandbox/runner"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tracerHandler struct {
|
type tracerHandler struct {
|
||||||
@ -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.StatusDisallowedSyscall
|
return runner.StatusDisallowedSyscall
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,11 @@ import (
|
|||||||
|
|
||||||
"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/runner"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Run starts the tracing process
|
// Run starts the tracing process
|
||||||
func (r *Runner) Run(c context.Context) <-chan types.Result {
|
func (r *Runner) Run(c context.Context) <-chan runner.Result {
|
||||||
ch := &forkexec.Runner{
|
ch := &forkexec.Runner{
|
||||||
Args: r.Args,
|
Args: r.Args,
|
||||||
Env: r.Env,
|
Env: r.Env,
|
||||||
@ -33,6 +33,5 @@ func (r *Runner) Run(c context.Context) <-chan types.Result {
|
|||||||
Runner: ch,
|
Runner: ch,
|
||||||
Limit: r.Limit,
|
Limit: r.Limit,
|
||||||
}
|
}
|
||||||
rt := tracer.Trace(c)
|
return tracer.Trace(c)
|
||||||
return rt
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/criyle/go-sandbox/pkg/rlimit"
|
"github.com/criyle/go-sandbox/pkg/rlimit"
|
||||||
"github.com/criyle/go-sandbox/pkg/seccomp"
|
"github.com/criyle/go-sandbox/pkg/seccomp"
|
||||||
"github.com/criyle/go-sandbox/ptracer"
|
"github.com/criyle/go-sandbox/ptracer"
|
||||||
"github.com/criyle/go-sandbox/types"
|
"github.com/criyle/go-sandbox/runner"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Runner defines the spec to run a program safely by ptracer
|
// Runner defines the spec to run a program safely by ptracer
|
||||||
@ -27,7 +27,7 @@ type Runner struct {
|
|||||||
RLimits []rlimit.RLimit
|
RLimits []rlimit.RLimit
|
||||||
|
|
||||||
// Res limit enforced by tracer
|
// Res limit enforced by tracer
|
||||||
Limit types.Limit
|
Limit runner.Limit
|
||||||
|
|
||||||
// Defines seccomp filter for the ptrace runner
|
// Defines seccomp filter for the ptrace runner
|
||||||
// file access syscalls need to set as ActionTrace
|
// file access syscalls need to set as ActionTrace
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package types
|
package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -2,11 +2,9 @@ package runner
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"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 {
|
||||||
Run(context.Context) <-chan types.Result
|
Run(context.Context) <-chan Result
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package types
|
package runner
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package types
|
package runner
|
||||||
|
|
||||||
// Status is the result Status
|
// Status is the result Status
|
||||||
type Status int
|
type Status int
|
3
runner/unshare/doc.go
Normal file
3
runner/unshare/doc.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// Package unshare implements runner that uses Linux unshare syscall & mount namespace & rlimit
|
||||||
|
// to restrict program access
|
||||||
|
package unshare
|
@ -11,7 +11,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/criyle/go-sandbox/pkg/forkexec"
|
"github.com/criyle/go-sandbox/pkg/forkexec"
|
||||||
"github.com/criyle/go-sandbox/types"
|
"github.com/criyle/go-sandbox/runner"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -20,7 +20,16 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Run starts the unshared process
|
// Run starts the unshared process
|
||||||
func (r *Runner) Run(c context.Context) <-chan types.Result {
|
func (r *Runner) Run(c context.Context) <-chan runner.Result {
|
||||||
|
result := make(chan runner.Result, 1)
|
||||||
|
go func() {
|
||||||
|
result <- r.trace(c)
|
||||||
|
}()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace tracks child processes
|
||||||
|
func (r *Runner) trace(c context.Context) (result runner.Result) {
|
||||||
ch := &forkexec.Runner{
|
ch := &forkexec.Runner{
|
||||||
Args: r.Args,
|
Args: r.Args,
|
||||||
Env: r.Env,
|
Env: r.Env,
|
||||||
@ -39,39 +48,29 @@ func (r *Runner) Run(c context.Context) <-chan types.Result {
|
|||||||
SyncFunc: r.SyncFunc,
|
SyncFunc: r.SyncFunc,
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make(chan types.Result, 1)
|
|
||||||
go func() {
|
|
||||||
result <- r.trace(c, ch)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trace tracks child processes
|
|
||||||
func (r *Runner) trace(c context.Context, runner *forkexec.Runner) (result types.Result) {
|
|
||||||
var (
|
var (
|
||||||
wstatus unix.WaitStatus // wait4 wait status
|
wstatus unix.WaitStatus // wait4 wait status
|
||||||
rusage unix.Rusage // wait4 rusage
|
rusage unix.Rusage // wait4 rusage
|
||||||
status = types.StatusNormal
|
status = runner.StatusNormal
|
||||||
sTime = time.Now() // start time
|
sTime = time.Now() // start time
|
||||||
fTime time.Time // finish time for setup
|
fTime time.Time // finish time for setup
|
||||||
)
|
)
|
||||||
|
|
||||||
// Start the runner
|
// Start the runner
|
||||||
pgid, err := runner.Start()
|
pgid, err := ch.Start()
|
||||||
r.println("Starts: ", pgid, err)
|
r.println("Starts: ", pgid, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Status = types.StatusRunnerError
|
result.Status = runner.StatusRunnerError
|
||||||
result.Error = err.Error()
|
result.Error = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cc, cancel := context.WithCancel(c)
|
ctx, cancel := context.WithCancel(c)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// handle cancel
|
// handle cancel
|
||||||
go func() {
|
go func() {
|
||||||
<-cc.Done()
|
<-ctx.Done()
|
||||||
killAll(pgid)
|
killAll(pgid)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -91,37 +90,37 @@ func (r *Runner) trace(c context.Context, runner *forkexec.Runner) (result types
|
|||||||
}
|
}
|
||||||
r.println("wait4: ", wstatus)
|
r.println("wait4: ", wstatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Status = types.StatusRunnerError
|
result.Status = runner.StatusRunnerError
|
||||||
result.Error = err.Error()
|
result.Error = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// update resource usage and check against limits
|
// update resource usage and check against limits
|
||||||
userTime := time.Duration(rusage.Utime.Nano()) // ns
|
userTime := time.Duration(rusage.Utime.Nano()) // ns
|
||||||
userMem := types.Size(rusage.Maxrss << 10) // bytes
|
userMem := runner.Size(rusage.Maxrss << 10) // bytes
|
||||||
|
|
||||||
// check tle / mle
|
// check tle / mle
|
||||||
if userTime > r.Limit.TimeLimit {
|
if userTime > r.Limit.TimeLimit {
|
||||||
status = types.StatusTimeLimitExceeded
|
status = runner.StatusTimeLimitExceeded
|
||||||
}
|
}
|
||||||
if userMem > r.Limit.MemoryLimit {
|
if userMem > r.Limit.MemoryLimit {
|
||||||
status = types.StatusMemoryLimitExceeded
|
status = runner.StatusMemoryLimitExceeded
|
||||||
}
|
}
|
||||||
result = types.Result{
|
result = runner.Result{
|
||||||
Status: status,
|
Status: status,
|
||||||
Time: userTime,
|
Time: userTime,
|
||||||
Memory: userMem,
|
Memory: userMem,
|
||||||
}
|
}
|
||||||
if status != types.StatusNormal {
|
if status != runner.StatusNormal {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case wstatus.Exited():
|
case wstatus.Exited():
|
||||||
result.Status = types.StatusNormal
|
result.Status = runner.StatusNormal
|
||||||
result.ExitStatus = wstatus.ExitStatus()
|
result.ExitStatus = wstatus.ExitStatus()
|
||||||
if result.ExitStatus != 0 {
|
if result.ExitStatus != 0 {
|
||||||
result.Status = types.StatusNonzeroExitStatus
|
result.Status = runner.StatusNonzeroExitStatus
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -129,13 +128,13 @@ func (r *Runner) trace(c context.Context, runner *forkexec.Runner) (result types
|
|||||||
sig := wstatus.Signal()
|
sig := wstatus.Signal()
|
||||||
switch sig {
|
switch sig {
|
||||||
case unix.SIGXCPU, unix.SIGKILL:
|
case unix.SIGXCPU, unix.SIGKILL:
|
||||||
status = types.StatusTimeLimitExceeded
|
status = runner.StatusTimeLimitExceeded
|
||||||
case unix.SIGXFSZ:
|
case unix.SIGXFSZ:
|
||||||
status = types.StatusOutputLimitExceeded
|
status = runner.StatusOutputLimitExceeded
|
||||||
case unix.SIGSYS:
|
case unix.SIGSYS:
|
||||||
status = types.StatusDisallowedSyscall
|
status = runner.StatusDisallowedSyscall
|
||||||
default:
|
default:
|
||||||
status = types.StatusSignalled
|
status = runner.StatusSignalled
|
||||||
}
|
}
|
||||||
result.Status = status
|
result.Status = status
|
||||||
result.ExitStatus = int(sig)
|
result.ExitStatus = int(sig)
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"github.com/criyle/go-sandbox/pkg/mount"
|
"github.com/criyle/go-sandbox/pkg/mount"
|
||||||
"github.com/criyle/go-sandbox/pkg/rlimit"
|
"github.com/criyle/go-sandbox/pkg/rlimit"
|
||||||
"github.com/criyle/go-sandbox/pkg/seccomp"
|
"github.com/criyle/go-sandbox/pkg/seccomp"
|
||||||
"github.com/criyle/go-sandbox/types"
|
"github.com/criyle/go-sandbox/runner"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Runner runs program in unshared namespaces
|
// Runner runs program in unshared namespaces
|
||||||
@ -26,7 +26,7 @@ type Runner struct {
|
|||||||
RLimits []rlimit.RLimit
|
RLimits []rlimit.RLimit
|
||||||
|
|
||||||
// Resource limit enforced by tracer
|
// Resource limit enforced by tracer
|
||||||
Limit types.Limit
|
Limit runner.Limit
|
||||||
|
|
||||||
// Seccomp defines the seccomp filter attach to the process (should be whitelist only)
|
// Seccomp defines the seccomp filter attach to the process (should be whitelist only)
|
||||||
Seccomp seccomp.Filter
|
Seccomp seccomp.Filter
|
||||||
|
Loading…
Reference in New Issue
Block a user