//go:build linux // +build linux package seccomp // import "github.com/docker/docker/profiles/seccomp" import ( "encoding/json" "os" "strings" "testing" "github.com/opencontainers/runtime-spec/specs-go" "gotest.tools/v3/assert" ) func TestLoadProfile(t *testing.T) { f, err := os.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 var expectedDefaultErrno uint = 1 expected := specs.LinuxSeccomp{ DefaultAction: specs.ActErrno, DefaultErrnoRet: &expectedDefaultErrno, 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 := os.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 := os.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 := os.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 := os.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 }