mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
Sandbox implemented in GO with container / ptrace / seccomp
| cgroup | ||
| cmd/run_program | ||
| deamon | ||
| forkexec | ||
| memfd | ||
| runconfig | ||
| runprogram | ||
| rununshared | ||
| seccomp | ||
| tracer | ||
| types | ||
| unixsocket | ||
| .gitignore | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| README.md | ||
go-sandbox
Original goal is to reimplement uoj-judger/run_program in GO language using libseccomp. As technology grows, it also implements new technologies including Linux namespace & cgroup.
Install
- install go compiler:
apt install golang-go - install libseccomp-dev:
apt install libseccomp-dev - install:
go install github.com/criyle/go-sandbox/...
Technologies
libseccomp + ptrace (improved UOJ sandbox)
- Restricted computing resource by POSIX rlimit: Time & Memory (Stack) & Output
- Restricted syscall access (by libseccomp & ptrace)
- Restricted file access (read & write & access & exec). Evaluated by UOJ FileSet
Improvements:
- Precise resource limits (s -> ms, mb -> kb)
- More architectures (arm32, arm64, x86)
- Allow multiple traced programs in different threads
- Allow pipes as input / output files
Default file access syscall check:
- check file read / write:
open,openat - check file read:
readlink,readlinkat - check file write:
unlink,unlinkat,chmod,rename - check file access:
stat,lstat,access,faccessat - check file exec:
execveat
linux namespace + cgroup
- Unshare & bind mount rootfs based on hostfs (elimilated ptrace)
- Use Linux Control Groups to limit & acct CPU & memory (elimilate wait4.rusage)
- Container tech with execveat memfd, sethostname, setdomainname
pre-forked container
- Pre-fork container deamons to run programs inside
- Unix socket to pass fd inside / outside
Packages
- seccomp: provides utility function that wrappers libseccomp
- forkexec: fork-exec provides mount, unshare, ptrace, seccomp, capset before exec
- memfd: read regular file and creates a seaed memfd for its contents
- unixsocket: send / recv oob msg from a unix socket
- cgroup: creates cgroup directories and collects resource usage / limits
- deamon: creates pre-forked container to run programs inside
- tracer: ptrace tracer and provides syscall trap filter context
- runprogram: wrapper to call forkexec and trecer
- rununshared: wrapper to call forkexec and unshared namespaces
- runconfig: defines arch & language specified trace condition for seccomp and ptrace
- types: general runtime specs
- mount: provides utility function that wrappers mount syscall
- rlimit: provides utility function that defines rlimit syscall
- specs: provides general res / result data structures
Executable
- run_program: safely run program by unshare / ptrace / pre-forked containers
Configurations
- run_program/config.go: all configs toward running specs
Benchmarks (docker desktop amd64 / native arm64)
- 1ms / 2ms: fork, unshare pid / user / cgroup
- 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.
$ go test -bench . -benchtime 10s
goos: linux
goarch: amd64
pkg: github.com/criyle/go-sandbox/forkexec
BenchmarkSimpleFork-4 10000 1106064 ns/op
BenchmarkUnsharePid-4 10000 1367824 ns/op
BenchmarkUnshareUser-4 10000 1311523 ns/op
BenchmarkUnshareUts-4 10000 1140427 ns/op
BenchmarkUnshareCgroup-4 10000 1112713 ns/op
BenchmarkUnshareIpc-4 300 58730786 ns/op
BenchmarkUnshareMount-4 300 55540758 ns/op
BenchmarkUnshareNet-4 100 396957720 ns/op
BenchmarkFastUnshareMountPivot-4 100 114364585 ns/op
BenchmarkUnshareAll-4 100 851014031 ns/op
BenchmarkUnshareMountPivot-4 20 901204445 ns/op
PASS
ok github.com/criyle/go-sandbox/forkexec 262.112s