diff --git a/runner/main.go b/runner/main.go index 9b3eb94..52a9360 100644 --- a/runner/main.go +++ b/runner/main.go @@ -33,6 +33,7 @@ func main() { t := tracer.NewTracer() t.Args = os.Args[1:] t.TraceHandle = handle + //t.Debug = true rt, err := t.StartTrace() log.Println(rt, err) } diff --git a/tracer/tracer.go b/tracer/tracer.go index 0e07e11..52a6aba 100644 --- a/tracer/tracer.go +++ b/tracer/tracer.go @@ -56,6 +56,7 @@ const ( TraceCodeMLE TraceCodeOLE TraceCodeBan + TraceCodeRE TraceCodeFatal ) @@ -69,6 +70,8 @@ func (t TraceCode) Error() string { return "output limit exceeded" case TraceCodeBan: return "syscall banned" + case TraceCodeRE: + return "runtime error" case TraceCodeFatal: return "handle failed" default: diff --git a/tracer/tracer_filter.go b/tracer/tracer_filter.go index fcae439..2b594bf 100644 --- a/tracer/tracer_filter.go +++ b/tracer/tracer_filter.go @@ -2,6 +2,11 @@ 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 { @@ -16,10 +21,10 @@ func addFilterAction(filter *libseccomp.ScmpFilter, name string, action libsecco func (r *Tracer) buildFilter() (*libseccomp.ScmpFilter, error) { // make filter - //filter, err := libseccomp.NewFilter(libseccomp.ActErrno.SetReturnCode(int16(syscall.EPERM))) var defaultAction libseccomp.ScmpAction + // if debug, allow all syscalls and output what was blocked if r.Debug { - defaultAction = libseccomp.ActTrace.SetReturnCode(100) + defaultAction = libseccomp.ActTrace.SetReturnCode(msgDisallow) } else { defaultAction = libseccomp.ActKill } @@ -30,15 +35,13 @@ func (r *Tracer) buildFilter() (*libseccomp.ScmpFilter, error) { for _, s := range r.Allow { err := addFilterAction(filter, s, libseccomp.ActAllow) - //log.Println("[+] allow syscall: ", s) if err != nil { return nil, err } } for _, s := range r.Trace { - err := addFilterAction(filter, s, libseccomp.ActTrace.SetReturnCode(10)) - //log.Println("[+] trace syscall: ", s) + err := addFilterAction(filter, s, libseccomp.ActTrace.SetReturnCode(msgHandle)) if err != nil { return nil, err } diff --git a/tracer/tracer_track.go b/tracer/tracer_track.go index fa05619..5a45005 100644 --- a/tracer/tracer_track.go +++ b/tracer/tracer_track.go @@ -41,11 +41,11 @@ func (r *Tracer) StartTrace() (result *TraceResult, err error) { return } - log.Println("Strart trace pid: ", pid) + r.println("Strart trace pid: ", pid) // store all tracee process pid existence pids := make(map[int]bool) - //rootPid := pid + rootPid := pid // kill all tracee according to pids killAll := func() { @@ -57,6 +57,13 @@ func (r *Tracer) StartTrace() (result *TraceResult, err error) { // kill all tracee upon return defer func() { killAll() + // collect zombies + for { + var wstatus unix.WaitStatus + if _, err := unix.Wait4(-1, &wstatus, unix.WALL|unix.WNOWAIT, nil); err != nil { + break + } + } }() // Set real time limit, kill process after it @@ -70,15 +77,19 @@ func (r *Tracer) StartTrace() (result *TraceResult, err error) { // Trace result var rt TraceResult + // If forked tracee execved, this syscall should not be handled + execved := false + // trace unixs for { + // Wait4 return values var wstatus unix.WaitStatus var rusage unix.Rusage // Wait for all child pid, err := unix.Wait4(-1, &wstatus, unix.WALL, &rusage) if err != nil { - log.Fatalln("Wait4 fatal: ", err) + r.println("Wait4 failed: ", err) break } // Set option if the process is newly forked @@ -90,6 +101,7 @@ func (r *Tracer) StartTrace() (result *TraceResult, err error) { return nil, err } } + // Update resource usage rt.UserTime = uint64(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms rt.UserMem = uint64(rusage.Maxrss) @@ -102,79 +114,89 @@ func (r *Tracer) StartTrace() (result *TraceResult, err error) { return nil, TraceCodeMLE } - if wstatus.Exited() { + // check process status + switch { + case wstatus.Exited(): delete(pids, pid) - log.Println("Exited", pid, wstatus.ExitStatus()) - break - } - if wstatus.Signaled() { + r.println("Exited", pid, wstatus.ExitStatus()) + if pid == rootPid { + if execved { + break + } + return nil, TraceCodeFatal + } + continue + + case wstatus.Signaled(): sig := wstatus.Signal() - log.Println("Signal", sig) - switch sig { - case unix.SIGKILL: - delete(pids, pid) - log.Println("Killed") - continue - case unix.SIGSYS: - delete(pids, pid) - log.Println("Blocked syscall") - default: + r.println("Signal", sig) + if pid == rootPid { + switch sig { + case unix.SIGXCPU: + return nil, TraceCodeTLE + case unix.SIGXFSZ: + return nil, TraceCodeOLE + case unix.SIGSYS: + return nil, TraceCodeBan + default: + return nil, TraceCodeRE + } + } else { delete(pids, pid) continue } - } - if wstatus.Stopped() { - if wstatus.StopSignal() == unix.SIGTRAP { + + case wstatus.Stopped(): + if stopSig := wstatus.StopSignal(); stopSig == unix.SIGTRAP { switch cause := wstatus.TrapCause(); cause { case unix.PTRACE_EVENT_SECCOMP: - log.Println("Seccomp Traced") - _, err := unix.PtraceGetEventMsg(pid) - if err != nil { - log.Fatalln(err) - } + if execved { + // give the customized handle for syscall + act, err := r.handleTrap(pid) + if err != nil { + return nil, err + } - // give the customized handle for syscall - act, err := r.handleTrap(pid) - if err != nil { - return nil, err - } - - switch act { - case TraceAllow: - // do nothing - case TraceBan: - // TODO: action according to handler soft ban - case TraceKill: - killAll() + switch act { + case TraceBan: + // TODO: action according to handler soft ban + case TraceKill: + return nil, TraceCodeBan + } } case unix.PTRACE_EVENT_CLONE: - log.Println("Ptrace stop clone") - + r.println("Ptrace stop clone") case unix.PTRACE_EVENT_VFORK: - log.Println("Ptrace stop vfork") - + r.println("Ptrace stop vfork") case unix.PTRACE_EVENT_FORK: - log.Println("Ptrace stop fork") - + r.println("Ptrace stop fork") case unix.PTRACE_EVENT_EXEC: - log.Println("Ptrace stop exec") + // forked tracee have successfully called execve + execved = true + r.println("Ptrace stop exec") default: - log.Println("Ptrace trap cause: ", cause, wstatus) + r.println("Unexpected ptrace trap cause: ", cause) } } else { - log.Println("Ptrace stop signal: ", wstatus.StopSignal()) + r.println("Unexpected ptrace stop signal: ", stopSig) } } // continue execution unix.PtraceCont(pid, 0) } - // TODO: finish resource consumption result return &rt, nil } +// println only print when debug flag is on +func (r *Tracer) println(v ...interface{}) { + if r.Debug { + log.Println(v...) + } +} + func (r *Tracer) verify() { if r.RealTimeLimit < r.TimeLimit { r.RealTimeLimit = r.TimeLimit + 2 @@ -196,14 +218,37 @@ func (r *Tracer) verify() { r.Allow = allow } +// handleTrap handles the seccomp trap including the custom handle func (r *Tracer) handleTrap(pid int) (TraceAction, error) { - if r.TraceHandle != nil { - context, err := getTrapContext(pid) - if err != nil { - return TraceKill, err - } - return r.TraceHandle(context), nil + r.println("Seccomp Traced") + msg, err := unix.PtraceGetEventMsg(pid) + if err != nil { + r.println(err) } + switch int16(msg) { + case msgDisallow: + ctx, err := getTrapContext(pid) + if err != nil { + r.println(err) + } else { + syscallNo := ctx.SyscallNo() + syscallName, err := libseccomp.ScmpSyscall(syscallNo).GetName() + r.println("disallowed syscall: ", syscallNo, syscallName, err) + } + + case msgHandle: + if r.TraceHandle != nil { + ctx, err := getTrapContext(pid) + if err != nil { + return TraceKill, err + } + return r.TraceHandle(ctx), nil + } + + default: + r.println("unknown message: ", msg) + } + return TraceAllow, nil }