...
1#!/usr/bin/env bats
2
3load 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.
10function setup() {
11 requires_kernel 5.6
12 requires arch_x86_64
13
14 setup_seccompagent
15 setup_busybox
16}
17
18function 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
27function 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}
201
202# Check that example config in the seccomp agent dir works.
203@test "runc run [seccomp] (SCMP_ACT_NOTIFY example config)" {
204 # Run the script used in the seccomp agent example.
205 # This takes a bare config.json and modifies it to run an example.
206 "${INTEGRATION_ROOT}/../../contrib/cmd/seccompagent/gen-seccomp-example-cfg.sh"
207
208 # The listenerPath the previous command uses is the default used by the
209 # seccomp agent. However, inside bats the socket is in a bats tmp dir.
210 update_config '.linux.seccomp.listenerPath = "'"$SECCCOMP_AGENT_SOCKET"'"'
211
212 runc run test_busybox
213
214 [ "$status" -eq 0 ]
215 [[ "$output" == *"chmod:"*"test-file"*"No medium found"* ]]
216}
View as plain text