normalize naming schema

This commit is contained in:
criyle 2019-08-30 00:57:07 -07:00
parent ebab7514d1
commit 7b06ce65ac
38 changed files with 269 additions and 258 deletions

View File

@ -54,21 +54,21 @@ Default file access syscall check:
## Packages ## Packages
- tracer: ptrace tracer and provides syscall trap filter context
- deamon: creates pre-forked container to run programs inside - deamon: creates pre-forked container to run programs inside
- runprogram: wrapper to call forkexec and trecer - runner: interface to run program
- rununshared: wrapper to call forkexec and unshared namespaces - config: defines arch & language specified trace condition for seccomp and ptrace
- runconfig: defines arch & language specified trace condition for seccomp and ptrace - ptrace: wrapper to call forkexec and ptracer
- types: general runtime specs - unshare: wrapper to call forkexec and unshared namespaces
- specs: provides general res / result data structures - ptracer: ptrace tracer and provides syscall trap filter context
- types: provides general res / result data structures
## Executable ## Executable
- run_program: safely run program by unshare / ptrace / pre-forked containers - runprog: safely run program by unshare / ptrace / pre-forked containers
## Configurations ## Configurations
- run_program/config.go: all configs toward running specs - run/config/config.go: all configs toward running specs
## Benchmarks (docker desktop amd64 / native arm64) ## Benchmarks (docker desktop amd64 / native arm64)

View File

