mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
move seccomp filter utility into separate package
This commit is contained in:
parent
ba976039cf
commit
8f4e4c3237
117
README.md
117
README.md
@ -7,12 +7,12 @@ Goal is to reimplement [uoj-judger/run_program](https://github.com/vfleaking/uoj
|
||||
Install:
|
||||
+ install go compiler: `apt install golang-go`
|
||||
+ install libseccomp-dev: `apt install libseccomp-dev`
|
||||
+ install: `go get -d github.com/criyle/go-judger`
|
||||
+ install: `go install github.com/criyle/go-judger/...`
|
||||
|
||||
Features (same as uoj-judger/run_program):
|
||||
1. Restricted computing resource: Time / Memory (Stack) / Output
|
||||
2. Restricted syscall access (by libseccomp / ptrace)
|
||||
3. Restricted file access (read / write / access / exec)
|
||||
1. Restricted computing resource: Time & Memory (Stack) & Output
|
||||
2. Restricted syscall access (by libseccomp & ptrace)
|
||||
3. Restricted file access (read & write & access & exec)
|
||||
|
||||
Default file access action:
|
||||
+ check file read / write: `open`, `openat`
|
||||
@ -22,33 +22,110 @@ Default file access action:
|
||||
+ check file exec: `execve`
|
||||
|
||||
Packages:
|
||||
+ Tracee: fork-exec with seccomp loaded
|
||||
+ Tracer: ptrace tracee and provides syscall trap
|
||||
+ Secutil: provides common utility function that wrappers libseccomp
|
||||
+ Tracee: ptraced fork-exec with seccomp loaded
|
||||
+ Tracer: ptrace tracee and provides syscall trap context
|
||||
|
||||
Executable:
|
||||
+ run_program: under construction
|
||||
|
||||
TODO:
|
||||
|
||||
Planned example config file format(yaml):
|
||||
Planned example config file format(yaml) `run_program.yaml`:
|
||||
``` yaml
|
||||
python2:
|
||||
+ extra_syscall_allow:
|
||||
- clone
|
||||
+ extra_syscall_count:
|
||||
- set_tid_address: 1
|
||||
+ extra_syscall_ban:
|
||||
- socket
|
||||
+ extra_file_read:
|
||||
- /usr/bin
|
||||
+ extra_file_write:
|
||||
- ./
|
||||
+ extra_file_stat:
|
||||
- /usr/bin
|
||||
syscall:
|
||||
extraAllow:
|
||||
- clone
|
||||
extraCount:
|
||||
set_tid_address: 1
|
||||
extraBan:
|
||||
- socket
|
||||
file:
|
||||
extraRead:
|
||||
- /usr/bin
|
||||
extraWrite:
|
||||
- ./
|
||||
extraStat:
|
||||
- /usr/bin
|
||||
extraBan:
|
||||
- /etc/passwd
|
||||
```
|
||||
|
||||
Planned config file format for compiler `compiler.yaml`:
|
||||
``` yaml
|
||||
python:
|
||||
exec: python ...
|
||||
...:
|
||||
```
|
||||
|
||||
Planned runtime spec(yaml) `run.yaml`:
|
||||
``` yaml
|
||||
inputFile: input.txt
|
||||
outputFile: output.txt
|
||||
...:
|
||||
```
|
||||
|
||||
+ allow multiple traced programs
|
||||
+ FD table instead of file names and allow pipes
|
||||
+ allow pipes
|
||||
+ Percise resource limits (s -> ms, mb -> kb)
|
||||
+ More architectures (arm32, x86)
|
||||
+ ...
|
||||
|
||||
Default Allowed Syscalls:
|
||||
``` go
|
||||
// file access through fd
|
||||
"read", "write", "readv", "writev",
|
||||
"close",
|
||||
"fstat",
|
||||
"lseek",
|
||||
"dup", "dup2", "dup3",
|
||||
"ioctl", "fcntl",
|
||||
|
||||
// memory action
|
||||
"mmap", "mprotect", "munmap", "brk",
|
||||
"mremap", "msync", "mincore", "madvise",
|
||||
|
||||
// signal action
|
||||
"rt_sigaction",
|
||||
"rt_sigprocmask",
|
||||
"rt_sigreturn",
|
||||
"rt_sigpending",
|
||||
"sigaltstack",
|
||||
|
||||
// get current work dir
|
||||
"getcwd",
|
||||
|
||||
// process exit
|
||||
"exit",
|
||||
"exit_group",
|
||||
|
||||
// others
|
||||
"arch_prctl",
|
||||
|
||||
"gettimeofday",
|
||||
"getrlimit",
|
||||
"getrusage",
|
||||
"times",
|
||||
"time",
|
||||
"clock_gettime",
|
||||
|
||||
"restart_syscall",
|
||||
```
|
||||
|
||||
Default Allowed File Reads:
|
||||
``` go
|
||||
"/etc/ld.so.nohwcap",
|
||||
"/etc/ld.so.preload",
|
||||
"/etc/ld.so.cache",
|
||||
"/lib/x86_64-linux-gnu/",
|
||||
"/usr/lib/x86_64-linux-gnu/",
|
||||
"/usr/lib/locale/locale-archive",
|
||||
"/proc/self/exe",
|
||||
"/etc/timezone",
|
||||
"/usr/share/zoneinfo/",
|
||||
"/dev/random",
|
||||
"/dev/urandom",
|
||||
"/proc/meminfo",
|
||||
"/etc/localtime",
|
||||
``
|
||||
|
||||
73
secutil/secutil.go
Normal file
73
secutil/secutil.go
Normal file
@ -0,0 +1,73 @@
|
||||
// Package secutil provides utility functions to manipulate seccomp filters
|
||||
// provided by libseccomp
|
||||
package secutil
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
)
|
||||
|
||||
// FilterToBPF convert libseccomp filter to kernel readable BPF style
|
||||
func FilterToBPF(filter *libseccomp.ScmpFilter) (*syscall.SockFprog, error) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// export to pipe
|
||||
go func() {
|
||||
filter.ExportBPF(w)
|
||||
w.Close()
|
||||
}()
|
||||
|
||||
// get BPF binary
|
||||
bin, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// directly convert pointer
|
||||
return &syscall.SockFprog{
|
||||
Len: uint16(len(bin) / 8),
|
||||
Filter: (*syscall.SockFilter)(unsafe.Pointer(&bin[0])),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BuildFilter builds libseccomp filter by defining the default action, trace action
|
||||
// allow and trace syscall names
|
||||
func BuildFilter(defaultAct, traceAct libseccomp.ScmpAction, allow, trace []string) (*libseccomp.ScmpFilter, error) {
|
||||
filter, err := libseccomp.NewFilter(defaultAct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, s := range allow {
|
||||
err := addFilterAction(filter, s, libseccomp.ActAllow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range trace {
|
||||
err := addFilterAction(filter, s, traceAct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func addFilterAction(filter *libseccomp.ScmpFilter, name string, action libseccomp.ScmpAction) error {
|
||||
syscallID, err := libseccomp.GetSyscallFromName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = filter.AddRule(syscallID, action)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,51 +1,38 @@
|
||||
// Package tracee provides interface to run a seccomp filtered, rlimited
|
||||
// executable and ptraced
|
||||
package tracee
|
||||
|
||||
import (
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
)
|
||||
import "syscall"
|
||||
|
||||
// Runner is the RunProgramConfig including the exec path, argv
|
||||
// and resource limits. It creates tracee for ptraced tracer
|
||||
// and resource limits. It creates tracee for ptrace-based tracer.
|
||||
type Runner 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
|
||||
// Resource limit set by set rlimit
|
||||
RLimits []RLimit
|
||||
|
||||
// file disriptors for new process, from 0 to len - 1
|
||||
Files []uintptr
|
||||
|
||||
// work path set by setcwd (current working directory for child)
|
||||
WorkDir string
|
||||
|
||||
// BPF syscall filter applied to child
|
||||
BPF *syscall.SockFprog
|
||||
}
|
||||
|
||||
// NewRunner creates program config with default setting
|
||||
// RLimit is the resource limits defined by Linux setrlimit
|
||||
type RLimit struct {
|
||||
// Res is the resource type (e.g. syscall.RLIMIT_CPU)
|
||||
Res int
|
||||
// Rlim is the limit applied to that resource
|
||||
Rlim syscall.Rlimit
|
||||
}
|
||||
|
||||
// NewRunner creates new runner struct
|
||||
func NewRunner() Runner {
|
||||
return Runner{
|
||||
TimeLimit: 1,
|
||||
RealTimeLimit: 0,
|
||||
MemoryLimit: 256,
|
||||
OutputLimit: 64,
|
||||
StackLimit: 1024,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) verify() {
|
||||
if r.RealTimeLimit < r.TimeLimit {
|
||||
r.RealTimeLimit = r.TimeLimit + 2
|
||||
}
|
||||
if r.StackLimit > r.MemoryLimit {
|
||||
r.StackLimit = r.MemoryLimit
|
||||
}
|
||||
return Runner{}
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package tracee
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe" // required for go:linkname.
|
||||
|
||||
@ -25,17 +24,17 @@ func afterForkInChild()
|
||||
//go:norace
|
||||
func (r *Runner) Start() (int, error) {
|
||||
var (
|
||||
err1 syscall.Errno
|
||||
bpf *syscall.SockFprog
|
||||
err1 syscall.Errno
|
||||
workdir *byte
|
||||
nextfd int
|
||||
)
|
||||
// verify
|
||||
r.verify()
|
||||
|
||||
// make exec args
|
||||
// make exec args0
|
||||
argv0, err := syscall.BytePtrFromString(r.Args[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// make exec args
|
||||
argv, err := syscall.SlicePtrFromStrings(r.Args)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@ -46,39 +45,24 @@ func (r *Runner) Start() (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// make bpf using libseccomp
|
||||
if r.Filter != nil {
|
||||
bpf, err = FilterToBPF(r.Filter)
|
||||
// make work dir
|
||||
if r.WorkDir != "" {
|
||||
workdir, err = syscall.BytePtrFromString(r.WorkDir)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// rlimit
|
||||
rlimits := r.prepareRLimit()
|
||||
|
||||
// work dir
|
||||
var dir *byte
|
||||
if r.WorkPath != "" {
|
||||
dir, err = syscall.BytePtrFromString(r.WorkPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// stdin, stdout, stderr
|
||||
files, err := r.prepareFile()
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
defer closeFiles(files)
|
||||
|
||||
fds := make([]uintptr, 3)
|
||||
for i, f := range files {
|
||||
if f != nil {
|
||||
fds[i] = f.Fd()
|
||||
// similar to exec_linux, avoid side effect by shuffling around
|
||||
fd := make([]int, len(r.Files))
|
||||
nextfd = len(r.Files)
|
||||
for i, ufd := range r.Files {
|
||||
if nextfd < int(ufd) {
|
||||
nextfd = int(ufd)
|
||||
}
|
||||
fd[i] = int(ufd)
|
||||
}
|
||||
nextfd++
|
||||
|
||||
// Acquire the fork lock so that no other threads
|
||||
// create new fds that are not yet close-on-exec
|
||||
@ -106,29 +90,52 @@ func (r *Runner) Start() (int, error) {
|
||||
// Notice: cannot call any GO 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)
|
||||
for _, rlim := range r.RLimits {
|
||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_SETRLIMIT, uintptr(rlim.Res), uintptr(unsafe.Pointer(&rlim.Rlim)), 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
}
|
||||
|
||||
// Chdir if needed
|
||||
if dir != nil {
|
||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_CHDIR, uintptr(unsafe.Pointer(dir)), 0, 0)
|
||||
if workdir != nil {
|
||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_CHDIR, uintptr(unsafe.Pointer(workdir)), 0, 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
}
|
||||
|
||||
// setup 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_DUP2, fd, uintptr(i), 0)
|
||||
// Pass 1: fd[i] < i => nextfd
|
||||
for i := 0; i < len(fd); i++ {
|
||||
if fd[i] >= 0 && fd[i] < int(i) {
|
||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_DUP2, uintptr(fd[i]), uintptr(nextfd), 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
// Set up close on exec
|
||||
syscall.RawSyscall(syscall.SYS_FCNTL, uintptr(nextfd), syscall.F_SETFD, syscall.FD_CLOEXEC)
|
||||
fd[i] = nextfd
|
||||
nextfd++
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: fd[i] => i
|
||||
for i := 0; i < len(fd); i++ {
|
||||
if fd[i] == -1 {
|
||||
syscall.RawSyscall(syscall.SYS_CLOSE, uintptr(i), 0, 0)
|
||||
continue
|
||||
}
|
||||
if fd[i] == int(i) {
|
||||
// dup2(i, i) will not clear close on exec flag, need to reset the flag
|
||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_FCNTL, uintptr(fd[i]), syscall.F_SETFD, 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_DUP2, uintptr(fd[i]), uintptr(i), 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,7 +146,7 @@ func (r *Runner) Start() (int, error) {
|
||||
}
|
||||
|
||||
// Load seccomp, stop and wait for tracer
|
||||
if r.Filter != nil {
|
||||
if r.BPF != nil {
|
||||
// Check if support
|
||||
// SECCOMP_SET_MODE_STRICT = 0, args = 1 for invalid operation
|
||||
_, _, err1 = syscall.Syscall(unix.SYS_SECCOMP, 0, 1, 0)
|
||||
@ -148,7 +155,7 @@ func (r *Runner) Start() (int, error) {
|
||||
}
|
||||
|
||||
// Load the filter manually
|
||||
// No new priv
|
||||
// No new privs
|
||||
_, _, err1 = syscall.Syscall6(syscall.SYS_PRCTL, unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0, 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
@ -173,7 +180,7 @@ func (r *Runner) Start() (int, error) {
|
||||
// Load seccomp filter
|
||||
// SECCOMP_SET_MODE_FILTER = 1
|
||||
// SECCOMP_FILTER_FLAG_TSYNC = 1
|
||||
_, _, err1 = syscall.Syscall(unix.SYS_SECCOMP, 1, 1, uintptr(unsafe.Pointer(bpf)))
|
||||
_, _, err1 = syscall.Syscall(unix.SYS_SECCOMP, 1, 1, uintptr(unsafe.Pointer(r.BPF)))
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
@ -192,75 +199,3 @@ childerror:
|
||||
// cannot reach this point
|
||||
panic("cannot reach")
|
||||
}
|
||||
|
||||
type rlimit struct {
|
||||
resource int
|
||||
rlim syscall.Rlimit
|
||||
}
|
||||
|
||||
// prepareRLimit creates rlimit structures for tracee
|
||||
func (r *Runner) prepareRLimit() []rlimit {
|
||||
return []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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// prepareFile opens file for new process
|
||||
func (r *Runner) prepareFile() ([]*os.File, error) {
|
||||
var err error
|
||||
files := make([]*os.File, 3)
|
||||
if r.InputFileName != "" {
|
||||
files[0], err = os.OpenFile(r.InputFileName, os.O_RDONLY, 0755)
|
||||
if err != nil {
|
||||
goto openerror
|
||||
}
|
||||
}
|
||||
if r.OutputFileName != "" {
|
||||
files[1], err = os.OpenFile(r.OutputFileName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
goto openerror
|
||||
}
|
||||
}
|
||||
if r.ErrorFileName != "" {
|
||||
files[2], err = os.OpenFile(r.ErrorFileName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
goto openerror
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
openerror:
|
||||
closeFiles(files)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// closeFiles close all file in the list
|
||||
func closeFiles(files []*os.File) {
|
||||
for _, f := range files {
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
package tracee
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
)
|
||||
|
||||
// FilterToBPF convert libseccomp filter to kernel readable BPF style
|
||||
func FilterToBPF(filter *libseccomp.ScmpFilter) (*syscall.SockFprog, error) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// export to pipe
|
||||
go func() {
|
||||
filter.ExportBPF(w)
|
||||
w.Close()
|
||||
}()
|
||||
|
||||
// get BPF binary
|
||||
bin, err := ioutil.ReadAll(r)
|
||||
filter.Release()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// directly convert pointer
|
||||
return &syscall.SockFprog{
|
||||
Len: uint16(len(bin) / 8),
|
||||
Filter: (*syscall.SockFilter)(unsafe.Pointer(&bin[0])),
|
||||
}, nil
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
package tracer
|
||||
|
||||
import libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
|
||||
const (
|
||||
msgDisallow int16 = iota + 1
|
||||
msgHandle
|
||||
)
|
||||
|
||||
func addFilterAction(filter *libseccomp.ScmpFilter, name string, action libseccomp.ScmpAction) error {
|
||||
syscallID, err := libseccomp.GetSyscallFromName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = filter.AddRule(syscallID, action)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Tracer) buildFilter() (*libseccomp.ScmpFilter, error) {
|
||||
// make filter
|
||||
var defaultAction libseccomp.ScmpAction
|
||||
// if debug, allow all syscalls and output what was blocked
|
||||
if r.Unsafe || r.ShowDetails {
|
||||
defaultAction = libseccomp.ActTrace.SetReturnCode(msgDisallow)
|
||||
} else {
|
||||
defaultAction = libseccomp.ActKill
|
||||
}
|
||||
filter, err := libseccomp.NewFilter(defaultAction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, s := range r.Allow {
|
||||
err := addFilterAction(filter, s, libseccomp.ActAllow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range r.Trace {
|
||||
err := addFilterAction(filter, s, libseccomp.ActTrace.SetReturnCode(msgHandle))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return filter, nil
|
||||
}
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
tracee "github.com/criyle/go-judger/tracee"
|
||||
secutil "github.com/criyle/go-judger/secutil"
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
unix "golang.org/x/sys/unix"
|
||||
)
|
||||
@ -33,8 +33,31 @@ func (r *Tracer) StartTrace() (result *TraceResult, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer filter.Release()
|
||||
|
||||
tr := r.getTraceeRunner(filter)
|
||||
bpf, err := secutil.FilterToBPF(filter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// open input / output / err files
|
||||
files, err := r.prepareFiles()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer closeFiles(files)
|
||||
|
||||
// if not defined, then use the original value
|
||||
fds := make([]uintptr, len(files))
|
||||
for i, f := range files {
|
||||
if f != nil {
|
||||
fds[i] = f.Fd()
|
||||
} else {
|
||||
fds[i] = uintptr(i)
|
||||
}
|
||||
}
|
||||
// get tracee
|
||||
tr := r.getTraceeRunner(bpf, fds)
|
||||
|
||||
// run in restricted mode
|
||||
pid, err := tr.Start()
|
||||
@ -273,24 +296,3 @@ func setPtraceOption(pid int) error {
|
||||
return 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)
|
||||
}
|
||||
|
||||
func (r *Tracer) getTraceeRunner(filter *libseccomp.ScmpFilter) tracee.Runner {
|
||||
tr := tracee.NewRunner()
|
||||
tr.TimeLimit = r.TimeLimit
|
||||
tr.RealTimeLimit = r.RealTimeLimit
|
||||
tr.MemoryLimit = r.MemoryLimit
|
||||
tr.OutputLimit = r.OutputLimit
|
||||
tr.StackLimit = r.StackLimit
|
||||
|
||||
tr.Args = r.Args
|
||||
tr.Env = r.Env
|
||||
|
||||
tr.InputFileName = r.InputFileName
|
||||
tr.OutputFileName = r.OutputFileName
|
||||
tr.ErrorFileName = r.ErrorFileName
|
||||
|
||||
tr.WorkPath = r.WorkPath
|
||||
|
||||
tr.Filter = filter
|
||||
return tr
|
||||
}
|
||||
|
||||
105
tracer/tracer_util.go
Normal file
105
tracer/tracer_util.go
Normal file
@ -0,0 +1,105 @@
|
||||
package tracer
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
secutil "github.com/criyle/go-judger/secutil"
|
||||
tracee "github.com/criyle/go-judger/tracee"
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
)
|
||||
|
||||
const (
|
||||
msgDisallow int16 = iota + 1
|
||||
msgHandle
|
||||
)
|
||||
|
||||
func (r *Tracer) buildFilter() (*libseccomp.ScmpFilter, error) {
|
||||
// make filter
|
||||
var defaultAction libseccomp.ScmpAction
|
||||
// if debug, allow all syscalls and output what was blocked
|
||||
if r.Unsafe || r.ShowDetails {
|
||||
defaultAction = libseccomp.ActTrace.SetReturnCode(msgDisallow)
|
||||
} else {
|
||||
defaultAction = libseccomp.ActKill
|
||||
}
|
||||
return secutil.BuildFilter(defaultAction, libseccomp.ActTrace.SetReturnCode(msgHandle), r.Allow, r.Trace)
|
||||
}
|
||||
|
||||
// prepareFile opens file for new process
|
||||
func (r *Tracer) prepareFiles() ([]*os.File, error) {
|
||||
var err error
|
||||
files := make([]*os.File, 3)
|
||||
if r.InputFileName != "" {
|
||||
files[0], err = os.OpenFile(r.InputFileName, os.O_RDONLY, 0755)
|
||||
if err != nil {
|
||||
goto openerror
|
||||
}
|
||||
}
|
||||
if r.OutputFileName != "" {
|
||||
files[1], err = os.OpenFile(r.OutputFileName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
goto openerror
|
||||
}
|
||||
}
|
||||
if r.ErrorFileName != "" {
|
||||
files[2], err = os.OpenFile(r.ErrorFileName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
goto openerror
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
openerror:
|
||||
closeFiles(files)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// closeFiles close all file in the list
|
||||
func closeFiles(files []*os.File) {
|
||||
for _, f := range files {
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Tracer) getTraceeRunner(bpf *syscall.SockFprog, fds []uintptr) tracee.Runner {
|
||||
tr := tracee.NewRunner()
|
||||
tr.RLimits = r.prepareRLimit()
|
||||
tr.WorkDir = r.WorkPath
|
||||
tr.BPF = bpf
|
||||
tr.Args = r.Args
|
||||
tr.Env = r.Env
|
||||
tr.Files = fds
|
||||
return tr
|
||||
}
|
||||
|
||||
// prepareRLimit creates rlimit structures for tracee
|
||||
func (r *Tracer) prepareRLimit() []tracee.RLimit {
|
||||
return []tracee.RLimit{
|
||||
// CPU limit
|
||||
{
|
||||
Res: syscall.RLIMIT_CPU,
|
||||
Rlim: syscall.Rlimit{
|
||||
Cur: r.TimeLimit,
|
||||
Max: r.RealTimeLimit,
|
||||
},
|
||||
},
|
||||
// File limit
|
||||
{
|
||||
Res: syscall.RLIMIT_FSIZE,
|
||||
Rlim: syscall.Rlimit{
|
||||
Cur: r.OutputLimit << 20,
|
||||
Max: r.OutputLimit << 20,
|
||||
},
|
||||
},
|
||||
// Stack limit
|
||||
{
|
||||
Res: syscall.RLIMIT_STACK,
|
||||
Rlim: syscall.Rlimit{
|
||||
Cur: r.StackLimit << 20,
|
||||
Max: r.StackLimit << 20,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user