Skip to content

Commit 00772ca

Browse files
tests: add functional tests for seccomp notify
Add functional test to check seccomp notify end-to-end. This test uses the sample seccomp agent from the contrib/cmd folder. Signed-off-by: Mauricio Vásquez <[email protected]> Signed-off-by: Rodrigo Campos <[email protected]> Co-authored-by: Rodrigo Campos <[email protected]>
1 parent 5ae831d commit 00772ca

File tree

3 files changed

+267
-0
lines changed

3 files changed

+267
-0
lines changed

tests/integration/helpers.bash

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ unset IMAGES
1717
RUNC="${INTEGRATION_ROOT}/../../runc"
1818
RECVTTY="${INTEGRATION_ROOT}/../../contrib/cmd/recvtty/recvtty"
1919
SD_HELPER="${INTEGRATION_ROOT}/../../contrib/cmd/sd-helper/sd-helper"
20+
SECCOMP_AGENT="${INTEGRATION_ROOT}/../../contrib/cmd/seccompagent/seccompagent"
2021

2122
# Test data path.
2223
# shellcheck disable=SC2034
@@ -31,6 +32,11 @@ KERNEL_MAJOR="${KERNEL_VERSION%%.*}"
3132
KERNEL_MINOR="${KERNEL_VERSION#$KERNEL_MAJOR.}"
3233
KERNEL_MINOR="${KERNEL_MINOR%%.*}"
3334

35+
ARCH=$(uname -m)
36+
37+
# Seccomp agent socket.
38+
SECCCOMP_AGENT_SOCKET="$BATS_TMPDIR/seccomp-agent.sock"
39+
3440
# Check if we're in rootless mode.
3541
ROOTLESS=$(id -u)
3642

@@ -428,6 +434,11 @@ function requires() {
428434
skip_me=1
429435
fi
430436
;;
437+
arch_x86_64)
438+
if [ "$ARCH" != "x86_64" ]; then
439+
skip_me=1
440+
fi
441+
;;
431442
*)
432443
fail "BUG: Invalid requires $var."
433444
;;
@@ -505,6 +516,18 @@ function teardown_recvtty() {
505516
rm -rf "$dir"
506517
}
507518