@ -12,10 +12,11 @@ import (
"github.com/criyle/go-sandbox/pkg/cgroup" "github.com/criyle/go-sandbox/pkg/cgroup"
"github.com/criyle/go-sandbox/pkg/memfd" "github.com/criyle/go-sandbox/pkg/memfd"
"github.com/criyle/go-sandbox/pkg/rlimit" "github.com/criyle/go-sandbox/pkg/rlimit"
"github.com/criyle/go-sandbox/runconfig" "github.com/criyle/go-sandbox/runner"
"github.com/criyle/go-sandbox/runprogram" "github.com/criyle/go-sandbox/runner/config"
"github.com/criyle/go-sandbox/rununshared" "github.com/criyle/go-sandbox/runner/ptrace"
"github.com/criyle/go-sandbox/types/specs" "github.com/criyle/go-sandbox/runner/unshare"
"github.com/criyle/go-sandbox/types"
) )
const ( const (
@ -33,11 +34,6 @@ var (
args []string args []string
) )
// Runner can be ptraced runner or namespaced runner
type Runner interface {
Start(<-chan struct{}) (<-chan specs.TraceResult, error)
}
func printUsage() { func printUsage() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] <args>\n", os.Args[0]) fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] <args>\n", os.Args[0])
flag.PrintDefaults() flag.PrintDefaults()
@ -104,28 +100,28 @@ func main() {
defer f.Close() defer f.Close()
} }
rt, err := run() rt, err := start()
if rt == nil { if rt == nil {
rt = &specs.TraceResult{ rt = &types.Result{
TraceStatus: specs.TraceCodeFatal, Status: types.StatusFatal,
} }
} }
if err == nil && rt.TraceStatus != specs.TraceCodeNormal { if err == nil && rt.Status != types.StatusNormal {
err = rt.TraceStatus err = rt.Status
} }
if err != nil { if err != nil {
debug(err) debug(err)
c, ok := err.(specs.TraceCode) c, ok := err.(types.Status)
if !ok { if !ok {
c = specs.TraceCodeFatal c = types.StatusFatal
} }
// Handle fatal error from trace // Handle fatal error from trace
fmt.Fprintf(f, "%d %d %d %d\n", int(c), rt.UserTime, rt.UserMem, rt.ExitCode) fmt.Fprintf(f, "%d %d %d %d\n", int(c), rt.UserTime, rt.UserMem, rt.ExitStatus)
if c == specs.TraceCodeFatal { if c == types.StatusFatal {
os.Exit(1) os.Exit(1)
} }
} else { } else {
fmt.Fprintf(f, "%d %d %d %d\n", 0, rt.UserTime, rt.UserMem, rt.ExitCode) fmt.Fprintf(f, "%d %d %d %d\n", 0, rt.UserTime, rt.UserMem, rt.ExitStatus)
} }
} }
@ -134,22 +130,22 @@ type deamonRunner struct {
*deamon.ExecveParam *deamon.ExecveParam
} }
func (r *deamonRunner) Start(done <-chan struct{}) (<-chan specs.TraceResult, error) { func (r *deamonRunner) Start(done <-chan struct{}) (<-chan types.Result, error) {
return r.Master.Execve(done, r.ExecveParam) return r.Master.Execve(done, r.ExecveParam)
} }
func run() (*specs.TraceResult, error) { func start() (*types.Result, error) {
var ( var (
runner Runner runner runner.Runner
cg *cgroup.CGroup cg *cgroup.CGroup
err error err error
execFile uintptr execFile uintptr
rt specs.TraceResult rt types.Result
) )
addRead := runconfig.GetExtraSet(addReadable, addRawReadable) addRead := config.GetExtraSet(addReadable, addRawReadable)
addWrite := runconfig.GetExtraSet(addWritable, addRawWritable) addWrite := config.GetExtraSet(addWritable, addRawWritable)
h := runconfig.GetConf(pType, workPath, args, addRead, addWrite, allowProc, showDetails) h := config.GetConf(pType, workPath, args, addRead, addWrite, allowProc, showDetails)
if useCGroup { if useCGroup {
cg, err = cgroup.NewCGroup("run_program") cg, err = cgroup.NewCGroup("run_program")
@ -245,21 +241,20 @@ func run() (*specs.TraceResult, error) {
} }
defer os.RemoveAll(root) defer os.RemoveAll(root)
runner = &rununshared.RunUnshared{ runner = &unshare.Runner{
Args: h.Args, Args: h.Args,
Env: []string{pathEnv}, Env: []string{pathEnv},
ExecFile: execFile, ExecFile: execFile,
WorkDir: "/w", WorkDir: "/w",
Files: fds, Files: fds,
RLimits: rlims, RLimits: rlims,
ResLimits: specs.ResLimit{ ResLimits: types.Limit{
TimeLimit: timeLimit * 1e3, TimeLimit: timeLimit * 1e3,
RealTimeLimit: realTimeLimit * 1e3, MemoryLimit: memoryLimit << 10,
MemoryLimit: memoryLimit << 10,
}, },
SyscallAllowed: h.SyscallAllow, SyscallAllowed: h.SyscallAllow,
Root: root, Root: root,
Mounts: rununshared.GetDefaultMounts(root, []rununshared.AddBind{ Mounts: unshare.GetDefaultMounts(root, []unshare.AddBind{
{ {
Source: workPath, Source: workPath,
Target: "w", Target: "w",
@ -271,16 +266,15 @@ func run() (*specs.TraceResult, error) {
DomainName: "run_program", DomainName: "run_program",
} }
} else { } else {
runner = &runprogram.RunProgram{ runner = &ptrace.Runner{
Args: h.Args, Args: h.Args,
Env: []string{pathEnv}, Env: []string{pathEnv},
ExecFile: execFile, ExecFile: execFile,
WorkDir: workPath, WorkDir: workPath,
RLimits: rlims, RLimits: rlims,
TraceLimit: specs.ResLimit{ TraceLimit: types.Limit{
TimeLimit: timeLimit * 1e3, TimeLimit: timeLimit * 1e3,
RealTimeLimit: realTimeLimit * 1e3, MemoryLimit: memoryLimit << 10,
MemoryLimit: memoryLimit << 10,
}, },
Files: fds, Files: fds,
SyscallAllowed: h.SyscallAllow, SyscallAllowed: h.SyscallAllow,
@ -309,7 +303,7 @@ func run() (*specs.TraceResult, error) {
case <-sig: case <-sig:
close(done) close(done)
rt = <-s rt = <-s
rt.TraceStatus = specs.TraceCodeFatal rt.Status = types.StatusFatal
case <-tC: case <-tC:
close(done) close(done)
@ -320,8 +314,8 @@ func run() (*specs.TraceResult, error) {
eTime := time.Now() eTime := time.Now()
if rt.SetUpTime == 0 { if rt.SetUpTime == 0 {
rt.SetUpTime = int64(rTime.Sub(sTime)) rt.SetUpTime = rTime.Sub(sTime)
rt.RunningTime = int64(eTime.Sub(rTime)) rt.RunningTime = eTime.Sub(rTime)
} }
debug("results:", rt, err) debug("results:", rt, err)

View File

@ -10,7 +10,7 @@ import (
"github.com/criyle/go-sandbox/pkg/forkexec" "github.com/criyle/go-sandbox/pkg/forkexec"
"github.com/criyle/go-sandbox/pkg/unixsocket" "github.com/criyle/go-sandbox/pkg/unixsocket"
"github.com/criyle/go-sandbox/types/specs" "github.com/criyle/go-sandbox/types"
) )
// ContainerInit is called for container init process // ContainerInit is called for container init process
@ -231,19 +231,19 @@ loop:
break loop break loop
case wstatus.Signaled(): case wstatus.Signaled():
var status specs.TraceCode var status types.Status
switch wstatus.Signal() { switch wstatus.Signal() {
// kill signal treats as TLE // kill signal treats as TLE
case syscall.SIGXCPU, syscall.SIGKILL: case syscall.SIGXCPU, syscall.SIGKILL:
status = specs.TraceCodeTLE status = types.StatusTLE
case syscall.SIGXFSZ: case syscall.SIGXFSZ:
status = specs.TraceCodeOLE status = types.StatusOLE
case syscall.SIGSYS: case syscall.SIGSYS:
status = specs.TraceCodeBan status = types.StatusBan
default: default:
status = specs.TraceCodeRE status = types.StatusRE
} }
reply := Reply{TraceStatus: status} reply := Reply{Status: status}
sendReply(s, &reply, nil) sendReply(s, &reply, nil)
break loop break loop
} }

View File

@ -35,7 +35,7 @@ Any socket related error will cause the deamon exit (with all process inside con
import ( import (
"github.com/criyle/go-sandbox/pkg/rlimit" "github.com/criyle/go-sandbox/pkg/rlimit"
"github.com/criyle/go-sandbox/types/specs" "github.com/criyle/go-sandbox/types"
) )
// Cmd is the control message send into deamon // Cmd is the control message send into deamon
@ -50,7 +50,7 @@ type Cmd struct {
// Reply is the reply message send back to controller // Reply is the reply message send back to controller
type Reply struct { type Reply struct {
Error string // empty if no error Error string // empty if no error
ExitStatus int // waitpid exit status ExitStatus int // waitpid exit status
TraceStatus specs.TraceCode // TraceCode Status types.Status // return status
} }

View File

@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"github.com/criyle/go-sandbox/pkg/rlimit" "github.com/criyle/go-sandbox/pkg/rlimit"
"github.com/criyle/go-sandbox/types/specs"
"github.com/criyle/go-sandbox/pkg/unixsocket" "github.com/criyle/go-sandbox/pkg/unixsocket"
"github.com/criyle/go-sandbox/types"
) )
// ExecveParam is parameters to run process inside container // ExecveParam is parameters to run process inside container
@ -23,7 +23,7 @@ type ExecveParam struct {
// Execve runs process inside container // Execve runs process inside container
// accepts done for cancelation // accepts done for cancelation
func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan specs.TraceResult, error) { func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan types.Result, error) {
var files []int var files []int
if param.ExecFile > 0 { if param.ExecFile > 0 {
files = append(files, int(param.ExecFile)) files = append(files, int(param.ExecFile))
@ -60,16 +60,16 @@ func (m *Master) Execve(done <-chan struct{}, param *ExecveParam) (<-chan specs.
return nil, fmt.Errorf("execve: ok failed(%v)", err) return nil, fmt.Errorf("execve: ok failed(%v)", err)
} }
// make sure goroutine not leaked (blocked) even if result is not consumed // make sure goroutine not leaked (blocked) even if result is not consumed
wait := make(chan specs.TraceResult, 1) wait := make(chan types.Result, 1)
waitDone := make(chan struct{}) waitDone := make(chan struct{})
// Wait // Wait
go func() { go func() {
defer close(wait) defer close(wait)
reply2, _, _ := m.recvReply() reply2, _, _ := m.recvReply()
close(waitDone) close(waitDone)
wait <- specs.TraceResult{ wait <- types.Result{
ExitCode: reply2.ExitStatus, ExitStatus: reply2.ExitStatus,
TraceStatus: reply2.TraceStatus, Status: reply2.Status,
} }
// done signal (should recv after kill) // done signal (should recv after kill)
m.recvReply() m.recvReply()

2
go.mod
View File

@ -4,5 +4,5 @@ go 1.12
require ( require (
github.com/seccomp/libseccomp-golang v0.9.1 github.com/seccomp/libseccomp-golang v0.9.1
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 golang.org/x/sys v0.0.0-20190830023255-19e00faab6ad
) )

4
go.sum
View File

@ -1,4 +1,4 @@
github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo= 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/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= golang.org/x/sys v0.0.0-20190830023255-19e00faab6ad h1:cCejgArrk10gX6kFqjWeLwXD7aVMqWoRpyUCaaJSggc=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190830023255-19e00faab6ad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -9,7 +9,7 @@ import (
"github.com/criyle/go-sandbox/pkg/rlimit" "github.com/criyle/go-sandbox/pkg/rlimit"
) )
// Runner is the RunProgramConfig including the exec path, argv // Runner is the runptraceConfig including the exec path, argv
// and resource limits. It creates tracee for ptrace-based tracer. // and resource limits. It creates tracee for ptrace-based tracer.
// It can also create unshared process in another namespace // It can also create unshared process in another namespace
type Runner struct { type Runner struct {

View File

@ -1,4 +1,4 @@
package tracer package ptracer
import ( import (
"os" "os"

View File

@ -1,4 +1,4 @@
package tracer package ptracer
import ( import (
"syscall" "syscall"

View File

@ -1,4 +1,4 @@
package tracer package ptracer
import ( import (
"syscall" "syscall"

View File

@ -1,4 +1,4 @@
package tracer package ptracer
import ( import (
unix "golang.org/x/sys/unix" unix "golang.org/x/sys/unix"

View File

@ -1,4 +1,4 @@
package tracer package ptracer
import ( import (
"syscall" "syscall"

View File

@ -1,4 +1,4 @@
package tracer package ptracer
import ( import (
"fmt" "fmt"

View File

@ -1,4 +1,4 @@
package tracer package ptracer
import ( import (
"syscall" "syscall"

View File

@ -1,4 +1,4 @@
package tracer package ptracer
// TraceAction defines the action returned by TraceHandle // TraceAction defines the action returned by TraceHandle
type TraceAction int type TraceAction int

View File

@ -1,11 +1,12 @@
package tracer package ptracer
import ( import (
"runtime" "runtime"
"time" "time"
"github.com/criyle/go-sandbox/types/specs"
unix "golang.org/x/sys/unix" unix "golang.org/x/sys/unix"
"github.com/criyle/go-sandbox/types"
) )
// MsgDisallow, Msghandle defines the action needed when traped by // MsgDisallow, Msghandle defines the action needed when traped by
@ -16,9 +17,9 @@ const (
) )
// Trace starts new goroutine and trace runner with ptrace // Trace starts new goroutine and trace runner with ptrace
func Trace(done <-chan struct{}, handler Handler, runner Runner, limits specs.ResLimit) (<-chan specs.TraceResult, error) { func Trace(done <-chan struct{}, handler Handler, runner Runner, limits types.Limit) (<-chan types.Result, error) {
var err error var err error
result := make(chan specs.TraceResult, 1) result := make(chan types.Result, 1)
start := make(chan struct{}) start := make(chan struct{})
finish := make(chan struct{}) finish := make(chan struct{})
@ -40,7 +41,7 @@ func Trace(done <-chan struct{}, handler Handler, runner Runner, limits specs.Re
// 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 TraceRun(done <-chan struct{}, start chan<- struct{}, func TraceRun(done <-chan struct{}, start chan<- struct{},
handler Handler, runner Runner, limits specs.ResLimit) (result specs.TraceResult, err error) { handler Handler, runner Runner, limits types.Limit) (result types.Result, 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
@ -61,7 +62,7 @@ func TraceRun(done <-chan struct{}, start chan<- struct{},
handler.Debug("tracer started: ", pgid, err) handler.Debug("tracer started: ", pgid, err)
if err != nil { if err != nil {
handler.Debug("start tracee failed: ", err) handler.Debug("start tracee failed: ", err)
result.TraceStatus = specs.TraceCodeRE result.Status = types.StatusRE
return result, err return result, err
} }
@ -83,17 +84,17 @@ func TraceRun(done <-chan struct{}, start chan<- struct{},
// also ensure processes was well terminated // also ensure processes was well terminated
defer func() { defer func() {
if tle { if tle {
err = specs.TraceCodeTLE err = types.StatusTLE
} }
if err2 := recover(); err2 != nil { if err2 := recover(); err2 != nil {
handler.Debug(err2) handler.Debug(err2)
err = specs.TraceCodeFatal err = types.StatusFatal
} }
// kill all tracee upon return // kill all tracee upon return
killAll(pgid) killAll(pgid)
collectZombie(pgid) collectZombie(pgid)
result.SetUpTime = fTime.Sub(sTime).Nanoseconds() result.SetUpTime = fTime.Sub(sTime)
result.RunningTime = time.Since(fTime).Nanoseconds() result.RunningTime = time.Since(fTime)
}() }()
// trace unixs // trace unixs
@ -107,11 +108,11 @@ func TraceRun(done <-chan struct{}, start chan<- struct{},
} }
if err != nil { if err != nil {
handler.Debug("wait4 failed: ", err) handler.Debug("wait4 failed: ", err)
return result, specs.TraceCodeFatal return result, types.StatusFatal
} }
handler.Debug("------ ", pid, " ------") handler.Debug("------ ", pid, " ------")
status := specs.TraceCodeNormal status := types.StatusNormal
if pid == pgid { if pid == pgid {
// update resource usage and check against limits // update resource usage and check against limits
userTime := uint64(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms userTime := uint64(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms
@ -119,17 +120,17 @@ func TraceRun(done <-chan struct{}, start chan<- struct{},
// check tle / mle // check tle / mle
if userTime > limits.TimeLimit { if userTime > limits.TimeLimit {
status = specs.TraceCodeTLE status = types.StatusTLE
} }
if userMem > limits.MemoryLimit { if userMem > limits.MemoryLimit {
status = specs.TraceCodeMLE status = types.StatusMLE
} }
result = specs.TraceResult{ result = types.Result{
UserTime: userTime, Status: status,
UserMem: userMem, UserTime: userTime,
TraceStatus: status, UserMem: userMem,
} }
if status != specs.TraceCodeNormal { if status != types.StatusNormal {
return result, status return result, status
} }
} }
@ -141,11 +142,11 @@ func TraceRun(done <-chan struct{}, start chan<- struct{},
handler.Debug("process exited: ", pid, wstatus.ExitStatus()) handler.Debug("process exited: ", pid, wstatus.ExitStatus())
if pid == pgid { if pid == pgid {
if execved { if execved {
result.ExitCode = wstatus.ExitStatus() result.ExitStatus = wstatus.ExitStatus()
return result, nil return result, nil
} }
result.TraceStatus = specs.TraceCodeFatal result.Status = types.StatusFatal
return result, specs.TraceCodeFatal return result, types.StatusFatal
} }
case wstatus.Signaled(): case wstatus.Signaled():
@ -155,15 +156,15 @@ func TraceRun(done <-chan struct{}, start chan<- struct{},
delete(traced, pid) delete(traced, pid)
switch sig { switch sig {
case unix.SIGXCPU, unix.SIGKILL: case unix.SIGXCPU, unix.SIGKILL:
status = specs.TraceCodeTLE status = types.StatusTLE
case unix.SIGXFSZ: case unix.SIGXFSZ:
status = specs.TraceCodeOLE status = types.StatusOLE
case unix.SIGSYS: case unix.SIGSYS:
status = specs.TraceCodeBan status = types.StatusBan
default: default:
status = specs.TraceCodeRE status = types.StatusRE
} }
result.TraceStatus = status result.Status = status
return result, status return result, status
} }
unix.PtraceCont(pid, int(sig)) unix.PtraceCont(pid, int(sig))
@ -176,7 +177,7 @@ func TraceRun(done <-chan struct{}, start chan<- struct{},
// 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 {
result.TraceStatus = specs.TraceCodeFatal result.Status = types.StatusFatal
return result, err return result, err
} }
} }
@ -189,7 +190,7 @@ func TraceRun(done <-chan struct{}, start chan<- struct{},
// 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 {
result.TraceStatus = specs.TraceCodeBan result.Status = types.StatusBan
return result, err return result, err
} }
} else { } else {
@ -218,12 +219,12 @@ func TraceRun(done <-chan struct{}, start chan<- struct{},
// check if cpu rlimit hit // check if cpu rlimit hit
switch stopSig { switch stopSig {
case unix.SIGXCPU: case unix.SIGXCPU:
status = specs.TraceCodeTLE status = types.StatusTLE
case unix.SIGXFSZ: case unix.SIGXFSZ:
status = specs.TraceCodeOLE status = types.StatusOLE
} }
if status != specs.TraceCodeNormal { if status != types.StatusNormal {
result.TraceStatus = status result.Status = status
return result, status return result, status
} }
// Likely encountered SIGSEGV (segment violation) // Likely encountered SIGSEGV (segment violation)
@ -272,7 +273,7 @@ func handleTrap(handler Handler, pid int) error {
return ctx.skipSyscall() return ctx.skipSyscall()
case TraceKill: case TraceKill:
return specs.TraceCodeBan return types.StatusBan
} }
} }

View File

@ -1,4 +1,4 @@
package runconfig package config
// This file includes configs for the run program settings // This file includes configs for the run program settings
@ -105,7 +105,7 @@ var (
// config for different type of program // config for different type of program
// workpath and arg0 have additional read / stat permission // workpath and arg0 have additional read / stat permission
runprogramConfig = map[string]ProgramConfig{ runptraceConfig = map[string]ProgramConfig{
"python2.7": ProgramConfig{ "python2.7": ProgramConfig{
Syscall: SyscallConfig{ Syscall: SyscallConfig{
ExtraAllow: []string{ ExtraAllow: []string{

View File

@ -1,4 +1,4 @@
package runconfig package config
// This file includes configs for the run program settings // This file includes configs for the run program settings

View File

@ -1,4 +1,4 @@
package runconfig package config
// This file includes configs for the run program settings // This file includes configs for the run program settings

View File

@ -1,4 +1,4 @@
package runconfig package config
// This file includes configs for the run program settings // This file includes configs for the run program settings

View File

@ -1,4 +1,4 @@
package runconfig package config
// GetConf return file access check set, syscall counter, allow and traced syscall arrays and new args // GetConf return file access check set, syscall counter, allow and traced syscall arrays and new args
func GetConf(pType, workPath string, args, addRead, addWrite []string, allowProc, showDetails bool) *Handler { func GetConf(pType, workPath string, args, addRead, addWrite []string, allowProc, showDetails bool) *Handler {
@ -18,7 +18,7 @@ func GetConf(pType, workPath string, args, addRead, addWrite []string, allowProc
fs.Readable.AddRange(addRead, workPath) fs.Readable.AddRange(addRead, workPath)
fs.Writable.AddRange(addWrite, workPath) fs.Writable.AddRange(addWrite, workPath)
if c, o := runprogramConfig[pType]; o { if c, o := runptraceConfig[pType]; o {
allow = append(allow, c.Syscall.ExtraAllow...) allow = append(allow, c.Syscall.ExtraAllow...)
trace = append(trace, c.Syscall.ExtraBan...) trace = append(trace, c.Syscall.ExtraBan...)
sc.AddRange(c.Syscall.ExtraCount) sc.AddRange(c.Syscall.ExtraCount)

View File

@ -1,4 +1,4 @@
package runconfig package config
// ProgramConfig defines the extra config apply to program type // ProgramConfig defines the extra config apply to program type
type ProgramConfig struct { type ProgramConfig struct {

View File

@ -1,4 +1,4 @@
package runconfig package config
import ( import (
"path/filepath" "path/filepath"

View File

@ -1,13 +1,13 @@
package runconfig package config
import ( import (
"fmt" "fmt"
"os" "os"
"github.com/criyle/go-sandbox/runprogram" "github.com/criyle/go-sandbox/runner/ptrace"
) )
// Handler defines file access restricted handler to call the runprogram // Handler defines file access restricted handler to call the ptrace
// safe runner // safe runner
type Handler struct { type Handler struct {
SyscallAllow, SyscallTrace, Args []string SyscallAllow, SyscallTrace, Args []string
@ -17,51 +17,51 @@ type Handler struct {
} }
// CheckRead checks whether the file have read permission // CheckRead checks whether the file have read permission
func (h *Handler) CheckRead(fn string) runprogram.TraceAction { func (h *Handler) CheckRead(fn string) ptrace.TraceAction {
if !h.FileSet.IsReadableFile(fn) { if !h.FileSet.IsReadableFile(fn) {
return h.onDgsFileDetect(fn) return h.onDgsFileDetect(fn)
} }
return runprogram.TraceAllow return ptrace.TraceAllow
} }
// CheckWrite checks whether the file have write permission // CheckWrite checks whether the file have write permission
func (h *Handler) CheckWrite(fn string) runprogram.TraceAction { func (h *Handler) CheckWrite(fn string) ptrace.TraceAction {
if !h.FileSet.IsWritableFile(fn) { if !h.FileSet.IsWritableFile(fn) {
return h.onDgsFileDetect(fn) return h.onDgsFileDetect(fn)
} }
return runprogram.TraceAllow return ptrace.TraceAllow
} }
// CheckStat checks whether the file have stat permission // CheckStat checks whether the file have stat permission
func (h *Handler) CheckStat(fn string) runprogram.TraceAction { func (h *Handler) CheckStat(fn string) ptrace.TraceAction {
if !h.FileSet.IsStatableFile(fn) { if !h.FileSet.IsStatableFile(fn) {
return h.onDgsFileDetect(fn) return h.onDgsFileDetect(fn)
} }
return runprogram.TraceAllow return ptrace.TraceAllow
} }
// CheckSyscall checks syscalls other than allowed and traced agianst the // CheckSyscall checks syscalls other than allowed and traced agianst the
// SyscallCounter // SyscallCounter
func (h *Handler) CheckSyscall(syscallName string) runprogram.TraceAction { func (h *Handler) CheckSyscall(syscallName string) ptrace.TraceAction {
// if it is traced, then try to count syscall // if it is traced, then try to count syscall
if inside, allow := h.SyscallCounter.Check(syscallName); inside { if inside, allow := h.SyscallCounter.Check(syscallName); inside {
if allow { if allow {
return runprogram.TraceAllow return ptrace.TraceAllow
} }
return runprogram.TraceKill return ptrace.TraceKill
} }
// if it is traced but not counted, it should be soft banned // if it is traced but not counted, it should be soft banned
return runprogram.TraceBan return ptrace.TraceBan
} }
// onDgsFileDetect soft ban file if in soft ban set // onDgsFileDetect soft ban file if in soft ban set
// otherwise stops the trace process // otherwise stops the trace process
func (h *Handler) onDgsFileDetect(name string) runprogram.TraceAction { func (h *Handler) onDgsFileDetect(name string) ptrace.TraceAction {
if h.FileSet.IsSoftBanFile(name) { if h.FileSet.IsSoftBanFile(name) {
return runprogram.TraceBan return ptrace.TraceBan
} }
h.print("Dangerous fileopen: ", name) h.print("Dangerous fileopen: ", name)
return runprogram.TraceKill return ptrace.TraceKill
} }
// print is used to print debug information // print is used to print debug information

View File

@ -1,4 +1,4 @@
package runconfig package config
// SyscallCounter defines a count-down for each each syscall occurs // SyscallCounter defines a count-down for each each syscall occurs
type SyscallCounter map[string]int type SyscallCounter map[string]int

View File

@ -1,4 +1,4 @@
package runprogram package ptrace
import ( import (
"fmt" "fmt"
@ -8,8 +8,8 @@ import (
libseccomp "github.com/seccomp/libseccomp-golang" libseccomp "github.com/seccomp/libseccomp-golang"
"github.com/criyle/go-sandbox/tracer" "github.com/criyle/go-sandbox/ptracer"
"github.com/criyle/go-sandbox/types/specs" "github.com/criyle/go-sandbox/types"
) )
type tracerHandler struct { type tracerHandler struct {
@ -23,11 +23,11 @@ func (h *tracerHandler) Debug(v ...interface{}) {
} }
} }
func (h *tracerHandler) getString(ctx *tracer.Context, addr uint) string { func (h *tracerHandler) getString(ctx *ptracer.Context, addr uint) string {
return absPath(ctx.Pid, ctx.GetString(uintptr(addr))) return absPath(ctx.Pid, ctx.GetString(uintptr(addr)))
} }
func (h *tracerHandler) checkOpen(ctx *tracer.Context, addr uint, flags uint) TraceAction { func (h *tracerHandler) checkOpen(ctx *ptracer.Context, addr uint, flags uint) TraceAction {
fn := h.getString(ctx, addr) fn := h.getString(ctx, addr)
isReadOnly := (flags&syscall.O_ACCMODE == syscall.O_RDONLY) && isReadOnly := (flags&syscall.O_ACCMODE == syscall.O_RDONLY) &&
(flags&syscall.O_CREAT == 0) && (flags&syscall.O_CREAT == 0) &&
@ -41,25 +41,25 @@ func (h *tracerHandler) checkOpen(ctx *tracer.Context, addr uint, flags uint) Tr
return h.Handler.CheckWrite(fn) return h.Handler.CheckWrite(fn)
} }
func (h *tracerHandler) checkRead(ctx *tracer.Context, addr uint) TraceAction { func (h *tracerHandler) checkRead(ctx *ptracer.Context, addr uint) TraceAction {
fn := h.getString(ctx, addr) fn := h.getString(ctx, addr)
h.Debug("check read: ", fn) h.Debug("check read: ", fn)
return h.Handler.CheckRead(fn) return h.Handler.CheckRead(fn)
} }
func (h *tracerHandler) checkWrite(ctx *tracer.Context, addr uint) TraceAction { func (h *tracerHandler) checkWrite(ctx *ptracer.Context, addr uint) TraceAction {
fn := h.getString(ctx, addr) fn := h.getString(ctx, addr)
h.Debug("check write: ", fn) h.Debug("check write: ", fn)
return h.Handler.CheckWrite(fn) return h.Handler.CheckWrite(fn)
} }
func (h *tracerHandler) checkStat(ctx *tracer.Context, addr uint) TraceAction { func (h *tracerHandler) checkStat(ctx *ptracer.Context, addr uint) TraceAction {
fn := h.getString(ctx, addr) fn := h.getString(ctx, addr)
h.Debug("check stat: ", fn) h.Debug("check stat: ", fn)
return h.Handler.CheckStat(fn) return h.Handler.CheckStat(fn)
} }
func (h *tracerHandler) Handle(ctx *tracer.Context) tracer.TraceAction { func (h *tracerHandler) Handle(ctx *ptracer.Context) ptracer.TraceAction {
var ( var (
action TraceAction action TraceAction
syscallNo = ctx.SyscallNo() syscallNo = ctx.SyscallNo()
@ -108,30 +108,30 @@ func (h *tracerHandler) Handle(ctx *tracer.Context) tracer.TraceAction {
switch action { switch action {
case TraceAllow: case TraceAllow:
return tracer.TraceAllow return ptracer.TraceAllow
case TraceBan: case TraceBan:
h.Debug("<soft ban syscall>") h.Debug("<soft ban syscall>")
return softBanSyscall(ctx) return softBanSyscall(ctx)
default: default:
return tracer.TraceKill return ptracer.TraceKill
} }
} }
func (h *tracerHandler) GetSyscallName(ctx *tracer.Context) (string, error) { func (h *tracerHandler) GetSyscallName(ctx *ptracer.Context) (string, error) {
syscallNo := ctx.SyscallNo() syscallNo := ctx.SyscallNo()
return libseccomp.ScmpSyscall(syscallNo).GetName() return libseccomp.ScmpSyscall(syscallNo).GetName()
} }
func (h *tracerHandler) HandlerDisallow(name string) error { func (h *tracerHandler) HandlerDisallow(name string) error {
if !h.Unsafe { if !h.Unsafe {
return specs.TraceCodeBan return types.StatusBan
} }
return nil return nil
} }
func softBanSyscall(ctx *tracer.Context) tracer.TraceAction { func softBanSyscall(ctx *ptracer.Context) ptracer.TraceAction {
ctx.SetReturnValue(-int(BanRet)) ctx.SetReturnValue(-int(BanRet))
return tracer.TraceBan return ptracer.TraceBan
} }
func getFileMode(flags uint) string { func getFileMode(flags uint) string {

View File

@ -1,16 +1,16 @@
package runprogram package ptrace
import ( import (
libseccomp "github.com/seccomp/libseccomp-golang" libseccomp "github.com/seccomp/libseccomp-golang"
"github.com/criyle/go-sandbox/pkg/forkexec" "github.com/criyle/go-sandbox/pkg/forkexec"
"github.com/criyle/go-sandbox/pkg/seccomp" "github.com/criyle/go-sandbox/pkg/seccomp"
"github.com/criyle/go-sandbox/tracer" "github.com/criyle/go-sandbox/ptracer"
"github.com/criyle/go-sandbox/types/specs" "github.com/criyle/go-sandbox/types"
) )
// Start starts the tracing process // Start starts the tracing process
func (r *RunProgram) Start(done <-chan struct{}) (<-chan specs.TraceResult, error) { func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) {
// build seccomp filter // build seccomp filter
filter, err := buildFilter(r.ShowDetails, r.SyscallAllowed, r.SyscallTraced) filter, err := buildFilter(r.ShowDetails, r.SyscallAllowed, r.SyscallTraced)
if err != nil { if err != nil {
@ -42,7 +42,7 @@ func (r *RunProgram) Start(done <-chan struct{}) (<-chan specs.TraceResult, erro
Unsafe: r.Unsafe, Unsafe: r.Unsafe,
Handler: r.Handler, Handler: r.Handler,
} }
return tracer.Trace(done, th, ch, specs.ResLimit(r.TraceLimit)) return ptracer.Trace(done, th, ch, types.Limit(r.TraceLimit))
} }
// build filter builds the libseccomp filter according to the allow, trace and show details // build filter builds the libseccomp filter according to the allow, trace and show details
@ -51,9 +51,9 @@ func buildFilter(showDetails bool, allow, trace []string) (*libseccomp.ScmpFilte
var defaultAction libseccomp.ScmpAction var defaultAction libseccomp.ScmpAction
// if debug, allow all syscalls and output what was blocked // if debug, allow all syscalls and output what was blocked
if showDetails { if showDetails {
defaultAction = libseccomp.ActTrace.SetReturnCode(tracer.MsgDisallow) defaultAction = libseccomp.ActTrace.SetReturnCode(ptracer.MsgDisallow)
} else { } else {
defaultAction = libseccomp.ActKill defaultAction = libseccomp.ActKill
} }
return seccomp.BuildFilter(defaultAction, libseccomp.ActTrace.SetReturnCode(tracer.MsgHandle), allow, trace) return seccomp.BuildFilter(defaultAction, libseccomp.ActTrace.SetReturnCode(ptracer.MsgHandle), allow, trace)
} }

View File

@ -1,14 +1,14 @@
package runprogram package ptrace
import ( import (
"syscall" "syscall"
"github.com/criyle/go-sandbox/pkg/rlimit" "github.com/criyle/go-sandbox/pkg/rlimit"
"github.com/criyle/go-sandbox/types/specs" "github.com/criyle/go-sandbox/types"
) )
// RunProgram defines the spec to run a program safely // Runner defines the spec to run a program safely by ptracer
type RunProgram struct { type Runner struct {
// argv and env for the child process // argv and env for the child process
// work path set by setcwd (current working directory for child) // work path set by setcwd (current working directory for child)
Args []string Args []string
@ -25,7 +25,7 @@ type RunProgram struct {
RLimits rlimit.RLimits RLimits rlimit.RLimits
// Res limit enforced by tracer // Res limit enforced by tracer
TraceLimit specs.ResLimit TraceLimit types.Limit
// Allowed / Traced syscall names // Allowed / Traced syscall names
// Notice: file access syscalls should be traced // Notice: file access syscalls should be traced

8
runner/runner.go Normal file
View File

@ -0,0 +1,8 @@
package runner
import "github.com/criyle/go-sandbox/types"
// Runner interface defines method to start running
type Runner interface {
Start(<-chan struct{}) (<-chan types.Result, error)
}

View File

@ -1,10 +1,11 @@
package rununshared package unshare
import ( import (
"os" "os"
"github.com/criyle/go-sandbox/pkg/mount"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/criyle/go-sandbox/pkg/mount"
) )
// AddBind is the additional bind mounts besides the default one // AddBind is the additional bind mounts besides the default one

View File

@ -1,15 +1,16 @@
package rununshared package unshare
import ( import (
"fmt" "fmt"
"os" "os"
"time" "time"
"github.com/criyle/go-sandbox/pkg/forkexec"
"github.com/criyle/go-sandbox/pkg/seccomp"
"github.com/criyle/go-sandbox/types/specs"
libseccomp "github.com/seccomp/libseccomp-golang" libseccomp "github.com/seccomp/libseccomp-golang"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/criyle/go-sandbox/pkg/forkexec"
"github.com/criyle/go-sandbox/pkg/seccomp"
"github.com/criyle/go-sandbox/types"
) )
const ( const (
@ -18,7 +19,7 @@ const (
) )
// Start starts the unshared process // Start starts the unshared process
func (r *RunUnshared) Start(done <-chan struct{}) (<-chan specs.TraceResult, error) { func (r *Runner) Start(done <-chan struct{}) (<-chan types.Result, error) {
filter, err := seccomp.BuildFilter(libseccomp.ActKill, libseccomp.ActTrap, r.SyscallAllowed, []string{}) filter, err := seccomp.BuildFilter(libseccomp.ActKill, libseccomp.ActTrap, r.SyscallAllowed, []string{})
if err != nil { if err != nil {
println(err) println(err)
@ -51,7 +52,7 @@ func (r *RunUnshared) Start(done <-chan struct{}) (<-chan specs.TraceResult, err
SyncFunc: r.SyncFunc, SyncFunc: r.SyncFunc,
} }
result := make(chan specs.TraceResult, 1) result := make(chan types.Result, 1)
start := make(chan struct{}) start := make(chan struct{})
finish := make(chan struct{}) finish := make(chan struct{})
@ -71,13 +72,13 @@ func (r *RunUnshared) Start(done <-chan struct{}) (<-chan specs.TraceResult, err
} }
// Trace tracks child processes // Trace tracks child processes
func (r *RunUnshared) Trace(done <-chan struct{}, start chan<- struct{}, func (r *Runner) Trace(done <-chan struct{}, start chan<- struct{},
runner *forkexec.Runner) (result specs.TraceResult, err error) { runner *forkexec.Runner) (result types.Result, 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 = false tle = false
status = specs.TraceCodeNormal status = types.StatusNormal
sTime = time.Now() // start time sTime = time.Now() // start time
fTime time.Time // finish time for setup fTime time.Time // finish time for setup
) )
@ -86,7 +87,7 @@ func (r *RunUnshared) Trace(done <-chan struct{}, start chan<- struct{},
pgid, err := runner.Start() pgid, err := runner.Start()
r.println("Starts: ", pgid, err) r.println("Starts: ", pgid, err)
if err != nil { if err != nil {
result.TraceStatus = specs.TraceCodeRE result.Status = types.StatusRE
return result, err return result, err
} }
@ -106,13 +107,13 @@ func (r *RunUnshared) Trace(done <-chan struct{}, start chan<- struct{},
defer func() { defer func() {
if tle { if tle {
err = specs.TraceCodeTLE err = types.StatusTLE
} }
// kill all tracee upon return // kill all tracee upon return
killAll(pgid) killAll(pgid)
collectZombie(pgid) collectZombie(pgid)
result.SetUpTime = fTime.Sub(sTime).Nanoseconds() result.SetUpTime = fTime.Sub(sTime)
result.RunningTime = time.Since(fTime).Nanoseconds() result.RunningTime = time.Since(fTime)
}() }()
fTime = time.Now() fTime = time.Now()
@ -120,7 +121,7 @@ func (r *RunUnshared) Trace(done <-chan struct{}, start chan<- struct{},
_, err := unix.Wait4(pgid, &wstatus, 0, &rusage) _, err := unix.Wait4(pgid, &wstatus, 0, &rusage)
r.println("wait4: ", wstatus) r.println("wait4: ", wstatus)
if err != nil { if err != nil {
return result, specs.TraceCodeFatal return result, types.StatusFatal
} }
// update resource usage and check against limits // update resource usage and check against limits
@ -129,37 +130,37 @@ func (r *RunUnshared) Trace(done <-chan struct{}, start chan<- struct{},
// check tle / mle // check tle / mle
if userTime > r.ResLimits.TimeLimit { if userTime > r.ResLimits.TimeLimit {
status = specs.TraceCodeTLE status = types.StatusTLE
} }
if userMem > r.ResLimits.MemoryLimit { if userMem > r.ResLimits.MemoryLimit {
status = specs.TraceCodeMLE status = types.StatusMLE
} }
result = specs.TraceResult{ result = types.Result{
UserTime: userTime, Status: status,
UserMem: userMem, UserTime: userTime,
TraceStatus: status, UserMem: userMem,
} }
if status != specs.TraceCodeNormal { if status != types.StatusNormal {
return result, status return result, status
} }
switch { switch {
case wstatus.Exited(): case wstatus.Exited():
result.ExitCode = wstatus.ExitStatus() result.ExitStatus = wstatus.ExitStatus()
return result, nil return result, nil
case wstatus.Signaled(): case wstatus.Signaled():
sig := wstatus.Signal() sig := wstatus.Signal()
switch sig { switch sig {
case unix.SIGXCPU, unix.SIGKILL: case unix.SIGXCPU, unix.SIGKILL:
status = specs.TraceCodeTLE status = types.StatusTLE
case unix.SIGXFSZ: case unix.SIGXFSZ:
status = specs.TraceCodeOLE status = types.StatusOLE
case unix.SIGSYS: case unix.SIGSYS:
status = specs.TraceCodeBan status = types.StatusBan
default: default:
status = specs.TraceCodeRE status = types.StatusRE
} }
result.TraceStatus = status result.Status = status
return result, status return result, status
} }
} }
@ -182,7 +183,7 @@ func collectZombie(pgid int) {
} }
} }
func (r *RunUnshared) println(v ...interface{}) { func (r *Runner) println(v ...interface{}) {
if r.ShowDetails { if r.ShowDetails {
fmt.Fprintln(os.Stderr, v...) fmt.Fprintln(os.Stderr, v...)
} }

View File

@ -1,13 +1,13 @@
package rununshared package unshare
import ( import (
"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/types/specs" "github.com/criyle/go-sandbox/types"
) )
// RunUnshared runs program in unshared namespaces // Runner runs program in unshared namespaces
type RunUnshared struct { type Runner struct {
// argv and env for the child process // argv and env for the child process
Args []string Args []string
Env []string Env []string
@ -25,7 +25,7 @@ type RunUnshared struct {
RLimits rlimit.RLimits RLimits rlimit.RLimits
// Resource limit enforced by tracer // Resource limit enforced by tracer
ResLimits specs.ResLimit ResLimits types.Limit
// Allowed syscall names // Allowed syscall names
SyscallAllowed []string SyscallAllowed []string

View File

@ -1,59 +0,0 @@
package specs
// TraceCode is the error type
type TraceCode int
// Different end condtion
const (
TraceCodeNormal TraceCode = iota // 0
TraceCodeInvalid // 1
TraceCodeRE // 2
TraceCodeMLE // 3
TraceCodeTLE // 4
TraceCodeOLE // 5
TraceCodeBan // 6
TraceCodeFatal // 7
)
func (t TraceCode) Error() string {
switch t {
case TraceCodeNormal:
return ""
case TraceCodeRE:
return "runtime error"
case TraceCodeTLE:
return "time limit exceeded"
case TraceCodeMLE:
return "memory limit exceeded"
case TraceCodeOLE:
return "output limit exceeded"
case TraceCodeBan:
return "syscall banned"
case TraceCodeFatal:
return "handle failed"
default:
return "invalid"
}
}
// TraceStat is the time usages in ns
type TraceStat struct {
SetUpTime int64
RunningTime int64
}
// TraceResult is the result returned by strat trace
type TraceResult struct {
UserTime uint64 // used user CPU time (in ms)
UserMem uint64 // used user memory (in kb)
ExitCode int // exit code
TraceStatus TraceCode // the final status for the process
TraceStat // collects time for the process
}
// ResLimit represents the resource limit for traced process
type ResLimit struct {
TimeLimit uint64 // user CPU time limit (in ms)
RealTimeLimit uint64 // sig_kill will force the process to exit after this limit (in ms)
MemoryLimit uint64 // user memory limit (in kB)
}

41
types/status.go Normal file
View File

@ -0,0 +1,41 @@
package types
// Status is the result Status
type Status int
// Different end condtion
const (
StatusNormal Status = iota // 0
StatusInvalid // 1
StatusRE // 2
StatusMLE // 3
StatusTLE // 4
StatusOLE // 5
StatusBan // 6
StatusFatal // 7
)
var (
statusString = []string{
"",
"invalid",
"runtime error",
"memory limit exceeded",
"time limit exceeded",
"output limit exceeded",
"syscall banned",
"runner failed",
}
)
func (t Status) String() string {
i := int(t)
if i >= 0 && i < len(statusString) {
return statusString[i]
}
return "invalid"
}
func (t Status) Error() string {
return t.String()
}

24
types/types.go Normal file
View File

@ -0,0 +1,24 @@
package types
import "time"
// Result is the result returned by strat trace
type Result struct {
Status // the final status for the process
ExitStatus int // exit Status
UserTime uint64 // used user CPU time (in ms)
UserMem uint64 // used user memory (in kb)
Stat // collects time usage for the runner
}
// Stat is the time usages in ns
type Stat struct {
SetUpTime time.Duration
RunningTime time.Duration
}
// Limit represents the resource limit for traced process
type Limit struct {
TimeLimit uint64 // user CPU time limit (in ms)
MemoryLimit uint64 // user memory limit (in kB)
}