Refactor seccomp ptrace & improve container

This commit is contained in:
criyle 2020-12-23 12:12:43 -08:00
parent 3c77bf81d1
commit 7603109e33
21 changed files with 247 additions and 238 deletions

View File

@ -163,7 +163,7 @@ type Environment interface {
## Packages ## 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 - container: creates pre-forked container to run programs inside
- runner: interface to run program - runner: interface to run program
- ptrace: wrapper to call forkexec and ptracer - ptrace: wrapper to call forkexec and ptracer

View File

@ -37,6 +37,8 @@ var (
"ioctl", "ioctl",
"fcntl", "fcntl",
"fadvise64", "fadvise64",
"pread64",
"pwrite64",
// memory action // memory action
"mmap", "mmap",

View File

@ -13,14 +13,13 @@ import (
"syscall" "syscall"
"time" "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/container"
"github.com/criyle/go-sandbox/pkg/cgroup" "github.com/criyle/go-sandbox/pkg/cgroup"
"github.com/criyle/go-sandbox/pkg/forkexec" "github.com/criyle/go-sandbox/pkg/forkexec"
"github.com/criyle/go-sandbox/pkg/memfd" "github.com/criyle/go-sandbox/pkg/memfd"
"github.com/criyle/go-sandbox/pkg/mount" "github.com/criyle/go-sandbox/pkg/mount"
"github.com/criyle/go-sandbox/pkg/rlimit" "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/pkg/seccomp/libseccomp"
"github.com/criyle/go-sandbox/runner" "github.com/criyle/go-sandbox/runner"
"github.com/criyle/go-sandbox/runner/ptrace" "github.com/criyle/go-sandbox/runner/ptrace"
@ -245,9 +244,9 @@ func start() (*runner.Result, error) {
} }
debug("rlimit: ", rlims) debug("rlimit: ", rlims)
actionDefault := seccomp.ActionKill actionDefault := libseccomp.ActionKill
if showDetails { if showDetails {
actionDefault = seccomp.ActionTrace.WithReturnCode(seccomp.MsgDisallow) actionDefault = libseccomp.ActionTrace
} }
if runt != "ptrace" { if runt != "ptrace" {
allow = append(allow, trace...) allow = append(allow, trace...)

View File

@ -1,15 +1,15 @@
package container package container
const ( const (
cmdPing = "ping" cmdPing cmdType = iota + 1
cmdCopyIn = "copyin" cmdCopyIn
cmdOpen = "open" cmdOpen
cmdDelete = "delete" cmdDelete
cmdReset = "reset" cmdReset
cmdExecve = "execve" cmdExecve
cmdOk = "ok" cmdOk
cmdKill = "kill" cmdKill
cmdConf = "conf" cmdConf
initArg = "container_init" initArg = "container_init"

View File

@ -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 // cannot exists now since host assumes the start will alway work
err = fmt.Errorf("execve: start: %v", err) 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 // done is to signal kill goroutine exits
killDone := make(chan struct{}) killDone := make(chan struct{})
// waitDone is to signal kill goroutine to collect zombies // waitDone is to signal kill goroutine to collect zombies

View File

@ -90,7 +90,7 @@ func (c *containerServer) handleCmd(cmd *cmd, msg *unixsocket.Msg) error {
case cmdExecve: case cmdExecve:
return c.handleExecve(cmd.ExecCmd, msg) 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 { func initContainer(c containerConfig) error {

View File

@ -11,9 +11,11 @@ import (
"github.com/criyle/go-sandbox/runner" "github.com/criyle/go-sandbox/runner"
) )
type cmdType int
// cmd is the control message send into container // cmd is the control message send into container
type cmd struct { type cmd struct {
Cmd string // type of the cmd Cmd cmdType // type of the cmd
OpenCmd []OpenCmd // open argument OpenCmd []OpenCmd // open argument
DeleteCmd *deleteCmd // delete argument DeleteCmd *deleteCmd // delete argument

7
go.mod
View File

@ -8,10 +8,9 @@ require (
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.6.1 // indirect github.com/stretchr/testify v1.6.1 // indirect
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
golang.org/x/sys v0.0.0-20201204225414-ed752295db88 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/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
) )
replace github.com/elastic/go-seccomp-bpf => ../go-seccomp-bpf

13
go.sum
View File

@ -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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/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 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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= 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-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-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 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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 h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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-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-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-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-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 h1:KmZPnMocC93w341XZp26yTJg8Za7lhb2KhkYmixoeso=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -1,4 +1,4 @@
package seccomp package libseccomp
// Action is seccomp trap action // Action is seccomp trap action
type Action uint32 type Action uint32
@ -19,16 +19,6 @@ const (
MsgHandle 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 // Action get the basic action
func (a Action) Action() Action { func (a Action) Action() Action {
return Action(a & 0xffff) return Action(a & 0xffff)

View File

@ -1,25 +1,24 @@
package libseccomp package libseccomp
import ( import (
"github.com/criyle/go-sandbox/pkg/seccomp"
libseccomp "github.com/elastic/go-seccomp-bpf" libseccomp "github.com/elastic/go-seccomp-bpf"
) )
// ToSeccompAction convert action to libseccomp compatible action // ToSeccompAction convert action to libseccomp compatible action
func ToSeccompAction(a seccomp.Action) libseccomp.Action { func ToSeccompAction(a Action) libseccomp.Action {
var action libseccomp.Action var action libseccomp.Action
switch a.Action() { switch a.Action() {
case seccomp.ActionAllow: case ActionAllow:
action = libseccomp.ActionAllow action = libseccomp.ActionAllow
case seccomp.ActionErrno: case ActionErrno:
action = libseccomp.ActionErrno action = libseccomp.ActionErrno
case seccomp.ActionTrace: case ActionTrace:
action = libseccomp.ActionTrace action = libseccomp.ActionTrace
default: default:
action = libseccomp.ActionKillProcess action = libseccomp.ActionKillProcess
} }
// the least 16 bit of ret value is SECCOMP_RET_DATA // the least 16 bit of ret value is SECCOMP_RET_DATA
// although it might not officially supported by go-seccomp-bpf // although it might not officially supported by go-seccomp-bpf
action = action.WithReturnData(int(a.ReturnCode())) // action = action.WithReturnData(int(a.ReturnCode()))
return action return action
} }

View File

@ -11,10 +11,10 @@ import (
// Builder is used to build the filter // Builder is used to build the filter
type Builder struct { type Builder struct {
Allow, Trace []string Allow, Trace []string
Default seccomp.Action Default Action
} }
var actTrace = libseccomp.ActionTrace | libseccomp.Action(seccomp.MsgHandle) var actTrace = libseccomp.ActionTrace
// Build builds the filter // Build builds the filter
func (b *Builder) Build() (seccomp.Filter, error) { func (b *Builder) Build() (seccomp.Filter, error) {

View File

@ -33,7 +33,7 @@ func BenchmarkBuildDefaultFilter(b *testing.B) {
builder := Builder{ builder := Builder{
Allow: defaultSyscallAllows, Allow: defaultSyscallAllows,
Trace: defaultSyscallTraces, Trace: defaultSyscallTraces,
Default: seccomp.ActionTrace, Default: ActionTrace,
} }
builder.Build() builder.Build()
} }
@ -43,7 +43,7 @@ func buildFilterMock() (seccomp.Filter, error) {
b := Builder{ b := Builder{
Allow: []string{"fork"}, Allow: []string{"fork"},
Trace: []string{"execve"}, Trace: []string{"execve"},
Default: seccomp.ActionTrace, Default: ActionTrace,
} }
return b.Build() return b.Build()
} }

View File

@ -8,7 +8,7 @@ type TraceAction int
const ( const (
// TraceAllow does not do anything // TraceAllow does not do anything
TraceAllow TraceAction = iota 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 TraceBan
// TraceKill referred as dangerous action have been detected // TraceKill referred as dangerous action have been detected
TraceKill TraceKill
@ -24,14 +24,15 @@ type Tracer struct {
// Runner represents the process runner // Runner represents the process runner
type Runner interface { type Runner interface {
// Starts starts the child process and return pid and error if failed // 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) Start() (int, error)
} }
// Handler defines customized handler for traced syscall // Handler defines customized handler for traced syscall
type Handler interface { type Handler interface {
// Handle returns action take to the traced program
Handle(*Context) TraceAction Handle(*Context) TraceAction
GetSyscallName(*Context) (string, error)
// Debug prints debug information when in debug mode
Debug(v ...interface{}) Debug(v ...interface{})
HandlerDisallow(string) error
} }

View File

@ -8,7 +8,6 @@ import (
unix "golang.org/x/sys/unix" unix "golang.org/x/sys/unix"
"github.com/criyle/go-sandbox/pkg/seccomp"
"github.com/criyle/go-sandbox/runner" "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 // 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 // parameter done used to cancel work, start is used notify child starts
func (t *Tracer) TraceRun(c context.Context) (result runner.Result) { 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) // ptrace is thread based (kernel proc)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
@ -48,7 +36,10 @@ func (t *Tracer) TraceRun(c context.Context) (result runner.Result) {
result.Error = err.Error() result.Error = err.Error()
return return
} }
return t.trace(c, pgid)
}
func (t *Tracer) trace(c context.Context, pgid int) (result runner.Result) {
cc, cancel := context.WithCancel(c) cc, cancel := context.WithCancel(c)
defer cancel() defer cancel()
@ -58,6 +49,9 @@ func (t *Tracer) TraceRun(c context.Context) (result runner.Result) {
killAll(pgid) killAll(pgid)
}() }()
sTime := time.Now()
ph := newPtraceHandle(t, pgid)
// handler potential panic and tle // handler potential panic and tle
// also ensure processes was well terminated // also ensure processes was well terminated
defer func() { defer func() {
@ -69,13 +63,21 @@ func (t *Tracer) TraceRun(c context.Context) (result runner.Result) {
// kill all tracee upon return // kill all tracee upon return
killAll(pgid) killAll(pgid)
collectZombie(pgid) collectZombie(pgid)
result.SetUpTime = fTime.Sub(sTime) if !ph.fTime.IsZero() {
result.RunningTime = time.Since(fTime) result.SetUpTime = ph.fTime.Sub(sTime)
result.RunningTime = time.Since(ph.fTime)
}
}() }()
// ptrace pool loop // ptrace pool loop
for { 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 // Wait for all child in the process group
pid, err = unix.Wait4(-pgid, &wstatus, unix.WALL, &rusage) pid, err = unix.Wait4(-pgid, &wstatus, unix.WALL, &rusage)
} else { } else {
@ -94,184 +96,194 @@ func (t *Tracer) TraceRun(c context.Context) (result runner.Result) {
} }
t.Handler.Debug("------ ", pid, " ------") t.Handler.Debug("------ ", pid, " ------")
// update rusage
if pid == pgid { if pid == pgid {
// update resource usage and check against limits userTime, userMem, curStatus := t.checkUsage(rusage)
userTime := time.Duration(rusage.Utime.Nano()) // ns result.Status = curStatus
userMem := runner.Size(rusage.Maxrss << 10) // bytes result.Time = userTime
result.Memory = userMem
// check tle / mle if curStatus != runner.StatusNormal {
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 {
return return
} }
} }
// check process status status, exitStatus, errStr, finished := ph.handle(pid, wstatus)
switch { if finished || status != runner.StatusNormal {
case wstatus.Exited(): result.Status = status
delete(traced, pid) result.ExitStatus = exitStatus
t.Handler.Debug("process exited: ", pid, wstatus.ExitStatus()) result.Error = errStr
if pid == pgid { return
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))
}
} }
} }
} }
// handleTrap handles the seccomp trap including the custom handle func (t *Tracer) checkUsage(rusage unix.Rusage) (time.Duration, runner.Size, runner.Status) {
func (t *Tracer) handleTrap(pid int) error { status := runner.StatusNormal
t.Handler.Debug("seccomp traced") // update resource usage and check against limits
msg, err := unix.PtraceGetEventMsg(pid) userTime := time.Duration(rusage.Utime.Nano()) // ns
if err != nil { userMem := runner.Size(rusage.Maxrss << 10) // bytes
t.Handler.Debug("PtraceGetEventMsg failed:", err)
return err // check tle / mle
if userTime > t.Limit.TimeLimit {
status = runner.StatusTimeLimitExceeded
} }
switch int16(msg) { if userMem > t.Limit.MemoryLimit {
case seccomp.MsgDisallow: 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) ctx, err := getTrapContext(pid)
if err != nil { if err != nil {
t.Handler.Debug("getTrapContext failed:", err)
return err return err
} }
syscallName, err := t.Handler.GetSyscallName(ctx) act := ph.Handler.Handle(ctx)
t.Handler.Debug("disallowed syscall: ", ctx.SyscallNo(), syscallName, err)
return t.Handler.HandlerDisallow(syscallName)
case seccomp.MsgHandle: switch act {
if t.Handler != nil { case TraceBan:
ctx, err := getTrapContext(pid) // Set the syscallno to -1 and return value into register to skip syscall.
if err != nil { // https://www.kernel.org/doc/Documentation/prctl/pkg/seccomp_filter.txt
return err return ctx.skipSyscall()
}
act := t.Handler.Handle(ctx)
switch act { case TraceKill:
case TraceBan: return runner.StatusDisallowedSyscall
// 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
}
} }
default:
// undefined seccomp message, possible set up filter wrong
t.Handler.Debug("unknown seccomp trap message: ", msg)
} }
return nil return nil
} }

View File

@ -8,7 +8,6 @@ import (
"github.com/criyle/go-sandbox/pkg/seccomp/libseccomp" "github.com/criyle/go-sandbox/pkg/seccomp/libseccomp"
"github.com/criyle/go-sandbox/ptracer" "github.com/criyle/go-sandbox/ptracer"
"github.com/criyle/go-sandbox/runner"
) )
type tracerHandler struct { 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 { func (h *tracerHandler) Handle(ctx *ptracer.Context) ptracer.TraceAction {
var ( syscallNo := ctx.SyscallNo()
action ptracer.TraceAction syscallName, err := libseccomp.ToSyscallName(syscallNo)
syscallNo = ctx.SyscallNo() h.Debug("syscall:", syscallNo, syscallName, err)
syscallName, err = libseccomp.ToSyscallName(syscallNo)
)
h.Debug("syscall: ", syscallNo, syscallName, err)
if err != nil { if err != nil {
h.Debug("invalid syscall no") h.Debug("invalid syscall no")
return ptracer.TraceKill return ptracer.TraceKill
} }
action := ptracer.TraceKill
switch syscallName { switch syscallName {
case "open": case "open":
action = h.checkOpen(ctx, ctx.Arg0(), ctx.Arg1()) 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()) action = h.checkWrite(ctx, ctx.Arg0())
case "rename": case "rename":
action = h.checkWrite(ctx, ctx.Arg0()) action = h.checkWrite(ctx, ctx.Arg0())
default: default:
action = h.Handler.CheckSyscall(syscallName) action = h.Handler.CheckSyscall(syscallName)
if h.Unsafe && action == ptracer.TraceKill {
action = ptracer.TraceBan
}
} }
switch action { 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 { func softBanSyscall(ctx *ptracer.Context) ptracer.TraceAction {
ctx.SetReturnValue(-int(BanRet)) ctx.SetReturnValue(-int(BanRet))
return ptracer.TraceBan return ptracer.TraceBan