diff --git a/.gitignore b/.gitignore index cdd35f0..9511e5a 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ package*.json example.js executor_server.h +/executor_server diff --git a/cmd/executorserver/main_darwin.go b/cmd/executorserver/main_darwin.go index d39a228..37a9b72 100644 --- a/cmd/executorserver/main_darwin.go +++ b/cmd/executorserver/main_darwin.go @@ -1,4 +1,22 @@ package main -func initEnvPool() { +import ( + "github.com/criyle/go-judge/pkg/macsandbox" + "github.com/criyle/go-judge/pkg/pool" +) + +var defaultRead = []string{ + "/", +} + +var defaultWrite = []string{ + "/tmp", + "/dev/null", + "/var/tmp", +} + +func initEnvPool() { + b := macsandbox.NewBuilder("", defaultRead, defaultWrite, *netShare) + printLog("created mac sandbox at", "") + envPool = pool.NewPool(b) } diff --git a/cmd/executorserver/worker.go b/cmd/executorserver/worker.go index 78cc24a..1086c94 100644 --- a/cmd/executorserver/worker.go +++ b/cmd/executorserver/worker.go @@ -194,10 +194,16 @@ func prepareCmd(rc cmd) (*envexec.Cmd, map[string]bool, error) { } } + timeLimit := time.Duration(rc.CPULimit) + if rc.RealCPULimit > rc.CPULimit { + timeLimit = time.Duration(rc.RealCPULimit) + } + return &envexec.Cmd{ Args: rc.Args, Env: rc.Env, Files: files, + TimeLimit: timeLimit, MemoryLimit: runner.Size(rc.MemoryLimit), ProcLimit: rc.ProcLimit, CopyIn: copyIn, diff --git a/go.mod b/go.mod index eeab5c6..9f2b136 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,13 @@ module github.com/criyle/go-judge go 1.14 require ( - github.com/criyle/go-sandbox v0.3.1 - github.com/gin-gonic/gin v1.6.2 - github.com/golang/protobuf v1.3.5 // indirect + github.com/criyle/go-sandbox v0.4.2 + github.com/gin-gonic/gin v1.6.3 + github.com/golang/protobuf v1.4.2 // indirect github.com/gorilla/websocket v1.4.2 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a - golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f - gopkg.in/yaml.v2 v2.2.8 + golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 + gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index ef42050..4b11d8a 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,14 @@ -github.com/criyle/go-sandbox v0.3.1 h1:M3xx4Gb/HZQxWfwtnkB49ypU3mnhQ94SGiKVLFthzeE= -github.com/criyle/go-sandbox v0.3.1/go.mod h1:Dn0/RUjRolP3Swt3QkxOoF8zK7q6B2bB9nB73IEGlnI= +github.com/criyle/go-sandbox v0.4.2 h1:1Ptrf+urY380hBRya5e77Iqsrk4lNK2lAk9LU00nt1s= +github.com/criyle/go-sandbox v0.4.2/go.mod h1:0RvgBkgfnuZLQv/0QUJ/No5JrHnt6u9D0iH/DS1u52I= 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/go.mod h1:l+89Vy5BzjVcaX8USZRMOwmwwDScE+vxCFzzvQwN7T8= +github.com/elastic/go-ucfg v0.7.0/go.mod h1:iaiY0NBIYeasNgycLyTvhJftQlQEUO2hpF+FX0JKxzo= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.2 h1:88crIK23zO6TqlQBt+f9FrPJNKm9ZEr7qjp9vl/d5TM= -github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= @@ -16,8 +18,17 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+ github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -35,10 +46,9 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLD github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pkg/errors v0.8.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/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= 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= @@ -48,18 +58,34 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200316214734-08c61614bede h1:+jwX0O5IlL7Cj6iam25CdV2pKzCV737/4ewDRGFvwFQ= -golang.org/x/sys v0.0.0-20200316214734-08c61614bede/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= -golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 h1:YTzHMGlqJu67/uEo1lBv0n3wBXhXNeUbB1XfN2vmTm0= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/macsandbox/builder_darwin.go b/pkg/macsandbox/builder_darwin.go new file mode 100644 index 0000000..19f4e4b --- /dev/null +++ b/pkg/macsandbox/builder_darwin.go @@ -0,0 +1,63 @@ +package macsandbox + +import ( + "io/ioutil" + "os" + + "github.com/criyle/go-judge/pkg/pool" +) + +var _ pool.EnvBuilder = &Builder{} + +// Builder to create sandbox environment +type Builder struct { + wd string + readablePath, writablePath []string + network bool +} + +// NewBuilder creates Builder to create sandbox environment +func NewBuilder(wd string, readablePath, writablePath []string, network bool) pool.EnvBuilder { + return &Builder{ + wd: wd, + readablePath: readablePath, + writablePath: writablePath, + network: network, + } +} + +// Build create a sandbox environment +func (b *Builder) Build() (pool.Environment, error) { + wd, err := ioutil.TempDir(b.wd, "es") + if err != nil { + return nil, err + } + + var rp, wp []string + rp = append(rp, b.readablePath...) + rp = append(rp, wd) + + wp = append(wp, b.writablePath...) + wp = append(wp, wd) + + p := &Profile{ + WritableDir: wp, + ReadableDir: rp, + Network: b.network, + } + profile, err := p.Build() + if err != nil { + return nil, err + } + + wdf, err := os.Open(wd) + if err != nil { + return nil, err + } + + return &environment{ + profile: profile, + wdPath: wd, + wd: wdf, + }, nil +} diff --git a/pkg/macsandbox/environment_darwin.go b/pkg/macsandbox/environment_darwin.go new file mode 100644 index 0000000..24eeb04 --- /dev/null +++ b/pkg/macsandbox/environment_darwin.go @@ -0,0 +1,172 @@ +package macsandbox + +import ( + "context" + "os" + "path" + "syscall" + "time" + + "github.com/criyle/go-judge/pkg/envexec" + "github.com/criyle/go-judge/pkg/pool" + "github.com/criyle/go-sandbox/pkg/forkexec" + "github.com/criyle/go-sandbox/runner" + "golang.org/x/sys/unix" +) + +var _ pool.Environment = &environment{} + +type environment struct { + profile string + wdPath string + wd *os.File +} + +func (e *environment) Execve(c context.Context, param envexec.ExecveParam) (envexec.Process, error) { + ch := &forkexec.Runner{ + Args: param.Args, + Env: param.Env, + Files: param.Files, + WorkDir: e.wdPath, + SandboxProfile: e.profile, + } + + pid, err := ch.Start() + if err != nil { + return nil, err + } + + p := &process{ + pid: pid, + done: make(chan struct{}), + } + + go func() { + defer close(p.done) + + var sTime, mTime, fTime time.Time + // handle cancel + ctx, cancel := context.WithCancel(c) + defer cancel() + + go func() { + <-ctx.Done() + killAll(pid) + }() + + // collect potential zombies + defer func() { + killAll(pid) + collectZombie(pid) + }() + + var ( + wstatus syscall.WaitStatus + rusage syscall.Rusage + ) + for { + _, err = syscall.Wait4(pid, &wstatus, 0, &rusage) + if err == syscall.EINTR { + continue + } + fTime = time.Now() + if err != nil { + p.result.Error = err.Error() + p.result.Status = runner.StatusRunnerError + return + } + p.result = runner.Result{ + Status: runner.StatusNormal, + Time: time.Duration(rusage.Utime.Nano()), + Memory: runner.Size(rusage.Maxrss), // seems MacOS uses bytes instead of kb + SetUpTime: mTime.Sub(sTime), + RunningTime: fTime.Sub(mTime), + } + if p.result.Time > param.Limit.Time { + p.result.Status = runner.StatusTimeLimitExceeded + } + if p.result.Memory > param.Limit.Memory { + p.result.Status = runner.StatusMemoryLimitExceeded + } + + switch { + case wstatus.Exited(): + if status := wstatus.ExitStatus(); status != 0 { + p.result.Status = runner.StatusNonzeroExitStatus + return + } + return + + case wstatus.Signaled(): + sig := wstatus.Signal() + switch sig { + case unix.SIGXCPU, unix.SIGKILL: + p.result.Status = runner.StatusTimeLimitExceeded + case unix.SIGXFSZ: + p.result.Status = runner.StatusOutputLimitExceeded + case unix.SIGSYS: + p.result.Status = runner.StatusDisallowedSyscall + default: + p.result.Status = runner.StatusSignalled + } + p.result.ExitStatus = int(sig) + return + } + } + }() + + return p, nil +} + +func (e *environment) WorkDir() *os.File { + return e.wd +} + +func (e *environment) Open(p string, flags int, perm os.FileMode) (*os.File, error) { + return os.OpenFile(path.Join(e.wdPath, p), flags, perm) +} + +func (e *environment) Destroy() error { + e.wd.Close() + return os.RemoveAll(e.wdPath) +} + +func (e *environment) Reset() error { + return removeContents(e.wdPath) +} + +// removeContents delete content of a directory +func removeContents(dir string) error { + d, err := os.Open(dir) + if err != nil { + return err + } + defer d.Close() + + names, err := d.Readdirnames(-1) + if err != nil { + return err + } + + for _, name := range names { + err = os.RemoveAll(path.Join(dir, name)) + if err != nil { + return err + } + } + return nil +} + +func killAll(pid int) { + syscall.Kill(pid, syscall.SIGKILL) +} + +// collect died child processes +func collectZombie(pgid int) { + var wstatus syscall.WaitStatus + for { + if _, err := syscall.Wait4(-pgid, &wstatus, syscall.WNOHANG, nil); err != syscall.EINTR && err != nil { + break + } + } +} diff --git a/pkg/macsandbox/process_darwin.go b/pkg/macsandbox/process_darwin.go new file mode 100644 index 0000000..53a6a70 --- /dev/null +++ b/pkg/macsandbox/process_darwin.go @@ -0,0 +1,27 @@ +package macsandbox + +import ( + "github.com/criyle/go-judge/pkg/envexec" + "github.com/criyle/go-sandbox/runner" +) + +var _ envexec.Process = &process{} + +type process struct { + pid int + done chan struct{} + result runner.Result +} + +func (p *process) Done() <-chan struct{} { + return p.done +} + +func (p *process) Result() runner.Result { + <-p.done + return p.result +} + +func (p *process) Usage() envexec.Usage { + return envexec.Usage{} +} diff --git a/pkg/macsandbox/profile_darwin.go b/pkg/macsandbox/profile_darwin.go new file mode 100644 index 0000000..5326c37 --- /dev/null +++ b/pkg/macsandbox/profile_darwin.go @@ -0,0 +1,122 @@ +package macsandbox + +import ( + "bytes" + "path/filepath" + "text/template" +) + +// Reference: https://github.com/chromium/chromium/blob/24d4eaa172c6a84974fe7cd4096a60a9b64abd9c/services/service_manager/sandbox/mac/common.sb +const sandboxTemplate = `(version 1) + +(deny default) + +; allow posix ipc +(allow ipc-posix*) + +; allow execve +(allow process-exec) + +; allow fork +(allow process-fork) + +; allow signal to self +(allow signal (target self)) + +; sysctls permitted. +(allow sysctl-read + (sysctl-name "hw.activecpu") + (sysctl-name "hw.busfrequency_compat") + (sysctl-name "hw.byteorder") + (sysctl-name "hw.cachelinesize_compat") + (sysctl-name "hw.cpufrequency_compat") + (sysctl-name "hw.cputype") + (sysctl-name "hw.logicalcpu_max") + (sysctl-name "hw.machine") + (sysctl-name "hw.ncpu") + (sysctl-name "hw.pagesize_compat") + (sysctl-name "hw.physicalcpu_max") + (sysctl-name "hw.tbfrequency_compat") + (sysctl-name "hw.vectorunit") + (sysctl-name "kern.hostname") + (sysctl-name "kern.maxfilesperproc") + (sysctl-name "kern.osrelease") + (sysctl-name "kern.ostype") + (sysctl-name "kern.osvariant_status") + (sysctl-name "kern.osversion") + (sysctl-name "kern.usrstack64") + (sysctl-name "kern.version") + (sysctl-name "sysctl.proc_cputype") + (sysctl-name "kern.proc.pid.CURRENT_PID") +) + +; allow read from dir +{{- range $dir := .ReadableDir }} +(allow file-read* (subpath "{{$dir}}")) +{{- end }} + +; deny users +(deny file-read* (subpath "/Users")) + +; allow write to dir +{{- range $dir := .WritableDir }} +(allow file-write* (subpath "{{$dir}}")) +{{- end }} + +{{- if .Network }} +(allow network-outbound) +{{- end }} +` + +var profileTpl = template.Must(template.New("profile").Parse(sandboxTemplate)) + +// Profile defines MacOS sandbox profile +type Profile struct { + WritableDir, ReadableDir []string + Network bool +} + +// DefaultProfile defines the minimun default profile to run programs +var DefaultProfile = Profile{ + ReadableDir: []string{"/usr/lib"}, +} + +// Build generates the sandbox profile +func (p *Profile) Build() (string, error) { + var buf bytes.Buffer + + realRead, err := getRealPaths(p.ReadableDir) + if err != nil { + return "", err + } + realWrite, err := getRealPaths(p.WritableDir) + if err != nil { + return "", err + } + realProfile := Profile{ + WritableDir: realWrite, + ReadableDir: realRead, + Network: p.Network, + } + + if err := profileTpl.Execute(&buf, realProfile); err != nil { + return "", err + } + return buf.String(), nil +} + +func getRealPath(s string) (string, error) { + return filepath.EvalSymlinks(s) +} + +func getRealPaths(original []string) ([]string, error) { + ret := make([]string, 0, len(original)) + for _, s := range original { + t, err := getRealPath(s) + if err != nil { + return nil, err + } + ret = append(ret, t) + } + return ret, nil +}