mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
add stdin/out/err and rlimit restrict
This commit is contained in:
parent
f6ed5aaa41
commit
ea21693afd
@ -108,8 +108,12 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal("Failed to create filter: ", err)
|
||||
}
|
||||
r := NewProgramRunner()
|
||||
r.Args = os.Args[1:]
|
||||
r.Filter = filter
|
||||
|
||||
// run in restricted mode
|
||||
pid, err := ForkAndLoadSeccomp(os.Args[1:], filter)
|
||||
pid, err := r.StartChild()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to fork: ", err)
|
||||
}
|
||||
@ -124,7 +128,9 @@ func main() {
|
||||
defer timer.Stop()
|
||||
|
||||
// Set trace seccomp
|
||||
unix.PtraceSetOptions(pid, unix.PTRACE_O_TRACESECCOMP)
|
||||
unix.PtraceSetOptions(pid, unix.PTRACE_O_TRACESECCOMP|unix.PTRACE_O_EXITKILL|
|
||||
unix.PTRACE_O_TRACEFORK|unix.PTRACE_O_TRACECLONE|unix.PTRACE_O_TRACEEXEC|
|
||||
unix.PTRACE_O_TRACEVFORK)
|
||||
log.Println("Strat trace pid: ", pid)
|
||||
|
||||
// trace unixs
|
||||
@ -144,16 +150,32 @@ func main() {
|
||||
log.Println("Signal", wstatus.Signal())
|
||||
}
|
||||
if wstatus.Stopped() {
|
||||
log.Println("Stopped")
|
||||
if wstatus.TrapCause() == unix.PTRACE_EVENT_SECCOMP {
|
||||
switch cause := wstatus.TrapCause(); cause {
|
||||
case unix.PTRACE_EVENT_SECCOMP:
|
||||
log.Println("Seccomp Traced")
|
||||
msg, err := unix.PtraceGetEventMsg(pid)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println("Ptrace Event: ", msg)
|
||||
} else {
|
||||
log.Println("Stop Cause: ", wstatus.TrapCause())
|
||||
|
||||
case unix.PTRACE_EVENT_CLONE:
|
||||
log.Println("Ptrace stop clone")
|
||||
|
||||
case unix.PTRACE_EVENT_VFORK:
|
||||
log.Println("Ptrace stop vfork")
|
||||
|
||||
case unix.PTRACE_EVENT_FORK:
|
||||
log.Println("Ptrace stop fork")
|
||||
|
||||
case unix.PTRACE_EVENT_EXEC:
|
||||
log.Println("Ptrace stop exec")
|
||||
|
||||
case -1:
|
||||
log.Println("Ptrace stop signal: ", wstatus.StopSignal())
|
||||
|
||||
default:
|
||||
log.Println("Ptrace trap cause: ", cause, wstatus)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe" // required for go:linkname.
|
||||
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
@ -17,35 +17,118 @@ func afterFork()
|
||||
//go:linkname afterForkInChild syscall.runtime_AfterForkInChild
|
||||
func afterForkInChild()
|
||||
|
||||
// ForkAndLoadSeccomp will fork, load seccomp and execv and being traced by ptrace
|
||||
type rlimit struct {
|
||||
resource int
|
||||
rlim syscall.Rlimit
|
||||
}
|
||||
|
||||
// StartChild will fork, load seccomp and execv and being traced by ptrace
|
||||
// Reference to src/syscall/exec_linux.go
|
||||
// The runtime OS thread must be locked before calling this function
|
||||
//go:noinline
|
||||
//go:norace
|
||||
func ForkAndLoadSeccomp(args []string, filter *libseccomp.ScmpFilter) (int, error) {
|
||||
func (r *ProgramRunner) StartChild() (int, error) {
|
||||
var (
|
||||
err1 syscall.Errno
|
||||
bpf *syscall.SockFprog
|
||||
)
|
||||
// verify
|
||||
r.verify()
|
||||
|
||||
// make exec args
|
||||
argv0, err := syscall.BytePtrFromString(args[0])
|
||||
argv0, err := syscall.BytePtrFromString(r.Args[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
argv, err := syscall.SlicePtrFromStrings(args)
|
||||
argv, err := syscall.SlicePtrFromStrings(r.Args)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
envv, err := syscall.SlicePtrFromStrings([]string{""})
|
||||
// make env
|
||||
envv, err := syscall.SlicePtrFromStrings(r.Env)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// make bpf using libseccomp
|
||||
bpf, err := FilterToBPF(filter)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
if r.Filter != nil {
|
||||
bpf, err = FilterToBPF(r.Filter)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
rlimits := []rlimit{
|
||||
// CPU limit
|
||||
{
|
||||
resource: syscall.RLIMIT_CPU,
|
||||
rlim: syscall.Rlimit{
|
||||
Cur: r.TimeLimit,
|
||||
Max: r.RealTimeLimit,
|
||||
},
|
||||
},
|
||||
// File limit
|
||||
{
|
||||
resource: syscall.RLIMIT_FSIZE,
|
||||
rlim: syscall.Rlimit{
|
||||
Cur: r.OutputLimit << 20,
|
||||
Max: r.OutputLimit << 20,
|
||||
},
|
||||
},
|
||||
// Stack limit
|
||||
{
|
||||
resource: syscall.RLIMIT_STACK,
|
||||
rlim: syscall.Rlimit{
|
||||
Cur: r.StackLimit << 20,
|
||||
Max: r.StackLimit << 20,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// work dir
|
||||
var dir *byte
|
||||
if r.WorkPath != "" {
|
||||
dir, err = syscall.BytePtrFromString(r.WorkPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// stdin, stdout, stderr
|
||||
files := make([]*os.File, 3)
|
||||
if r.InputFileName != "" {
|
||||
files[0], err = os.OpenFile(r.InputFileName, os.O_RDONLY, 0755)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer files[0].Close()
|
||||
}
|
||||
if r.OutputFileName != "" {
|
||||
files[1], err = os.OpenFile(r.OutputFileName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer files[1].Close()
|
||||
}
|
||||
if r.ErrorFileName != "" {
|
||||
files[2], err = os.OpenFile(r.ErrorFileName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer files[2].Close()
|
||||
}
|
||||
fds := make([]uintptr, 3)
|
||||
for i, f := range files {
|
||||
if f != nil {
|
||||
fds[i] = f.Fd()
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire the fork lock so that no other threads
|
||||
// create new fds that are not yet close-on-exec
|
||||
// before we fork.
|
||||
syscall.ForkLock.Lock()
|
||||
|
||||
// About to call fork.
|
||||
// No more allocation or calls of non-assembly functions.
|
||||
beforeFork()
|
||||
@ -54,6 +137,8 @@ func ForkAndLoadSeccomp(args []string, filter *libseccomp.ScmpFilter) (int, erro
|
||||
if err1 != 0 || pid != 0 {
|
||||
// restore all signals
|
||||
afterFork()
|
||||
syscall.ForkLock.Unlock()
|
||||
|
||||
if err1 != 0 {
|
||||
return int(pid), syscall.Errno(err1)
|
||||
}
|
||||
@ -64,24 +149,53 @@ func ForkAndLoadSeccomp(args []string, filter *libseccomp.ScmpFilter) (int, erro
|
||||
afterForkInChild()
|
||||
// Notice: cannot call any functions beyond this point
|
||||
|
||||
// Set limit
|
||||
for _, rlim := range rlimits {
|
||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_SETRLIMIT, uintptr(rlim.resource), uintptr(unsafe.Pointer(&rlim.rlim)), 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
}
|
||||
|
||||
// Chdir
|
||||
if dir != nil {
|
||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_CHDIR, uintptr(unsafe.Pointer(dir)), 0, 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
}
|
||||
|
||||
// stdin, stdout, stderr
|
||||
// the other file already marked as close on exec
|
||||
for i, fd := range fds {
|
||||
if fd != 0 {
|
||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_DUP, fd, uintptr(i), 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable ptrace
|
||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_PTRACE, uintptr(syscall.PTRACE_TRACEME), 0, 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
|
||||
// Check if support
|
||||
// SECCOMP_SET_MODE_STRICT = 0, args = 1 for invalid operation
|
||||
_, _, err1 = syscall.Syscall(unix.SYS_SECCOMP, 0, 1, 0)
|
||||
if err1 != syscall.EINVAL {
|
||||
goto childerror
|
||||
}
|
||||
if r.Filter != nil {
|
||||
// Check if support
|
||||
// SECCOMP_SET_MODE_STRICT = 0, args = 1 for invalid operation
|
||||
_, _, err1 = syscall.Syscall(unix.SYS_SECCOMP, 0, 1, 0)
|
||||
if err1 != syscall.EINVAL {
|
||||
goto childerror
|
||||
}
|
||||
|
||||
// Load the filter manually
|
||||
// No new priv
|
||||
_, _, err1 = syscall.Syscall6(syscall.SYS_PRCTL, unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0, 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
// Load the filter manually
|
||||
// No new priv
|
||||
_, _, err1 = syscall.Syscall6(syscall.SYS_PRCTL, unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0, 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
}
|
||||
|
||||
// Get pid of child
|
||||
@ -96,13 +210,15 @@ func ForkAndLoadSeccomp(args []string, filter *libseccomp.ScmpFilter) (int, erro
|
||||
goto childerror
|
||||
}
|
||||
|
||||
// set seccomp
|
||||
//_, _, err1 = syscall.Syscall6(syscall.SYS_PRCTL, unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, uintptr(unsafe.Pointer(&bpf[0])), 0, 0, 0)
|
||||
// SECCOMP_SET_MODE_FILTER = 1
|
||||
// SECCOMP_FILTER_FLAG_TSYNC = 1
|
||||
_, _, err1 = syscall.Syscall(unix.SYS_SECCOMP, 1, 1, uintptr(unsafe.Pointer(bpf)))
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
if r.Filter != nil {
|
||||
// set seccomp
|
||||
//_, _, err1 = syscall.Syscall6(syscall.SYS_PRCTL, unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, uintptr(unsafe.Pointer(&bpf[0])), 0, 0, 0)
|
||||
// SECCOMP_SET_MODE_FILTER = 1
|
||||
// SECCOMP_FILTER_FLAG_TSYNC = 1
|
||||
_, _, err1 = syscall.Syscall(unix.SYS_SECCOMP, 1, 1, uintptr(unsafe.Pointer(bpf)))
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
}
|
||||
|
||||
// time to exec
|
||||
|
||||
52
runner/runner.go
Normal file
52
runner/runner.go
Normal file
@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
)
|
||||
|
||||
// ProgramRunner is the RunProgramConfig
|
||||
// including the exec path, argv
|
||||
// and resource limits
|
||||
type ProgramRunner struct {
|
||||
// Resource limit set by set rlimit
|
||||
TimeLimit uint64 // second
|
||||
RealTimeLimit uint64 // second
|
||||
MemoryLimit uint64 // mb
|
||||
OutputLimit uint64 // mb
|
||||
StackLimit uint64 // mb
|
||||
|
||||
// stdin, stdout, stderr file name. nil for default
|
||||
InputFileName string
|
||||
OutputFileName string
|
||||
ErrorFileName string
|
||||
|
||||
// work path
|
||||
WorkPath string
|
||||
|
||||
// argv and env for the child process
|
||||
Args []string
|
||||
Env []string
|
||||
|
||||
// libseccomp Filter applied to child
|
||||
Filter *libseccomp.ScmpFilter
|
||||
}
|
||||
|
||||
// NewProgramRunner creates program config with default setting
|
||||
func NewProgramRunner() ProgramRunner {
|
||||
return ProgramRunner{
|
||||
TimeLimit: 1,
|
||||
RealTimeLimit: 0,
|
||||
MemoryLimit: 256,
|
||||
OutputLimit: 64,
|
||||
StackLimit: 1024,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ProgramRunner) verify() {
|
||||
if r.RealTimeLimit < r.TimeLimit {
|
||||
r.RealTimeLimit = r.TimeLimit + 2
|
||||
}
|
||||
if r.StackLimit > r.MemoryLimit {
|
||||
r.StackLimit = r.MemoryLimit
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user