mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00

This patch, similar to d92739713c
, embeds the
`LinuxSeccomp` type of the runtime-spec, so that we can support all options
provided by the spec, and decorates it with our own fields.
With this, profiles can make use of the recently added "Flags" field, to
specify flags that must be passed to seccomp(2) when installing the filter.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
294 lines
8.1 KiB
Go
294 lines
8.1 KiB
Go
// +build linux
|
|
|
|
package seccomp // import "github.com/docker/docker/profiles/seccomp"
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
|
"gotest.tools/v3/assert"
|
|
)
|
|
|
|
func TestLoadProfile(t *testing.T) {
|
|
f, err := ioutil.ReadFile("fixtures/example.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rs := createSpec()
|
|
p, err := LoadProfile(string(f), &rs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var expectedErrno uint = 12345
|
|
expected := specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActErrno,
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{"clone"},
|
|
Action: specs.ActAllow,
|
|
Args: []specs.LinuxSeccompArg{{
|
|
Index: 0,
|
|
Value: 2114060288,
|
|
ValueTwo: 0,
|
|
Op: specs.OpMaskedEqual,
|
|
}},
|
|
},
|
|
{
|
|
|
|
Names: []string{"open"},
|
|
Action: specs.ActAllow,
|
|
Args: []specs.LinuxSeccompArg{},
|
|
},
|
|
{
|
|
Names: []string{"close"},
|
|
Action: specs.ActAllow,
|
|
Args: []specs.LinuxSeccompArg{},
|
|
},
|
|
{
|
|
Names: []string{"syslog"},
|
|
Action: specs.ActErrno,
|
|
ErrnoRet: &expectedErrno,
|
|
Args: []specs.LinuxSeccompArg{},
|
|
},
|
|
},
|
|
}
|
|
|
|
assert.DeepEqual(t, expected, *p)
|
|
}
|
|
|
|
func TestLoadProfileWithDefaultErrnoRet(t *testing.T) {
|
|
var profile = []byte(`{
|
|
"defaultAction": "SCMP_ACT_ERRNO",
|
|
"defaultErrnoRet": 6
|
|
}`)
|
|
rs := createSpec()
|
|
p, err := LoadProfile(string(profile), &rs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expectedErrnoRet := uint(6)
|
|
expected := specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActErrno,
|
|
DefaultErrnoRet: &expectedErrnoRet,
|
|
}
|
|
|
|
assert.DeepEqual(t, expected, *p)
|
|
}
|
|
|
|
func TestLoadProfileWithListenerPath(t *testing.T) {
|
|
var profile = []byte(`{
|
|
"defaultAction": "SCMP_ACT_ERRNO",
|
|
"listenerPath": "/var/run/seccompaget.sock",
|
|
"listenerMetadata": "opaque-metadata"
|
|
}`)
|
|
rs := createSpec()
|
|
p, err := LoadProfile(string(profile), &rs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expected := specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActErrno,
|
|
ListenerPath: "/var/run/seccompaget.sock",
|
|
ListenerMetadata: "opaque-metadata",
|
|
}
|
|
|
|
assert.DeepEqual(t, expected, *p)
|
|
}
|
|
|
|
func TestLoadProfileWithFlag(t *testing.T) {
|
|
profile := `{"defaultAction": "SCMP_ACT_ERRNO", "flags": ["SECCOMP_FILTER_FLAG_SPEC_ALLOW", "SECCOMP_FILTER_FLAG_LOG"]}`
|
|
expected := specs.LinuxSeccomp{
|
|
DefaultAction: specs.ActErrno,
|
|
Flags: []specs.LinuxSeccompFlag{"SECCOMP_FILTER_FLAG_SPEC_ALLOW", "SECCOMP_FILTER_FLAG_LOG"},
|
|
}
|
|
rs := createSpec()
|
|
p, err := LoadProfile(profile, &rs)
|
|
assert.NilError(t, err)
|
|
assert.DeepEqual(t, expected, *p)
|
|
}
|
|
|
|
// TestLoadProfileValidation tests that invalid profiles produce the correct error.
|
|
func TestLoadProfileValidation(t *testing.T) {
|
|
tests := []struct {
|
|
doc string
|
|
profile string
|
|
expected string
|
|
}{
|
|
{
|
|
doc: "conflicting architectures and archMap",
|
|
profile: `{"defaultAction": "SCMP_ACT_ERRNO", "architectures": ["A", "B", "C"], "archMap": [{"architecture": "A", "subArchitectures": ["B", "C"]}]}`,
|
|
expected: `use either 'architectures' or 'archMap'`,
|
|
},
|
|
{
|
|
doc: "conflicting syscall.name and syscall.names",
|
|
profile: `{"defaultAction": "SCMP_ACT_ERRNO", "syscalls": [{"name": "accept", "names": ["accept"], "action": "SCMP_ACT_ALLOW"}]}`,
|
|
expected: `use either 'name' or 'names'`,
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
rs := createSpec()
|
|
t.Run(tc.doc, func(t *testing.T) {
|
|
_, err := LoadProfile(tc.profile, &rs)
|
|
assert.ErrorContains(t, err, tc.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestLoadLegacyProfile tests loading a seccomp profile in the old format
|
|
// (before https://github.com/docker/docker/pull/24510)
|
|
func TestLoadLegacyProfile(t *testing.T) {
|
|
f, err := ioutil.ReadFile("fixtures/default-old-format.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rs := createSpec()
|
|
p, err := LoadProfile(string(f), &rs)
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, p.DefaultAction, specs.ActErrno)
|
|
assert.DeepEqual(t, p.Architectures, []specs.Arch{"SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32"})
|
|
assert.Equal(t, len(p.Syscalls), 311)
|
|
expected := specs.LinuxSyscall{
|
|
Names: []string{"accept"},
|
|
Action: specs.ActAllow,
|
|
Args: []specs.LinuxSeccompArg{},
|
|
}
|
|
assert.DeepEqual(t, p.Syscalls[0], expected)
|
|
}
|
|
|
|
func TestLoadDefaultProfile(t *testing.T) {
|
|
f, err := ioutil.ReadFile("default.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rs := createSpec()
|
|
if _, err := LoadProfile(string(f), &rs); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestUnmarshalDefaultProfile(t *testing.T) {
|
|
expected := DefaultProfile()
|
|
if expected == nil {
|
|
t.Skip("seccomp not supported")
|
|
}
|
|
|
|
f, err := ioutil.ReadFile("default.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var profile Seccomp
|
|
err = json.Unmarshal(f, &profile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.DeepEqual(t, expected.Architectures, profile.Architectures)
|
|
assert.DeepEqual(t, expected.ArchMap, profile.ArchMap)
|
|
assert.DeepEqual(t, expected.DefaultAction, profile.DefaultAction)
|
|
assert.DeepEqual(t, expected.Syscalls, profile.Syscalls)
|
|
}
|
|
|
|
func TestMarshalUnmarshalFilter(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
in string
|
|
out string
|
|
error bool
|
|
}{
|
|
{in: `{"arches":["s390x"],"minKernel":3}`, error: true},
|
|
{in: `{"arches":["s390x"],"minKernel":3.12}`, error: true},
|
|
{in: `{"arches":["s390x"],"minKernel":true}`, error: true},
|
|
{in: `{"arches":["s390x"],"minKernel":"0.0"}`, error: true},
|
|
{in: `{"arches":["s390x"],"minKernel":"3"}`, error: true},
|
|
{in: `{"arches":["s390x"],"minKernel":".3"}`, error: true},
|
|
{in: `{"arches":["s390x"],"minKernel":"3."}`, error: true},
|
|
{in: `{"arches":["s390x"],"minKernel":"true"}`, error: true},
|
|
{in: `{"arches":["s390x"],"minKernel":"3.12.1\""}`, error: true},
|
|
{in: `{"arches":["s390x"],"minKernel":"4.15abc"}`, error: true},
|
|
{in: `{"arches":["s390x"],"minKernel":null}`, out: `{"arches":["s390x"]}`},
|
|
{in: `{"arches":["s390x"],"minKernel":""}`, out: `{"arches":["s390x"],"minKernel":""}`}, // FIXME: try to fix omitempty for this
|
|
{in: `{"arches":["s390x"],"minKernel":"0.5"}`, out: `{"arches":["s390x"],"minKernel":"0.5"}`},
|
|
{in: `{"arches":["s390x"],"minKernel":"0.50"}`, out: `{"arches":["s390x"],"minKernel":"0.50"}`},
|
|
{in: `{"arches":["s390x"],"minKernel":"5.0"}`, out: `{"arches":["s390x"],"minKernel":"5.0"}`},
|
|
{in: `{"arches":["s390x"],"minKernel":"50.0"}`, out: `{"arches":["s390x"],"minKernel":"50.0"}`},
|
|
{in: `{"arches":["s390x"],"minKernel":"4.15"}`, out: `{"arches":["s390x"],"minKernel":"4.15"}`},
|
|
}
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
var filter Filter
|
|
err := json.Unmarshal([]byte(tc.in), &filter)
|
|
if tc.error {
|
|
if err == nil {
|
|
t.Fatal("expected an error")
|
|
} else if !strings.Contains(err.Error(), "invalid kernel version") {
|
|
t.Fatal("unexpected error:", err)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out, err := json.Marshal(filter)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(out) != tc.out {
|
|
t.Fatalf("expected %s, got %s", tc.out, string(out))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadConditional(t *testing.T) {
|
|
f, err := ioutil.ReadFile("fixtures/conditional_include.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tests := []struct {
|
|
doc string
|
|
cap string
|
|
expected []string
|
|
}{
|
|
{doc: "no caps", expected: []string{"chmod", "ptrace"}},
|
|
{doc: "with syslog", cap: "CAP_SYSLOG", expected: []string{"chmod", "syslog", "ptrace"}},
|
|
{doc: "no ptrace", cap: "CAP_SYS_ADMIN", expected: []string{"chmod"}},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.doc, func(t *testing.T) {
|
|
rs := createSpec(tc.cap)
|
|
p, err := LoadProfile(string(f), &rs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(p.Syscalls) != len(tc.expected) {
|
|
t.Fatalf("expected %d syscalls in profile, have %d", len(tc.expected), len(p.Syscalls))
|
|
}
|
|
for i, v := range p.Syscalls {
|
|
if v.Names[0] != tc.expected[i] {
|
|
t.Fatalf("expected %s syscall, have %s", tc.expected[i], v.Names[0])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// createSpec() creates a minimum spec for testing
|
|
func createSpec(caps ...string) specs.Spec {
|
|
rs := specs.Spec{
|
|
Process: &specs.Process{
|
|
Capabilities: &specs.LinuxCapabilities{},
|
|
},
|
|
}
|
|
if caps != nil {
|
|
rs.Process.Capabilities.Bounding = append(rs.Process.Capabilities.Bounding, caps...)
|
|
}
|
|
return rs
|
|
}
|