519+
function setup_seccompagent() {
520+
("${SECCOMP_AGENT}" -socketfile="$SECCCOMP_AGENT_SOCKET" -pid-file "$BATS_TMPDIR/seccompagent.pid" &) &
521+
}
522+
523+
function teardown_seccompagent() {
524+
if [ -f "$BATS_TMPDIR/seccompagent.pid" ]; then
525+
kill -9 "$(cat "$BATS_TMPDIR/seccompagent.pid")"
526+
fi
527+
rm -f "$BATS_TMPDIR/seccompagent.pid"
528+
rm -f "$SECCCOMP_AGENT_SOCKET"
529+
}
530+
508531
function setup_bundle() {
509532
local image="$1"
510533

@@ -545,3 +568,12 @@ function teardown_bundle() {
545568
rm -rf "$ROOT"
546569
remove_parent
547570
}
571+
572+
function requires_kernel() {
573+
local major_required minor_required
574+
major_required=$(echo "$1" | cut -d. -f1)
575+
minor_required=$(echo "$1" | cut -d. -f2)
576+
if [[ "$KERNEL_MAJOR" -lt $major_required || ("$KERNEL_MAJOR" -eq $major_required && "$KERNEL_MINOR" -lt $minor_required) ]]; then
577+
skip "requires kernel $1"
578+
fi
579+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env bats
2+
3+
load helpers
4+
5+
function setup() {
6+
if [[ "$KERNEL_MAJOR" -gt 5 || ("$KERNEL_MAJOR" -eq 5 && "$KERNEL_MINOR" -ge 6) ]]; then
7+
skip "requires kernel less than 5.6"
8+
fi
9+
10+
requires arch_x86_64
11+
12+
setup_seccompagent
13+
setup_busybox
14+
}
15+
16+
function teardown() {
17+
teardown_seccompagent
18+
teardown_bundle
19+
}
20+
21+
# Support for seccomp notify requires Linux > 5.6, check that on older kernels
22+
# return an error.
23+
@test "runc run [seccomp] (SCMP_ACT_NOTIFY old kernel)" {
24+
# Use just any seccomp profile with a notify action.
25+
update_config ' .linux.seccomp = {
26+
"defaultAction": "SCMP_ACT_ALLOW",
27+
"listenerPath": "'"$SECCCOMP_AGENT_SOCKET"'",
28+
"architectures": [ "SCMP_ARCH_X86","SCMP_ARCH_X32", "SCMP_ARCH_X86_64" ],
29+
"syscalls": [{ "names": [ "mkdir" ], "action": "SCMP_ACT_NOTIFY" }]
30+
}'
31+
32+
runc run test_busybox
33+
[ "$status" -ne 0 ]
34+
[[ "$output" == *"seccomp notify unsupported:"* ]]
35+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
#!/usr/bin/env bats
2+
3+
load helpers
4+
5+
# Support for seccomp notify requires Linux > 5.6 because
6+
# runc uses the pidfd_getfd system call to fetch the seccomp fd.
7+
# https://github.com/torvalds/linux/commit/8649c322f75c96e7ced2fec201e123b2b073bf09
8+
# We also require arch x86_64, to not make this fail when people run tests
9+
# locally on other archs.
10+
function setup() {
11+
requires_kernel 5.6
12+
requires arch_x86_64
13+
14+
setup_seccompagent
15+
setup_busybox
16+
}
17+
18+
function teardown() {
19+
teardown_seccompagent
20+
teardown_bundle
21+
}
22+
23+
# Create config.json template with SCMP_ACT_NOTIFY actions
24+
# $1: command to run
25+
# $2: noNewPrivileges (false/true)
26+
# $3: list of syscalls
27+
function scmp_act_notify_template() {
28+
# The agent intercepts mkdir syscalls and creates the folder appending
29+
# "-bar" (listenerMetadata below) to the name.
30+
update_config ' .process.args = ["/bin/sh", "-c", "'"$1"'"]
31+
| .process.noNewPrivileges = '"$2"'
32+
| .linux.seccomp = {
33+
"defaultAction":"SCMP_ACT_ALLOW",
34+
"listenerPath": "'"$SECCCOMP_AGENT_SOCKET"'",
35+
"listenerMetadata": "bar",
36+
"architectures": [ "SCMP_ARCH_X86","SCMP_ARCH_X32", "SCMP_ARCH_X86_64" ],
37+
"syscalls": [{ "names": ['"$3"'], "action": "SCMP_ACT_NOTIFY" }]
38+
}'
39+
}
40+
41+
# The call to seccomp is done at different places according to the value of
42+
# noNewPrivileges, for this reason many of the following cases are tested with
43+
# both values.
44+
45+
@test "runc run [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges false)" {
46+
scmp_act_notify_template "mkdir /dev/shm/foo && stat /dev/shm/foo-bar" false '"mkdir"'
47+
48+
runc run test_busybox
49+
[ "$status" -eq 0 ]
50+
}
51+
52+
@test "runc run [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges true)" {
53+
scmp_act_notify_template "mkdir /dev/shm/foo && stat /dev/shm/foo-bar" true '"mkdir"'
54+
55+
runc run test_busybox
56+
[ "$status" -eq 0 ]
57+
}
58+
59+
@test "runc exec [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges false)" {
60+
requires root
61+
62+
scmp_act_notify_template "sleep infinity" false '"mkdir"'
63+
64+
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
65+
[ "$status" -eq 0 ]
66+
67+
runc exec test_busybox /bin/sh -c "mkdir /dev/shm/foo && stat /dev/shm/foo-bar"
68+
[ "$status" -eq 0 ]
69+
}
70+
71+
@test "runc exec [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges true)" {
72+
requires root
73+
74+
scmp_act_notify_template "sleep infinity" true '"mkdir"'
75+
76+
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
77+
runc exec test_busybox /bin/sh -c "mkdir /dev/shm/foo && stat /dev/shm/foo-bar"
78+
[ "$status" -eq 0 ]
79+
}
80+
81+
@test "runc run [seccomp] (SCMP_ACT_NOTIFY important syscalls noNewPrivileges false)" {
82+
scmp_act_notify_template "/bin/true" false '"execve","openat","open","read","close"'
83+
84+
runc run test_busybox
85+
[ "$status" -eq 0 ]
86+
}
87+
88+
@test "runc run [seccomp] (SCMP_ACT_NOTIFY important syscalls noNewPrivileges true)" {
89+
scmp_act_notify_template "/bin/true" true '"execve","openat","open","read","close"'
90+
91+
runc run test_busybox
92+
[ "$status" -eq 0 ]
93+
}
94+
95+
@test "runc run [seccomp] (empty listener path)" {
96+
update_config ' .process.args = ["/bin/sh", "-c", "mkdir /dev/shm/foo && stat /dev/shm/foo"]
97+
| .linux.seccomp = {
98+
"defaultAction":"SCMP_ACT_ALLOW",
99+
"listenerPath": "'"$SECCCOMP_AGENT_SOCKET"'",
100+
"listenerMetadata": "bar",
101+
}'
102+
103+
runc run test_busybox
104+
[ "$status" -eq 0 ]
105+
}
106+
107+
@test "runc run [seccomp] (SCMP_ACT_NOTIFY empty listener path)" {
108+
scmp_act_notify_template "/bin/true" false '"mkdir"'
109+
update_config '.linux.seccomp.listenerPath = ""'
110+
111+
runc run test_busybox
112+
[ "$status" -ne 0 ]
113+
}
114+
115+
@test "runc run [seccomp] (SCMP_ACT_NOTIFY wrong listener path)" {
116+
scmp_act_notify_template "/bin/true" false '"mkdir"'
117+
update_config '.linux.seccomp.listenerPath = "/some-non-existing-listener-path.sock"'
118+
119+
runc run test_busybox
120+
[ "$status" -ne 0 ]
121+
}
122+
123+
@test "runc run [seccomp] (SCMP_ACT_NOTIFY abstract listener path)" {
124+
scmp_act_notify_template "/bin/true" false '"mkdir"'
125+
update_config '.linux.seccomp.listenerPath = "@mysocketishere"'
126+
127+
runc run test_busybox
128+
[ "$status" -ne 0 ]
129+
}
130+
131+
# Check that killing the seccompagent doesn't block syscalls in
132+
# the container. They should return ENOSYS instead.
133+
@test "runc run [seccomp] (SCMP_ACT_NOTIFY kill seccompagent)" {
134+
scmp_act_notify_template "sleep 4 && mkdir /dev/shm/foo" false '"mkdir"'
135+
136+
sleep 2 && teardown_seccompagent &
137+
runc run test_busybox
138+
[ "$status" -ne 0 ]
139+
[[ "$output" == *"mkdir:"*"/dev/shm/foo"*"Function not implemented"* ]]
140+
}
141+
142+
# Check that starting with no seccomp agent running fails with a clear error.
143+
@test "runc run [seccomp] (SCMP_ACT_NOTIFY no seccompagent)" {
144+
teardown_seccompagent
145+
146+
scmp_act_notify_template "/bin/true" false '"mkdir"'
147+
148+
runc run test_busybox
149+
[ "$status" -ne 0 ]
150+
[[ "$output" == *"failed to connect with seccomp agent"* ]]
151+
}
152+
153+
# Check that agent-returned error for the syscall works.
154+
@test "runc run [seccomp] (SCMP_ACT_NOTIFY error chmod)" {
155+
scmp_act_notify_template "touch /dev/shm/foo && chmod 777 /dev/shm/foo" false '"chmod", "fchmod", "fchmodat"'
156+
157+
runc run test_busybox
158+
[ "$status" -ne 0 ]
159+
[[ "$output" == *"chmod:"*"/dev/shm/foo"*"No medium found"* ]]
160+
}
161+
162+
# check that trying to use SCMP_ACT_NOTIFY with write() gives a meaningful error.
163+
@test "runc run [seccomp] (SCMP_ACT_NOTIFY write)" {
164+
scmp_act_notify_template "/bin/true" false '"write"'
165+
166+
runc run test_busybox
167+
[ "$status" -ne 0 ]
168+
[[ "$output" == *"SCMP_ACT_NOTIFY cannot be used for the write syscall"* ]]
169+
}
170+
171+
# check that a startContainer hook doesn't get any extra file descriptor.
172+
@test "runc run [seccomp] (SCMP_ACT_NOTIFY startContainer hook)" {
173+
# shellcheck disable=SC2016
174+
# We use single quotes to properly delimit the $1 param to
175+
# update_config(), but this shellshcheck is quite silly and fails if the
176+
# multi-line string includes some $var (even when it is properly outside of the
177+
# single quotes) or when we use this syntax to execute commands in the
178+
# string: $(command).
179+
# So, just disable this check for our usage of update_config().
180+
update_config ' .process.args = ["/bin/true"]
181+
| .linux.seccomp = {
182+
"defaultAction":"SCMP_ACT_ALLOW",
183+
"listenerPath": "'"$SECCCOMP_AGENT_SOCKET"'",
184+
"architectures": [ "SCMP_ARCH_X86", "SCMP_ARCH_X32", "SCMP_ARCH_X86_64" ],
185+
"syscalls":[{ "names": [ "mkdir" ], "action": "SCMP_ACT_NOTIFY" }]
186+
}
187+
|.hooks = {
188+
"startContainer": [ {
189+
"path": "/bin/sh",
190+
"args": [
191+
"sh",
192+
"-c",
193+
"if [ $(ls /proc/self/fd/ | wc -l) -ne 4 ]; then echo \"File descriptors is not 4\". && ls /proc/self/fd/ | wc -l && exit 1; fi"
194+
],
195+
} ]
196+
}'
197+
198+
runc run test_busybox
199+
[ "$status" -eq 0 ]
200+
}

0 commit comments

Comments
 (0)