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
# Test Env
test1/
test*/
env.sh

View File

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

View File

@ -89,6 +89,13 @@ func (r *Runner) Start() (int, error) {
afterForkInChild()
// 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
for _, rlim := range r.RLimits {
// 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
type ResLimit struct {
TimeLimit uint // user CPU time limit (in ms)
MemoryLimit uint // user memory limit (in kB)
TimeLimit uint // user CPU time limit (in ms)
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

View File

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