diff --git a/README.md b/README.md index 7a779d5..7944002 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ type Environment interface { ## Packages -- config: defines arch & language specified trace condition for ptrace runner from UOJ +- cmd/runprog/config: defines arch & language specified trace condition for ptrace runner from UOJ - container: creates pre-forked container to run programs inside - runner: interface to run program - ptrace: wrapper to call forkexec and ptracer diff --git a/config/config.go b/cmd/runprog/config/config.go similarity index 99% rename from config/config.go rename to cmd/runprog/config/config.go index 98237ec..7d299ae 100644 --- a/config/config.go +++ b/cmd/runprog/config/config.go @@ -37,6 +37,8 @@ var ( "ioctl", "fcntl", "fadvise64", + "pread64", + "pwrite64", // memory action "mmap", diff --git a/config/config_amd64.go b/cmd/runprog/config/config_amd64.go similarity index 100% rename from config/config_amd64.go rename to cmd/runprog/config/config_amd64.go diff --git a/config/config_arm.go b/cmd/runprog/config/config_arm.go similarity index 100% rename from config/config_arm.go rename to cmd/runprog/config/config_arm.go diff --git a/config/config_arm64.go b/cmd/runprog/config/config_arm64.go similarity index 100% rename from config/config_arm64.go rename to cmd/runprog/config/config_arm64.go diff --git a/config/config_loader.go b/cmd/runprog/config/config_loader.go similarity index 100% rename from config/config_loader.go rename to cmd/runprog/config/config_loader.go diff --git a/config/config_type.go b/cmd/runprog/config/config_type.go similarity index 100% rename from config/config_type.go rename to cmd/runprog/config/config_type.go diff --git a/cmd/runprog/main_linux.go b/cmd/runprog/main_linux.go index acebd8d..b281ebf 100644 --- a/cmd/runprog/main_linux.go +++ b/cmd/runprog/main_linux.go @@ -13,14 +13,13 @@ import ( "syscall" "time" - "github.com/criyle/go-sandbox/config" + "github.com/criyle/go-sandbox/cmd/runprog/config" "github.com/criyle/go-sandbox/container" "github.com/criyle/go-sandbox/pkg/cgroup" "github.com/criyle/go-sandbox/pkg/forkexec" "github.com/criyle/go-sandbox/pkg/memfd" "github.com/criyle/go-sandbox/pkg/mount" "github.com/criyle/go-sandbox/pkg/rlimit" - "github.com/criyle/go-sandbox/pkg/seccomp" "github.com/criyle/go-sandbox/pkg/seccomp/libseccomp" "github.com/criyle/go-sandbox/runner" "github.com/criyle/go-sandbox/runner/ptrace" @@ -245,9 +244,9 @@ func start() (*runner.Result, error) { } debug("rlimit: ", rlims) - actionDefault := seccomp.ActionKill + actionDefault := libseccomp.ActionKill if showDetails { - actionDefault = seccomp.ActionTrace.WithReturnCode(seccomp.MsgDisallow) + actionDefault = libseccomp.ActionTrace } if runt != "ptrace" { allow = append(allow, trace...) diff --git a/container/consts.go b/container/consts.go index 9eec142..7d2fa59 100644 --- a/container/consts.go +++ b/container/consts.go @@ -1,15 +1,15 @@ package container const ( - cmdPing = "ping" - cmdCopyIn = "copyin" - cmdOpen = "open" - cmdDelete = "delete" - cmdReset = "reset" - cmdExecve = "execve" - cmdOk = "ok" - cmdKill = "kill" - cmdConf = "conf" + cmdPing cmdType = iota + 1 + cmdCopyIn + cmdOpen + cmdDelete + cmdReset + cmdExecve + cmdOk + cmdKill + cmdConf initArg = "container_init" diff --git a/container/container_exec_linux.go b/container/container_exec_linux.go index 95637b5..67c70d1 100644 --- a/container/container_exec_linux.go +++ b/container/container_exec_linux.go @@ -92,7 +92,10 @@ func (c *containerServer) handleExecve(cmd *execCmd, msg *unixsocket.Msg) error // cannot exists now since host assumes the start will alway work err = fmt.Errorf("execve: start: %v", err) } + return c.handleExecveStarted(pid, err) +} +func (c *containerServer) handleExecveStarted(pid int, err error) error { // done is to signal kill goroutine exits killDone := make(chan struct{}) // waitDone is to signal kill goroutine to collect zombies diff --git a/container/container_init_linux.go b/container/container_init_linux.go index 124ba51..ca8bf7b 100644 --- a/container/container_init_linux.go +++ b/container/container_init_linux.go @@ -90,7 +90,7 @@ func (c *containerServer) handleCmd(cmd *cmd, msg *unixsocket.Msg) error { case cmdExecve: return c.handleExecve(cmd.ExecCmd, msg) } - return fmt.Errorf("unknown command: %s", cmd.Cmd) + return fmt.Errorf("unknown command: %v", cmd.Cmd) } func initContainer(c containerConfig) error { diff --git a/container/protocol_linux.go b/container/protocol_linux.go index b1857b2..5cb34bf 100644 --- a/container/protocol_linux.go +++ b/container/protocol_linux.go @@ -11,9 +11,11 @@ import ( "github.com/criyle/go-sandbox/runner" ) +type cmdType int + // cmd is the control message send into container type cmd struct { - Cmd string // type of the cmd + Cmd cmdType // type of the cmd OpenCmd []OpenCmd // open argument DeleteCmd *deleteCmd // delete argument diff --git a/go.mod b/go.mod index 4a33941..8c2c5b0 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,9 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/stretchr/testify v1.6.1 // indirect - golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb - golang.org/x/sys v0.0.0-20201204225414-ed752295db88 + golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect + golang.org/x/net v0.0.0-20201216054612-986b41b23924 + golang.org/x/sys v0.0.0-20201223074533-0d417f636930 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect ) - -replace github.com/elastic/go-seccomp-bpf => ../go-seccomp-bpf diff --git a/go.sum b/go.sum index 84d8cff..b3bfb93 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elastic/go-seccomp-bpf v1.1.0 h1:jUzzDc6LyCtdolZdvL/26dad6rZ9vsc7xZ2eadKECAU= +github.com/elastic/go-seccomp-bpf v1.1.0/go.mod h1:l+89Vy5BzjVcaX8USZRMOwmwwDScE+vxCFzzvQwN7T8= github.com/elastic/go-ucfg v0.7.0/go.mod h1:iaiY0NBIYeasNgycLyTvhJftQlQEUO2hpF+FX0JKxzo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -17,6 +19,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -24,16 +27,26 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY= +golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88 h1:KmZPnMocC93w341XZp26yTJg8Za7lhb2KhkYmixoeso= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo= +golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/seccomp/action.go b/pkg/seccomp/libseccomp/action.go similarity index 62% rename from pkg/seccomp/action.go rename to pkg/seccomp/libseccomp/action.go index af43579..5ddedf1 100644 --- a/pkg/seccomp/action.go +++ b/pkg/seccomp/libseccomp/action.go @@ -1,4 +1,4 @@ -package seccomp +package libseccomp // Action is seccomp trap action type Action uint32 @@ -19,16 +19,6 @@ const ( MsgHandle ) -// WithReturnCode set the return code when action is trace or ban -func (a Action) WithReturnCode(code int16) Action { - return a.Action() | Action(code)<<16 -} - -// ReturnCode get the return code -func (a Action) ReturnCode() int16 { - return int16(a >> 16) -} - // Action get the basic action func (a Action) Action() Action { return Action(a & 0xffff) diff --git a/pkg/seccomp/libseccomp/action_linux.go b/pkg/seccomp/libseccomp/action_linux.go index b6d276e..1602c37 100644 --- a/pkg/seccomp/libseccomp/action_linux.go +++ b/pkg/seccomp/libseccomp/action_linux.go @@ -1,25 +1,24 @@ package libseccomp import ( - "github.com/criyle/go-sandbox/pkg/seccomp" libseccomp "github.com/elastic/go-seccomp-bpf" ) // ToSeccompAction convert action to libseccomp compatible action -func ToSeccompAction(a seccomp.Action) libseccomp.Action { +func ToSeccompAction(a Action) libseccomp.Action { var action libseccomp.Action switch a.Action() { - case seccomp.ActionAllow: + case ActionAllow: action = libseccomp.ActionAllow - case seccomp.ActionErrno: + case ActionErrno: action = libseccomp.ActionErrno - case seccomp.ActionTrace: + case ActionTrace: action = libseccomp.ActionTrace default: action = libseccomp.ActionKillProcess } // the least 16 bit of ret value is SECCOMP_RET_DATA // although it might not officially supported by go-seccomp-bpf - action = action.WithReturnData(int(a.ReturnCode())) + // action = action.WithReturnData(int(a.ReturnCode())) return action } diff --git a/pkg/seccomp/libseccomp/builder_linux.go b/pkg/seccomp/libseccomp/builder_linux.go index 620c36a..4d0f7ee 100644 --- a/pkg/seccomp/libseccomp/builder_linux.go +++ b/pkg/seccomp/libseccomp/builder_linux.go @@ -11,10 +11,10 @@ import ( // Builder is used to build the filter type Builder struct { Allow, Trace []string - Default seccomp.Action + Default Action } -var actTrace = libseccomp.ActionTrace | libseccomp.Action(seccomp.MsgHandle) +var actTrace = libseccomp.ActionTrace // Build builds the filter func (b *Builder) Build() (seccomp.Filter, error) { diff --git a/pkg/seccomp/libseccomp/seccomp_linux_test.go b/pkg/seccomp/libseccomp/seccomp_linux_test.go index 95e5d6c..0de33b0 100644 --- a/pkg/seccomp/libseccomp/seccomp_linux_test.go +++ b/pkg/seccomp/libseccomp/seccomp_linux_test.go @@ -33,7 +33,7 @@ func BenchmarkBuildDefaultFilter(b *testing.B) { builder := Builder{ Allow: defaultSyscallAllows, Trace: defaultSyscallTraces, - Default: seccomp.ActionTrace, + Default: ActionTrace, } builder.Build() } @@ -43,7 +43,7 @@ func buildFilterMock() (seccomp.Filter, error) { b := Builder{ Allow: []string{"fork"}, Trace: []string{"execve"}, - Default: seccomp.ActionTrace, + Default: ActionTrace, } return b.Build() } diff --git a/ptracer/tracer.go b/ptracer/tracer.go index 2973b61..f997fda 100644 --- a/ptracer/tracer.go +++ b/ptracer/tracer.go @@ -8,7 +8,7 @@ type TraceAction int const ( // TraceAllow does not do anything TraceAllow TraceAction = iota - // TraceBan blocked the syscall and set the return code specified by SetReturnCode + // TraceBan skippes the syscall and set the return code specified by SetReturnCode TraceBan // TraceKill referred as dangerous action have been detected TraceKill @@ -24,14 +24,15 @@ type Tracer struct { // Runner represents the process runner type Runner interface { // Starts starts the child process and return pid and error if failed + // the child process should enable ptrace and should stop before ptrace Start() (int, error) } // Handler defines customized handler for traced syscall type Handler interface { + // Handle returns action take to the traced program Handle(*Context) TraceAction - GetSyscallName(*Context) (string, error) + // Debug prints debug information when in debug mode Debug(v ...interface{}) - HandlerDisallow(string) error } diff --git a/ptracer/tracer_track_linux.go b/ptracer/tracer_track_linux.go index d21deef..db55ef8 100644 --- a/ptracer/tracer_track_linux.go +++ b/ptracer/tracer_track_linux.go @@ -8,7 +8,6 @@ import ( unix "golang.org/x/sys/unix" - "github.com/criyle/go-sandbox/pkg/seccomp" "github.com/criyle/go-sandbox/runner" ) @@ -24,17 +23,6 @@ func (t *Tracer) Trace(c context.Context) <-chan runner.Result { // TraceRun start and traces all child process by runner in the calling goroutine // parameter done used to cancel work, start is used notify child starts func (t *Tracer) TraceRun(c context.Context) (result runner.Result) { - var ( - status = runner.StatusNormal - wstatus unix.WaitStatus // wait4 wait status - rusage unix.Rusage // wait4 rusage - traced = make(map[int]bool) // store all process that have set ptrace options - execved = false // store whether the runner process have successfully execvd - pid int // store pid of wait4 result - sTime = time.Now() // records start time for trace process - fTime time.Time // records finish time for execve - ) - // ptrace is thread based (kernel proc) runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -48,7 +36,10 @@ func (t *Tracer) TraceRun(c context.Context) (result runner.Result) { result.Error = err.Error() return } + return t.trace(c, pgid) +} +func (t *Tracer) trace(c context.Context, pgid int) (result runner.Result) { cc, cancel := context.WithCancel(c) defer cancel() @@ -58,6 +49,9 @@ func (t *Tracer) TraceRun(c context.Context) (result runner.Result) { killAll(pgid) }() + sTime := time.Now() + ph := newPtraceHandle(t, pgid) + // handler potential panic and tle // also ensure processes was well terminated defer func() { @@ -69,13 +63,21 @@ func (t *Tracer) TraceRun(c context.Context) (result runner.Result) { // kill all tracee upon return killAll(pgid) collectZombie(pgid) - result.SetUpTime = fTime.Sub(sTime) - result.RunningTime = time.Since(fTime) + if !ph.fTime.IsZero() { + result.SetUpTime = ph.fTime.Sub(sTime) + result.RunningTime = time.Since(ph.fTime) + } }() // ptrace pool loop for { - if execved { + var ( + wstatus unix.WaitStatus // wait4 wait status + rusage unix.Rusage // wait4 rusage + pid int // store pid of wait4 result + err error + ) + if ph.execved { // Wait for all child in the process group pid, err = unix.Wait4(-pgid, &wstatus, unix.WALL, &rusage) } else { @@ -94,184 +96,194 @@ func (t *Tracer) TraceRun(c context.Context) (result runner.Result) { } t.Handler.Debug("------ ", pid, " ------") + // update rusage if pid == pgid { - // update resource usage and check against limits - userTime := time.Duration(rusage.Utime.Nano()) // ns - userMem := runner.Size(rusage.Maxrss << 10) // bytes - - // check tle / mle - if userTime > t.Limit.TimeLimit { - status = runner.StatusTimeLimitExceeded - } - if userMem > t.Limit.MemoryLimit { - status = runner.StatusMemoryLimitExceeded - } - result = runner.Result{ - Status: status, - Time: userTime, - Memory: userMem, - } - if status != runner.StatusNormal { + userTime, userMem, curStatus := t.checkUsage(rusage) + result.Status = curStatus + result.Time = userTime + result.Memory = userMem + if curStatus != runner.StatusNormal { return } } - // check process status - switch { - case wstatus.Exited(): - delete(traced, pid) - t.Handler.Debug("process exited: ", pid, wstatus.ExitStatus()) - if pid == pgid { - if execved { - result.ExitStatus = wstatus.ExitStatus() - if result.ExitStatus == 0 { - result.Status = runner.StatusNormal - } else { - result.Status = runner.StatusNonzeroExitStatus - } - return - } - result.Status = runner.StatusRunnerError - result.Error = "child process exit before execve" - return - } - - case wstatus.Signaled(): - sig := wstatus.Signal() - t.Handler.Debug("ptrace signaled: ", sig) - if pid == pgid { - delete(traced, pid) - switch sig { - case unix.SIGXCPU, unix.SIGKILL: - status = runner.StatusTimeLimitExceeded - case unix.SIGXFSZ: - status = runner.StatusOutputLimitExceeded - case unix.SIGSYS: - status = runner.StatusDisallowedSyscall - default: - status = runner.StatusSignalled - } - result.Status = status - result.ExitStatus = int(sig) - return - } - unix.PtraceCont(pid, int(sig)) - - case wstatus.Stopped(): - // Set option if the process is newly forked - if !traced[pid] { - t.Handler.Debug("set ptrace option for", pid) - traced[pid] = true - // Ptrace set option valid if the tracee is stopped - err = setPtraceOption(pid) - if err != nil { - result.Status = runner.StatusRunnerError - result.Error = err.Error() - return - } - } - - // Check stop signal, if trap then check seccomp - if stopSig := wstatus.StopSignal(); stopSig == unix.SIGTRAP { - switch trapCause := wstatus.TrapCause(); trapCause { - case unix.PTRACE_EVENT_SECCOMP: - if execved { - // give the customized handle for syscall - err := t.handleTrap(pid) - if err != nil { - result.Status = runner.StatusDisallowedSyscall - result.Error = err.Error() - return - } - } else { - t.Handler.Debug("ptrace seccomp before execve (should be the execve syscall)") - } - - case unix.PTRACE_EVENT_CLONE: - t.Handler.Debug("ptrace stop clone") - case unix.PTRACE_EVENT_VFORK: - t.Handler.Debug("ptrace stop vfork") - case unix.PTRACE_EVENT_FORK: - t.Handler.Debug("ptrace stop fork") - case unix.PTRACE_EVENT_EXEC: - // forked tracee have successfully called execve - if !execved { - fTime = time.Now() - execved = true - } - t.Handler.Debug("ptrace stop exec") - - default: - t.Handler.Debug("ptrace unexpected trap cause: ", trapCause) - } - unix.PtraceCont(pid, 0) - } else { - // check if cpu rlimit hit - switch stopSig { - case unix.SIGXCPU: - status = runner.StatusTimeLimitExceeded - case unix.SIGXFSZ: - status = runner.StatusOutputLimitExceeded - } - if status != runner.StatusNormal { - result.Status = status - return - } - // Likely encountered SIGSEGV (segment violation) - // Or compiler child exited - if stopSig != unix.SIGSTOP { - t.Handler.Debug("ptrace unexpected stop signal: ", stopSig) - } - t.Handler.Debug("ptrace stopped") - unix.PtraceCont(pid, int(stopSig)) - } + status, exitStatus, errStr, finished := ph.handle(pid, wstatus) + if finished || status != runner.StatusNormal { + result.Status = status + result.ExitStatus = exitStatus + result.Error = errStr + return } } } -// handleTrap handles the seccomp trap including the custom handle -func (t *Tracer) handleTrap(pid int) error { - t.Handler.Debug("seccomp traced") - msg, err := unix.PtraceGetEventMsg(pid) - if err != nil { - t.Handler.Debug("PtraceGetEventMsg failed:", err) - return err +func (t *Tracer) checkUsage(rusage unix.Rusage) (time.Duration, runner.Size, runner.Status) { + status := runner.StatusNormal + // update resource usage and check against limits + userTime := time.Duration(rusage.Utime.Nano()) // ns + userMem := runner.Size(rusage.Maxrss << 10) // bytes + + // check tle / mle + if userTime > t.Limit.TimeLimit { + status = runner.StatusTimeLimitExceeded } - switch int16(msg) { - case seccomp.MsgDisallow: + if userMem > t.Limit.MemoryLimit { + status = runner.StatusMemoryLimitExceeded + } + return userTime, userMem, status +} + +type ptraceHandle struct { + *Tracer + pgid int + traced map[int]bool + execved bool + fTime time.Time +} + +func newPtraceHandle(t *Tracer, pgid int) *ptraceHandle { + return &ptraceHandle{t, pgid, make(map[int]bool), false, time.Time{}} +} + +func (ph *ptraceHandle) handle(pid int, wstatus unix.WaitStatus) (status runner.Status, exitStatus int, errStr string, finished bool) { + status = runner.StatusNormal + // check process status + switch { + case wstatus.Exited(): + delete(ph.traced, pid) + ph.Handler.Debug("process exited: ", pid, wstatus.ExitStatus()) + if pid == ph.pgid { + finished = true + if ph.execved { + exitStatus = wstatus.ExitStatus() + if exitStatus == 0 { + status = runner.StatusNormal + } else { + status = runner.StatusNonzeroExitStatus + } + return + } + status = runner.StatusRunnerError + errStr = "child process exit before execve" + return + } + + case wstatus.Signaled(): + sig := wstatus.Signal() + ph.Handler.Debug("ptrace signaled: ", sig) + if pid == ph.pgid { + delete(ph.traced, pid) + switch sig { + case unix.SIGXCPU, unix.SIGKILL: + status = runner.StatusTimeLimitExceeded + case unix.SIGXFSZ: + status = runner.StatusOutputLimitExceeded + case unix.SIGSYS: + status = runner.StatusDisallowedSyscall + default: + status = runner.StatusSignalled + } + exitStatus = int(sig) + return + } + unix.PtraceCont(pid, int(sig)) + + case wstatus.Stopped(): + // Set option if the process is newly forked + if !ph.traced[pid] { + ph.Handler.Debug("set ptrace option for", pid) + ph.traced[pid] = true + // Ptrace set option valid if the tracee is stopped + if err := setPtraceOption(pid); err != nil { + status = runner.StatusRunnerError + errStr = err.Error() + return + } + } + + stopSig := wstatus.StopSignal() + // Check stop signal, if trap then check seccomp + switch stopSig { + case unix.SIGTRAP: + switch trapCause := wstatus.TrapCause(); trapCause { + case unix.PTRACE_EVENT_SECCOMP: + if ph.execved { + // give the customized handle for syscall + err := ph.handleTrap(pid) + if err != nil { + status = runner.StatusDisallowedSyscall + errStr = err.Error() + return + } + } else { + ph.Handler.Debug("ptrace seccomp before execve (should be the execve syscall)") + } + + case unix.PTRACE_EVENT_CLONE: + ph.Handler.Debug("ptrace stop clone") + case unix.PTRACE_EVENT_VFORK: + ph.Handler.Debug("ptrace stop vfork") + case unix.PTRACE_EVENT_FORK: + ph.Handler.Debug("ptrace stop fork") + case unix.PTRACE_EVENT_EXEC: + // forked tracee have successfully called execve + if !ph.execved { + ph.fTime = time.Now() + ph.execved = true + } + ph.Handler.Debug("ptrace stop exec") + + default: + ph.Handler.Debug("ptrace unexpected trap cause: ", trapCause) + } + unix.PtraceCont(pid, 0) + return + + // check if cpu rlimit hit + case unix.SIGXCPU: + status = runner.StatusTimeLimitExceeded + case unix.SIGXFSZ: + status = runner.StatusOutputLimitExceeded + } + if status != runner.StatusNormal { + return + } + // Likely encountered SIGSEGV (segment violation) + // Or compiler child exited + if stopSig != unix.SIGSTOP { + ph.Handler.Debug("ptrace unexpected stop signal: ", stopSig) + } + ph.Handler.Debug("ptrace stopped") + unix.PtraceCont(pid, int(stopSig)) + } + return +} + +// handleTrap handles the seccomp trap including the custom handle +func (ph *ptraceHandle) handleTrap(pid int) error { + ph.Handler.Debug("seccomp traced") + // msg, err := unix.PtraceGetEventMsg(pid) + // if err != nil { + // t.Handler.Debug("PtraceGetEventMsg failed:", err) + // return err + // } + if ph.Handler != nil { ctx, err := getTrapContext(pid) if err != nil { - t.Handler.Debug("getTrapContext failed:", err) return err } - syscallName, err := t.Handler.GetSyscallName(ctx) - t.Handler.Debug("disallowed syscall: ", ctx.SyscallNo(), syscallName, err) - return t.Handler.HandlerDisallow(syscallName) + act := ph.Handler.Handle(ctx) - case seccomp.MsgHandle: - if t.Handler != nil { - ctx, err := getTrapContext(pid) - if err != nil { - return err - } - act := t.Handler.Handle(ctx) + switch act { + case TraceBan: + // Set the syscallno to -1 and return value into register to skip syscall. + // https://www.kernel.org/doc/Documentation/prctl/pkg/seccomp_filter.txt + return ctx.skipSyscall() - switch act { - case TraceBan: - // Set the syscallno to -1 and return value into register to skip syscall. - // https://www.kernel.org/doc/Documentation/prctl/pkg/seccomp_filter.txt - return ctx.skipSyscall() - - case TraceKill: - return runner.StatusDisallowedSyscall - } + case TraceKill: + return runner.StatusDisallowedSyscall } - - default: - // undefined seccomp message, possible set up filter wrong - t.Handler.Debug("unknown seccomp trap message: ", msg) } - return nil } diff --git a/runner/ptrace/handle_linux.go b/runner/ptrace/handle_linux.go index 295da50..cd83afc 100644 --- a/runner/ptrace/handle_linux.go +++ b/runner/ptrace/handle_linux.go @@ -8,7 +8,6 @@ import ( "github.com/criyle/go-sandbox/pkg/seccomp/libseccomp" "github.com/criyle/go-sandbox/ptracer" - "github.com/criyle/go-sandbox/runner" ) type tracerHandler struct { @@ -59,17 +58,15 @@ func (h *tracerHandler) checkStat(ctx *ptracer.Context, addr uint) ptracer.Trace } func (h *tracerHandler) Handle(ctx *ptracer.Context) ptracer.TraceAction { - var ( - action ptracer.TraceAction - syscallNo = ctx.SyscallNo() - syscallName, err = libseccomp.ToSyscallName(syscallNo) - ) - h.Debug("syscall: ", syscallNo, syscallName, err) + syscallNo := ctx.SyscallNo() + syscallName, err := libseccomp.ToSyscallName(syscallNo) + h.Debug("syscall:", syscallNo, syscallName, err) if err != nil { h.Debug("invalid syscall no") return ptracer.TraceKill } + action := ptracer.TraceKill switch syscallName { case "open": action = h.checkOpen(ctx, ctx.Arg0(), ctx.Arg1()) @@ -105,8 +102,12 @@ func (h *tracerHandler) Handle(ctx *ptracer.Context) ptracer.TraceAction { action = h.checkWrite(ctx, ctx.Arg0()) case "rename": action = h.checkWrite(ctx, ctx.Arg0()) + default: action = h.Handler.CheckSyscall(syscallName) + if h.Unsafe && action == ptracer.TraceKill { + action = ptracer.TraceBan + } } switch action { @@ -120,18 +121,6 @@ func (h *tracerHandler) Handle(ctx *ptracer.Context) ptracer.TraceAction { } } -func (h *tracerHandler) GetSyscallName(ctx *ptracer.Context) (string, error) { - syscallNo := ctx.SyscallNo() - return libseccomp.ToSyscallName(syscallNo) -} - -func (h *tracerHandler) HandlerDisallow(name string) error { - if !h.Unsafe { - return runner.StatusDisallowedSyscall - } - return nil -} - func softBanSyscall(ctx *ptracer.Context) ptracer.TraceAction { ctx.SetReturnValue(-int(BanRet)) return ptracer.TraceBan