From d1ca50d49e52338feb1d46b69725b9ac21cc3ccc Mon Sep 17 00:00:00 2001 From: Zhengyu He Date: Fri, 1 Jun 2018 13:39:53 -0700 Subject: Add SyscallRules that supports argument filtering PiperOrigin-RevId: 198919043 Change-Id: I7f1f0a3b3430cd0936a4ee4fc6859aab71820bdf --- pkg/bpf/program_builder.go | 64 ++++++++--- pkg/seccomp/BUILD | 4 + pkg/seccomp/seccomp.go | 217 +++++++++++++++++++++++-------------- pkg/seccomp/seccomp_rules.go | 119 ++++++++++++++++++++ pkg/seccomp/seccomp_test.go | 125 ++++++++++++++++++--- pkg/seccomp/seccomp_test_victim.go | 141 ++++++++++++------------ 6 files changed, 486 insertions(+), 184 deletions(-) create mode 100644 pkg/seccomp/seccomp_rules.go (limited to 'pkg') diff --git a/pkg/bpf/program_builder.go b/pkg/bpf/program_builder.go index 7554d47c1..bad56d7ac 100644 --- a/pkg/bpf/program_builder.go +++ b/pkg/bpf/program_builder.go @@ -21,7 +21,10 @@ import ( "gvisor.googlesource.com/gvisor/pkg/abi/linux" ) -const labelTarget = math.MaxUint8 +const ( + labelTarget = math.MaxUint8 + labelDirectTarget = math.MaxUint32 +) // ProgramBuilder assists with building a BPF program with jump // labels that are resolved to their proper offsets. @@ -47,6 +50,14 @@ type label struct { target int } +type jmpType int + +const ( + jDirect jmpType = iota + jTrue + jFalse +) + // source contains information about a single reference to a label. type source struct { // Program line where the label reference is present. @@ -54,7 +65,7 @@ type source struct { // True if label reference is in the 'jump if true' part of the jump. // False if label reference is in the 'jump if false' part of the jump. - jt bool + jt jmpType } // AddStmt adds a new statement to the program. @@ -67,23 +78,29 @@ func (b *ProgramBuilder) AddJump(code uint16, k uint32, jt, jf uint8) { b.instructions = append(b.instructions, Jump(code, k, jt, jf)) } +// AddDirectJumpLabel adds a new jump to the program where is labelled. +func (b *ProgramBuilder) AddDirectJumpLabel(labelName string) { + b.addLabelSource(labelName, jDirect) + b.AddJump(Jmp|Ja, labelDirectTarget, 0, 0) +} + // AddJumpTrueLabel adds a new jump to the program where 'jump if true' is a label. func (b *ProgramBuilder) AddJumpTrueLabel(code uint16, k uint32, jtLabel string, jf uint8) { - b.addLabelSource(jtLabel, true) + b.addLabelSource(jtLabel, jTrue) b.AddJump(code, k, labelTarget, jf) } // AddJumpFalseLabel adds a new jump to the program where 'jump if false' is a label. func (b *ProgramBuilder) AddJumpFalseLabel(code uint16, k uint32, jt uint8, jfLabel string) { - b.addLabelSource(jfLabel, false) - b.AddJump(code, k, jt, math.MaxUint8) + b.addLabelSource(jfLabel, jFalse) + b.AddJump(code, k, jt, labelTarget) } // AddJumpLabels adds a new jump to the program where both jump targets are labels. func (b *ProgramBuilder) AddJumpLabels(code uint16, k uint32, jtLabel, jfLabel string) { - b.addLabelSource(jtLabel, true) - b.addLabelSource(jfLabel, false) - b.AddJump(code, k, math.MaxUint8, math.MaxUint8) + b.addLabelSource(jtLabel, jTrue) + b.addLabelSource(jfLabel, jFalse) + b.AddJump(code, k, labelTarget, labelTarget) } // AddLabel sets the given label name at the current location. The next instruction is executed @@ -104,20 +121,22 @@ func (b *ProgramBuilder) AddLabel(name string) error { // Instructions returns an array of BPF instructions representing the program with all labels // resolved. Return error in case label resolution failed due to an invalid program. +// +// N.B. Partial results will be returned in the error case, which is useful for debugging. func (b *ProgramBuilder) Instructions() ([]linux.BPFInstruction, error) { if err := b.resolveLabels(); err != nil { - return nil, err + return b.instructions, err } return b.instructions, nil } -func (b *ProgramBuilder) addLabelSource(labelName string, jt bool) { +func (b *ProgramBuilder) addLabelSource(labelName string, t jmpType) { l, ok := b.labels[labelName] if !ok { l = &label{sources: make([]source, 0), target: -1} b.labels[labelName] = l } - l.sources = append(l.sources, source{line: len(b.instructions), jt: jt}) + l.sources = append(l.sources, source{line: len(b.instructions), jt: t}) } func (b *ProgramBuilder) resolveLabels() error { @@ -136,21 +155,34 @@ func (b *ProgramBuilder) resolveLabels() error { } // Calculates the jump offset from current line. offset := v.target - s.line - 1 - if offset > math.MaxUint8 { - return fmt.Errorf("jump offset to label '%v' is too large: %v", key, offset) - } // Sets offset into jump instruction. - if s.jt { + switch s.jt { + case jDirect: + if offset > labelDirectTarget { + return fmt.Errorf("jump offset to label '%v' is too large: %v, inst: %v, lineno: %v", key, offset, inst, s.line) + } + if inst.K != labelDirectTarget { + return fmt.Errorf("jump target is not a label") + } + inst.K = uint32(offset) + case jTrue: + if offset > labelTarget { + return fmt.Errorf("jump offset to label '%v' is too large: %v, inst: %v, lineno: %v", key, offset, inst, s.line) + } if inst.JumpIfTrue != labelTarget { return fmt.Errorf("jump target is not a label") } inst.JumpIfTrue = uint8(offset) - } else { + case jFalse: + if offset > labelTarget { + return fmt.Errorf("jump offset to label '%v' is too large: %v, inst: %v, lineno: %v", key, offset, inst, s.line) + } if inst.JumpIfFalse != labelTarget { return fmt.Errorf("jump target is not a label") } inst.JumpIfFalse = uint8(offset) } + b.instructions[s.line] = inst } } diff --git a/pkg/seccomp/BUILD b/pkg/seccomp/BUILD index 1e19b1d25..cadd24505 100644 --- a/pkg/seccomp/BUILD +++ b/pkg/seccomp/BUILD @@ -21,14 +21,18 @@ go_library( name = "seccomp", srcs = [ "seccomp.go", + "seccomp_rules.go", "seccomp_unsafe.go", ], importpath = "gvisor.googlesource.com/gvisor/pkg/seccomp", visibility = ["//visibility:public"], deps = [ + "//pkg/abi", "//pkg/abi/linux", "//pkg/bpf", "//pkg/log", + "//pkg/sentry/arch", + "//pkg/sentry/strace", ], ) diff --git a/pkg/seccomp/seccomp.go b/pkg/seccomp/seccomp.go index 7ee63140c..cd6b0b4bc 100644 --- a/pkg/seccomp/seccomp.go +++ b/pkg/seccomp/seccomp.go @@ -12,24 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package seccomp provides basic seccomp filters. +// Package seccomp provides basic seccomp filters for x86_64 (little endian). package seccomp import ( "fmt" + "reflect" "sort" + "gvisor.googlesource.com/gvisor/pkg/abi" "gvisor.googlesource.com/gvisor/pkg/abi/linux" "gvisor.googlesource.com/gvisor/pkg/bpf" "gvisor.googlesource.com/gvisor/pkg/log" + "gvisor.googlesource.com/gvisor/pkg/sentry/arch" + "gvisor.googlesource.com/gvisor/pkg/sentry/strace" ) const ( // violationLabel is added to the program to take action on a violation. violationLabel = "violation" - // allowLabel is added to the program to allow the syscall to take place. - allowLabel = "allow" + // skipOneInst is the offset to take for skipping one instruction. + skipOneInst = 1 ) // Install generates BPF code based on the set of syscalls provided. It only @@ -38,27 +42,19 @@ const ( // // (*) The current implementation only checks the syscall number. It does NOT // validate any of the arguments. -func Install(syscalls []uintptr, kill bool) error { - // Sort syscalls and remove duplicates to build the BST. - sort.Slice(syscalls, func(i, j int) bool { return syscalls[i] < syscalls[j] }) - syscalls = filterUnique(syscalls) - - log.Infof("Installing seccomp filters for %d syscalls (kill=%t)", len(syscalls), kill) - for _, s := range syscalls { - log.Infof("syscall filter: %v", s) - } - - instrs, err := buildProgram(syscalls, kill) - if err != nil { - return err - } +func Install(rules SyscallRules, kill bool) error { + log.Infof("Installing seccomp filters for %d syscalls (kill=%t)", len(rules), kill) + instrs, err := buildProgram(rules, kill) if log.IsLogging(log.Debug) { - programStr, err := bpf.DecodeProgram(instrs) - if err != nil { - programStr = fmt.Sprintf("Error: %v\n%s", err, programStr) + programStr, errDecode := bpf.DecodeProgram(instrs) + if errDecode != nil { + programStr = fmt.Sprintf("Error: %v\n%s", errDecode, programStr) } log.Debugf("Seccomp program dump:\n%s", programStr) } + if err != nil { + return err + } if err := seccomp(instrs); err != nil { return err @@ -68,11 +64,8 @@ func Install(syscalls []uintptr, kill bool) error { return nil } -// buildProgram builds a BPF program that whitelists all given syscalls. -// -// Precondition: syscalls must be sorted and unique. -func buildProgram(syscalls []uintptr, kill bool) ([]linux.BPFInstruction, error) { - const archOffset = 4 // offsetof(seccomp_data, arch) +// buildProgram builds a BPF program that whitelists all given syscall rules. +func buildProgram(rules SyscallRules, kill bool) ([]linux.BPFInstruction, error) { program := bpf.NewProgramBuilder() violationAction := uint32(linux.SECCOMP_RET_KILL) if !kill { @@ -83,10 +76,13 @@ func buildProgram(syscalls []uintptr, kill bool) ([]linux.BPFInstruction, error) // // A = seccomp_data.arch // if (A != AUDIT_ARCH_X86_64) goto violation - program.AddStmt(bpf.Ld|bpf.Abs|bpf.W, archOffset) - program.AddJumpFalseLabel(bpf.Jmp|bpf.Jeq|bpf.K, linux.AUDIT_ARCH_X86_64, 0, violationLabel) + program.AddStmt(bpf.Ld|bpf.Abs|bpf.W, seccompDataOffsetArch) + // violationLabel is at the bottom of the program. The size of program + // may exceeds 255 lines, which is the limit of a condition jump. + program.AddJump(bpf.Jmp|bpf.Jeq|bpf.K, linux.AUDIT_ARCH_X86_64, skipOneInst, 0) + program.AddDirectJumpLabel(violationLabel) - if err := buildIndex(syscalls, program); err != nil { + if err := buildIndex(rules, program); err != nil { return nil, err } @@ -96,41 +92,34 @@ func buildProgram(syscalls []uintptr, kill bool) ([]linux.BPFInstruction, error) } program.AddStmt(bpf.Ret|bpf.K, violationAction) - // allow: return SECCOMP_RET_ALLOW - if err := program.AddLabel(allowLabel); err != nil { - return nil, err - } - program.AddStmt(bpf.Ret|bpf.K, linux.SECCOMP_RET_ALLOW) - return program.Instructions() } -// filterUnique filters unique system calls. -// -// Precondition: syscalls must be sorted. -func filterUnique(syscalls []uintptr) []uintptr { - filtered := make([]uintptr, 0, len(syscalls)) - for i := 0; i < len(syscalls); i++ { - if len(filtered) > 0 && syscalls[i] == filtered[len(filtered)-1] { - // This call has already been inserted, skip. - continue - } - filtered = append(filtered, syscalls[i]) +// buildIndex builds a BST to quickly search through all syscalls that are whitelisted. +func buildIndex(rules SyscallRules, program *bpf.ProgramBuilder) error { + syscalls := []uintptr{} + for sysno, _ := range rules { + syscalls = append(syscalls, sysno) + } + + t, ok := strace.Lookup(abi.Linux, arch.AMD64) + if !ok { + panic("Can't find amd64 Linux syscall table") + } + + sort.Slice(syscalls, func(i, j int) bool { return syscalls[i] < syscalls[j] }) + for _, s := range syscalls { + log.Infof("syscall filter: %v (%v): %s", s, t.Name(s), rules[s]) } - return filtered -} -// buildIndex builds a BST to quickly search through all syscalls that are whitelisted. -// -// Precondition: syscalls must be sorted and unique. -func buildIndex(syscalls []uintptr, program *bpf.ProgramBuilder) error { root := createBST(syscalls) + root.root = true // Load syscall number into A and run through BST. // // A = seccomp_data.nr - program.AddStmt(bpf.Ld|bpf.Abs|bpf.W, 0) - return root.buildBSTProgram(program, true) + program.AddStmt(bpf.Ld|bpf.Abs|bpf.W, seccompDataOffsetNR) + return root.traverse(buildBSTProgram, program, rules) } // createBST converts sorted syscall slice into a balanced BST. @@ -147,64 +136,128 @@ func createBST(syscalls []uintptr) *node { return &parent } -// node represents a tree node. -type node struct { - value uintptr - left *node - right *node +func ruleViolationLabel(sysno uintptr, idx int) string { + return fmt.Sprintf("ruleViolation_%v_%v", sysno, idx) } -// label returns the label corresponding to this node. If node is nil (syscall not present), -// violationLabel is returned for convenience. -func (n *node) label() string { - if n == nil { - return violationLabel +func checkArgsLabel(sysno uintptr) string { + return fmt.Sprintf("checkArgs_%v", sysno) +} + +func addSyscallArgsCheck(p *bpf.ProgramBuilder, rules []Rule, sysno uintptr) error { + for ruleidx, rule := range rules { + labelled := false + for i, arg := range rule { + if arg != nil { + switch a := arg.(type) { + case AllowAny: + case AllowValue: + high, low := uint32(a>>32), uint32(a) + // assert arg_low == low + p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, seccompDataOffsetArgLow(i)) + p.AddJumpFalseLabel(bpf.Jmp|bpf.Jeq|bpf.K, low, 0, ruleViolationLabel(sysno, ruleidx)) + // assert arg_high == high + p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, seccompDataOffsetArgHigh(i)) + p.AddJumpFalseLabel(bpf.Jmp|bpf.Jeq|bpf.K, high, 0, ruleViolationLabel(sysno, ruleidx)) + labelled = true + + default: + return fmt.Errorf("unknown syscall rule type: %v", reflect.TypeOf(a)) + } + } + } + // Matched, allow the syscall. + p.AddStmt(bpf.Ret|bpf.K, linux.SECCOMP_RET_ALLOW) + // Label the end of the rule if necessary. + if labelled { + if err := p.AddLabel(ruleViolationLabel(sysno, ruleidx)); err != nil { + return err + } + } } - return fmt.Sprintf("index_%v", n.value) + // Not matched? + p.AddDirectJumpLabel(violationLabel) + return nil } // buildBSTProgram converts a binary tree started in 'root' into BPF code. The ouline of the code // is as follows: // // // SYS_PIPE(22), root -// (A == 22) ? goto allow : continue +// (A == 22) ? goto argument check : continue // (A > 22) ? goto index_35 : goto index_9 // // index_9: // SYS_MMAP(9), leaf -// (A == 9) ? goto allow : goto violation +// A == 9) ? goto argument check : violation // // index_35: // SYS_NANOSLEEP(35), single child -// (A == 35) ? goto allow : continue +// (A == 35) ? goto argument check : continue // (A > 35) ? goto index_50 : goto violation // // index_50: // SYS_LISTEN(50), leaf -// (A == 50) ? goto allow : goto violation +// (A == 50) ? goto argument check : goto violation // -func (n *node) buildBSTProgram(program *bpf.ProgramBuilder, root bool) error { - if n == nil { - return nil - } - +func buildBSTProgram(program *bpf.ProgramBuilder, rules SyscallRules, n *node) error { // Root node is never referenced by label, skip it. - if !root { + if !n.root { if err := program.AddLabel(n.label()); err != nil { return err } } - // Leaf nodes don't require extra check, they either allow or violate! + sysno := n.value + program.AddJumpTrueLabel(bpf.Jmp|bpf.Jeq|bpf.K, uint32(sysno), checkArgsLabel(sysno), 0) if n.left == nil && n.right == nil { - program.AddJumpLabels(bpf.Jmp|bpf.Jeq|bpf.K, uint32(n.value), allowLabel, violationLabel) + // Leaf nodes don't require extra check. + program.AddDirectJumpLabel(violationLabel) + } else { + // Non-leaf node. Check which turn to take otherwise. Using direct jumps + // in case that the offset may exceed the limit of a conditional jump (255) + // Note that 'violationLabel' is returned for nil children. + program.AddJump(bpf.Jmp|bpf.Jgt|bpf.K, uint32(sysno), 0, skipOneInst) + program.AddDirectJumpLabel(n.right.label()) + program.AddDirectJumpLabel(n.left.label()) + } + + if err := program.AddLabel(checkArgsLabel(sysno)); err != nil { + return err + } + // No rules, just allow it and save one jmp. + if len(rules[sysno]) == 0 { + program.AddStmt(bpf.Ret|bpf.K, linux.SECCOMP_RET_ALLOW) return nil } + return addSyscallArgsCheck(program, rules[sysno], sysno) +} - // Non-leaf node. Allows syscall if it matches, check which turn to take otherwise. Note - // that 'violationLabel' is returned for nil children. - program.AddJumpTrueLabel(bpf.Jmp|bpf.Jeq|bpf.K, uint32(n.value), allowLabel, 0) - program.AddJumpLabels(bpf.Jmp|bpf.Jgt|bpf.K, uint32(n.value), n.right.label(), n.left.label()) +// node represents a tree node. +type node struct { + value uintptr + left *node + right *node + root bool +} + +// label returns the label corresponding to this node. If node is nil (syscall not present), +// violationLabel is returned for convenience. +func (n *node) label() string { + if n == nil { + return violationLabel + } + return fmt.Sprintf("index_%v", n.value) +} - if err := n.left.buildBSTProgram(program, false); err != nil { +type traverseFunc func(*bpf.ProgramBuilder, SyscallRules, *node) error + +func (n *node) traverse(fn traverseFunc, p *bpf.ProgramBuilder, rules SyscallRules) error { + if n == nil { + return nil + } + if err := fn(p, rules, n); err != nil { + return err + } + if err := n.left.traverse(fn, p, rules); err != nil { return err } - return n.right.buildBSTProgram(program, false) + return n.right.traverse(fn, p, rules) } diff --git a/pkg/seccomp/seccomp_rules.go b/pkg/seccomp/seccomp_rules.go new file mode 100644 index 000000000..892ccabb4 --- /dev/null +++ b/pkg/seccomp/seccomp_rules.go @@ -0,0 +1,119 @@ +// 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 "fmt" + +// The offsets are based on the following struct in include/linux/seccomp.h. +// struct seccomp_data { +// int nr; +// __u32 arch; +// __u64 instruction_pointer; +// __u64 args[6]; +// }; +const ( + seccompDataOffsetNR = 0 + seccompDataOffsetArch = 4 + seccompDataOffsetArgs = 16 +) + +func seccompDataOffsetArgLow(i int) uint32 { + return uint32(seccompDataOffsetArgs + i*8) +} + +func seccompDataOffsetArgHigh(i int) uint32 { + return uint32(seccompDataOffsetArgs + i*8 + 4) +} + +// AllowAny is marker to indicate any value will be accepted. +type AllowAny struct{} + +func (a AllowAny) String() (s string) { + return "*" +} + +// AllowValue specifies a value that needs to be strictly matched. +type AllowValue uintptr + +func (a AllowValue) String() (s string) { + return fmt.Sprintf("%#x ", uintptr(a)) +} + +// Rule stores the whitelist of syscall arguments. +// +// For example: +// rule := Rule { +// AllowValue(linux.ARCH_GET_FS | linux.ARCH_SET_FS), // arg0 +// } +type Rule [6]interface{} + +func (r Rule) String() (s string) { + if len(r) == 0 { + return + } + s += "( " + for _, arg := range r { + if arg != nil { + s += fmt.Sprintf("%v ", arg) + } + } + s += ")" + return +} + +// SyscallRules stores a map of OR'ed whitelist rules indexed by the syscall number. +// If the 'Rules' is empty, we treat it as any argument is allowed. +// +// For example: +// rules := SyscallRules{ +// syscall.SYS_FUTEX: []Rule{ +// { +// AllowAny{}, +// AllowValue(linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG), +// }, // OR +// { +// AllowAny{}, +// AllowValue(linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG), +// }, +// }, +// syscall.SYS_GETPID: []Rule{}, +// } +type SyscallRules map[uintptr][]Rule + +// NewSyscallRules returns a new SyscallRules. +func NewSyscallRules() SyscallRules { + return make(map[uintptr][]Rule) +} + +// AddRule adds the given rule. It will create a new entry for a new syscall, otherwise +// it will append to the existing rules. +func (sr SyscallRules) AddRule(sysno uintptr, r Rule) { + if _, ok := sr[sysno]; ok { + sr[sysno] = append(sr[sysno], r) + } else { + sr[sysno] = []Rule{r} + } +} + +// Merge merges the given SyscallRules. +func (sr SyscallRules) Merge(rules SyscallRules) { + for sysno, rs := range rules { + if _, ok := sr[sysno]; ok { + sr[sysno] = append(sr[sysno], rs...) + } else { + sr[sysno] = rs + } + } +} diff --git a/pkg/seccomp/seccomp_test.go b/pkg/seccomp/seccomp_test.go index c700d88d6..d3aca7ee9 100644 --- a/pkg/seccomp/seccomp_test.go +++ b/pkg/seccomp/seccomp_test.go @@ -19,10 +19,10 @@ import ( "fmt" "io" "io/ioutil" + "math" "math/rand" "os" "os/exec" - "sort" "strings" "testing" "time" @@ -77,12 +77,12 @@ func TestBasic(t *testing.T) { for _, test := range []struct { // filters are the set of syscall that are allowed. - filters []uintptr + filters SyscallRules kill bool specs []spec }{ { - filters: []uintptr{1}, + filters: SyscallRules{1: {}}, kill: false, specs: []spec{ { @@ -98,8 +98,12 @@ func TestBasic(t *testing.T) { }, }, { - filters: []uintptr{1, 3, 5}, - kill: false, + filters: SyscallRules{ + 1: {}, + 3: {}, + 5: {}, + }, + kill: false, specs: []spec{ { desc: "Multiple syscalls allowed (1)", @@ -144,7 +148,7 @@ func TestBasic(t *testing.T) { }, }, { - filters: []uintptr{1}, + filters: SyscallRules{1: {}}, kill: false, specs: []spec{ { @@ -155,7 +159,7 @@ func TestBasic(t *testing.T) { }, }, { - filters: []uintptr{1}, + filters: SyscallRules{1: {}}, kill: true, specs: []spec{ { @@ -165,8 +169,96 @@ func TestBasic(t *testing.T) { }, }, }, + { + filters: SyscallRules{ + 1: []Rule{ + { + AllowAny{}, + AllowValue(0xf), + }, + }, + }, + kill: false, + specs: []spec{ + { + desc: "Syscall argument allowed", + data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64, args: [6]uint64{0xf, 0xf}}, + want: linux.SECCOMP_RET_ALLOW, + }, + { + desc: "Syscall argument disallowed", + data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64, args: [6]uint64{0xf, 0xe}}, + want: linux.SECCOMP_RET_TRAP, + }, + }, + }, + { + filters: SyscallRules{ + 1: []Rule{ + { + AllowValue(0xf), + }, + { + AllowValue(0xe), + }, + }, + }, + kill: false, + specs: []spec{ + { + desc: "Syscall argument allowed, two rules", + data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64, args: [6]uint64{0xf}}, + want: linux.SECCOMP_RET_ALLOW, + }, + { + desc: "Syscall argument allowed, two rules", + data: seccompData{nr: 1, arch: linux.AUDIT_ARCH_X86_64, args: [6]uint64{0xe}}, + want: linux.SECCOMP_RET_ALLOW, + }, + }, + }, + { + filters: SyscallRules{ + 1: []Rule{ + { + AllowValue(0), + AllowValue(math.MaxUint64 - 1), + AllowValue(math.MaxUint32), + }, + }, + }, + kill: false, + specs: []spec{ + { + desc: "64bit syscall argument allowed", + data: seccompData{ + nr: 1, + arch: linux.AUDIT_ARCH_X86_64, + args: [6]uint64{0, math.MaxUint64 - 1, math.MaxUint32}, + }, + want: linux.SECCOMP_RET_ALLOW, + }, + { + desc: "64bit syscall argument disallowed", + data: seccompData{ + nr: 1, + arch: linux.AUDIT_ARCH_X86_64, + args: [6]uint64{0, math.MaxUint64, math.MaxUint32}, + }, + want: linux.SECCOMP_RET_TRAP, + }, + { + desc: "64bit syscall argument disallowed", + data: seccompData{ + nr: 1, + arch: linux.AUDIT_ARCH_X86_64, + args: [6]uint64{0, math.MaxUint64, math.MaxUint32 - 1}, + }, + want: linux.SECCOMP_RET_TRAP, + }, + }, + }, } { - 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) @@ -193,19 +285,16 @@ func TestBasic(t *testing.T) { 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 { + syscallRules := make(map[uintptr][]Rule) + for len(syscallRules) < size { n := uintptr(rand.Intn(200)) - if _, ok := syscallMap[n]; !ok { - syscalls = append(syscalls, n) - syscallMap[n] = struct{}{} + if _, ok := syscallRules[n]; !ok { + syscallRules[n] = []Rule{} } } - sort.Slice(syscalls, func(i, j int) bool { return syscalls[i] < syscalls[j] }) - fmt.Printf("Testing filters: %v", syscalls) - instrs, err := buildProgram(syscalls, false) + fmt.Printf("Testing filters: %v", syscallRules) + instrs, err := buildProgram(syscallRules, false) if err != nil { t.Fatalf("buildProgram() got error: %v", err) } @@ -221,7 +310,7 @@ func TestRandom(t *testing.T) { continue } want := uint32(linux.SECCOMP_RET_TRAP) - if _, ok := syscallMap[uintptr(i)]; ok { + if _, ok := syscallRules[uintptr(i)]; ok { want = linux.SECCOMP_RET_ALLOW } if got != want { diff --git a/pkg/seccomp/seccomp_test_victim.go b/pkg/seccomp/seccomp_test_victim.go index fe3f96901..4f2ae4dac 100644 --- a/pkg/seccomp/seccomp_test_victim.go +++ b/pkg/seccomp/seccomp_test_victim.go @@ -29,76 +29,81 @@ func main() { dieFlag := flag.Bool("die", false, "trips over the filter if true") flag.Parse() - syscalls := []uintptr{ - syscall.SYS_ACCEPT, - syscall.SYS_ARCH_PRCTL, - syscall.SYS_BIND, - syscall.SYS_BRK, - syscall.SYS_CLOCK_GETTIME, - syscall.SYS_CLONE, - syscall.SYS_CLOSE, - syscall.SYS_DUP, - syscall.SYS_DUP2, - syscall.SYS_EPOLL_CREATE1, - syscall.SYS_EPOLL_CTL, - syscall.SYS_EPOLL_WAIT, - syscall.SYS_EXIT, - syscall.SYS_EXIT_GROUP, - syscall.SYS_FALLOCATE, - syscall.SYS_FCHMOD, - syscall.SYS_FCNTL, - syscall.SYS_FSTAT, - syscall.SYS_FSYNC, - syscall.SYS_FTRUNCATE, - syscall.SYS_FUTEX, - syscall.SYS_GETDENTS64, - syscall.SYS_GETPEERNAME, - syscall.SYS_GETPID, - syscall.SYS_GETSOCKNAME, - syscall.SYS_GETSOCKOPT, - syscall.SYS_GETTID, - syscall.SYS_GETTIMEOFDAY, - syscall.SYS_LISTEN, - syscall.SYS_LSEEK, - syscall.SYS_MADVISE, - syscall.SYS_MINCORE, - syscall.SYS_MMAP, - syscall.SYS_MPROTECT, - syscall.SYS_MUNLOCK, - syscall.SYS_MUNMAP, - syscall.SYS_NANOSLEEP, - syscall.SYS_NEWFSTATAT, - syscall.SYS_OPEN, - syscall.SYS_POLL, - syscall.SYS_PREAD64, - syscall.SYS_PSELECT6, - syscall.SYS_PWRITE64, - syscall.SYS_READ, - syscall.SYS_READLINKAT, - syscall.SYS_READV, - syscall.SYS_RECVMSG, - syscall.SYS_RENAMEAT, - syscall.SYS_RESTART_SYSCALL, - syscall.SYS_RT_SIGACTION, - syscall.SYS_RT_SIGPROCMASK, - syscall.SYS_RT_SIGRETURN, - syscall.SYS_SCHED_YIELD, - syscall.SYS_SENDMSG, - syscall.SYS_SETITIMER, - syscall.SYS_SET_ROBUST_LIST, - syscall.SYS_SETSOCKOPT, - syscall.SYS_SHUTDOWN, - syscall.SYS_SIGALTSTACK, - syscall.SYS_SOCKET, - syscall.SYS_SYNC_FILE_RANGE, - syscall.SYS_TGKILL, - syscall.SYS_UTIMENSAT, - syscall.SYS_WRITE, - syscall.SYS_WRITEV, + syscalls := seccomp.SyscallRules{ + syscall.SYS_ACCEPT: {}, + syscall.SYS_ARCH_PRCTL: {}, + syscall.SYS_BIND: {}, + syscall.SYS_BRK: {}, + syscall.SYS_CLOCK_GETTIME: {}, + syscall.SYS_CLONE: {}, + syscall.SYS_CLOSE: {}, + syscall.SYS_DUP: {}, + syscall.SYS_DUP2: {}, + syscall.SYS_EPOLL_CREATE1: {}, + syscall.SYS_EPOLL_CTL: {}, + syscall.SYS_EPOLL_WAIT: {}, + syscall.SYS_EPOLL_PWAIT: {}, + syscall.SYS_EXIT: {}, + syscall.SYS_EXIT_GROUP: {}, + syscall.SYS_FALLOCATE: {}, + syscall.SYS_FCHMOD: {}, + syscall.SYS_FCNTL: {}, + syscall.SYS_FSTAT: {}, + syscall.SYS_FSYNC: {}, + syscall.SYS_FTRUNCATE: {}, + syscall.SYS_FUTEX: {}, + syscall.SYS_GETDENTS64: {}, + syscall.SYS_GETPEERNAME: {}, + syscall.SYS_GETPID: {}, + syscall.SYS_GETSOCKNAME: {}, + syscall.SYS_GETSOCKOPT: {}, + syscall.SYS_GETTID: {}, + syscall.SYS_GETTIMEOFDAY: {}, + syscall.SYS_LISTEN: {}, + syscall.SYS_LSEEK: {}, + syscall.SYS_MADVISE: {}, + syscall.SYS_MINCORE: {}, + syscall.SYS_MMAP: {}, + syscall.SYS_MPROTECT: {}, + syscall.SYS_MUNLOCK: {}, + syscall.SYS_MUNMAP: {}, + syscall.SYS_NANOSLEEP: {}, + syscall.SYS_NEWFSTATAT: {}, + syscall.SYS_OPEN: {}, + syscall.SYS_POLL: {}, + syscall.SYS_PREAD64: {}, + syscall.SYS_PSELECT6: {}, + syscall.SYS_PWRITE64: {}, + syscall.SYS_READ: {}, + syscall.SYS_READLINKAT: {}, + syscall.SYS_READV: {}, + syscall.SYS_RECVMSG: {}, + syscall.SYS_RENAMEAT: {}, + syscall.SYS_RESTART_SYSCALL: {}, + syscall.SYS_RT_SIGACTION: {}, + syscall.SYS_RT_SIGPROCMASK: {}, + syscall.SYS_RT_SIGRETURN: {}, + syscall.SYS_SCHED_YIELD: {}, + syscall.SYS_SENDMSG: {}, + syscall.SYS_SETITIMER: {}, + syscall.SYS_SET_ROBUST_LIST: {}, + syscall.SYS_SETSOCKOPT: {}, + syscall.SYS_SHUTDOWN: {}, + syscall.SYS_SIGALTSTACK: {}, + syscall.SYS_SOCKET: {}, + syscall.SYS_SYNC_FILE_RANGE: {}, + syscall.SYS_TGKILL: {}, + syscall.SYS_UTIMENSAT: {}, + syscall.SYS_WRITE: {}, + syscall.SYS_WRITEV: {}, } die := *dieFlag if !die { - syscalls = append(syscalls, syscall.SYS_OPENAT) + syscalls[syscall.SYS_OPENAT] = []seccomp.Rule{ + { + seccomp.AllowValue(10), + }, + } } if err := seccomp.Install(syscalls, false); err != nil { @@ -107,6 +112,6 @@ func main() { } fmt.Printf("Filters installed\n") - syscall.RawSyscall(syscall.SYS_OPENAT, 0, 0, 0) + syscall.RawSyscall(syscall.SYS_OPENAT, 10, 0, 0) fmt.Printf("Syscall was allowed!!!\n") } -- cgit v1.2.3