// Copyright 2018 The gVisor Authors. // // 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" "math/rand" "os" "os/exec" "strings" "testing" "time" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/bpf" "gvisor.dev/gvisor/pkg/usermem" ) // 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 } // dataAsInput converts a linux.SeccompData to a bpf.Input. func dataAsInput(d *linux.SeccompData) bpf.Input { buf := make([]byte, d.SizeBytes()) d.MarshalUnsafe(buf) return bpf.InputBytes{ Data: buf, Order: usermem.ByteOrder, } } func TestBasic(t *testing.T) { type spec struct { // desc is the test's description. desc string // data is the input data. data linux.SeccompData // want is the expected return value of the BPF program. want linux.BPFAction } for _, test := range []struct { name string ruleSets []RuleSet defaultAction linux.BPFAction badArchAction linux.BPFAction specs []spec }{ { name: "Single syscall", ruleSets: []RuleSet{ { Rules: SyscallRules{1: {}}, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "syscall allowed", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "syscall disallowed", data: linux.SeccompData{Nr: 2, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_TRAP, }, }, }, { name: "Multiple rulesets", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { EqualTo(0x1), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, { Rules: SyscallRules{ 1: {}, 2: {}, }, Action: linux.SECCOMP_RET_TRAP, }, }, defaultAction: linux.SECCOMP_RET_KILL_THREAD, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "allowed (1a)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x1}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "allowed (1b)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_TRAP, }, { desc: "syscall 1 matched 2nd rule", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_TRAP, }, { desc: "no match", data: linux.SeccompData{Nr: 0, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_KILL_THREAD, }, }, }, { name: "Multiple syscalls", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: {}, 3: {}, 5: {}, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "allowed (1)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "allowed (3)", data: linux.SeccompData{Nr: 3, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "allowed (5)", data: linux.SeccompData{Nr: 5, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "disallowed (0)", data: linux.SeccompData{Nr: 0, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_TRAP, }, { desc: "disallowed (2)", data: linux.SeccompData{Nr: 2, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_TRAP, }, { desc: "disallowed (4)", data: linux.SeccompData{Nr: 4, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_TRAP, }, { desc: "disallowed (6)", data: linux.SeccompData{Nr: 6, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_TRAP, }, { desc: "disallowed (100)", data: linux.SeccompData{Nr: 100, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_TRAP, }, }, }, { name: "Wrong architecture", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: {}, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "arch (123)", data: linux.SeccompData{Nr: 1, Arch: 123}, want: linux.SECCOMP_RET_KILL_THREAD, }, }, }, { name: "Syscall disallowed", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: {}, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "action trap", data: linux.SeccompData{Nr: 2, Arch: LINUX_AUDIT_ARCH}, want: linux.SECCOMP_RET_TRAP, }, }, }, { name: "Syscall arguments", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { MatchAny{}, EqualTo(0xf), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "allowed", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0xf, 0xf}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "disallowed", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0xf, 0xe}}, want: linux.SECCOMP_RET_TRAP, }, }, }, { name: "Multiple arguments", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { EqualTo(0xf), }, { EqualTo(0xe), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "match first rule", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0xf}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "match 2nd rule", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0xe}}, want: linux.SECCOMP_RET_ALLOW, }, }, }, { name: "EqualTo", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { EqualTo(0), EqualTo(math.MaxUint64 - 1), EqualTo(math.MaxUint32), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "argument allowed (all match)", data: linux.SeccompData{ Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0, math.MaxUint64 - 1, math.MaxUint32}, }, want: linux.SECCOMP_RET_ALLOW, }, { desc: "argument disallowed (one mismatch)", data: linux.SeccompData{ Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0, math.MaxUint64, math.MaxUint32}, }, want: linux.SECCOMP_RET_TRAP, }, { desc: "argument disallowed (multiple mismatch)", data: linux.SeccompData{ Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0, math.MaxUint64, math.MaxUint32 - 1}, }, want: linux.SECCOMP_RET_TRAP, }, }, }, { name: "NotEqual", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { NotEqual(0x7aabbccdd), NotEqual(math.MaxUint64 - 1), NotEqual(math.MaxUint32), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "arg allowed", data: linux.SeccompData{ Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0, math.MaxUint64, math.MaxUint32 - 1}, }, want: linux.SECCOMP_RET_ALLOW, }, { desc: "arg disallowed (one equal)", data: linux.SeccompData{ Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x7aabbccdd, math.MaxUint64, math.MaxUint32 - 1}, }, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg disallowed (all equal)", data: linux.SeccompData{ Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x7aabbccdd, math.MaxUint64 - 1, math.MaxUint32}, }, want: linux.SECCOMP_RET_TRAP, }, }, }, { name: "GreaterThan", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { // 4294967298 // Both upper 32 bits and lower 32 bits are non-zero. // 00000000000000000000000000000010 // 00000000000000000000000000000010 GreaterThan(0x00000002_00000002), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "high 32bits greater", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000003_00000002}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "high 32bits equal, low 32bits greater", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000002_00000003}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "high 32bits equal, low 32bits equal", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000002_00000002}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "high 32bits equal, low 32bits less", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000002_00000001}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "high 32bits less", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000001_00000003}}, want: linux.SECCOMP_RET_TRAP, }, }, }, { name: "GreaterThan (multi)", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { GreaterThan(0xf), GreaterThan(0xabcd000d), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "arg allowed", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x10, 0xffffffff}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "arg disallowed (first arg equal)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0xf, 0xffffffff}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg disallowed (first arg smaller)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x0, 0xffffffff}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg disallowed (second arg equal)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x10, 0xabcd000d}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg disallowed (second arg smaller)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x10, 0xa000ffff}}, want: linux.SECCOMP_RET_TRAP, }, }, }, { name: "GreaterThanOrEqual", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { // 4294967298 // Both upper 32 bits and lower 32 bits are non-zero. // 00000000000000000000000000000010 // 00000000000000000000000000000010 GreaterThanOrEqual(0x00000002_00000002), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "high 32bits greater", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000003_00000002}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "high 32bits equal, low 32bits greater", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000002_00000003}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "high 32bits equal, low 32bits equal", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000002_00000002}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "high 32bits equal, low 32bits less", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000002_00000001}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "high 32bits less", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000001_00000002}}, want: linux.SECCOMP_RET_TRAP, }, }, }, { name: "GreaterThanOrEqual (multi)", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { GreaterThanOrEqual(0xf), GreaterThanOrEqual(0xabcd000d), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "arg allowed (both greater)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x10, 0xffffffff}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "arg allowed (first arg equal)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0xf, 0xffffffff}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "arg disallowed (first arg smaller)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x0, 0xffffffff}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg allowed (second arg equal)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x10, 0xabcd000d}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "arg disallowed (second arg smaller)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x10, 0xa000ffff}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg disallowed (both arg smaller)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x0, 0xa000ffff}}, want: linux.SECCOMP_RET_TRAP, }, }, }, { name: "LessThan", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { // 4294967298 // Both upper 32 bits and lower 32 bits are non-zero. // 00000000000000000000000000000010 // 00000000000000000000000000000010 LessThan(0x00000002_00000002), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "high 32bits greater", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000003_00000002}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "high 32bits equal, low 32bits greater", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000002_00000003}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "high 32bits equal, low 32bits equal", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000002_00000002}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "high 32bits equal, low 32bits less", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000002_00000001}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "high 32bits less", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000001_00000002}}, want: linux.SECCOMP_RET_ALLOW, }, }, }, { name: "LessThan (multi)", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { LessThan(0x1), LessThan(0xabcd000d), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "arg allowed", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x0, 0x0}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "arg disallowed (first arg equal)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x1, 0x0}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg disallowed (first arg greater)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x2, 0x0}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg disallowed (second arg equal)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x0, 0xabcd000d}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg disallowed (second arg greater)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x0, 0xffffffff}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg disallowed (both arg greater)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x2, 0xffffffff}}, want: linux.SECCOMP_RET_TRAP, }, }, }, { name: "LessThanOrEqual", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { // 4294967298 // Both upper 32 bits and lower 32 bits are non-zero. // 00000000000000000000000000000010 // 00000000000000000000000000000010 LessThanOrEqual(0x00000002_00000002), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "high 32bits greater", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000003_00000002}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "high 32bits equal, low 32bits greater", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000002_00000003}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "high 32bits equal, low 32bits equal", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000002_00000002}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "high 32bits equal, low 32bits less", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000002_00000001}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "high 32bits less", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x00000001_00000002}}, want: linux.SECCOMP_RET_ALLOW, }, }, }, { name: "LessThanOrEqual (multi)", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { LessThanOrEqual(0x1), LessThanOrEqual(0xabcd000d), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "arg allowed", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x0, 0x0}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "arg allowed (first arg equal)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x1, 0x0}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "arg disallowed (first arg greater)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x2, 0x0}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg allowed (second arg equal)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x0, 0xabcd000d}}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "arg disallowed (second arg greater)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x0, 0xffffffff}}, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg disallowed (both arg greater)", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{0x2, 0xffffffff}}, want: linux.SECCOMP_RET_TRAP, }, }, }, { name: "MaskedEqual", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { // x & 00000001 00000011 (0x103) == 00000000 00000001 (0x1) // Input x must have lowest order bit set and // must *not* have 8th or second lowest order bit set. MaskedEqual(0x103, 0x1), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "arg allowed (low order mandatory bit)", data: linux.SeccompData{ Nr: 1, Arch: LINUX_AUDIT_ARCH, // 00000000 00000000 00000000 00000001 Args: [6]uint64{0x1}, }, want: linux.SECCOMP_RET_ALLOW, }, { desc: "arg allowed (low order optional bit)", data: linux.SeccompData{ Nr: 1, Arch: LINUX_AUDIT_ARCH, // 00000000 00000000 00000000 00000101 Args: [6]uint64{0x5}, }, want: linux.SECCOMP_RET_ALLOW, }, { desc: "arg disallowed (lowest order bit not set)", data: linux.SeccompData{ Nr: 1, Arch: LINUX_AUDIT_ARCH, // 00000000 00000000 00000000 00000010 Args: [6]uint64{0x2}, }, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg disallowed (second lowest order bit set)", data: linux.SeccompData{ Nr: 1, Arch: LINUX_AUDIT_ARCH, // 00000000 00000000 00000000 00000011 Args: [6]uint64{0x3}, }, want: linux.SECCOMP_RET_TRAP, }, { desc: "arg disallowed (8th bit set)", data: linux.SeccompData{ Nr: 1, Arch: LINUX_AUDIT_ARCH, // 00000000 00000000 00000001 00000000 Args: [6]uint64{0x100}, }, want: linux.SECCOMP_RET_TRAP, }, }, }, { name: "Instruction Pointer", ruleSets: []RuleSet{ { Rules: SyscallRules{ 1: []Rule{ { RuleIP: EqualTo(0x7aabbccdd), }, }, }, Action: linux.SECCOMP_RET_ALLOW, }, }, defaultAction: linux.SECCOMP_RET_TRAP, badArchAction: linux.SECCOMP_RET_KILL_THREAD, specs: []spec{ { desc: "allowed", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{}, InstructionPointer: 0x7aabbccdd}, want: linux.SECCOMP_RET_ALLOW, }, { desc: "disallowed", data: linux.SeccompData{Nr: 1, Arch: LINUX_AUDIT_ARCH, Args: [6]uint64{}, InstructionPointer: 0x711223344}, want: linux.SECCOMP_RET_TRAP, }, }, }, } { t.Run(test.name, func(t *testing.T) { instrs, err := BuildProgram(test.ruleSets, test.defaultAction, test.badArchAction) 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 _, spec := range test.specs { got, err := bpf.Exec(p, dataAsInput(&spec.data)) if err != nil { t.Fatalf("%s: bpf.Exec() got error: %v", spec.desc, err) } if got != uint32(spec.want) { // Include a decoded version of the program in output for debugging purposes. decoded, _ := bpf.DecodeInstructions(instrs) t.Fatalf("%s: got: %d, want: %d\nBPF Program\n%s", spec.desc, got, spec.want, decoded) } } }) } } // TestRandom tests that randomly generated rules are encoded correctly. func TestRandom(t *testing.T) { rand.Seed(time.Now().UnixNano()) size := rand.Intn(50) + 1 syscallRules := make(map[uintptr][]Rule) for len(syscallRules) < size { n := uintptr(rand.Intn(200)) if _, ok := syscallRules[n]; !ok { syscallRules[n] = []Rule{} } } t.Logf("Testing filters: %v", syscallRules) instrs, err := BuildProgram([]RuleSet{ { Rules: syscallRules, Action: linux.SECCOMP_RET_ALLOW, }, }, linux.SECCOMP_RET_TRAP, linux.SECCOMP_RET_KILL_THREAD) 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 := linux.SeccompData{Nr: int32(i), Arch: LINUX_AUDIT_ARCH} got, err := bpf.Exec(p, dataAsInput(&data)) if err != nil { t.Errorf("bpf.Exec() got error: %v, for syscall %d", err, i) continue } want := linux.SECCOMP_RET_TRAP if _, ok := syscallRules[uintptr(i)]; ok { want = linux.SECCOMP_RET_ALLOW } if got != uint32(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 } // Depending on kernel version, either RET_TRAP or RET_KILL_PROCESS is // used. RET_TRAP dumps reason for exit in output, while RET_KILL_PROCESS // returns SIGSYS as exit status. if !strings.Contains(string(out), test.want) && !strings.Contains(err.Error(), test.want) { t.Errorf("Victim error is wrong, got: %v, err: %v, want: %v", string(out), err, test.want) 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", string(out), test.want) continue } } } } // TestMerge ensures that empty rules are not erased when rules are merged. func TestMerge(t *testing.T) { for _, tst := range []struct { name string main []Rule merge []Rule want []Rule }{ { name: "empty both", main: nil, merge: nil, want: []Rule{{}, {}}, }, { name: "empty main", main: nil, merge: []Rule{{}}, want: []Rule{{}, {}}, }, { name: "empty merge", main: []Rule{{}}, merge: nil, want: []Rule{{}, {}}, }, } { t.Run(tst.name, func(t *testing.T) { mainRules := SyscallRules{1: tst.main} mergeRules := SyscallRules{1: tst.merge} mainRules.Merge(mergeRules) if got, want := len(mainRules[1]), len(tst.want); got != want { t.Errorf("wrong length, got: %d, want: %d", got, want) } for i, r := range mainRules[1] { if r != tst.want[i] { t.Errorf("result, got: %v, want: %v", r, tst.want[i]) } } }) } } // TestAddRule ensures that empty rules are not erased when rules are added. func TestAddRule(t *testing.T) { rules := SyscallRules{1: {}} rules.AddRule(1, Rule{}) if got, want := len(rules[1]), 2; got != want { t.Errorf("len(rules[1]), got: %d, want: %d", got, want) } }