415 lines
9.5 KiB
Go
415 lines
9.5 KiB
Go
// Copyright 2020 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 (
|
|
"fmt"
|
|
"syscall"
|
|
"testing"
|
|
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
"gvisor.dev/gvisor/pkg/binary"
|
|
"gvisor.dev/gvisor/pkg/bpf"
|
|
)
|
|
|
|
type seccompData struct {
|
|
nr uint32
|
|
arch uint32
|
|
instructionPointer uint64
|
|
args [6]uint64
|
|
}
|
|
|
|
// asInput converts a seccompData to a bpf.Input.
|
|
func asInput(d seccompData) bpf.Input {
|
|
return bpf.InputBytes{binary.Marshal(nil, binary.LittleEndian, d), binary.LittleEndian}
|
|
}
|
|
|
|
// testInput creates an Input struct with given seccomp input values.
|
|
func testInput(arch uint32, syscallName string, args *[6]uint64) bpf.Input {
|
|
syscallNo, err := lookupSyscallNo(arch, syscallName)
|
|
if err != nil {
|
|
// Assume tests set valid syscall names.
|
|
panic(err)
|
|
}
|
|
|
|
if args == nil {
|
|
argArray := [6]uint64{0, 0, 0, 0, 0, 0}
|
|
args = &argArray
|
|
}
|
|
|
|
data := seccompData{
|
|
nr: syscallNo,
|
|
arch: arch,
|
|
args: *args,
|
|
}
|
|
|
|
return asInput(data)
|
|
}
|
|
|
|
// testCase holds a seccomp test case.
|
|
type testCase struct {
|
|
name string
|
|
config specs.LinuxSeccomp
|
|
input bpf.Input
|
|
expected uint32
|
|
}
|
|
|
|
var (
|
|
// seccompTests is a list of speccomp test cases.
|
|
seccompTests = []testCase{
|
|
{
|
|
name: "default_allow",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActAllow,
|
|
},
|
|
input: testInput(nativeArchAuditNo, "read", nil),
|
|
expected: uint32(allowAction),
|
|
},
|
|
{
|
|
name: "default_deny",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActErrno,
|
|
},
|
|
input: testInput(nativeArchAuditNo, "read", nil),
|
|
expected: uint32(errnoAction),
|
|
},
|
|
{
|
|
name: "deny_arch",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActAllow,
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{
|
|
"getcwd",
|
|
},
|
|
Action: specs.ActErrno,
|
|
},
|
|
},
|
|
},
|
|
// Syscall matches but the arch is AUDIT_ARCH_X86 so the return
|
|
// value is the bad arch action.
|
|
input: asInput(seccompData{nr: 183, arch: 0x40000003}), //
|
|
expected: uint32(killThreadAction),
|
|
},
|
|
{
|
|
name: "match_name_errno",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActAllow,
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{
|
|
"getcwd",
|
|
"chmod",
|
|
},
|
|
Action: specs.ActErrno,
|
|
},
|
|
{
|
|
Names: []string{
|
|
"write",
|
|
},
|
|
Action: specs.ActTrace,
|
|
},
|
|
},
|
|
},
|
|
input: testInput(nativeArchAuditNo, "getcwd", nil),
|
|
expected: uint32(errnoAction),
|
|
},
|
|
{
|
|
name: "match_name_trace",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActAllow,
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{
|
|
"getcwd",
|
|
"chmod",
|
|
},
|
|
Action: specs.ActErrno,
|
|
},
|
|
{
|
|
Names: []string{
|
|
"write",
|
|
},
|
|
Action: specs.ActTrace,
|
|
},
|
|
},
|
|
},
|
|
input: testInput(nativeArchAuditNo, "write", nil),
|
|
expected: uint32(traceAction),
|
|
},
|
|
{
|
|
name: "no_match_name_allow",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActAllow,
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{
|
|
"getcwd",
|
|
"chmod",
|
|
},
|
|
Action: specs.ActErrno,
|
|
},
|
|
{
|
|
Names: []string{
|
|
"write",
|
|
},
|
|
Action: specs.ActTrace,
|
|
},
|
|
},
|
|
},
|
|
input: testInput(nativeArchAuditNo, "openat", nil),
|
|
expected: uint32(allowAction),
|
|
},
|
|
{
|
|
name: "simple_match_args",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActAllow,
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{
|
|
"clone",
|
|
},
|
|
Args: []specs.LinuxSeccompArg{
|
|
{
|
|
Index: 0,
|
|
Value: syscall.CLONE_FS,
|
|
Op: specs.OpEqualTo,
|
|
},
|
|
},
|
|
Action: specs.ActErrno,
|
|
},
|
|
},
|
|
},
|
|
input: testInput(nativeArchAuditNo, "clone", &[6]uint64{syscall.CLONE_FS}),
|
|
expected: uint32(errnoAction),
|
|
},
|
|
{
|
|
name: "match_args_or",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActAllow,
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{
|
|
"clone",
|
|
},
|
|
Args: []specs.LinuxSeccompArg{
|
|
{
|
|
Index: 0,
|
|
Value: syscall.CLONE_FS,
|
|
Op: specs.OpEqualTo,
|
|
},
|
|
{
|
|
Index: 0,
|
|
Value: syscall.CLONE_VM,
|
|
Op: specs.OpEqualTo,
|
|
},
|
|
},
|
|
Action: specs.ActErrno,
|
|
},
|
|
},
|
|
},
|
|
input: testInput(nativeArchAuditNo, "clone", &[6]uint64{syscall.CLONE_FS}),
|
|
expected: uint32(errnoAction),
|
|
},
|
|
{
|
|
name: "match_args_and",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActAllow,
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{
|
|
"getsockopt",
|
|
},
|
|
Args: []specs.LinuxSeccompArg{
|
|
{
|
|
Index: 1,
|
|
Value: syscall.SOL_SOCKET,
|
|
Op: specs.OpEqualTo,
|
|
},
|
|
{
|
|
Index: 2,
|
|
Value: syscall.SO_PEERCRED,
|
|
Op: specs.OpEqualTo,
|
|
},
|
|
},
|
|
Action: specs.ActErrno,
|
|
},
|
|
},
|
|
},
|
|
input: testInput(nativeArchAuditNo, "getsockopt", &[6]uint64{0, syscall.SOL_SOCKET, syscall.SO_PEERCRED}),
|
|
expected: uint32(errnoAction),
|
|
},
|
|
{
|
|
name: "no_match_args_and",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActAllow,
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{
|
|
"getsockopt",
|
|
},
|
|
Args: []specs.LinuxSeccompArg{
|
|
{
|
|
Index: 1,
|
|
Value: syscall.SOL_SOCKET,
|
|
Op: specs.OpEqualTo,
|
|
},
|
|
{
|
|
Index: 2,
|
|
Value: syscall.SO_PEERCRED,
|
|
Op: specs.OpEqualTo,
|
|
},
|
|
},
|
|
Action: specs.ActErrno,
|
|
},
|
|
},
|
|
},
|
|
input: testInput(nativeArchAuditNo, "getsockopt", &[6]uint64{0, syscall.SOL_SOCKET}),
|
|
expected: uint32(allowAction),
|
|
},
|
|
{
|
|
name: "Simple args (no match)",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActAllow,
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{
|
|
"clone",
|
|
},
|
|
Args: []specs.LinuxSeccompArg{
|
|
{
|
|
Index: 0,
|
|
Value: syscall.CLONE_FS,
|
|
Op: specs.OpEqualTo,
|
|
},
|
|
},
|
|
Action: specs.ActErrno,
|
|
},
|
|
},
|
|
},
|
|
input: testInput(nativeArchAuditNo, "clone", &[6]uint64{syscall.CLONE_VM}),
|
|
expected: uint32(allowAction),
|
|
},
|
|
{
|
|
name: "OpMaskedEqual (match)",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActAllow,
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{
|
|
"clone",
|
|
},
|
|
Args: []specs.LinuxSeccompArg{
|
|
{
|
|
Index: 0,
|
|
Value: syscall.CLONE_FS,
|
|
ValueTwo: syscall.CLONE_FS,
|
|
Op: specs.OpMaskedEqual,
|
|
},
|
|
},
|
|
Action: specs.ActErrno,
|
|
},
|
|
},
|
|
},
|
|
input: testInput(nativeArchAuditNo, "clone", &[6]uint64{syscall.CLONE_FS | syscall.CLONE_VM}),
|
|
expected: uint32(errnoAction),
|
|
},
|
|
{
|
|
name: "OpMaskedEqual (no match)",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActAllow,
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{
|
|
"clone",
|
|
},
|
|
Args: []specs.LinuxSeccompArg{
|
|
{
|
|
Index: 0,
|
|
Value: syscall.CLONE_FS | syscall.CLONE_VM,
|
|
ValueTwo: syscall.CLONE_FS | syscall.CLONE_VM,
|
|
Op: specs.OpMaskedEqual,
|
|
},
|
|
},
|
|
Action: specs.ActErrno,
|
|
},
|
|
},
|
|
},
|
|
input: testInput(nativeArchAuditNo, "clone", &[6]uint64{syscall.CLONE_FS}),
|
|
expected: uint32(allowAction),
|
|
},
|
|
{
|
|
name: "OpMaskedEqual (clone)",
|
|
config: specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActErrno,
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{
|
|
"clone",
|
|
},
|
|
// This comes from the Docker default seccomp
|
|
// profile for clone.
|
|
Args: []specs.LinuxSeccompArg{
|
|
{
|
|
Index: 0,
|
|
Value: 0x7e020000,
|
|
ValueTwo: 0x0,
|
|
Op: specs.OpMaskedEqual,
|
|
},
|
|
},
|
|
Action: specs.ActAllow,
|
|
},
|
|
},
|
|
},
|
|
input: testInput(nativeArchAuditNo, "clone", &[6]uint64{0x50f00}),
|
|
expected: uint32(allowAction),
|
|
},
|
|
}
|
|
)
|
|
|
|
// TestRunscSeccomp generates seccomp programs from OCI config and executes
|
|
// them using runsc's library, comparing against expected results.
|
|
func TestRunscSeccomp(t *testing.T) {
|
|
for _, tc := range seccompTests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
runscProgram, err := BuildProgram(&tc.config)
|
|
if err != nil {
|
|
t.Fatalf("generating runsc BPF: %v", err)
|
|
}
|
|
|
|
if err := checkProgram(runscProgram, tc.input, tc.expected); err != nil {
|
|
t.Fatalf("running runsc BPF: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// checkProgram runs the given program over the given input and checks the
|
|
// result against the expected output.
|
|
func checkProgram(p bpf.Program, in bpf.Input, expected uint32) error {
|
|
result, err := bpf.Exec(p, in)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result != expected {
|
|
// Include a decoded version of the program in output for debugging purposes.
|
|
decoded, _ := bpf.DecodeProgram(p)
|
|
return fmt.Errorf("Unexpected result: got: %d, expected: %d\nBPF Program\n%s", result, expected, decoded)
|
|
}
|
|
|
|
return nil
|
|
}
|