add stdin/out/err and rlimit restrict

This commit is contained in:
criyle 2019-03-28 20:11:47 -04:00
parent f6ed5aaa41
commit ea21693afd
3 changed files with 223 additions and 33 deletions

View File

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

View File

@ -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
View 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
}
}