From a122ceed5a8414a19ffc98a932aeb53b0bd0c1c2 Mon Sep 17 00:00:00 2001 From: criyle Date: Sat, 18 May 2019 16:18:14 -0700 Subject: [PATCH] fix race problem of setpgid and refactor some package --- .../run_program}/array_flags.go | 0 .../run_program}/config_amd64.go | 0 .../run_program}/config_arm.go | 0 .../run_program}/config_loader.go | 0 .../run_program}/config_type.go | 0 {run_program => cmd/run_program}/file_set.go | 0 cmd/run_program/handle.go | 61 ++++++++ {run_program => cmd/run_program}/main.go | 63 +++----- cmd/run_program/run_util.go | 40 +++++ .../run_program}/syscall_counter.go | 0 run_program/handle.go | 148 ------------------ run_program/run_util.go | 87 ---------- runprogram/handle.go | 137 ++++++++++++++++ runprogram/rlimit.go | 63 ++++++++ runprogram/runprogram.go | 63 ++++++++ runprogram/runprogram_run.go | 55 +++++++ tracer/tracer.go | 4 +- tracer/tracer_track.go | 78 ++++----- 18 files changed, 474 insertions(+), 325 deletions(-) rename {run_program => cmd/run_program}/array_flags.go (100%) rename {run_program => cmd/run_program}/config_amd64.go (100%) rename {run_program => cmd/run_program}/config_arm.go (100%) rename {run_program => cmd/run_program}/config_loader.go (100%) rename {run_program => cmd/run_program}/config_type.go (100%) rename {run_program => cmd/run_program}/file_set.go (100%) create mode 100644 cmd/run_program/handle.go rename {run_program => cmd/run_program}/main.go (78%) create mode 100644 cmd/run_program/run_util.go rename {run_program => cmd/run_program}/syscall_counter.go (100%) delete mode 100644 run_program/handle.go delete mode 100644 run_program/run_util.go create mode 100644 runprogram/handle.go create mode 100644 runprogram/rlimit.go create mode 100644 runprogram/runprogram.go create mode 100644 runprogram/runprogram_run.go diff --git a/run_program/array_flags.go b/cmd/run_program/array_flags.go similarity index 100% rename from run_program/array_flags.go rename to cmd/run_program/array_flags.go diff --git a/run_program/config_amd64.go b/cmd/run_program/config_amd64.go similarity index 100% rename from run_program/config_amd64.go rename to cmd/run_program/config_amd64.go diff --git a/run_program/config_arm.go b/cmd/run_program/config_arm.go similarity index 100% rename from run_program/config_arm.go rename to cmd/run_program/config_arm.go diff --git a/run_program/config_loader.go b/cmd/run_program/config_loader.go similarity index 100% rename from run_program/config_loader.go rename to cmd/run_program/config_loader.go diff --git a/run_program/config_type.go b/cmd/run_program/config_type.go similarity index 100% rename from run_program/config_type.go rename to cmd/run_program/config_type.go diff --git a/run_program/file_set.go b/cmd/run_program/file_set.go similarity index 100% rename from run_program/file_set.go rename to cmd/run_program/file_set.go diff --git a/cmd/run_program/handle.go b/cmd/run_program/handle.go new file mode 100644 index 0000000..b55fdc2 --- /dev/null +++ b/cmd/run_program/handle.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + "os" + + "github.com/criyle/go-judger/runprogram" +) + +type handler struct { + fs *fileSets + sc syscallCounter + showDetails bool +} + +func (h *handler) print(v ...interface{}) { + if h.showDetails { + fmt.Fprintln(os.Stderr, v...) + } +} + +func (h *handler) onDgsFileDetect(name string) runprogram.TraceAction { + if h.fs.isSoftBanFile(name) { + return runprogram.TraceBan + } + h.print("Dangerous fileopen: ", name) + return runprogram.TraceKill +} + +func (h *handler) CheckRead(fn string) runprogram.TraceAction { + if !h.fs.isReadableFile(fn) { + return h.onDgsFileDetect(fn) + } + return runprogram.TraceAllow +} + +func (h *handler) CheckWrite(fn string) runprogram.TraceAction { + if !h.fs.isWritableFile(fn) { + return h.onDgsFileDetect(fn) + } + return runprogram.TraceAllow +} + +func (h *handler) CheckStat(fn string) runprogram.TraceAction { + if !h.fs.isStatableFile(fn) { + return h.onDgsFileDetect(fn) + } + return runprogram.TraceAllow +} + +func (h *handler) CheckSyscall(syscallName string) runprogram.TraceAction { + // if it is traced, then try to count syscall + if inside, allow := h.sc.check(syscallName); inside { + if allow { + return runprogram.TraceAllow + } + return runprogram.TraceKill + } + // if it is traced but not counted, it should be soft banned + return runprogram.TraceBan +} diff --git a/run_program/main.go b/cmd/run_program/main.go similarity index 78% rename from run_program/main.go rename to cmd/run_program/main.go index 60305f2..15d9856 100644 --- a/run_program/main.go +++ b/cmd/run_program/main.go @@ -4,11 +4,9 @@ import ( "flag" "fmt" "os" - "runtime" - secutil "github.com/criyle/go-judger/secutil" - tracee "github.com/criyle/go-judger/tracee" - tracer "github.com/criyle/go-judger/tracer" + "github.com/criyle/go-judger/runprogram" + "github.com/criyle/go-judger/tracer" ) // TODO: syscall handle, file access checker @@ -64,24 +62,6 @@ func main() { } } - // Ptrace require running at the same OS thread - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - // build seccomp filter - filter, err := buildFilter(showDetails, allow, trace) - if err != nil { - println(err) - os.Exit(1) - } - defer filter.Release() - - bpf, err := secutil.FilterToBPF(filter) - if err != nil { - println(err) - os.Exit(1) - } - // open input / output / err files files, err := prepareFiles(inputFileName, outputFileName, errorFileName) if err != nil { @@ -100,20 +80,28 @@ func main() { } } - // Rlimit - rlimit := prepareRLimit(timeLimit, realTimeLimit, outputLimit<<20, stackLimit<<20) - - // get tracee - ch := &tracee.Runner{ + runner := &runprogram.RunProgram{ Args: args, Env: []string{"PATH=/"}, - RLimits: rlimit, - Files: fds, WorkDir: workPath, - BPF: bpf, + RLimits: runprogram.RLimits{ + CPU: timeLimit, + CPUHard: realTimeLimit, + FileSize: outputLimit, + Stack: stackLimit, + }, + TraceLimit: runprogram.TraceLimit{ + TimeLimit: timeLimit * 1e3, + RealTimeLimit: realTimeLimit * 1e3, + MemoryLimit: memoryLimit << 10, + }, + Files: fds, + SyscallAllowed: allow, + SyscallTraced: trace, + ShowDetails: showDetails, + Unsafe: unsafe, + Handler: &handler{fs, sc, showDetails}, } - // get syscall handler - h := &handler{fs, sc, showDetails} var f *os.File if result == "stdout" { @@ -129,17 +117,8 @@ func main() { defer f.Close() } - tracer.ShowDetails = showDetails - tracer.Unsafe = unsafe - limits := tracer.ResLimit{ - TimeLimit: timeLimit * 1e3, - RealTimeLimit: realTimeLimit * 1e3, - MemoryLimit: memoryLimit << 10, - } - // Run tracer - rt, err := tracer.Trace(h, ch, limits) - + rt, err := runner.Start() println("used process_vm_readv: ", tracer.UseVMReadv) if err != nil { diff --git a/cmd/run_program/run_util.go b/cmd/run_program/run_util.go new file mode 100644 index 0000000..2c4eac2 --- /dev/null +++ b/cmd/run_program/run_util.go @@ -0,0 +1,40 @@ +package main + +import "os" + +// prepareFile opens file for new process +func prepareFiles(inputFile, outputFile, errorFile string) ([]*os.File, error) { + var err error + files := make([]*os.File, 3) + if inputFile != "" { + files[0], err = os.OpenFile(inputFile, os.O_RDONLY, 0755) + if err != nil { + goto openerror + } + } + if outputFile != "" { + files[1], err = os.OpenFile(outputFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755) + if err != nil { + goto openerror + } + } + if errorFile != "" { + files[2], err = os.OpenFile(errorFile, 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() + } + } +} diff --git a/run_program/syscall_counter.go b/cmd/run_program/syscall_counter.go similarity index 100% rename from run_program/syscall_counter.go rename to cmd/run_program/syscall_counter.go diff --git a/run_program/handle.go b/run_program/handle.go deleted file mode 100644 index d7de230..0000000 --- a/run_program/handle.go +++ /dev/null @@ -1,148 +0,0 @@ -package main - -import ( - "fmt" - "os" - "syscall" - - tracer "github.com/criyle/go-judger/tracer" - libseccomp "github.com/seccomp/libseccomp-golang" -) - -type handler struct { - fs *fileSets - sc syscallCounter - showDetails bool -} - -func softBanSyscall(ctx *tracer.Context) tracer.TraceAction { - ctx.SetReturnValue(-int(syscall.EACCES)) - return tracer.TraceBan -} - -func getFileMode(flags uint) string { - switch flags & syscall.O_ACCMODE { - case syscall.O_RDONLY: - return "r " - case syscall.O_WRONLY: - return "w " - case syscall.O_RDWR: - return "wr" - default: - return "??" - } -} - -func (h *handler) print(v ...interface{}) { - if h.showDetails { - fmt.Fprintln(os.Stderr, v...) - } -} - -func (h *handler) onDgsFileDetect(ctx *tracer.Context, name string) tracer.TraceAction { - if h.fs.isSoftBanFile(name) { - return softBanSyscall(ctx) - } - h.print("Dangerous fileopen: (killed)", name) - return tracer.TraceKill -} - -func (h *handler) checkOpen(ctx *tracer.Context, addr uint, flags uint) tracer.TraceAction { - fn := ctx.GetString(uintptr(addr)) - isReadOnly := (flags&syscall.O_ACCMODE == syscall.O_RDONLY) && - (flags&syscall.O_CREAT == 0) && - (flags&syscall.O_EXCL == 0) && - (flags&syscall.O_TRUNC == 0) - - h.print("open: ", fn, getFileMode(flags)) - if isReadOnly { - if realPath(fn) != "" && !h.fs.isReadableFile(fn) { - return h.onDgsFileDetect(ctx, fn) - } - } else { - if realPath(fn) != "" && !h.fs.isWritableFile(fn) { - return h.onDgsFileDetect(ctx, fn) - } - } - return tracer.TraceAllow -} - -func (h *handler) checkRead(ctx *tracer.Context, addr uint) tracer.TraceAction { - fn := ctx.GetString(uintptr(addr)) - h.print("check read: ", fn) - if !h.fs.isReadableFile(fn) { - return h.onDgsFileDetect(ctx, fn) - } - return tracer.TraceAllow -} - -func (h *handler) checkWrite(ctx *tracer.Context, addr uint) tracer.TraceAction { - fn := ctx.GetString(uintptr(addr)) - h.print("check write: ", fn) - if !h.fs.isWritableFile(fn) { - return h.onDgsFileDetect(ctx, fn) - } - return tracer.TraceAllow -} - -func (h *handler) checkStat(ctx *tracer.Context, addr uint) tracer.TraceAction { - fn := ctx.GetString(uintptr(addr)) - h.print("check stat: ", fn) - if !h.fs.isStatableFile(fn) { - return h.onDgsFileDetect(ctx, fn) - } - return tracer.TraceAllow -} - -func (h *handler) Handle(ctx *tracer.Context) tracer.TraceAction { - syscallNo := ctx.SyscallNo() - syscallName, err := libseccomp.ScmpSyscall(syscallNo).GetName() - h.print("syscall: ", syscallNo, syscallName, err) - - switch syscallName { - case "open": - return h.checkOpen(ctx, ctx.Arg0(), ctx.Arg1()) - case "openat": - return h.checkOpen(ctx, ctx.Arg1(), ctx.Arg2()) - - case "readlink": - return h.checkRead(ctx, ctx.Arg0()) - case "readlinkat": - return h.checkRead(ctx, ctx.Arg1()) - - case "unlink": - return h.checkWrite(ctx, ctx.Arg0()) - case "unlinkat": - return h.checkWrite(ctx, ctx.Arg1()) - - case "access": - return h.checkStat(ctx, ctx.Arg0()) - - case "stat", "stat64": - return h.checkStat(ctx, ctx.Arg0()) - case "lstat", "lstat64": - return h.checkStat(ctx, ctx.Arg0()) - - case "execve": - return h.checkRead(ctx, ctx.Arg0()) - - case "chmod": - return h.checkWrite(ctx, ctx.Arg0()) - case "rename": - return h.checkWrite(ctx, ctx.Arg0()) - default: - // if it is traced, then try to count syscall - if inside, allow := h.sc.check(syscallName); !allow { - return tracer.TraceKill - } else if !inside { - // if it is traced but not counted, it should be soft banned - return softBanSyscall(ctx) - } - } - return tracer.TraceAllow -} - -func (h *handler) GetSyscallName(ctx *tracer.Context) (string, error) { - syscallNo := ctx.SyscallNo() - return libseccomp.ScmpSyscall(syscallNo).GetName() -} diff --git a/run_program/run_util.go b/run_program/run_util.go deleted file mode 100644 index 844409a..0000000 --- a/run_program/run_util.go +++ /dev/null @@ -1,87 +0,0 @@ -package main - -import ( - "os" - "syscall" - - secutil "github.com/criyle/go-judger/secutil" - tracee "github.com/criyle/go-judger/tracee" - tracer "github.com/criyle/go-judger/tracer" - libseccomp "github.com/seccomp/libseccomp-golang" -) - -// build filter builds the libseccomp filter according to the allow, trace and show details -func buildFilter(showDetails bool, allow, trace []string) (*libseccomp.ScmpFilter, error) { - // make filter - var defaultAction libseccomp.ScmpAction - // if debug, allow all syscalls and output what was blocked - if showDetails { - defaultAction = libseccomp.ActTrace.SetReturnCode(tracer.MsgDisallow) - } else { - defaultAction = libseccomp.ActKill - } - return secutil.BuildFilter(defaultAction, libseccomp.ActTrace.SetReturnCode(tracer.MsgHandle), allow, trace) -} - -// prepareFile opens file for new process -func prepareFiles(inputFile, outputFile, errorFile string) ([]*os.File, error) { - var err error - files := make([]*os.File, 3) - if inputFile != "" { - files[0], err = os.OpenFile(inputFile, os.O_RDONLY, 0755) - if err != nil { - goto openerror - } - } - if outputFile != "" { - files[1], err = os.OpenFile(outputFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755) - if err != nil { - goto openerror - } - } - if errorFile != "" { - files[2], err = os.OpenFile(errorFile, 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 getRlimit(cur, max uint) syscall.Rlimit { - return syscall.Rlimit{Cur: uint64(cur), Max: uint64(max)} -} - -// prepareRLimit creates rlimit structures for tracee -// TimeLimit in s, SizeLimit in byte -func prepareRLimit(TimeLimit, RealTimeLimit, OutputLimit, StackLimit uint) []tracee.RLimit { - return []tracee.RLimit{ - // CPU limit - { - Res: syscall.RLIMIT_CPU, - Rlim: getRlimit(TimeLimit, RealTimeLimit), - }, - // File limit - { - Res: syscall.RLIMIT_FSIZE, - Rlim: getRlimit(OutputLimit, OutputLimit), - }, - // Stack limit - { - Res: syscall.RLIMIT_STACK, - Rlim: getRlimit(StackLimit, StackLimit), - }, - } -} diff --git a/runprogram/handle.go b/runprogram/handle.go new file mode 100644 index 0000000..6253e21 --- /dev/null +++ b/runprogram/handle.go @@ -0,0 +1,137 @@ +package runprogram + +import ( + "fmt" + "os" + "syscall" + + libseccomp "github.com/seccomp/libseccomp-golang" + + "github.com/criyle/go-judger/tracer" +) + +type tracerHandler struct { + ShowDetails, Unsafe bool + Handler Handler +} + +func (h *tracerHandler) Debug(v ...interface{}) { + if h.ShowDetails { + fmt.Fprintln(os.Stderr, v...) + } +} + +func (h *tracerHandler) checkOpen(ctx *tracer.Context, addr uint, flags uint) TraceAction { + fn := ctx.GetString(uintptr(addr)) + isReadOnly := (flags&syscall.O_ACCMODE == syscall.O_RDONLY) && + (flags&syscall.O_CREAT == 0) && + (flags&syscall.O_EXCL == 0) && + (flags&syscall.O_TRUNC == 0) + + h.Debug("open: ", fn, getFileMode(flags)) + if isReadOnly { + return h.Handler.CheckRead(fn) + } + return h.Handler.CheckWrite(fn) +} + +func (h *tracerHandler) checkRead(ctx *tracer.Context, addr uint) TraceAction { + fn := ctx.GetString(uintptr(addr)) + h.Debug("check read: ", fn) + return h.Handler.CheckRead(fn) +} + +func (h *tracerHandler) checkWrite(ctx *tracer.Context, addr uint) TraceAction { + fn := ctx.GetString(uintptr(addr)) + h.Debug("check write: ", fn) + return h.Handler.CheckWrite(fn) +} + +func (h *tracerHandler) checkStat(ctx *tracer.Context, addr uint) TraceAction { + fn := ctx.GetString(uintptr(addr)) + h.Debug("check stat: ", fn) + return h.Handler.CheckStat(fn) +} + +func (h *tracerHandler) Handle(ctx *tracer.Context) tracer.TraceAction { + var ( + action TraceAction + syscallNo = ctx.SyscallNo() + syscallName, err = libseccomp.ScmpSyscall(syscallNo).GetName() + ) + h.Debug("syscall: ", syscallNo, syscallName, err) + + switch syscallName { + case "open": + action = h.checkOpen(ctx, ctx.Arg0(), ctx.Arg1()) + case "openat": + action = h.checkOpen(ctx, ctx.Arg1(), ctx.Arg2()) + + case "readlink": + action = h.checkRead(ctx, ctx.Arg0()) + case "readlinkat": + action = h.checkRead(ctx, ctx.Arg1()) + + case "unlink": + action = h.checkWrite(ctx, ctx.Arg0()) + case "unlinkat": + action = h.checkWrite(ctx, ctx.Arg1()) + + case "access": + action = h.checkStat(ctx, ctx.Arg0()) + + case "stat", "stat64": + action = h.checkStat(ctx, ctx.Arg0()) + case "lstat", "lstat64": + action = h.checkStat(ctx, ctx.Arg0()) + + case "execve": + action = h.checkRead(ctx, ctx.Arg0()) + + case "chmod": + action = h.checkWrite(ctx, ctx.Arg0()) + case "rename": + action = h.checkWrite(ctx, ctx.Arg0()) + default: + action = h.Handler.CheckSyscall(syscallName) + } + + switch action { + case TraceAllow: + return tracer.TraceAllow + case TraceBan: + return softBanSyscall(ctx) + default: + return tracer.TraceKill + } +} + +func (h *tracerHandler) GetSyscallName(ctx *tracer.Context) (string, error) { + syscallNo := ctx.SyscallNo() + return libseccomp.ScmpSyscall(syscallNo).GetName() +} + +func (h *tracerHandler) HandlerDisallow(name string) error { + if !h.Unsafe { + return tracer.TraceCodeBan + } + return nil +} + +func softBanSyscall(ctx *tracer.Context) tracer.TraceAction { + ctx.SetReturnValue(-int(BanRet)) + return tracer.TraceBan +} + +func getFileMode(flags uint) string { + switch flags & syscall.O_ACCMODE { + case syscall.O_RDONLY: + return "r " + case syscall.O_WRONLY: + return "w " + case syscall.O_RDWR: + return "wr" + default: + return "??" + } +} diff --git a/runprogram/rlimit.go b/runprogram/rlimit.go new file mode 100644 index 0000000..0c37b83 --- /dev/null +++ b/runprogram/rlimit.go @@ -0,0 +1,63 @@ +package runprogram + +import ( + "syscall" + + "github.com/criyle/go-judger/tracee" +) + +// RLimits defines the rlimit applied by setrlimit syscall to traced process +type RLimits struct { + CPU uint // in s + CPUHard uint // in s + Data uint // in kb + FileSize uint // in kb + Stack uint // in kb + AddressSpace uint // in kb +} + +func getRlimit(cur, max uint64) syscall.Rlimit { + return syscall.Rlimit{Cur: uint64(cur), Max: uint64(max)} +} + +// prepareRLimit creates rlimit structures for tracee +// TimeLimit in s, SizeLimit in byte +func (r *RLimits) prepareRLimit() []tracee.RLimit { + var ret []tracee.RLimit + if r.CPU > 0 { + cpuHard := r.CPUHard + if cpuHard < r.CPU { + cpuHard = r.CPU + } + + ret = append(ret, tracee.RLimit{ + Res: syscall.RLIMIT_CPU, + Rlim: getRlimit(uint64(r.CPU), uint64(cpuHard)), + }) + } + if r.Data > 0 { + ret = append(ret, tracee.RLimit{ + Res: syscall.RLIMIT_DATA, + Rlim: getRlimit(uint64(r.Data)<<10, uint64(r.Data)<<10), + }) + } + if r.FileSize > 0 { + ret = append(ret, tracee.RLimit{ + Res: syscall.RLIMIT_FSIZE, + Rlim: getRlimit(uint64(r.FileSize)<<10, uint64(r.FileSize)<<10), + }) + } + if r.Stack > 0 { + ret = append(ret, tracee.RLimit{ + Res: syscall.RLIMIT_STACK, + Rlim: getRlimit(uint64(r.Stack)<<10, uint64(r.Stack)<<10), + }) + } + if r.AddressSpace > 0 { + ret = append(ret, tracee.RLimit{ + Res: syscall.RLIMIT_AS, + Rlim: getRlimit(uint64(r.AddressSpace)<<10, uint64(r.AddressSpace)<<10), + }) + } + return ret +} diff --git a/runprogram/runprogram.go b/runprogram/runprogram.go new file mode 100644 index 0000000..9c18620 --- /dev/null +++ b/runprogram/runprogram.go @@ -0,0 +1,63 @@ +package runprogram + +import ( + "syscall" + + "github.com/criyle/go-judger/tracer" +) + +// RunProgram defines the spec to run a program safely +type RunProgram struct { + // argv and env for the child process + // work path set by setcwd (current working directory for child) + Args []string + Env []string + WorkDir string + + // file disriptors for new process, from 0 to len - 1 + Files []uintptr + + // Resource limit set by set rlimit + RLimits RLimits + + // Res limit enforced by tracer + TraceLimit TraceLimit + + // Allowed / Traced syscall names + // Notice: file access syscalls should be traced + // If traced syscall is file access, it will checked by file access handler + // otherwise it will checked by syscall access handler + SyscallAllowed []string + SyscallTraced []string + + // Traced syscall handler + Handler Handler + + // ShowDetails / Unsafe debug flag + ShowDetails, Unsafe bool +} + +// TraceLimit defines the limits enforced by tracer +type TraceLimit tracer.ResLimit + +// TraceAction defines action against a syscall check +type TraceAction int + +// BanRet defines the return value for a syscall ban acction +var BanRet syscall.Errno = syscall.EACCES + +// TraceAllow allow the access, trace ban ignores the syscall and set the +// return value to BanRet, TraceKill stops the trace action +const ( + TraceAllow = iota + 1 + TraceBan + TraceKill +) + +// Handler defines the action when a file access encountered +type Handler interface { + CheckRead(string) TraceAction + CheckWrite(string) TraceAction + CheckStat(string) TraceAction + CheckSyscall(string) TraceAction +} diff --git a/runprogram/runprogram_run.go b/runprogram/runprogram_run.go new file mode 100644 index 0000000..fb52e51 --- /dev/null +++ b/runprogram/runprogram_run.go @@ -0,0 +1,55 @@ +package runprogram + +import ( + libseccomp "github.com/seccomp/libseccomp-golang" + + "github.com/criyle/go-judger/secutil" + "github.com/criyle/go-judger/tracee" + "github.com/criyle/go-judger/tracer" +) + +// Start starts the tracing process +func (r *RunProgram) Start() (rt tracer.TraceResult, err error) { + // build seccomp filter + filter, err := buildFilter(r.ShowDetails, r.SyscallAllowed, r.SyscallTraced) + if err != nil { + println(err) + return + } + defer filter.Release() + + bpf, err := secutil.FilterToBPF(filter) + if err != nil { + println(err) + return + } + + ch := &tracee.Runner{ + Args: r.Args, + Env: r.Env, + RLimits: r.RLimits.prepareRLimit(), + Files: r.Files, + WorkDir: r.WorkDir, + BPF: bpf, + } + + th := &tracerHandler{ + ShowDetails: r.ShowDetails, + Unsafe: r.Unsafe, + Handler: r.Handler, + } + return tracer.Trace(th, ch, tracer.ResLimit(r.TraceLimit)) +} + +// build filter builds the libseccomp filter according to the allow, trace and show details +func buildFilter(showDetails bool, allow, trace []string) (*libseccomp.ScmpFilter, error) { + // make filter + var defaultAction libseccomp.ScmpAction + // if debug, allow all syscalls and output what was blocked + if showDetails { + defaultAction = libseccomp.ActTrace.SetReturnCode(tracer.MsgDisallow) + } else { + defaultAction = libseccomp.ActKill + } + return secutil.BuildFilter(defaultAction, libseccomp.ActTrace.SetReturnCode(tracer.MsgHandle), allow, trace) +} diff --git a/tracer/tracer.go b/tracer/tracer.go index dfd8e1c..5381dae 100644 --- a/tracer/tracer.go +++ b/tracer/tracer.go @@ -25,7 +25,6 @@ const ( TraceCodeOLE // 5 TraceCodeBan // 6 TraceCodeFatal // 7 - ) func (t TraceCode) Error() string { @@ -74,4 +73,7 @@ type ResLimit struct { type Handler interface { Handle(*Context) TraceAction GetSyscallName(*Context) (string, error) + + Debug(v ...interface{}) + HandlerDisallow(string) error } diff --git a/tracer/tracer_track.go b/tracer/tracer_track.go index d929eb4..0af1a75 100644 --- a/tracer/tracer_track.go +++ b/tracer/tracer_track.go @@ -1,8 +1,6 @@ package tracer import ( - "fmt" - "os" "runtime" "time" @@ -16,13 +14,6 @@ const ( MsgHandle ) -var ( - // ShowDetails is switch to trun on / off whether to show log message - ShowDetails bool - // Unsafe determines whether to terminate tracing when bad syscall caught - Unsafe bool -) - // Trace traces all child process that created by runner // this function should called only once and in the same thread that // exec tracee @@ -41,7 +32,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult, // Start the runner pgid, err := runner.Start() - println("tracer started: ", pgid, err) + handler.Debug("tracer started: ", pgid, err) if err != nil { result.TraceStatus = TraceCodeRE return result, err @@ -61,7 +52,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult, err = TraceCodeTLE } if err2 := recover(); err2 != nil { - println(err2) + handler.Debug(err2) err = TraceCodeFatal } // kill all tracee upon return @@ -71,13 +62,19 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult, // trace unixs for { - // Wait for all child - pid, err := unix.Wait4(-pgid, &wstatus, unix.WALL, &rusage) + var pid int + if execved { + // Wait for all child in the process group + pid, err = unix.Wait4(-pgid, &wstatus, unix.WALL, &rusage) + } else { + // Ensure the process have called setpgid + pid, err = unix.Wait4(pgid, &wstatus, unix.WALL, &rusage) + } if err != nil { - println("wait4 failed: ", err) + handler.Debug("wait4 failed: ", err) return result, TraceCodeFatal } - println("------ ", pid, " ------") + handler.Debug("------ ", pid, " ------") // update resource usage and check against limits userTime := uint(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms @@ -104,7 +101,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult, switch { case wstatus.Exited(): delete(traced, pid) - println("process exited: ", pid, wstatus.ExitStatus()) + handler.Debug("process exited: ", pid, wstatus.ExitStatus()) if execved { result.ExitCode = wstatus.ExitStatus() return result, nil @@ -114,7 +111,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult, case wstatus.Signaled(): sig := wstatus.Signal() - println("ptrace signaled: ", sig) + handler.Debug("ptrace signaled: ", sig) if pid == pgid { switch sig { case unix.SIGXCPU: @@ -134,7 +131,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult, case wstatus.Stopped(): // Set option if the process is newly forked if !traced[pid] { - println("set ptrace option") + handler.Debug("set ptrace option") traced[pid] = true // Ptrace set option valid if the tracee is stopped err = setPtraceOption(pid) @@ -156,31 +153,31 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult, return result, err } } else { - println("ptrace seccomp before execve (should be the execve syscall)") + handler.Debug("ptrace seccomp before execve (should be the execve syscall)") } case unix.PTRACE_EVENT_CLONE: - println("ptrace stop clone") + handler.Debug("ptrace stop clone") case unix.PTRACE_EVENT_VFORK: - println("ptrace stop vfork") + handler.Debug("ptrace stop vfork") case unix.PTRACE_EVENT_FORK: - println("ptrace stop fork") + handler.Debug("ptrace stop fork") case unix.PTRACE_EVENT_EXEC: // forked tracee have successfully called execve execved = true - println("ptrace stop exec") + handler.Debug("ptrace stop exec") default: - println("ptrace unexpected trap cause: ", trapCause) + handler.Debug("ptrace unexpected trap cause: ", trapCause) } } else { // Likely encountered SIGSEGV (segment violation) if stopSig != unix.SIGSTOP { - println("ptrace unexpected stop signal: ", stopSig) + handler.Debug("ptrace unexpected stop signal: ", stopSig) result.TraceStatus = TraceCodeRE return result, TraceCodeRE } - println("ptrace stopped") + handler.Debug("ptrace stopped") } unix.PtraceCont(pid, 0) } @@ -189,26 +186,22 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult, // handleTrap handles the seccomp trap including the custom handle func handleTrap(handler Handler, pid int) error { - println("seccomp traced") + handler.Debug("seccomp traced") msg, err := unix.PtraceGetEventMsg(pid) if err != nil { - println(err) + handler.Debug(err) return err } switch int16(msg) { case MsgDisallow: ctx, err := getTrapContext(pid) if err != nil { - println(err) + handler.Debug(err) return err } - if ShowDetails { - syscallName, err := handler.GetSyscallName(ctx) - println("disallowed syscall: ", ctx.SyscallNo(), syscallName, err) - } - if !Unsafe { - return TraceCodeBan - } + syscallName, err := handler.GetSyscallName(ctx) + handler.Debug("disallowed syscall: ", ctx.SyscallNo(), syscallName, err) + return handler.HandlerDisallow(syscallName) case MsgHandle: if handler != nil { @@ -230,7 +223,7 @@ func handleTrap(handler Handler, pid int) error { default: // undefined seccomp message, possible set up filter wrong - println("unknown seccomp trap message: ", msg) + handler.Debug("unknown seccomp trap message: ", msg) } return nil @@ -242,13 +235,6 @@ func setPtraceOption(pid int) error { unix.PTRACE_O_TRACEFORK|unix.PTRACE_O_TRACECLONE|unix.PTRACE_O_TRACEEXEC|unix.PTRACE_O_TRACEVFORK) } -// println only print when debug flag is on -func println(v ...interface{}) { - if ShowDetails { - fmt.Fprintln(os.Stderr, v...) - } -} - // kill all tracee according to pids func killAll(pgid int) { unix.Kill(-pgid, unix.SIGKILL) @@ -259,10 +245,8 @@ func collectZombie(pgid int) { // collect zombies for { var wstatus unix.WaitStatus - if p, err := unix.Wait4(-pgid, &wstatus, unix.WALL|unix.WNOWAIT, nil); err != nil { + if _, err := unix.Wait4(-pgid, &wstatus, unix.WALL|unix.WNOWAIT, nil); err != nil { break - } else { - println("collect: ", p) } } }