github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/tests/integration/seccomp-notify.bats (about) 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 basic actions handled by the agent work fine. noNewPrivileges FALSE. 46 @test "runc run [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges false)" { 47 scmp_act_notify_template "mkdir /dev/shm/foo && stat /dev/shm/foo-bar" false '"mkdir"' 48 49 runc run test_busybox 50 [ "$status" -eq 0 ] 51 } 52 53 # Test basic actions handled by the agent work fine. noNewPrivileges TRUE. 54 @test "runc run [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges true)" { 55 scmp_act_notify_template "mkdir /dev/shm/foo && stat /dev/shm/foo-bar" true '"mkdir"' 56 57 runc run test_busybox 58 [ "$status" -eq 0 ] 59 } 60 61 # Test actions not-handled by the agent work fine. noNewPrivileges FALSE. 62 @test "runc exec [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges false)" { 63 requires root 64 65 scmp_act_notify_template "sleep infinity" false '"mkdir"' 66 67 runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 68 [ "$status" -eq 0 ] 69 70 runc exec test_busybox /bin/sh -c "mkdir /dev/shm/foo && stat /dev/shm/foo-bar" 71 [ "$status" -eq 0 ] 72 } 73 74 # Test actions not-handled by the agent work fine. noNewPrivileges TRUE. 75 @test "runc exec [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges true)" { 76 requires root 77 78 scmp_act_notify_template "sleep infinity" true '"mkdir"' 79 80 runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 81 runc exec test_busybox /bin/sh -c "mkdir /dev/shm/foo && stat /dev/shm/foo-bar" 82 [ "$status" -eq 0 ] 83 } 84 85 # Test important syscalls (some might be executed by runc) work fine when handled by the agent. noNewPrivileges FALSE. 86 @test "runc run [seccomp] (SCMP_ACT_NOTIFY important syscalls noNewPrivileges false)" { 87 scmp_act_notify_template "/bin/true" false '"execve","openat","open","read","close"' 88 89 runc run test_busybox 90 [ "$status" -eq 0 ] 91 } 92 93 # Test important syscalls (some might be executed by runc) work fine when handled by the agent. noNewPrivileges TRUE. 94 @test "runc run [seccomp] (SCMP_ACT_NOTIFY important syscalls noNewPrivileges true)" { 95 scmp_act_notify_template "/bin/true" true '"execve","openat","open","read","close"' 96 97 runc run test_busybox 98 [ "$status" -eq 0 ] 99 } 100 101 # Ignore listenerPath if the profile doesn't use seccomp notify actions. 102 @test "runc run [seccomp] (ignore listener path if no notify act)" { 103 update_config ' .process.args = ["/bin/sh", "-c", "mkdir /dev/shm/foo && stat /dev/shm/foo"] 104 | .linux.seccomp = { 105 "defaultAction":"SCMP_ACT_ALLOW", 106 "listenerPath": "'"$SECCCOMP_AGENT_SOCKET"'", 107 "listenerMetadata": "bar", 108 }' 109 110 runc run test_busybox 111 [ "$status" -eq 0 ] 112 } 113 114 # Ensure listenerPath is present if the profile uses seccomp notify actions. 115 @test "runc run [seccomp] (SCMP_ACT_NOTIFY empty listener path and notify act)" { 116 scmp_act_notify_template "/bin/true" false '"mkdir"' 117 update_config '.linux.seccomp.listenerPath = ""' 118 119 runc run test_busybox 120 [ "$status" -ne 0 ] 121 } 122 123 # Test using an invalid socket (none listening) as listenerPath fails. 124 @test "runc run [seccomp] (SCMP_ACT_NOTIFY wrong listener path)" { 125 scmp_act_notify_template "/bin/true" false '"mkdir"' 126 update_config '.linux.seccomp.listenerPath = "/some-non-existing-listener-path.sock"' 127 128 runc run test_busybox 129 [ "$status" -ne 0 ] 130 } 131 132 # Test using an invalid abstract socket as listenerPath fails. 133 @test "runc run [seccomp] (SCMP_ACT_NOTIFY wrong abstract listener path)" { 134 scmp_act_notify_template "/bin/true" false '"mkdir"' 135 update_config '.linux.seccomp.listenerPath = "@mysocketishere"' 136 137 runc run test_busybox 138 [ "$status" -ne 0 ] 139 } 140 141 # Check that killing the seccompagent doesn't block syscalls in 142 # the container. They should return ENOSYS instead. 143 @test "runc run [seccomp] (SCMP_ACT_NOTIFY kill seccompagent)" { 144 scmp_act_notify_template "sleep 4 && mkdir /dev/shm/foo" false '"mkdir"' 145 146 sleep 2 && teardown_seccompagent & 147 runc run test_busybox 148 [ "$status" -ne 0 ] 149 [[ "$output" == *"mkdir:"*"/dev/shm/foo"*"Function not implemented"* ]] 150 } 151 152 # Check that starting with no seccomp agent running fails with a clear error. 153 @test "runc run [seccomp] (SCMP_ACT_NOTIFY no seccompagent)" { 154 teardown_seccompagent 155 156 scmp_act_notify_template "/bin/true" false '"mkdir"' 157 158 runc run test_busybox 159 [ "$status" -ne 0 ] 160 [[ "$output" == *"failed to connect with seccomp agent"* ]] 161 } 162 163 # Check that agent-returned error for the syscall works. 164 @test "runc run [seccomp] (SCMP_ACT_NOTIFY error chmod)" { 165 scmp_act_notify_template "touch /dev/shm/foo && chmod 777 /dev/shm/foo" false '"chmod", "fchmod", "fchmodat"' 166 167 runc run test_busybox 168 [ "$status" -ne 0 ] 169 [[ "$output" == *"chmod:"*"/dev/shm/foo"*"No medium found"* ]] 170 } 171 172 # check that trying to use SCMP_ACT_NOTIFY with write() gives a meaningful error. 173 @test "runc run [seccomp] (SCMP_ACT_NOTIFY write)" { 174 scmp_act_notify_template "/bin/true" false '"write"' 175 176 runc run test_busybox 177 [ "$status" -ne 0 ] 178 [[ "$output" == *"SCMP_ACT_NOTIFY cannot be used for the write syscall"* ]] 179 } 180 181 # check that a startContainer hook doesn't get any extra file descriptor. 182 @test "runc run [seccomp] (SCMP_ACT_NOTIFY startContainer hook)" { 183 # shellcheck disable=SC2016 184 # We use single quotes to properly delimit the $1 param to 185 # update_config(), but this shellshcheck is quite silly and fails if the 186 # multi-line string includes some $var (even when it is properly outside of the 187 # single quotes) or when we use this syntax to execute commands in the 188 # string: $(command). 189 # So, just disable this check for our usage of update_config(). 190 update_config ' .process.args = ["/bin/true"] 191 | .linux.seccomp = { 192 "defaultAction":"SCMP_ACT_ALLOW", 193 "listenerPath": "'"$SECCCOMP_AGENT_SOCKET"'", 194 "architectures": [ "SCMP_ARCH_X86", "SCMP_ARCH_X32", "SCMP_ARCH_X86_64" ], 195 "syscalls":[{ "names": [ "mkdir" ], "action": "SCMP_ACT_NOTIFY" }] 196 } 197 |.hooks = { 198 "startContainer": [ { 199 "path": "/bin/sh", 200 "args": [ 201 "sh", 202 "-c", 203 "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" 204 ], 205 } ] 206 }' 207 208 runc run test_busybox 209 [ "$status" -eq 0 ] 210 } 211 212 # Check that example config in the seccomp agent dir works. 213 @test "runc run [seccomp] (SCMP_ACT_NOTIFY example config)" { 214 # Run the script used in the seccomp agent example. 215 # This takes a bare config.json and modifies it to run an example. 216 "${INTEGRATION_ROOT}/../../contrib/cmd/seccompagent/gen-seccomp-example-cfg.sh" 217 218 # The listenerPath the previous command uses is the default used by the 219 # seccomp agent. However, inside bats the socket is in a bats tmp dir. 220 update_config '.linux.seccomp.listenerPath = "'"$SECCCOMP_AGENT_SOCKET"'"' 221 222 runc run test_busybox 223 224 [ "$status" -eq 0 ] 225 [[ "$output" == *"chmod:"*"test-file"*"No medium found"* ]] 226 }