From 04cfb3672293489444fd60472386e0c10030fba6 Mon Sep 17 00:00:00 2001 From: criyle Date: Sun, 13 Dec 2020 21:08:52 -0800 Subject: [PATCH] Add support to load a seccomp filter --- README.md | 4 + cmd/executorserver/config/config.go | 3 +- cmd/executorserver/main.go | 1 + env/config.go | 1 + env/env_linux.go | 18 +++- env/seccomp_linux.go | 47 +++++++++ go.mod | 3 + go.sum | 2 + pkg/pool/envbuilder_linux.go | 25 +++-- pkg/pool/environment_linux.go | 2 + seccomp_example.yaml | 154 ++++++++++++++++++++++++++++ 11 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 env/seccomp_linux.go create mode 100644 seccomp_example.yaml diff --git a/README.md b/README.md index ed17e0b..3772a96 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,10 @@ The `executorserver` need root privilege to create `cgroup`. Either creates sub- - for example, by default container 0 will run with 10001 uid & gid and container 1 will run with 10002 uid & gid... - `-enable-cpu-rate` enabled `cpu` cgroup to control cpu rate using cfs_quota & cfs_period control - `-cpu-cfs-period` specifies cfs_period if cpu rate is enabled (default 100ms) (valid value: \[1ms, 1s\]) +- `-seccomp-conf` specifies `seecomp` filter setting to load when running program + - for example, by `strace -c prog` to get all `syscall` needed and restrict to that sub set + - however, the `syscall` count in one platform(e.g. x86_64) is not suitable for all platform, so this option is not recommended + - the program killed by seccomp filter will have status `Dangerous Syscall` #### Environment Variables diff --git a/cmd/executorserver/config/config.go b/cmd/executorserver/config/config.go index fd177f0..2d1e012 100644 --- a/cmd/executorserver/config/config.go +++ b/cmd/executorserver/config/config.go @@ -15,7 +15,8 @@ type Config struct { PreFork int `flagUsage:"control # of the prefork workers"` TmpFsParam string `flagUsage:"tmpfs mount data (only for default mount with no mount.yaml)" default:"size=16m,nr_inodes=4k"` NetShare bool `flagUsage:"share net namespace with host"` - MountConf string `flagUsage:"specifics mount configuration file" default:"mount.yaml"` + MountConf string `flagUsage:"specifies mount configuration file" default:"mount.yaml"` + SeccompConf string `flagUsage:"specifies seccomp filter" default:"seccomp.yaml"` Parallelism int `flagUsage:"control the # of concurrency execution" default:"4"` CgroupPrefix string `flagUsage:"control cgroup prefix" default:"executor_server"` ContainerCredStart int `flagUsage:"control the start uid&gid for container" default:"10000"` diff --git a/cmd/executorserver/main.go b/cmd/executorserver/main.go index 937ecbe..d7c4240 100644 --- a/cmd/executorserver/main.go +++ b/cmd/executorserver/main.go @@ -79,6 +79,7 @@ func main() { ContainerCredStart: conf.ContainerCredStart, EnableCPURate: conf.EnableCPURate, CPUCfsPeriod: conf.CPUCfsPeriod, + SeccompConf: conf.SeccompConf, Logger: logger.Sugar(), }) if err != nil { diff --git a/env/config.go b/env/config.go index 74d7951..143659b 100644 --- a/env/config.go +++ b/env/config.go @@ -16,6 +16,7 @@ type Config struct { TmpFsParam string NetShare bool MountConf string + SeccompConf string CgroupPrefix string Cpuset string ContainerCredStart int diff --git a/env/env_linux.go b/env/env_linux.go index 90600e6..574dc66 100644 --- a/env/env_linux.go +++ b/env/env_linux.go @@ -1,6 +1,7 @@ package env import ( + "fmt" "io/ioutil" "os" "sync/atomic" @@ -45,6 +46,14 @@ func NewBuilder(c Config) (pool.EnvBuilder, error) { m := mb.FilterNotExist().Mounts c.Info("Created container mount at:", mb) + seccomp, err := readSeccompConf(c.SeccompConf) + if err != nil { + return nil, fmt.Errorf("failed to load seccomp config: %v", err) + } + if seccomp != nil { + c.Info("Load seccomp filter: ", c.SeccompConf) + } + unshareFlags := uintptr(forkexec.UnshareFlags) if c.NetShare { unshareFlags ^= syscall.CLONE_NEWNET @@ -111,7 +120,14 @@ func NewBuilder(c Config) (pool.EnvBuilder, error) { if cgb != nil { cgroupPool = pool.NewFakeCgroupPool(cgb, c.CPUCfsPeriod) } - return pool.NewEnvBuilder(b, cgroupPool, workDir, c.Cpuset, c.EnableCPURate), nil + return pool.NewEnvBuilder(pool.Config{ + Builder: b, + CgroupPool: cgroupPool, + WorkDir: workDir, + Cpuset: c.Cpuset, + CPURate: c.EnableCPURate, + Seccomp: seccomp, + }), nil } type credGen struct { diff --git a/env/seccomp_linux.go b/env/seccomp_linux.go new file mode 100644 index 0000000..467b018 --- /dev/null +++ b/env/seccomp_linux.go @@ -0,0 +1,47 @@ +package env + +import ( + "os" + "syscall" + + "github.com/elastic/go-seccomp-bpf" + "github.com/elastic/go-ucfg/yaml" + "golang.org/x/net/bpf" +) + +func readSeccompConf(name string) ([]syscall.SockFilter, error) { + conf, err := yaml.NewConfigWithFile(name) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + + var policy seccomp.Policy + if err := conf.Unpack(&policy); err != nil { + return nil, err + } + inst, err := policy.Assemble() + if err != nil { + return nil, err + } + rawInst, err := bpf.Assemble(inst) + if err != nil { + return nil, err + } + return toSockFilter(rawInst), nil +} + +func toSockFilter(raw []bpf.RawInstruction) []syscall.SockFilter { + filter := make([]syscall.SockFilter, 0, len(raw)) + for _, instruction := range raw { + filter = append(filter, syscall.SockFilter{ + Code: instruction.Op, + Jt: instruction.Jt, + Jf: instruction.Jf, + K: instruction.K, + }) + } + return filter +} diff --git a/go.mod b/go.mod index 93bdd9e..18a880b 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( cloud.google.com/go v0.74.0 // indirect github.com/creack/pty v1.1.11 github.com/criyle/go-sandbox v0.5.8 + github.com/elastic/go-seccomp-bpf v1.1.0 + github.com/elastic/go-ucfg v0.7.0 github.com/fatih/camelcase v1.0.0 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/gin-contrib/pprof v1.3.0 @@ -26,6 +28,7 @@ require ( go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.16.0 golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 + golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a golang.org/x/sys v0.0.0-20201211090839-8ad439b19e0f golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect diff --git a/go.sum b/go.sum index c9f4991..38a5ad0 100644 --- a/go.sum +++ b/go.sum @@ -96,7 +96,9 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +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 h1:1+C/sZdJKww8hKl7XtLPTjs4cFslhQF2fazKTF+ZE+4= github.com/elastic/go-ucfg v0.7.0/go.mod h1:iaiY0NBIYeasNgycLyTvhJftQlQEUO2hpF+FX0JKxzo= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/pkg/pool/envbuilder_linux.go b/pkg/pool/envbuilder_linux.go index 6d4adfd..00c6723 100644 --- a/pkg/pool/envbuilder_linux.go +++ b/pkg/pool/envbuilder_linux.go @@ -7,22 +7,34 @@ import ( "github.com/criyle/go-sandbox/container" ) +// Config specifies configuration to build environment builder +type Config struct { + Builder EnvironmentBuilder + CgroupPool CgroupPool + WorkDir string + Seccomp []syscall.SockFilter + Cpuset string + CPURate bool +} + type environmentBuilder struct { builder EnvironmentBuilder cgPool CgroupPool workDir string + seccomp []syscall.SockFilter cpuset string cpuRate bool } // NewEnvBuilder creates builder for linux container pools -func NewEnvBuilder(builder EnvironmentBuilder, cgPool CgroupPool, workDir, cpuset string, cpuRate bool) EnvBuilder { +func NewEnvBuilder(c Config) EnvBuilder { return &environmentBuilder{ - builder: builder, - cgPool: cgPool, - workDir: workDir, - cpuset: cpuset, - cpuRate: cpuRate, + builder: c.Builder, + cgPool: c.CgroupPool, + workDir: c.WorkDir, + seccomp: c.Seccomp, + cpuset: c.Cpuset, + cpuRate: c.CPURate, } } @@ -46,5 +58,6 @@ func (b *environmentBuilder) Build() (Environment, error) { wd: wd[0], cpuset: b.cpuset, cpuRate: b.cpuRate, + seccomp: b.seccomp, }, nil } diff --git a/pkg/pool/environment_linux.go b/pkg/pool/environment_linux.go index 911b8c0..134aa87 100644 --- a/pkg/pool/environment_linux.go +++ b/pkg/pool/environment_linux.go @@ -20,6 +20,7 @@ type environ struct { cgPool CgroupPool wd *os.File // container work dir cpuset string + seccomp []syscall.SockFilter cpuRate bool } @@ -70,6 +71,7 @@ func (c *environ) Execve(ctx context.Context, param envexec.ExecveParam) (envexe CTTY: param.TTY, ExecFile: param.ExecFile, RLimits: rLimits.PrepareRLimit(), + Seccomp: c.seccomp, SyncFunc: syncFunc, } rt := c.Environment.Execve(ctx, p) diff --git a/seccomp_example.yaml b/seccomp_example.yaml new file mode 100644 index 0000000..c79bf8b --- /dev/null +++ b/seccomp_example.yaml @@ -0,0 +1,154 @@ +# This is an example seccomp policy for x86_64 that whitelist regular syscalls and prevents network syscalls. +# The example is not ready for production use now, should be tuned for specific usages + +# The default action is applied if none of the syscalls match. +# +# Possible actions: +# - kill_process (since Linux 4.14) +# - kill_thread +# - trap +# - errno (returns EPERM) +# - trace +# - log (since Linux 4.14) +# - allow +default_action: kill_process + +syscalls: +- action: allow + names: + # file actions + - read + - write + - readv + - writev + - close + - fstat + - lseek + - dup + - dup2 + - dup3 + - ioctl + - fcntl + - fadvise64 + - pipe + - pread64 + - pwrite64 + + # memory action + - mmap + - mprotect + - munmap + - brk + - mremap + - msync + - mincore + - madvise + + # signal + - rt_sigaction + - rt_sigprocmask + - rt_sigreturn + - rt_sigpending + - sigaltstack + + # work dir + - getcwd + + # exit + - exit + - exit_group + + # others + - arch_prctl + - restart_syscall + + # time + - gettimeofday + - times + - time + - clock_gettime + - clock_getres + + # rlimit + - getrlimit + - getrusage + - setrlimit + + # execve + - execve + - execveat + + # open + - open + - openat + + # delete + - unlink + - unlinkat + + # link + - readlink + - readlinkat + + # permission check + - lstat + - stat + - access + - faccessat + + # process + - clone + - fork + - vfork + - nanosleep + - wait4 + + - getpid + - gettid + + # lock + - futex + + # other + - getdents + - getdents64 + - sysinfo + - set_tid_address + - set_robust_list + - getrandom + + - umask + - rename + - chmod + - mkdir + - chdir + - fchdir + - ftruncate + - sched_getaffinity + - sched_yield + - uname + - prlimit64 + - fchmodat + - poll + + # user + - getuid + - geteuid + - getgid + - getegid + +- action: errno + names: + - connect + - accept + - sendto + - recvfrom + - sendmsg + - recvmsg + - bind + - listen + - setsockopt + - socketpair + - getsockopt + +