From 4258342ea59c041266653accab7729fb7d069bb4 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 8 Jan 2015 10:29:30 -0500 Subject: [PATCH] Introduce support for syscall filtering in containers #237 This PR introduces the ability to filter system calls on a per-container basis on Linux, using libseccomp to support multiple architectures. This adds another layer of security between containers and the kernel. System calls which are unnecessary in a container or problematic from a security perspective can be restricted to prevent their use. Most of the truly problematic syscalls are already restricted by dropping capabilities; this adds an additional, finer-grained layer of protection. This PR adds a vendored library dependency (Go bindings for libseccomp) and a build dependency on libseccomp >= v2.1. The actual changes to libcontainer are fairly minimal, most of the delta is in the libseccomp bindings. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) Docker-DCO-1.1-Signed-off-by: Matt Heon (github: mheon) --- Dockerfile | 5 +- configs/config.go | 9 +- integration/seccomp_test.go | 208 +++++ sample_configs/seccomp.json | 225 ++++++ security/seccomp/seccomp.go | 141 ++++ security/seccomp/types.go | 20 + security/seccomp/unsupported.go | 7 + setns_init_linux.go | 4 + standard_init_linux.go | 4 + update-vendor.sh | 4 + .../src/github.com/mheon/libseccomp/LICENSE | 456 +++++++++++ .../mheon/libseccomp/src/goseccomp/seccomp.go | 715 ++++++++++++++++++ .../src/goseccomp/seccomp_internal.go | 380 ++++++++++ .../libseccomp/src/goseccomp/seccomp_test.go | 468 ++++++++++++ 14 files changed, 2643 insertions(+), 3 deletions(-) create mode 100644 integration/seccomp_test.go create mode 100644 sample_configs/seccomp.json create mode 100644 security/seccomp/seccomp.go create mode 100644 security/seccomp/types.go create mode 100644 security/seccomp/unsupported.go create mode 100644 vendor/src/github.com/mheon/libseccomp/LICENSE create mode 100644 vendor/src/github.com/mheon/libseccomp/src/goseccomp/seccomp.go create mode 100644 vendor/src/github.com/mheon/libseccomp/src/goseccomp/seccomp_internal.go create mode 100644 vendor/src/github.com/mheon/libseccomp/src/goseccomp/seccomp_test.go diff --git a/Dockerfile b/Dockerfile index fb34c8c95..9b5dc8c50 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ FROM golang:1.4 +RUN apt-get update && apt-get install -y libseccomp2 libseccomp-dev RUN go get golang.org/x/tools/cmd/cover ENV GOPATH $GOPATH:/go/src/github.com/docker/libcontainer/vendor @@ -17,7 +18,7 @@ WORKDIR /go/src/github.com/docker/libcontainer RUN cp sample_configs/minimal.json /busybox/container.json RUN go get -d -v ./... -RUN make direct-install +RUN make TEST_TAGS='-tags seccomp' direct-install ENTRYPOINT ["/dind"] -CMD ["make", "direct-test"] +CMD ["make", "TEST_TAGS=-tags seccomp", "direct-test"] diff --git a/configs/config.go b/configs/config.go index b07f252b5..918d649d4 100644 --- a/configs/config.go +++ b/configs/config.go @@ -1,6 +1,10 @@ package configs -import "fmt" +import ( + "fmt" + + "github.com/docker/libcontainer/security/seccomp" +) type Rlimit struct { Type int `json:"type"` @@ -96,6 +100,9 @@ type Config struct { // ReadonlyPaths specifies paths within the container's rootfs to remount as read-only // so that these files prevent any writes. ReadonlyPaths []string `json:"readonly_paths"` + + // SeccompConfig holds information on system calls to be restricted in the container + SeccompConfig seccomp.SeccompConfig `json:"seccomp_config,omitempty"` } // Gets the root uid for the process on host which could be non-zero diff --git a/integration/seccomp_test.go b/integration/seccomp_test.go new file mode 100644 index 000000000..9085aa8eb --- /dev/null +++ b/integration/seccomp_test.go @@ -0,0 +1,208 @@ +// +build seccomp,linux,cgo + +package integration + +import ( + "strings" + "syscall" + "testing" + + "github.com/docker/libcontainer" + "github.com/docker/libcontainer/security/seccomp" +) + +func TestSeccompDenyGetcwd(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + config.SeccompConfig = seccomp.SeccompConfig{ + Enable: true, + WhitelistToggle: false, + Syscalls: []seccomp.BlockedSyscall{ + { + Name: "getcwd", + }, + }, + } + + container, err := newContainer(config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + buffers := newStdBuffers() + pwd := &libcontainer.Process{ + Args: []string{"pwd"}, + Env: standardEnvironment, + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + + err = container.Start(pwd) + if err != nil { + t.Fatal(err) + } + ps, err := pwd.Wait() + if err == nil { + t.Fatal("Expecting error (negative return code); instead exited cleanly!") + } + + var exitCode int + status := ps.Sys().(syscall.WaitStatus) + if status.Exited() { + exitCode = status.ExitStatus() + } else if status.Signaled() { + exitCode = -int(status.Signal()) + } else { + t.Fatalf("Unrecognized exit reason!") + } + + if exitCode == 0 { + t.Fatalf("Getcwd should fail with negative exit code, instead got %d!", exitCode) + } + + expected := "pwd: getcwd: Operation not permitted" + actual := strings.Trim(buffers.Stderr.String(), "\n") + if actual != expected { + t.Fatalf("Expected output %s but got %s\n", expected, actual) + } +} + +func TestSeccompPermitWriteConditional(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + config.SeccompConfig = seccomp.SeccompConfig{ + Enable: true, + WhitelistToggle: false, + Syscalls: []seccomp.BlockedSyscall{ + { + Name: "write", + Conditions: []seccomp.SyscallCondition{ + { + Argument: 0, + Operator: ">", + ValueOne: 1, + }, + }, + }, + }, + } + + container, err := newContainer(config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + buffers := newStdBuffers() + dmesg := &libcontainer.Process{ + Args: []string{"busybox", "ls", "/"}, + Env: standardEnvironment, + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + + err = container.Start(dmesg) + if err != nil { + t.Fatal(err) + } + if _, err := dmesg.Wait(); err != nil { + t.Fatalf("%s: %s", err, buffers.Stderr) + } +} + +func TestSeccompDenyWriteConditional(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + config.SeccompConfig = seccomp.SeccompConfig{ + Enable: true, + WhitelistToggle: false, + Syscalls: []seccomp.BlockedSyscall{ + { + Name: "write", + Conditions: []seccomp.SyscallCondition{ + { + Argument: 0, + Operator: ">", + ValueOne: 1, + }, + }, + }, + }, + } + + container, err := newContainer(config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + buffers := newStdBuffers() + dmesg := &libcontainer.Process{ + Args: []string{"busybox", "ls", "does_not_exist"}, + Env: standardEnvironment, + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + + err = container.Start(dmesg) + if err != nil { + t.Fatal(err) + } + + ps, err := dmesg.Wait() + if err == nil { + t.Fatal("Expecting negative return, instead got 0!") + } + + var exitCode int + status := ps.Sys().(syscall.WaitStatus) + if status.Exited() { + exitCode = status.ExitStatus() + } else if status.Signaled() { + exitCode = -int(status.Signal()) + } else { + t.Fatalf("Unrecognized exit reason!") + } + + if exitCode == 0 { + t.Fatalf("Busybox should fail with negative exit code, instead got %d!", exitCode) + } + + // We're denying write to stderr, so we expect an empty buffer + expected := "" + actual := strings.Trim(buffers.Stderr.String(), "\n") + if actual != expected { + t.Fatalf("Expected output %s but got %s\n", expected, actual) + } +} diff --git a/sample_configs/seccomp.json b/sample_configs/seccomp.json new file mode 100644 index 000000000..6c8f83b95 --- /dev/null +++ b/sample_configs/seccomp.json @@ -0,0 +1,225 @@ +{ + "capabilities": [ + "CHOWN", + "DAC_OVERRIDE", + "FOWNER", + "MKNOD", + "NET_RAW", + "SETGID", + "SETUID", + "SETFCAP", + "SETPCAP", + "NET_BIND_SERVICE", + "SYS_CHROOT", + "KILL" + ], + "cgroups": { + "allowed_devices": [ + { + "cgroup_permissions": "m", + "major_number": -1, + "minor_number": -1, + "type": 99 + }, + { + "cgroup_permissions": "m", + "major_number": -1, + "minor_number": -1, + "type": 98 + }, + { + "cgroup_permissions": "rwm", + "major_number": 5, + "minor_number": 1, + "path": "/dev/console", + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "major_number": 4, + "path": "/dev/tty0", + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "major_number": 4, + "minor_number": 1, + "path": "/dev/tty1", + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "major_number": 136, + "minor_number": -1, + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "major_number": 5, + "minor_number": 2, + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "major_number": 10, + "minor_number": 200, + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "file_mode": 438, + "major_number": 1, + "minor_number": 3, + "path": "/dev/null", + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "file_mode": 438, + "major_number": 1, + "minor_number": 5, + "path": "/dev/zero", + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "file_mode": 438, + "major_number": 1, + "minor_number": 7, + "path": "/dev/full", + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "file_mode": 438, + "major_number": 5, + "path": "/dev/tty", + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "file_mode": 438, + "major_number": 1, + "minor_number": 9, + "path": "/dev/urandom", + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "file_mode": 438, + "major_number": 1, + "minor_number": 8, + "path": "/dev/random", + "type": 99 + } + ], + "name": "docker-koye", + "parent": "docker" + }, + "restrict_sys": true, + "mount_config": { + "device_nodes": [ + { + "cgroup_permissions": "rwm", + "file_mode": 438, + "major_number": 1, + "minor_number": 3, + "path": "/dev/null", + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "file_mode": 438, + "major_number": 1, + "minor_number": 5, + "path": "/dev/zero", + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "file_mode": 438, + "major_number": 1, + "minor_number": 7, + "path": "/dev/full", + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "file_mode": 438, + "major_number": 5, + "path": "/dev/tty", + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "file_mode": 438, + "major_number": 1, + "minor_number": 9, + "path": "/dev/urandom", + "type": 99 + }, + { + "cgroup_permissions": "rwm", + "file_mode": 438, + "major_number": 1, + "minor_number": 8, + "path": "/dev/random", + "type": 99 + } + ], + "mounts": [ + { + "type": "tmpfs", + "destination": "/tmp" + } + ] + }, + "environment": [ + "HOME=/", + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "HOSTNAME=koye", + "TERM=xterm" + ], + "hostname": "koye", + "namespaces": [ + {"type": "NEWIPC"}, + {"type": "NEWNET"}, + {"type": "NEWNS"}, + {"type": "NEWPID"}, + {"type": "NEWUTS"} + ], + "networks": [ + { + "address": "127.0.0.1/0", + "gateway": "localhost", + "mtu": 1500, + "type": "loopback" + } + ], + "tty": true, + "user": "daemon", + "seccomp_config": { + "enable": true, + "whitelist_toggle": false, + "syscalls": [ + { + "name": "getcwd" + }, + { + "name": "socket", + "conditions": [ + { + "argument": 0, + "operator": "=", + "value_one": 5 + }, + { + "argument": 0, + "operator": ">", + "value_one": 15 + } + ] + } + ] + } +} diff --git a/security/seccomp/seccomp.go b/security/seccomp/seccomp.go new file mode 100644 index 000000000..fb0b6ba48 --- /dev/null +++ b/security/seccomp/seccomp.go @@ -0,0 +1,141 @@ +// +build linux,cgo,seccomp + +package seccomp + +import ( + "fmt" + "log" + "strings" + "syscall" + + libseccomp "github.com/mheon/libseccomp/src/goseccomp" +) + +var ( + actAllow libseccomp.ScmpAction = libseccomp.ActAllow + actDeny libseccomp.ScmpAction = libseccomp.ActErrno.SetReturnCode(int16(syscall.EPERM)) +) + +// Filters given syscalls in a container, preventing them from being used +// Started in the container init process, and carried over to all child processes +func InitSeccomp(config SeccompConfig) error { + if !config.Enable { + return nil + } + + var defaultAction libseccomp.ScmpAction + if config.WhitelistToggle { + defaultAction = actDeny + } else { + defaultAction = actAllow + } + + filter, err := libseccomp.NewFilter(defaultAction) + if err != nil { + return fmt.Errorf("Error creating filter: %s", err) + } + + // Unset no new privs bit + if err = filter.SetNoNewPrivsBit(false); err != nil { + return fmt.Errorf("Error setting no new privileges: %s", err) + } + + // Add all additional architectures to the filter + for _, arch := range config.Architectures { + archConst, err := libseccomp.GetArchFromString(arch) + if err != nil { + return fmt.Errorf("Error adding architecture to filter: %s", err) + } + + if err = filter.AddArch(archConst); err != nil { + return fmt.Errorf("Error adding architecture %s to filter: %s", arch, err) + } + } + + // Add a rule for each syscall + for _, call := range config.Syscalls { + if err = blockCall(config.WhitelistToggle, filter, call); err != nil { + return err + } + } + + if err != nil { + return fmt.Errorf("Error initializing filter: %s", err) + } + + if err = filter.Load(); err != nil { + return fmt.Errorf("Error loading seccomp filter into kernel: %s", err) + } + + return nil +} + +// Return an ScmpCompareOp +func compareOpFromString(op string) (libseccomp.ScmpCompareOp, error) { + switch strings.ToLower(op) { + case "ne", "!=", "notequal": + return libseccomp.CompareNotEqual, nil + case "l", "<", "lessthan": + return libseccomp.CompareLess, nil + case "le", "<=", "lessthanorequal": + return libseccomp.CompareLessOrEqual, nil + case "eq", "=", "==", "equal": + return libseccomp.CompareEqual, nil + case "ge", ">=", "greaterthanorequal": + return libseccomp.CompareGreaterEqual, nil + case "g", ">", "greaterthan": + return libseccomp.CompareGreater, nil + case "me", "|=", "maskedequal": + return libseccomp.CompareMaskedEqual, nil + default: + return libseccomp.CompareInvalid, fmt.Errorf("Cannot convert string %s into a comparison operator", op) + } +} + +func blockCall(isWhitelist bool, filter *libseccomp.ScmpFilter, call BlockedSyscall) error { + if len(call.Name) == 0 { + return fmt.Errorf("Empty string is not a valid syscall!") + } + + callNum, err := libseccomp.GetSyscallFromName(call.Name) + if err != nil { + log.Printf("Error resolving syscall name %s: %s. Ignoring syscall.", call.Name, err) + return nil + } + + var action libseccomp.ScmpAction + + if isWhitelist { + action = actAllow + } else { + action = actDeny + } + + if len(call.Conditions) == 0 { + if err = filter.AddRule(callNum, action); err != nil { + return err + } + } else { + conditions := []libseccomp.ScmpCondition{} + + for _, cond := range call.Conditions { + compareOp, err := compareOpFromString(cond.Operator) + if err != nil { + return err + } + + newCond, err := libseccomp.MakeCondition(cond.Argument, compareOp, cond.ValueOne, cond.ValueTwo) + if err != nil { + return err + } + + conditions = append(conditions, newCond) + } + + if err = filter.AddRuleConditional(callNum, action, conditions); err != nil { + return err + } + } + + return nil +} diff --git a/security/seccomp/types.go b/security/seccomp/types.go new file mode 100644 index 000000000..5dc9cace6 --- /dev/null +++ b/security/seccomp/types.go @@ -0,0 +1,20 @@ +package seccomp + +type SyscallCondition struct { + Argument uint `json:"argument"` + Operator string `json:"operator"` + ValueOne uint64 `json:"value_one"` + ValueTwo uint64 `json:"value_two,omitempty"` +} + +type BlockedSyscall struct { + Name string `json:"name,"` + Conditions []SyscallCondition `json:"conditions,omitempty"` +} + +type SeccompConfig struct { + Enable bool `json:"enable"` + WhitelistToggle bool `json:"whitelist_toggle"` + Architectures []string `json:"architectures,omitempty"` + Syscalls []BlockedSyscall `json:"syscalls"` +} diff --git a/security/seccomp/unsupported.go b/security/seccomp/unsupported.go new file mode 100644 index 000000000..4e9a4a22d --- /dev/null +++ b/security/seccomp/unsupported.go @@ -0,0 +1,7 @@ +// +build !linux !cgo !seccomp + +package seccomp + +func InitSeccomp(config SeccompConfig) error { + return nil +} diff --git a/setns_init_linux.go b/setns_init_linux.go index f77219d27..16032b068 100644 --- a/setns_init_linux.go +++ b/setns_init_linux.go @@ -7,6 +7,7 @@ import ( "github.com/docker/libcontainer/apparmor" "github.com/docker/libcontainer/label" + "github.com/docker/libcontainer/security/seccomp" "github.com/docker/libcontainer/system" ) @@ -20,6 +21,9 @@ func (l *linuxSetnsInit) Init() error { if err := setupRlimits(l.config.Config); err != nil { return err } + if err := seccomp.InitSeccomp(l.config.Config.SeccompConfig); err != nil { + return err + } if err := finalizeNamespace(l.config); err != nil { return err } diff --git a/standard_init_linux.go b/standard_init_linux.go index 29619d3cd..67a9df804 100644 --- a/standard_init_linux.go +++ b/standard_init_linux.go @@ -9,6 +9,7 @@ import ( "github.com/docker/libcontainer/apparmor" "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/label" + "github.com/docker/libcontainer/security/seccomp" "github.com/docker/libcontainer/system" ) @@ -77,6 +78,9 @@ func (l *linuxStandardInit) Init() error { if err != nil { return err } + if err := seccomp.InitSeccomp(l.config.Config.SeccompConfig); err != nil { + return err + } if err := finalizeNamespace(l.config); err != nil { return err } diff --git a/update-vendor.sh b/update-vendor.sh index b68f5d461..3ce19ecbc 100755 --- a/update-vendor.sh +++ b/update-vendor.sh @@ -45,5 +45,9 @@ clone git github.com/coreos/go-systemd v2 clone git github.com/godbus/dbus v2 clone git github.com/Sirupsen/logrus v0.6.6 clone git github.com/syndtr/gocapability 8e4cdcb +clone git github.com/mheon/libseccomp e84f3b382810c + +cd src/github.com/mheon/libseccomp/ +find ! \( -name '*.go' -or -name 'LICENSE' \) -type f -exec rm -f {} + # intentionally not vendoring Docker itself... that'd be a circle :) diff --git a/vendor/src/github.com/mheon/libseccomp/LICENSE b/vendor/src/github.com/mheon/libseccomp/LICENSE new file mode 100644 index 000000000..ca4f73b7b --- /dev/null +++ b/vendor/src/github.com/mheon/libseccomp/LICENSE @@ -0,0 +1,456 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. diff --git a/vendor/src/github.com/mheon/libseccomp/src/goseccomp/seccomp.go b/vendor/src/github.com/mheon/libseccomp/src/goseccomp/seccomp.go new file mode 100644 index 000000000..baaedc098 --- /dev/null +++ b/vendor/src/github.com/mheon/libseccomp/src/goseccomp/seccomp.go @@ -0,0 +1,715 @@ +// +build linux + +// Public API specification for libseccomp Go bindings +// Contains public API, save filter-related functions + +/* + * This library is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License as + * published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see . + */ + +// Provides bindings for libseccomp, a library wrapping the Linux seccomp +// syscall. Seccomp enables an application to restrict system call use for +// itself and its children. +package seccomp + +import ( + "fmt" + "os" + "runtime" + "strings" + "sync" + "syscall" + "unsafe" +) + +// This file contains the public API of the bindings + +// C wrapping code + +// #cgo LDFLAGS: -lseccomp +// #include +// #include +import "C" + +// Exported types + +// Represents a CPU architecture. +// Seccomp can restrict syscalls on a per-architecture basis. +type ScmpArch uint + +// Represents an action to be taken on a filter rule match in libseccomp +type ScmpAction uint + +// Represents a comparison operator which can be used in a filter rule +type ScmpCompareOp uint + +// Represents a rule in a libseccomp filter context +type ScmpCondition struct { + Argument uint `json:"argument,omitempty"` + Op ScmpCompareOp `json:"operator,omitempty"` + Operand1 uint64 `json:"operand_one,omitempty"` + Operand2 uint64 `json:"operand_two,omitempty"` +} + +// Represents a Linux System Call +type ScmpSyscall int32 + +// Exported Constants + +const ( + // Valid architectures recognized by libseccomp + + // Ensure uninitialized ScmpArch variables are invalid + ArchInvalid ScmpArch = iota + // The native architecture of the kernel + ArchNative ScmpArch = iota + // 32-bit x86 syscalls + ArchX86 ScmpArch = iota + // 64-bit x86-64 syscalls + ArchAMD64 ScmpArch = iota + // Syscalls in the kernel x32 ABI + ArchX32 ScmpArch = iota + // 32-bit ARM syscalls + ArchARM ScmpArch = iota + // 64-bit ARM syscalls + ArchARM64 ScmpArch = iota +) + +const ( + // Supported actions on filter match + + // Ensure uninitialized ScmpAction variables are invalid + ActInvalid ScmpAction = iota + // Kill process + ActKill ScmpAction = iota + // Throw SIGSYS + ActTrap ScmpAction = iota + // The syscall will return an negative error code + // This code can be set with the SetReturnCode method + ActErrno ScmpAction = iota + // Notify tracing processes with given error code + // This code can be set with the SetReturnCode method + ActTrace ScmpAction = iota + // Permit the syscall to continue execution + ActAllow ScmpAction = iota +) + +const ( + // These are comparison operators used in conditional seccomp rules + + // Ensure uninitialized ScmpCompareOp variables are invalid + CompareInvalid ScmpCompareOp = iota + CompareNotEqual ScmpCompareOp = iota + CompareLess ScmpCompareOp = iota + CompareLessOrEqual ScmpCompareOp = iota + CompareEqual ScmpCompareOp = iota + CompareGreaterEqual ScmpCompareOp = iota + CompareGreater ScmpCompareOp = iota + CompareMaskedEqual ScmpCompareOp = iota +) + +// Helpers for types + +// Return an ScmpArch constant from a string representing an architecture +func GetArchFromString(arch string) (ScmpArch, error) { + switch strings.ToLower(arch) { + case "x86": + return ArchX86, nil + case "amd64", "x86-64", "x86_64", "x64": + return ArchAMD64, nil + case "x32": + return ArchX32, nil + case "arm": + return ArchARM, nil + case "arm64", "aarch64": + return ArchARM64, nil + default: + return ArchInvalid, fmt.Errorf("Cannot convert unrecognized string %s", arch) + } +} + +// Returns a string representation of an architecture constant +func (a ScmpArch) String() string { + switch a { + case ArchX86: + return "x86" + case ArchAMD64: + return "amd64" + case ArchX32: + return "x32" + case ArchARM: + return "arm" + case ArchARM64: + return "arm64" + case ArchNative: + return "native" + case ArchInvalid: + return "Invalid architecture" + default: + return "Unknown architecture" + } +} + +// Returns a string representation of a comparison operator constant +func (a ScmpCompareOp) String() string { + switch a { + case CompareNotEqual: + return "Not equal" + case CompareLess: + return "Less than" + case CompareLessOrEqual: + return "Less than or equal to" + case CompareEqual: + return "Equal" + case CompareGreaterEqual: + return "Greater than or equal to" + case CompareGreater: + return "Greater than" + case CompareMaskedEqual: + return "Masked equality" + case CompareInvalid: + return "Invalid comparison operator" + default: + return "Unrecognized comparison operator" + } +} + +// Returns a string representation of a seccomp match action +func (a ScmpAction) String() string { + switch a & 0xFFFF { + case ActKill: + return "Action: Kill Process" + case ActTrap: + return "Action: Send SIGSYS" + case ActErrno: + return fmt.Sprintf("Action: Return error code %d", (a >> 16)) + case ActTrace: + return fmt.Sprintf("Action: Notify tracing processes with code %d", + (a >> 16)) + case ActAllow: + return "Action: Allow system call" + default: + return "Unrecognized Action" + } +} + +// Add a return code to a supporting ScmpAction, clearing any existing code +// Only valid on ActErrno and ActTrace. Takes no action otherwise. +// Accepts 16-bit return code as argument. +// Returns a valid ScmpAction of the original type with the new error code set. +func (a ScmpAction) SetReturnCode(code int16) ScmpAction { + aTmp := a & 0x0000FFFF + if aTmp == ActErrno || aTmp == ActTrace { + return (aTmp | (ScmpAction(code)&0xFFFF)<<16) + } + return a +} + +// Get the return code of an ScmpAction +func (a ScmpAction) GetReturnCode() int16 { + return int16(a >> 16) +} + +// Syscall functions + +// Get the name of a syscall from its number. +// Acts on any syscall number. +// Returns either a string containing the name of the syscall, or an error. +func (s ScmpSyscall) GetName() (string, error) { + return s.GetNameByArch(ArchNative) +} + +// Get the name of a syscall from its number for a given architecture. +// Acts on any syscall number. +// Accepts a valid architecture constant. +// Returns either a string containing the name of the syscall, or an error. +// if the syscall is unrecognized or an issue occurred. +func (s ScmpSyscall) GetNameByArch(arch ScmpArch) (string, error) { + if err := sanitizeArch(arch); err != nil { + return "", err + } + + cString := C.seccomp_syscall_resolve_num_arch(arch.toNative(), C.int(s)) + if cString == nil { + return "", fmt.Errorf("Could not resolve syscall name") + } + defer C.free(unsafe.Pointer(cString)) + + finalStr := C.GoString(cString) + return finalStr, nil +} + +// Get the number of a syscall by name on the kernel's native architecture. +// Accepts a string containing the name of a syscall. +// Returns the number of the syscall, or an error if no syscall with that name +// was found. +func GetSyscallFromName(name string) (ScmpSyscall, error) { + cString := C.CString(name) + defer C.free(unsafe.Pointer(cString)) + + result := C.seccomp_syscall_resolve_name(cString) + if result == scmpError { + return 0, fmt.Errorf("Could not resolve name to syscall") + } + + return ScmpSyscall(result), nil +} + +// Get the number of a syscall by name for a given architecture's ABI. +// Accepts the name of a syscall and an architecture constant. +// Returns the number of the syscall, or an error if an invalid architecture is +// passed or a syscall with that name was not found. +func GetSyscallFromNameByArch(name string, arch ScmpArch) (ScmpSyscall, error) { + if err := sanitizeArch(arch); err != nil { + return 0, err + } + + cString := C.CString(name) + defer C.free(unsafe.Pointer(cString)) + + result := C.seccomp_syscall_resolve_name_arch(arch.toNative(), cString) + if result == scmpError { + return 0, fmt.Errorf("Could not resolve name to syscall") + } + + return ScmpSyscall(result), nil +} + +// Make a new condition to attach to a filter rule. +// Associated rules will only match if this condition is true. +// Accepts the number the argument we are checking, and a comparison operator +// and value to compare to. +// The rule will match if argument $arg (zero-indexed) of the syscall is +// $COMPARE_OP the provided comparison value. +// Some comparison operators accept two values. Masked equals, for example, +// will mask $arg of the syscall with the second value provided (via bitwise +// AND) and then compare against the first value provided. +// For example, in the less than or equal case, if the syscall argument was +// 0 and the value provided was 1, the condition would match, as 0 is less +// than or equal to 1. +// Return either an error on bad argument or a valid ScmpCondition struct. +func MakeCondition(arg uint, comparison ScmpCompareOp, values ...uint64) (ScmpCondition, error) { + var condStruct ScmpCondition + + if comparison == CompareInvalid { + return condStruct, fmt.Errorf("Invalid comparison operator!") + } else if arg > 5 { + return condStruct, fmt.Errorf("Syscalls only have up to 6 arguments!") + } else if len(values) > 2 { + return condStruct, fmt.Errorf("Conditions can have at most 2 arguments!") + } else if len(values) == 0 { + return condStruct, fmt.Errorf("Must provide at least one value to compare against!") + } + + condStruct.Argument = arg + condStruct.Op = comparison + condStruct.Operand1 = values[0] + if len(values) == 2 { + condStruct.Operand2 = values[1] + } else { + condStruct.Operand2 = 0 // Unused + } + + return condStruct, nil +} + +// Utility Functions + +// Returns architecture token representing the native kernel architecture +func GetNativeArch() (ScmpArch, error) { + arch := C.seccomp_arch_native() + + return archFromNative(arch) +} + +// Public Filter API + +// Represents a filter context in libseccomp. +// A filter context is initially empty. Rules can be added to it, and it can +// then be loaded into the kernel. +type ScmpFilter struct { + filterCtx C.scmp_filter_ctx + valid bool + lock sync.Mutex +} + +// Create a new filter context. +// Accepts a default action to be taken for syscalls which match no rules in +// the filter. +// Returns a reference to a valid filter context, or nil and an error if the +// filter context could not be created or an invalid default action was given. +func NewFilter(defaultAction ScmpAction) (*ScmpFilter, error) { + if err := sanitizeAction(defaultAction); err != nil { + return nil, err + } + + fPtr := C.seccomp_init(defaultAction.toNative()) + if fPtr == nil { + return nil, fmt.Errorf("Could not create filter") + } + + filter := new(ScmpFilter) + filter.filterCtx = fPtr + filter.valid = true + runtime.SetFinalizer(filter, filterFinalizer) + + return filter, nil +} + +// Determine whether a filter context is valid to use. +// Some operations (Release and Merge) render filter contexts invalid and +// consequently prevent further use. +func (f *ScmpFilter) IsValid() bool { + f.lock.Lock() + defer f.lock.Unlock() + + return f.valid +} + +// Reset a filter context, removing all its existing state. +// Accepts a new default action to be taken for syscalls which do not match. +// Returns an error if the filter or action provided are invalid. +func (f *ScmpFilter) Reset(defaultAction ScmpAction) error { + f.lock.Lock() + defer f.lock.Unlock() + + if err := sanitizeAction(defaultAction); err != nil { + return err + } else if !f.valid { + return fmt.Errorf("Filter is invalid or uninitialized") + } + + retCode := C.seccomp_reset(f.filterCtx, defaultAction.toNative()) + if retCode != 0 { + return syscall.Errno(-1 * retCode) + } + + return nil +} + +// Releases a filter context, freeing its memory. Should be called after +// loading into the kernel, when the filter is no longer needed. +// After calling this function, the given filter is no longer valid and cannot +// be used. +// Release() will be invoked automatically when a filter context is garbage +// collected, but can also be called manually to free memory. +func (f *ScmpFilter) Release() { + f.lock.Lock() + defer f.lock.Unlock() + + if !f.valid { + return + } + + f.valid = false + C.seccomp_release(f.filterCtx) +} + +// Merge two filter contexts. +// The source filter src will be released as part of the process, and will no +// longer be usable or valid after this call. +// To be merged, filters must NOT share any architectures, and all their +// attributes must match. +// The filter src will be merged into the filter this is called on. +// The architectures of the src filter not present in the destination, and all +// associated rules, will be added to the destination. +// Returns an error if merging the filters failed. +func (f *ScmpFilter) Merge(src *ScmpFilter) error { + f.lock.Lock() + defer f.lock.Unlock() + + src.lock.Lock() + defer src.lock.Unlock() + + if !src.valid || !f.valid { + return fmt.Errorf( + "One or more of the filter contexts is invalid or uninitialized") + } + + // Merge the filters + retCode := C.seccomp_merge(f.filterCtx, src.filterCtx) + if syscall.Errno(-1*retCode) == syscall.EINVAL { + return fmt.Errorf("Filters could not be merged due to a mismatch in attributes or invalid filter!") + } else if retCode != 0 { + return syscall.Errno(-1 * retCode) + } + + src.valid = false + + return nil +} + +// Check if an architecture is present in a filter. +// If a filter contains an architecture, it uses its default action for +// syscalls which do not match rules in it, and its rules can match syscalls +// for that ABI. +// If a filter does not contain an architecture, all syscalls made to that +// kernel ABI will fail with the filter's default Bad Architecture Action +// (by default, killing the process). +// Accepts an architecture constant. +// Returns true if the architecture is present in the filter, false otherwise, +// and an error on an invalid filter context, architecture constant, or an +// issue with the call to libseccomp. +func (f *ScmpFilter) IsArchPresent(arch ScmpArch) (bool, error) { + f.lock.Lock() + defer f.lock.Unlock() + + if err := sanitizeArch(arch); err != nil { + return false, err + } else if !f.valid { + return false, fmt.Errorf("Filter is invalid or uninitialized") + } + + retCode := C.seccomp_arch_exist(f.filterCtx, arch.toNative()) + if syscall.Errno(-1*retCode) == syscall.EEXIST { + // -EEXIST is "arch not present" + return false, nil + } else if retCode != 0 { + return false, syscall.Errno(-1 * retCode) + } + + return true, nil +} + +// Add an architecture to the filter. +// Accepts an architecture constant. +// Returns an error on invalid filter context or architecture token, or an +// issue with the call to libseccomp. +func (f *ScmpFilter) AddArch(arch ScmpArch) error { + f.lock.Lock() + defer f.lock.Unlock() + + if err := sanitizeArch(arch); err != nil { + return err + } else if !f.valid { + return fmt.Errorf("Filter is invalid or uninitialized") + } + + // Libseccomp returns -EEXIST if the specified architecture is already + // present. Succeed silently in this case, as it's not fatal, and the + // architecture is present already. + retCode := C.seccomp_arch_add(f.filterCtx, arch.toNative()) + if retCode != 0 && syscall.Errno(-1*retCode) != syscall.EEXIST { + return syscall.Errno(-1 * retCode) + } + + return nil +} + +// Remove an architecture from the filter. +// Accepts an architecture constant. +// Returns an error on invalid filter context or architecture token, or an +// issue with the call to libseccomp. +func (f *ScmpFilter) RemoveArch(arch ScmpArch) error { + f.lock.Lock() + defer f.lock.Unlock() + + if err := sanitizeArch(arch); err != nil { + return err + } else if !f.valid { + return fmt.Errorf("Filter is invalid or uninitialized") + } + + // Similar to AddArch, -EEXIST is returned if the arch is not present + // Succeed silently in that case, this is not fatal and the architecture + // is not present in the filter after RemoveArch + retCode := C.seccomp_arch_remove(f.filterCtx, arch.toNative()) + if retCode != 0 && syscall.Errno(-1*retCode) != syscall.EEXIST { + return syscall.Errno(-1 * retCode) + } + + return nil +} + +// Load a filter context into the kernel. +// Returns an error if the filter context is invalid or the syscall failed. +func (f *ScmpFilter) Load() error { + f.lock.Lock() + defer f.lock.Unlock() + + if !f.valid { + return fmt.Errorf("Filter is invalid or uninitialized") + } + + if retCode := C.seccomp_load(f.filterCtx); retCode != 0 { + return syscall.Errno(-1 * retCode) + } + + return nil +} + +// Returns the default action taken on a syscall which does not match a rule in +// the filter, or an error if an issue was encountered retrieving the value. +func (f *ScmpFilter) GetDefaultAction() (ScmpAction, error) { + action, err := f.getFilterAttr(filterAttrActDefault, true) + if err != nil { + return 0x0, err + } + + return actionFromNative(action) +} + +// Returns the default action taken on a syscall for an architecture not in the +// filter, or an error if an issue was encountered retrieving the value. +func (f *ScmpFilter) GetBadArchAction() (ScmpAction, error) { + action, err := f.getFilterAttr(filterAttrActBadArch, true) + if err != nil { + return 0x0, err + } + + return actionFromNative(action) +} + +// Returns the current state the No New Privileges bit will be set to on the +// filter being loaded, or an error if an issue was encountered retrieving the +// value. +// The No New Privileges bit tells the kernel that new processes run with exec() +// cannot gain more privileges than the process that ran exec(). +// For example, a process with No New Privileges set would be unable to exec +// setuid/setgid executables. +func (f *ScmpFilter) GetNoNewPrivsBit() (bool, error) { + noNewPrivs, err := f.getFilterAttr(filterAttrNNP, true) + if err != nil { + return false, err + } + + if noNewPrivs == 0 { + return false, nil + } + + return true, nil +} + +// Set the default action taken on a syscall for an architecture not in the +// filter, or an error if an issue was encountered setting the value. +func (f *ScmpFilter) SetBadArchAction(action ScmpAction) error { + if err := sanitizeAction(action); err != nil { + return err + } + + return f.setFilterAttr(filterAttrActBadArch, action.toNative()) +} + +// Set the state of the No New Privileges bit, which will be applied on filter +// load, or an error if an issue was encountered setting the value. +// Filters with No New Privileges set to 0 can only be loaded with the +// CAP_SYS_ADMIN privilege. +func (f *ScmpFilter) SetNoNewPrivsBit(state bool) error { + var toSet C.uint32_t = 0x0 + + if state { + toSet = 0x1 + } + + return f.setFilterAttr(filterAttrNNP, toSet) +} + +// Set a syscall's priority. +// This provides a hint to the filter generator in libseccomp about the +// importance of this syscall. High-priority syscalls are placed +// first in the filter code, and incur less overhead (at the expense of +// lower-priority syscalls). +func (f *ScmpFilter) SetSyscallPriority(call ScmpSyscall, priority uint8) error { + f.lock.Lock() + defer f.lock.Unlock() + + if !f.valid { + return fmt.Errorf("Filter is invalid or uninitialized") + } + + if retCode := C.seccomp_syscall_priority(f.filterCtx, C.int(call), + C.uint8_t(priority)); retCode != 0 { + return syscall.Errno(-1 * retCode) + } + + return nil +} + +// Add a single rule for an unconditional action on a syscall. +// Accepts the number of the syscall and the action to be taken on the call +// being made. +// Returns an error if an issue was encountered adding the rule. +func (f *ScmpFilter) AddRule(call ScmpSyscall, action ScmpAction) error { + return f.addRuleGeneric(call, action, false, nil) +} + +// Add a single rule for an unconditional action on a syscall. +// Accepts the number of the syscall and the action to be taken on the call +// being made. +// No modifications will be made to the rule, and it will fail to add if it +// cannot be applied to the current architecture without modification. +// The rule will function exactly as described, but it may not function identically +// (or be able to be applied to) all architectures. +// Returns an error if an issue was encountered adding the rule. +func (f *ScmpFilter) AddRuleExact(call ScmpSyscall, action ScmpAction) error { + return f.addRuleGeneric(call, action, true, nil) +} + +// Add a single rule for a conditional action on a syscall. +// Returns an error if an issue was encountered adding the rule. +// All conditions must match for the rule to match. +func (f *ScmpFilter) AddRuleConditional(call ScmpSyscall, action ScmpAction, conds []ScmpCondition) error { + return f.addRuleGeneric(call, action, false, conds) +} + +// Add a single rule for a conditional action on a syscall. +// No modifications will be made to the rule, and it will fail to add if it +// cannot be applied to the current architecture without modification. +// The rule will function exactly as described, but it may not function identically +// (or be able to be applied to) all architectures. +// Returns an error if an issue was encountered adding the rule. +func (f *ScmpFilter) AddRuleConditionalExact(call ScmpSyscall, action ScmpAction, conds []ScmpCondition) error { + return f.addRuleGeneric(call, action, true, conds) +} + +// Output PFC-formatted, human-readable dump of a filter context's rules to a +// file. +// Accepts file to write to (must be open for writing). +// Returns an error if writing to the file fails. +func (f *ScmpFilter) ExportPFC(file *os.File) error { + f.lock.Lock() + defer f.lock.Unlock() + + fd := file.Fd() + + if !f.valid { + return fmt.Errorf("Filter is invalid or uninitialized") + } + + if retCode := C.seccomp_export_pfc(f.filterCtx, C.int(fd)); retCode != 0 { + return syscall.Errno(-1 * retCode) + } + + return nil +} + +// Output Berkeley Packet Filter-formatted, kernel-readable dump of a filter +// context's rules to a file. +// Accepts file to write to (must be open for writing). +// Returns an error if writing to the file fails. +func (f *ScmpFilter) ExportBPF(file *os.File) error { + f.lock.Lock() + defer f.lock.Unlock() + + fd := file.Fd() + + if !f.valid { + return fmt.Errorf("Filter is invalid or uninitialized") + } + + if retCode := C.seccomp_export_bpf(f.filterCtx, C.int(fd)); retCode != 0 { + return syscall.Errno(-1 * retCode) + } + + return nil +} diff --git a/vendor/src/github.com/mheon/libseccomp/src/goseccomp/seccomp_internal.go b/vendor/src/github.com/mheon/libseccomp/src/goseccomp/seccomp_internal.go new file mode 100644 index 000000000..d46df842f --- /dev/null +++ b/vendor/src/github.com/mheon/libseccomp/src/goseccomp/seccomp_internal.go @@ -0,0 +1,380 @@ +// +build linux + +// Internal functions for libseccomp Go bindings +// No exported functions + +/* + * This library is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License as + * published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see . + */ + +package seccomp + +import ( + "fmt" + "os" + "syscall" +) + +// Unexported C wrapping code - provides the C-Golang interface +// Get the seccomp header in scope +// Need stdlib.h for free() on cstrings + +// #cgo LDFLAGS: -lseccomp +/* +#include +#include + +const uint32_t C_ARCH_NATIVE = SCMP_ARCH_NATIVE; +const uint32_t C_ARCH_X86 = SCMP_ARCH_X86; +const uint32_t C_ARCH_X86_64 = SCMP_ARCH_X86_64; +const uint32_t C_ARCH_X32 = SCMP_ARCH_X32; +const uint32_t C_ARCH_ARM = SCMP_ARCH_ARM; + +// Only in v2.2.0 and higher +#ifdef SCMP_ARCH_AARCH64 +const uint32_t C_ARCH_AARCH64 = SCMP_ARCH_AARCH64; +#else +const uint32_t C_ARCH_AARCH64 = 0; +#endif + +const uint32_t C_ACT_KILL = SCMP_ACT_KILL; +const uint32_t C_ACT_TRAP = SCMP_ACT_TRAP; +const uint32_t C_ACT_ERRNO = SCMP_ACT_ERRNO(0); +const uint32_t C_ACT_TRACE = SCMP_ACT_TRACE(0); +const uint32_t C_ACT_ALLOW = SCMP_ACT_ALLOW; + +const uint32_t C_ATTRIBUTE_DEFAULT = (uint32_t)SCMP_FLTATR_ACT_DEFAULT; +const uint32_t C_ATTRIBUTE_BADARCH = (uint32_t)SCMP_FLTATR_ACT_BADARCH; +const uint32_t C_ATTRIBUTE_NNP = (uint32_t)SCMP_FLTATR_CTL_NNP; + +const int C_CMP_NE = (int)SCMP_CMP_NE; +const int C_CMP_LT = (int)SCMP_CMP_LT; +const int C_CMP_LE = (int)SCMP_CMP_LE; +const int C_CMP_EQ = (int)SCMP_CMP_EQ; +const int C_CMP_GE = (int)SCMP_CMP_GE; +const int C_CMP_GT = (int)SCMP_CMP_GT; +const int C_CMP_MASKED_EQ = (int)SCMP_CMP_MASKED_EQ; + +const int C_VERSION_MAJOR = SCMP_VER_MAJOR; +const int C_VERSION_MINOR = SCMP_VER_MINOR; +const int C_VERSION_MICRO = SCMP_VER_MICRO; + +typedef struct scmp_arg_cmp* scmp_cast_t; + +// Wrapper to create an scmp_arg_cmp struct +void* +make_struct_arg_cmp( + unsigned int arg, + int compare, + uint64_t a, + uint64_t b + ) +{ + struct scmp_arg_cmp *s = malloc(sizeof(struct scmp_arg_cmp)); + + s->arg = arg; + s->op = compare; + s->datum_a = a; + s->datum_b = b; + + return s; +} +*/ +import "C" + +// Nonexported types +type scmpFilterAttr uint32 + +// Nonexported constants + +const ( + filterAttrActDefault scmpFilterAttr = iota + filterAttrActBadArch scmpFilterAttr = iota + filterAttrNNP scmpFilterAttr = iota +) + +const ( + // An error return from certain libseccomp functions + scmpError C.int = -1 + // Comparison boundaries to check for architecture validity + archStart ScmpArch = ArchNative + archEnd ScmpArch = ArchARM + // Comparison boundaries to check for action validity + actionStart ScmpAction = ActKill + actionEnd ScmpAction = ActAllow + // Comparison boundaries to check for comparison operator validity + compareOpStart ScmpCompareOp = CompareNotEqual + compareOpEnd ScmpCompareOp = CompareMaskedEqual +) + +// Nonexported functions + +// Init function: Verify library version is appropriate +func init() { + if C.C_VERSION_MAJOR < 2 || C.C_VERSION_MAJOR == 2 && + C.C_VERSION_MINOR < 1 { + + fmt.Fprintf(os.Stderr, "Libseccomp version too low:"+ + "minimum supported is 2.1.0, detected %d.%d.%d", C.C_VERSION_MAJOR, + C.C_VERSION_MINOR, C.C_VERSION_MICRO) + os.Exit(-1) + } +} + +// Filter helpers + +// Filter finalizer - ensure that kernel context for filters is freed +func filterFinalizer(f *ScmpFilter) { + f.Release() +} + +// Get a raw filter attribute +func (f *ScmpFilter) getFilterAttr(attr scmpFilterAttr, lock bool) (C.uint32_t, error) { + if lock { + f.lock.Lock() + defer f.lock.Unlock() + + if !f.valid { + return 0x0, fmt.Errorf("Filter is invalid or uninitialized") + } + } + + var attribute C.uint32_t + + retCode := C.seccomp_attr_get(f.filterCtx, attr.toNative(), &attribute) + if retCode != 0 { + return 0x0, syscall.Errno(-1 * retCode) + } + + return attribute, nil +} + +// Set a raw filter attribute +func (f *ScmpFilter) setFilterAttr(attr scmpFilterAttr, value C.uint32_t) error { + f.lock.Lock() + defer f.lock.Unlock() + + if !f.valid { + return fmt.Errorf("Filter is invalid or uninitialized") + } + + retCode := C.seccomp_attr_set(f.filterCtx, attr.toNative(), value) + if retCode != 0 { + return syscall.Errno(-1 * retCode) + } + + return nil +} + +// DOES NOT LOCK OR CHECK VALIDITY +// Assumes caller has already done this +// Wrapper for seccomp_rule_add_... functions +func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact bool, cond C.scmp_cast_t) error { + var length C.uint + if cond != nil { + length = 1 + } else { + length = 0 + } + + var retCode C.int + if exact { + retCode = C.seccomp_rule_add_exact_array(f.filterCtx, action.toNative(), C.int(call), length, cond) + } else { + retCode = C.seccomp_rule_add_array(f.filterCtx, action.toNative(), C.int(call), length, cond) + } + + if syscall.Errno(-1*retCode) == syscall.EFAULT { + return fmt.Errorf("Unrecognized syscall") + } else if syscall.Errno(-1*retCode) == syscall.EPERM { + return fmt.Errorf("Requested action matches default action of filter") + } else if retCode != 0 { + return syscall.Errno(-1 * retCode) + } + + return nil +} + +// Generic add function for filter rules +func (f *ScmpFilter) addRuleGeneric(call ScmpSyscall, action ScmpAction, exact bool, conds []ScmpCondition) error { + + f.lock.Lock() + defer f.lock.Unlock() + + if !f.valid { + return fmt.Errorf("Filter is invalid or uninitialized") + } + + if len(conds) == 0 { + if err := f.addRuleWrapper(call, action, exact, nil); err != nil { + return err + } + } else { + for _, cond := range conds { + cmpStruct := C.make_struct_arg_cmp(C.uint(cond.Argument), cond.Op.toNative(), C.uint64_t(cond.Operand1), C.uint64_t(cond.Operand2)) + defer C.free(cmpStruct) + + if err := f.addRuleWrapper(call, action, exact, C.scmp_cast_t(cmpStruct)); err != nil { + return err + } + } + } + + return nil +} + +// Generic Helpers + +// Helper - Sanitize Arch token input +func sanitizeArch(in ScmpArch) error { + if in < archStart || in > archEnd { + return fmt.Errorf("Unrecognized architecture") + } else if in == ArchARM64 && C.C_VERSION_MAJOR == 2 && C.C_VERSION_MINOR < 2 { + return fmt.Errorf("AArch64 is not supported before Libseccomp 2.2") + } + + return nil +} + +func sanitizeAction(in ScmpAction) error { + inTmp := in & 0x0000FFFF + if inTmp < actionStart || inTmp > actionEnd { + return fmt.Errorf("Unrecognized action") + } + + if inTmp != ActTrace && inTmp != ActErrno && (in&0xFFFF0000) != 0 { + return fmt.Errorf("Lowest 16 bits must be zeroed except for Trace " + + "and Errno") + } + + return nil +} + +func sanitizeCompareOp(in ScmpCompareOp) error { + if in < compareOpStart || in > compareOpEnd { + return fmt.Errorf("Unrecognized comparison operator") + } + + return nil +} + +func archFromNative(a C.uint32_t) (ScmpArch, error) { + switch a { + case C.C_ARCH_X86: + return ArchX86, nil + case C.C_ARCH_X86_64: + return ArchAMD64, nil + case C.C_ARCH_X32: + return ArchX32, nil + case C.C_ARCH_ARM: + return ArchARM, nil + case C.C_ARCH_NATIVE: + return ArchNative, nil + case C.C_ARCH_AARCH64: + return ArchARM64, nil + default: + return 0x0, fmt.Errorf("Unrecognized architecture") + } +} + +// Only use with sanitized arches, no error handling +func (a ScmpArch) toNative() C.uint32_t { + switch a { + case ArchX86: + return C.C_ARCH_X86 + case ArchAMD64: + return C.C_ARCH_X86_64 + case ArchX32: + return C.C_ARCH_X32 + case ArchARM: + return C.C_ARCH_ARM + case ArchARM64: + return C.C_ARCH_AARCH64 + case ArchNative: + return C.C_ARCH_NATIVE + default: + return 0x0 + } +} + +// Only use with sanitized ops, no error handling +func (a ScmpCompareOp) toNative() C.int { + switch a { + case CompareNotEqual: + return C.C_CMP_NE + case CompareLess: + return C.C_CMP_LT + case CompareLessOrEqual: + return C.C_CMP_LE + case CompareEqual: + return C.C_CMP_EQ + case CompareGreaterEqual: + return C.C_CMP_GE + case CompareGreater: + return C.C_CMP_GT + case CompareMaskedEqual: + return C.C_CMP_MASKED_EQ + default: + return 0x0 + } +} + +func actionFromNative(a C.uint32_t) (ScmpAction, error) { + aTmp := a & 0xFFFF + switch a & 0xFFFF0000 { + case C.C_ACT_KILL: + return ActKill, nil + case C.C_ACT_TRAP: + return ActTrap, nil + case C.C_ACT_ERRNO: + return ActErrno.SetReturnCode(int16(aTmp)), nil + case C.C_ACT_TRACE: + return ActTrace.SetReturnCode(int16(aTmp)), nil + case C.C_ACT_ALLOW: + return ActAllow, nil + default: + return 0x0, fmt.Errorf("Unrecognized action") + } +} + +// Only use with sanitized actions, no error handling +func (a ScmpAction) toNative() C.uint32_t { + switch a & 0xFFFF { + case ActKill: + return C.C_ACT_KILL + case ActTrap: + return C.C_ACT_TRAP + case ActErrno: + return C.C_ACT_ERRNO | (C.uint32_t(a) >> 16) + case ActTrace: + return C.C_ACT_TRACE | (C.uint32_t(a) >> 16) + case ActAllow: + return C.C_ACT_ALLOW + default: + return 0x0 + } +} + +// Internal only, assumes safe action +func (a scmpFilterAttr) toNative() uint32 { + switch a { + case filterAttrActDefault: + return uint32(C.C_ATTRIBUTE_DEFAULT) + case filterAttrActBadArch: + return uint32(C.C_ATTRIBUTE_BADARCH) + case filterAttrNNP: + return uint32(C.C_ATTRIBUTE_NNP) + default: + return 0x0 + } +} diff --git a/vendor/src/github.com/mheon/libseccomp/src/goseccomp/seccomp_test.go b/vendor/src/github.com/mheon/libseccomp/src/goseccomp/seccomp_test.go new file mode 100644 index 000000000..dbf0ef5ca --- /dev/null +++ b/vendor/src/github.com/mheon/libseccomp/src/goseccomp/seccomp_test.go @@ -0,0 +1,468 @@ +// +build linux + +// Tests for public API of libseccomp Go bindings + +/* + * This library is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License as + * published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see . + */ + +package seccomp + +import ( + "fmt" + "syscall" + "testing" +) + +// Type Function Tests + +func TestActionSetReturnCode(t *testing.T) { + if ActInvalid.SetReturnCode(0x0010) != ActInvalid { + t.Errorf("Able to set a return code on invalid action!") + } + + codeSet := ActErrno.SetReturnCode(0x0001) + if codeSet == ActErrno || codeSet.GetReturnCode() != 0x0001 { + t.Errorf("Could not set return code on ActErrno") + } +} + +func TestSyscallGetName(t *testing.T) { + call1 := ScmpSyscall(0x1) + callFail := ScmpSyscall(0x999) + + name, err := call1.GetName() + if err != nil { + t.Errorf("Error getting syscall name for number 0x1") + } else if len(name) == 0 { + t.Errorf("Empty name returned for syscall 0x1") + } + fmt.Printf("Got name of syscall 0x1 on native arch as %s\n", name) + + _, err = callFail.GetName() + if err == nil { + t.Errorf("Getting nonexistant syscall should error!") + } +} + +func TestSyscallGetNameByArch(t *testing.T) { + call1 := ScmpSyscall(0x1) + callInvalid := ScmpSyscall(0x999) + archGood := ArchAMD64 + archBad := ArchInvalid + + name, err := call1.GetNameByArch(archGood) + if err != nil { + t.Errorf("Error getting syscall name for number 0x1 and arch AMD64") + } else if name != "write" { + t.Errorf("Got incorrect name for syscall 0x1 - expected write, got %s", name) + } + + _, err = call1.GetNameByArch(archBad) + if err == nil { + t.Errorf("Bad architecture GetNameByArch() should error!") + } + + _, err = callInvalid.GetNameByArch(archGood) + if err == nil { + t.Errorf("Bad syscall GetNameByArch() should error!") + } + + _, err = callInvalid.GetNameByArch(archBad) + if err == nil { + t.Errorf("Bad syscall and bad arch GetNameByArch() should error!") + } +} + +func TestGetSyscallFromName(t *testing.T) { + name1 := "write" + nameInval := "NOTASYSCALL" + + syscall, err := GetSyscallFromName(name1) + if err != nil { + t.Errorf("Error getting syscall number of write: %s", err) + } + fmt.Printf("Got syscall number of write on native arch as %d\n", syscall) + + _, err = GetSyscallFromName(nameInval) + if err == nil { + t.Errorf("Getting an invalid syscall should error!") + } +} + +func TestGetSyscallFromNameByArch(t *testing.T) { + name1 := "write" + nameInval := "NOTASYSCALL" + arch1 := ArchAMD64 + archInval := ArchInvalid + + syscall, err := GetSyscallFromNameByArch(name1, arch1) + if err != nil { + t.Errorf("Error getting syscall number of write on AMD64: %s", err) + } + fmt.Printf("Got syscall number of write on AMD64 as %d\n", syscall) + + _, err = GetSyscallFromNameByArch(nameInval, arch1) + if err == nil { + t.Errorf("Getting invalid syscall with valid arch should error") + } + + _, err = GetSyscallFromNameByArch(name1, archInval) + if err == nil { + t.Errorf("Getting valid syscall for invalid arch should error") + } + + _, err = GetSyscallFromNameByArch(nameInval, archInval) + if err == nil { + t.Errorf("Getting invalid syscall for invalid arch should error") + } +} + +func TestMakeCondition(t *testing.T) { + condition, err := MakeCondition(3, CompareNotEqual, 0x10) + if err != nil { + t.Errorf("Error making condition struct: %s", err) + } else if condition.Argument != 3 || condition.Operand1 != 0x10 || + condition.Operand2 != 0 || condition.Op != CompareNotEqual { + t.Errorf("Condition struct was filled incorrectly") + } + + condition, err = MakeCondition(3, CompareMaskedEqual, 0x10, 0x20) + if err != nil { + t.Errorf("Error making condition struct: %s", err) + } else if condition.Argument != 3 || condition.Operand1 != 0x10 || + condition.Operand2 != 0x20 || condition.Op != CompareMaskedEqual { + t.Errorf("Condition struct was filled incorrectly") + } + + _, err = MakeCondition(7, CompareNotEqual, 0x10) + if err == nil { + t.Errorf("Condition struct with bad syscall argument number should error") + } + + _, err = MakeCondition(3, CompareInvalid, 0x10) + if err == nil { + t.Errorf("Condition struct with bad comparison operator should error") + } + + _, err = MakeCondition(3, CompareMaskedEqual, 0x10, 0x20, 0x30) + if err == nil { + t.Errorf("MakeCondition with more than 2 arguments should fail") + } + + _, err = MakeCondition(3, CompareMaskedEqual) + if err == nil { + t.Errorf("MakeCondition with no arguments should fail") + } +} + +// Utility Function Tests + +func TestGetNativeArch(t *testing.T) { + arch, err := GetNativeArch() + if err != nil { + t.Errorf("GetNativeArch should not error!") + } + fmt.Printf("Got native arch of system as %s\n", arch.String()) +} + +// Filter Tests + +func TestFilterCreateRelease(t *testing.T) { + _, err := NewFilter(ActInvalid) + if err == nil { + t.Errorf("Can create filter with invalid action") + } + + filter, err := NewFilter(ActKill) + if err != nil { + t.Errorf("Error creating filter: %s", err) + } + + if !filter.IsValid() { + t.Errorf("Filter created by NewFilter was not valid") + } + + filter.Release() + + if filter.IsValid() { + t.Errorf("Filter is valid after being released") + } +} + +func TestFilterReset(t *testing.T) { + filter, err := NewFilter(ActKill) + if err != nil { + t.Errorf("Error creating filter: %s", err) + } + defer filter.Release() + + // Ensure the default action is ActKill + action, err := filter.GetDefaultAction() + if err != nil { + t.Errorf("Error getting default action of filter") + } else if action != ActKill { + t.Errorf("Default action of filter was set incorrectly!") + } + + // Reset with a different default action + err = filter.Reset(ActAllow) + if err != nil { + t.Errorf("Error resetting filter!") + } + + valid := filter.IsValid() + if !valid { + t.Errorf("Filter is no longer valid after reset!") + } + + // The default action should no longer be ActKill + action, err = filter.GetDefaultAction() + if err != nil { + t.Errorf("Error getting default action of filter") + } else if action != ActAllow { + t.Errorf("Default action of filter was set incorrectly!") + } +} + +func TestFilterArchFunctions(t *testing.T) { + filter, err := NewFilter(ActKill) + if err != nil { + t.Errorf("Error creating filter: %s", err) + } + defer filter.Release() + + arch, err := GetNativeArch() + if err != nil { + t.Errorf("Error getting native architecture: %s", err) + } + + present, err := filter.IsArchPresent(arch) + if err != nil { + t.Errorf("Error retrieving arch from filter: %s", err) + } else if !present { + t.Errorf("Filter does not contain native architecture by default") + } + + // Adding the native arch again should succeed, as it's already present + err = filter.AddArch(arch) + if err != nil { + t.Errorf("Adding arch to filter already containing it should succeed") + } + + // Make sure we don't add the native arch again + prospectiveArch := ArchX86 + if arch == ArchX86 { + prospectiveArch = ArchAMD64 + } + + // Check to make sure this other arch isn't in the filter + present, err = filter.IsArchPresent(prospectiveArch) + if err != nil { + t.Errorf("Error retrieving arch from filter: %s", err) + } else if present { + t.Errorf("Arch not added to filter is present") + } + + // Try removing the nonexistant arch - should succeed + err = filter.RemoveArch(prospectiveArch) + if err != nil { + t.Errorf("Error removing nonexistant arch: %s", err) + } + + // Add an arch, see if it's in the filter + err = filter.AddArch(prospectiveArch) + if err != nil { + t.Errorf("Could not add arch %s to filter: %s", + prospectiveArch.String(), err) + } + + present, err = filter.IsArchPresent(prospectiveArch) + if err != nil { + t.Errorf("Error retrieving arch from filter: %s", err) + } else if !present { + t.Errorf("Filter does not contain architecture %s after it was added", + prospectiveArch.String()) + } + + // Remove the arch again, make sure it's not in the filter + err = filter.RemoveArch(prospectiveArch) + if err != nil { + fmt.Errorf("Could not remove arch %s from filter: %s", + prospectiveArch.String(), err) + } + + present, err = filter.IsArchPresent(prospectiveArch) + if err != nil { + t.Errorf("Error retrieving arch from filter: %s", err) + } else if present { + t.Errorf("Filter contains architecture %s after it was removed", + prospectiveArch.String()) + } +} + +func TestFilterAttributeGettersAndSetters(t *testing.T) { + filter, err := NewFilter(ActKill) + if err != nil { + t.Errorf("Error creating filter: %s", err) + } + defer filter.Release() + + act, err := filter.GetDefaultAction() + if err != nil { + t.Errorf("Error getting default action: %s", err) + } else if act != ActKill { + t.Errorf("Default action was set incorrectly") + } + + err = filter.SetBadArchAction(ActAllow) + if err != nil { + t.Errorf("Error setting bad arch action: %s", err) + } + + act, err = filter.GetBadArchAction() + if err != nil { + t.Errorf("Error getting bad arch action") + } else if act != ActAllow { + t.Errorf("Bad arch action was not set correcly!") + } + + err = filter.SetNoNewPrivsBit(false) + if err != nil { + t.Errorf("Error setting no new privileges bit") + } + + privs, err := filter.GetNoNewPrivsBit() + if err != nil { + t.Errorf("Error getting no new privileges bit!") + } else if privs != false { + t.Errorf("No new privileges bit was not set correctly") + } + + err = filter.SetBadArchAction(ActInvalid) + if err == nil { + t.Errorf("Setting bad arch action to an invalid action should error") + } +} + +func TestMergeFilters(t *testing.T) { + filter1, err := NewFilter(ActAllow) + if err != nil { + t.Errorf("Error creating filter: %s", err) + } + + filter2, err := NewFilter(ActAllow) + if err != nil { + t.Errorf("Error creating filter: %s", err) + } + + // Need to remove the native arch and add another to the second filter + // Filters must NOT share architectures to be successfully merged + nativeArch, err := GetNativeArch() + if err != nil { + t.Errorf("Error getting native arch: %s", err) + } + + prospectiveArch := ArchAMD64 + if nativeArch == ArchAMD64 { + prospectiveArch = ArchX86 + } + + err = filter2.AddArch(prospectiveArch) + if err != nil { + t.Errorf("Error adding architecture to filter: %s", err) + } + + err = filter2.RemoveArch(nativeArch) + if err != nil { + t.Errorf("Error removing architecture from filter: %s", err) + } + + err = filter1.Merge(filter2) + if err != nil { + t.Errorf("Error merging filters: %s", err) + } + + if filter2.IsValid() { + t.Errorf("Source filter should not be valid after merging") + } + + filter3, err := NewFilter(ActKill) + if err != nil { + t.Errorf("Error creating filter: %s", err) + } + defer filter3.Release() + + err = filter1.Merge(filter3) + if err == nil { + t.Errorf("Attributes should have to match to merge filters") + } +} + +func TestRuleAddAndLoad(t *testing.T) { + // Test #1: Add a trivial filter + filter1, err := NewFilter(ActAllow) + if err != nil { + t.Errorf("Error creating filter: %s", err) + } + defer filter1.Release() + + call, err := GetSyscallFromName("getpid") + if err != nil { + t.Errorf("Error getting syscall number of getpid: %s", err) + } + + call2, err := GetSyscallFromName("setreuid") + if err != nil { + t.Errorf("Error getting syscall number of setreuid: %s", err) + } + + uid := syscall.Getuid() + euid := syscall.Geteuid() + + err = filter1.AddRule(call, ActErrno.SetReturnCode(0x1)) + if err != nil { + t.Errorf("Error adding rule to restrict syscall: %s", err) + } + + cond, err := MakeCondition(1, CompareEqual, uint64(euid)) + if err != nil { + t.Errorf("Error making rule to restrict syscall: %s", err) + } + + cond2, err := MakeCondition(0, CompareEqual, uint64(uid)) + if err != nil { + t.Errorf("Error making rule to restrict syscall: %s", err) + } + + conditions := []ScmpCondition{cond, cond2} + + err = filter1.AddRuleConditional(call2, ActErrno.SetReturnCode(0x2), conditions) + + err = filter1.Load() + if err != nil { + t.Errorf("Error loading filter: %s", err) + } + + // Try making a simple syscall, it should error + pid := syscall.Getpid() + if pid != -1 { + t.Errorf("Syscall should have returned error code!") + } + + // Try making a Geteuid syscall that should normally succeed + err = syscall.Setreuid(uid, euid) + if err != syscall.Errno(2) { + t.Errorf("Syscall should have returned error code!") + } +}