make tracee into individual process group

This commit is contained in:
criyle 2019-05-18 01:40:37 -07:00
parent 4828363e1d
commit 7cfcc5c391
5 changed files with 56 additions and 73 deletions

2
.gitignore vendored
View File

@ -2,5 +2,5 @@
.DS_Store .DS_Store
# Test Env # Test Env
test1/ test*/
env.sh env.sh

View File

@ -132,14 +132,13 @@ func main() {
tracer.ShowDetails = showDetails tracer.ShowDetails = showDetails
tracer.Unsafe = unsafe tracer.Unsafe = unsafe
limits := tracer.ResLimit{ limits := tracer.ResLimit{
TimeLimit: timeLimit * 1e3, TimeLimit: timeLimit * 1e3,
MemoryLimit: memoryLimit << 10, RealTimeLimit: realTimeLimit * 1e3,
MemoryLimit: memoryLimit << 10,
} }
runners := []tracer.Runner{ch}
// Run tracer // Run tracer
results, err := tracer.Trace(h, runners, limits, int64(realTimeLimit)*1e9) rt, err := tracer.Trace(h, ch, limits)
rt := results[0]
println("used process_vm_readv: ", tracer.UseVMReadv) println("used process_vm_readv: ", tracer.UseVMReadv)

View File

@ -89,6 +89,13 @@ func (r *Runner) Start() (int, error) {
afterForkInChild() afterForkInChild()
// Notice: cannot call any GO functions beyond this point // Notice: cannot call any GO functions beyond this point
// Set the pgid, so that the wait operation can apply to only certain
// subgroup of processes
_, _, err1 = syscall.RawSyscall(syscall.SYS_SETPGID, 0, 0, 0)
if err1 != 0 {
goto childerror
}
// Set limit // Set limit
for _, rlim := range r.RLimits { for _, rlim := range r.RLimits {
// Prlimit instead of setrlimit to avoid 32-bit limitation (linux > 3.2) // Prlimit instead of setrlimit to avoid 32-bit limitation (linux > 3.2)

View File

@ -65,8 +65,9 @@ type Runner interface {
// ResLimit represents the resource limit for traced process // ResLimit represents the resource limit for traced process
type ResLimit struct { type ResLimit struct {
TimeLimit uint // user CPU time limit (in ms) TimeLimit uint // user CPU time limit (in ms)
MemoryLimit uint // user memory limit (in kB) RealTimeLimit uint // sig_kill will force the process to exit after this limit (in ms)
MemoryLimit uint // user memory limit (in kB)
} }
// Handler defines customized handler for traced syscall // Handler defines customized handler for traced syscall

View File

@ -26,36 +26,31 @@ var (
// Trace traces all child process that created by runner // Trace traces all child process that created by runner
// this function should called only once and in the same thread that // this function should called only once and in the same thread that
// exec tracee // exec tracee
func Trace(handler Handler, runners []Runner, limits ResLimit, timeout int64) (results []TraceResult, err error) { func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult, err error) {
var ( var (
wstatus unix.WaitStatus // wait4 wait status wstatus unix.WaitStatus // wait4 wait status
rusage unix.Rusage // wait4 rusage rusage unix.Rusage // wait4 rusage
tle bool // whether the timmer triggered due to timeout tle bool // whether the timmer triggered due to timeout
traced = make(map[int]bool) // store all process that have set ptrace options traced = make(map[int]bool) // store all process that have set ptrace options
execved = make(map[int]bool) // store whether a runner process have successfully execvd execved = false // store whether the runner process have successfully execvd
pidmap = make(map[int]int) // pid -> index map
running = len(runners) // total number of remained runner process
) )
results = make([]TraceResult, len(runners))
// make this thread exit after trace, ensure no process escaped // ptrace is thread based (kernel proc)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread()
// Starts all runners // Start the runner
for i, r := range runners { pgid, err := runner.Start()
pid, err := r.Start() println("tracer started: ", pgid, err)
println("tracer started: ", pid, err) if err != nil {
if err != nil { result.TraceStatus = TraceCodeRE
results[i].TraceStatus = TraceCodeRE return result, err
return results, err
}
pidmap[pid] = i
} }
// Set real time limit, kill process after it // Set real time limit, kill process after it
timer := time.AfterFunc(time.Duration(timeout), func() { timer := time.AfterFunc(time.Duration(limits.RealTimeLimit*1e6), func() {
tle = true tle = true
killAll(traced) killAll(pgid)
}) })
// handler potential panic and tle // handler potential panic and tle
@ -70,24 +65,23 @@ func Trace(handler Handler, runners []Runner, limits ResLimit, timeout int64) (r
err = TraceCodeFatal err = TraceCodeFatal
} }
// kill all tracee upon return // kill all tracee upon return
killAll(traced) killAll(pgid)
collectZombie() collectZombie(pgid)
}() }()
// trace unixs // trace unixs
for { for {
// Wait for all child // Wait for all child
pid, err := unix.Wait4(-1, &wstatus, unix.WALL, &rusage) pid, err := unix.Wait4(-pgid, &wstatus, unix.WALL, &rusage)
if err != nil { if err != nil {
println("wait4 failed: ", err) println("wait4 failed: ", err)
return results, TraceCodeFatal return result, TraceCodeFatal
} }
println("------ ", pid, " ------") println("------ ", pid, " ------")
// update resource usage and check against limits // update resource usage and check against limits
userTime := uint(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms userTime := uint(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms
userMem := uint(rusage.Maxrss) // kb userMem := uint(rusage.Maxrss) // kb
idx, inside := pidmap[pid] // index
status := TraceCodeNormal // check limit status := TraceCodeNormal // check limit
// check tle / mle // check tle / mle
@ -97,15 +91,13 @@ func Trace(handler Handler, runners []Runner, limits ResLimit, timeout int64) (r
if userMem > limits.MemoryLimit { if userMem > limits.MemoryLimit {
status = TraceCodeMLE status = TraceCodeMLE
} }
if inside { result = TraceResult{
results[idx] = TraceResult{ UserTime: userTime,
UserTime: userTime, UserMem: userMem,
UserMem: userMem, TraceStatus: status,
TraceStatus: status,
}
} }
if status != TraceCodeNormal { if status != TraceCodeNormal {
return results, status return result, status
} }
// check process status // check process status
@ -113,22 +105,17 @@ func Trace(handler Handler, runners []Runner, limits ResLimit, timeout int64) (r
case wstatus.Exited(): case wstatus.Exited():
delete(traced, pid) delete(traced, pid)
println("process exited: ", pid, wstatus.ExitStatus()) println("process exited: ", pid, wstatus.ExitStatus())
if inside { if execved {
if execved[pid] { result.ExitCode = wstatus.ExitStatus()
results[idx].ExitCode = wstatus.ExitStatus() return result, nil
if running--; running == 0 {
return results, nil
}
} else {
results[idx].TraceStatus = TraceCodeFatal
return results, TraceCodeFatal
}
} }
result.TraceStatus = TraceCodeFatal
return result, TraceCodeFatal
case wstatus.Signaled(): case wstatus.Signaled():
sig := wstatus.Signal() sig := wstatus.Signal()
println("ptrace signaled: ", sig) println("ptrace signaled: ", sig)
if inside { if pid == pgid {
switch sig { switch sig {
case unix.SIGXCPU: case unix.SIGXCPU:
status = TraceCodeTLE status = TraceCodeTLE
@ -139,8 +126,8 @@ func Trace(handler Handler, runners []Runner, limits ResLimit, timeout int64) (r
default: default:
status = TraceCodeRE status = TraceCodeRE
} }
results[idx].TraceStatus = status result.TraceStatus = status
return results, status return result, status
} }
delete(traced, pid) delete(traced, pid)
@ -152,7 +139,8 @@ func Trace(handler Handler, runners []Runner, limits ResLimit, timeout int64) (r
// Ptrace set option valid if the tracee is stopped // Ptrace set option valid if the tracee is stopped
err = setPtraceOption(pid) err = setPtraceOption(pid)
if err != nil { if err != nil {
return results, err result.TraceStatus = TraceCodeFatal
return result, err
} }
} }
@ -160,14 +148,12 @@ func Trace(handler Handler, runners []Runner, limits ResLimit, timeout int64) (r
if stopSig := wstatus.StopSignal(); stopSig == unix.SIGTRAP { if stopSig := wstatus.StopSignal(); stopSig == unix.SIGTRAP {
switch trapCause := wstatus.TrapCause(); trapCause { switch trapCause := wstatus.TrapCause(); trapCause {
case unix.PTRACE_EVENT_SECCOMP: case unix.PTRACE_EVENT_SECCOMP:
if !inside || execved[pid] { if execved {
// give the customized handle for syscall // give the customized handle for syscall
err := handleTrap(handler, pid) err := handleTrap(handler, pid)
if err != nil { if err != nil {
if inside { result.TraceStatus = TraceCodeBan
results[idx].TraceStatus = TraceCodeBan return result, err
}
return results, err
} }
} else { } else {
println("ptrace seccomp before execve (should be the execve syscall)") println("ptrace seccomp before execve (should be the execve syscall)")
@ -181,7 +167,7 @@ func Trace(handler Handler, runners []Runner, limits ResLimit, timeout int64) (r
println("ptrace stop fork") println("ptrace stop fork")
case unix.PTRACE_EVENT_EXEC: case unix.PTRACE_EVENT_EXEC:
// forked tracee have successfully called execve // forked tracee have successfully called execve
execved[pid] = true execved = true
println("ptrace stop exec") println("ptrace stop exec")
default: default:
@ -191,19 +177,12 @@ func Trace(handler Handler, runners []Runner, limits ResLimit, timeout int64) (r
// Likely encountered SIGSEGV (segment violation) // Likely encountered SIGSEGV (segment violation)
if stopSig != unix.SIGSTOP { if stopSig != unix.SIGSTOP {
println("ptrace unexpected stop signal: ", stopSig) println("ptrace unexpected stop signal: ", stopSig)
if inside { result.TraceStatus = TraceCodeRE
results[idx].TraceStatus = TraceCodeRE return result, TraceCodeRE
}
return results, TraceCodeRE
} }
println("ptrace stopped") println("ptrace stopped")
} }
unix.PtraceCont(pid, 0) unix.PtraceCont(pid, 0)
default:
// should never happen
println("unexpected wait status: ", wstatus)
unix.PtraceCont(pid, 0)
} }
} }
} }
@ -271,19 +250,16 @@ func println(v ...interface{}) {
} }
// kill all tracee according to pids // kill all tracee according to pids
func killAll(pids map[int]bool) { func killAll(pgid int) {
for p := range pids { unix.Kill(-pgid, unix.SIGKILL)
println("kill: ", p)
unix.Kill(p, unix.SIGKILL)
}
} }
// collect died child processes // collect died child processes
func collectZombie() { func collectZombie(pgid int) {
// collect zombies // collect zombies
for { for {
var wstatus unix.WaitStatus var wstatus unix.WaitStatus
if p, err := unix.Wait4(-1, &wstatus, unix.WALL|unix.WNOWAIT, nil); err != nil { if p, err := unix.Wait4(-pgid, &wstatus, unix.WALL|unix.WNOWAIT, nil); err != nil {
break break
} else { } else {
println("collect: ", p) println("collect: ", p)