summaryrefslogtreecommitdiffhomepage
path: root/pkg/seccomp/seccomp_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/seccomp/seccomp_test.go')
-rw-r--r--pkg/seccomp/seccomp_test.go268
1 files changed, 268 insertions, 0 deletions
diff --git a/pkg/seccomp/seccomp_test.go b/pkg/seccomp/seccomp_test.go
new file mode 100644
index 000000000..c700d88d6
--- /dev/null
+++ b/pkg/seccomp/seccomp_test.go
@@ -0,0 +1,268 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package seccomp
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "math/rand"
+ "os"
+ "os/exec"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+
+ "gvisor.googlesource.com/gvisor/pkg/abi/linux"
+ "gvisor.googlesource.com/gvisor/pkg/binary"
+ "gvisor.googlesource.com/gvisor/pkg/bpf"
+)
+
+type seccompData struct {
+ nr uint32
+ arch uint32
+ instructionPointer uint64
+ args [6]uint64
+}
+
+// newVictim makes a victim binary.
+func newVictim() (string, error) {
+ f, err := ioutil.TempFile("", "victim")
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+ path := f.Name()
+ if _, err := io.Copy(f, bytes.NewBuffer(victimData)); err != nil {
+ os.Remove(path)
+ return "", err
+ }
+ if err := os.Chmod(path, 0755); err != nil {
+ os.Remove(path)
+ return "", err
+ }
+ return path, nil
+}
+
+// asInput converts a seccompData to a bpf.Input.
+func (d *seccompData) asInput() bpf.Input {
+ return bpf.InputBytes{binary.Marshal(nil, binary.LittleEndian, d), binary.LittleEndian}
+}
+
+func TestBasic(t *testing.T) {
+ type spec struct {
+ // desc is the test's description.
+ desc string
+
+ // data is the input data.
+ data seccompData
+
+ // want is the expected return value of the BPF program.
+ want uint32
+ }
+
+ for _, test := range []struct {
+ // filters are the set of syscall that are allowed.
+ filters []uintptr
+ kill bool
+ specs []spec
+ }{
+ {
+ filters: []uintptr{1},
+ kill: false,
+ specs: []spec{
+ {
+ desc: "Single syscall allowed",
+ data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64},
+ want: linux.SECCOMP_RET_ALLOW,
+ },
+ {
+ desc: "Single syscall disallowed",
+ data: seccompData{nr: 2, arch: linux.AUDIT_ARCH_X86_64},
+ want: linux.SECCOMP_RET_TRAP,
+ },
+ },
+ },
+ {
+ filters: []uintptr{1, 3, 5},
+ kill: false,
+ specs: []spec{
+ {
+ desc: "Multiple syscalls allowed (1)",
+ data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64},
+ want: linux.SECCOMP_RET_ALLOW,
+ },
+ {
+ desc: "Multiple syscalls allowed (3)",
+ data: seccompData{nr: 3, arch: linux.AUDIT_ARCH_X86_64},
+ want: linux.SECCOMP_RET_ALLOW,
+ },
+ {
+ desc: "Multiple syscalls allowed (5)",
+ data: seccompData{nr: 5, arch: linux.AUDIT_ARCH_X86_64},
+ want: linux.SECCOMP_RET_ALLOW,
+ },
+ {
+ desc: "Multiple syscalls disallowed (0)",
+ data: seccompData{nr: 0, arch: linux.AUDIT_ARCH_X86_64},
+ want: linux.SECCOMP_RET_TRAP,
+ },
+ {
+ desc: "Multiple syscalls disallowed (2)",
+ data: seccompData{nr: 2, arch: linux.AUDIT_ARCH_X86_64},
+ want: linux.SECCOMP_RET_TRAP,
+ },
+ {
+ desc: "Multiple syscalls disallowed (4)",
+ data: seccompData{nr: 4, arch: linux.AUDIT_ARCH_X86_64},
+ want: linux.SECCOMP_RET_TRAP,
+ },
+ {
+ desc: "Multiple syscalls disallowed (6)",
+ data: seccompData{nr: 6, arch: linux.AUDIT_ARCH_X86_64},
+ want: linux.SECCOMP_RET_TRAP,
+ },
+ {
+ desc: "Multiple syscalls disallowed (100)",
+ data: seccompData{nr: 100, arch: linux.AUDIT_ARCH_X86_64},
+ want: linux.SECCOMP_RET_TRAP,
+ },
+ },
+ },
+ {
+ filters: []uintptr{1},
+ kill: false,
+ specs: []spec{
+ {
+ desc: "Wrong architecture",
+ data: seccompData{nr: 1, arch: 123},
+ want: linux.SECCOMP_RET_TRAP,
+ },
+ },
+ },
+ {
+ filters: []uintptr{1},
+ kill: true,
+ specs: []spec{
+ {
+ desc: "Syscall disallowed, action kill",
+ data: seccompData{nr: 2, arch: linux.AUDIT_ARCH_X86_64},
+ want: linux.SECCOMP_RET_KILL,
+ },
+ },
+ },
+ } {
+ sort.Slice(test.filters, func(i, j int) bool { return test.filters[i] < test.filters[j] })
+ instrs, err := buildProgram(test.filters, test.kill)
+ if err != nil {
+ t.Errorf("%s: buildProgram() got error: %v", test.specs[0].desc, err)
+ continue
+ }
+ p, err := bpf.Compile(instrs)
+ if err != nil {
+ t.Errorf("%s: bpf.Compile() got error: %v", test.specs[0].desc, err)
+ continue
+ }
+ for _, spec := range test.specs {
+ got, err := bpf.Exec(p, spec.data.asInput())
+ if err != nil {
+ t.Errorf("%s: bpf.Exec() got error: %v", spec.desc, err)
+ continue
+ }
+ if got != spec.want {
+ t.Errorf("%s: bpd.Exec() = %d, want: %d", spec.desc, got, spec.want)
+ }
+ }
+ }
+}
+
+func TestRandom(t *testing.T) {
+ rand.Seed(time.Now().UnixNano())
+ size := rand.Intn(50) + 1
+ syscalls := make([]uintptr, 0, size)
+ syscallMap := make(map[uintptr]struct{})
+ for len(syscalls) < size {
+ n := uintptr(rand.Intn(200))
+ if _, ok := syscallMap[n]; !ok {
+ syscalls = append(syscalls, n)
+ syscallMap[n] = struct{}{}
+ }
+ }
+
+ sort.Slice(syscalls, func(i, j int) bool { return syscalls[i] < syscalls[j] })
+ fmt.Printf("Testing filters: %v", syscalls)
+ instrs, err := buildProgram(syscalls, false)
+ if err != nil {
+ t.Fatalf("buildProgram() got error: %v", err)
+ }
+ p, err := bpf.Compile(instrs)
+ if err != nil {
+ t.Fatalf("bpf.Compile() got error: %v", err)
+ }
+ for i := uint32(0); i < 200; i++ {
+ data := seccompData{nr: i, arch: linux.AUDIT_ARCH_X86_64}
+ got, err := bpf.Exec(p, data.asInput())
+ if err != nil {
+ t.Errorf("bpf.Exec() got error: %v, for syscall %d", err, i)
+ continue
+ }
+ want := uint32(linux.SECCOMP_RET_TRAP)
+ if _, ok := syscallMap[uintptr(i)]; ok {
+ want = linux.SECCOMP_RET_ALLOW
+ }
+ if got != want {
+ t.Errorf("bpf.Exec() = %d, want: %d, for syscall %d", got, want, i)
+ }
+ }
+}
+
+// TestReadDeal checks that a process dies when it trips over the filter and that it
+// doesn't die when the filter is not triggered.
+func TestRealDeal(t *testing.T) {
+ for _, test := range []struct {
+ die bool
+ want string
+ }{
+ {die: true, want: "bad system call"},
+ {die: false, want: "Syscall was allowed!!!"},
+ } {
+ victim, err := newVictim()
+ if err != nil {
+ t.Fatalf("unable to get victim: %v", err)
+ }
+ defer os.Remove(victim)
+ dieFlag := fmt.Sprintf("-die=%v", test.die)
+ cmd := exec.Command(victim, dieFlag)
+
+ out, err := cmd.CombinedOutput()
+ if test.die {
+ if err == nil {
+ t.Errorf("victim was not killed as expected, output: %s", out)
+ continue
+ }
+ } else {
+ if err != nil {
+ t.Errorf("victim failed to execute, err: %v", err)
+ continue
+ }
+ }
+ if !strings.Contains(string(out), test.want) {
+ t.Errorf("Victim output is wrong, got: %v, want: %v", err, test.want)
+ continue
+ }
+ }
+}