Files
runc/tests/integration/seccomp.bats
T
Kir Kolyshkin 19a9d9fc9e tests/int: use runc features in seccomp flags test
This test (initially added by commit 58ea21daef and later amended in
commit 26dc55ef1a) currently has two major deficiencies:

1. All possible flag combinations, and their respective numeric values,
   have to be explicitly listed. Currently we support 3 flags, so
   there is only 2^3 - 1 = 7 combinations, but adding more flags will
   become increasingly difficult (for example, 5 flags will result in
   31 combinations).

2. The test requires kernel 4.17 (for SECCOMP_FILTER_FLAG_SPEC_ALLOW),
   and not doing any tests when running on an older kernel. This, too,
   will make it more difficult to add extra flags in the future.

Both issues can be solved by using runc features which now prints all
known and supported runc flags. We still have to hardcode the numeric
values of all flags, but most of the other work is coded now.

In particular:

 * The test only uses supported flags, meaning it can be used with
   older kernels, removing the limitation (2) above.

 * The test calculates the powerset (all possible combinations) of
   flags and their numeric values. This makes it easier to add more
   flags, removing the limitation (1) above.

 * The test will fail (in flags_value) if any new flags will be added
   to runc but the test itself is not amended.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2022-11-29 17:24:32 -08:00

188 lines
5.8 KiB
Bash

#!/usr/bin/env bats
load helpers
function setup() {
setup_busybox
}
function teardown() {
teardown_bundle
}
@test "runc run [seccomp -ENOSYS handling]" {
TEST_NAME="seccomp_syscall_test1"
# Compile the test binary and update the config to run it.
gcc -static -o rootfs/seccomp_test "${TESTDATA}/${TEST_NAME}.c"
update_config ".linux.seccomp = $(<"${TESTDATA}/${TEST_NAME}.json")"
update_config '.process.args = ["/seccomp_test"]'
runc run test_busybox
[ "$status" -eq 0 ]
}
@test "runc run [seccomp defaultErrnoRet=ENXIO]" {
TEST_NAME="seccomp_syscall_test2"
# Compile the test binary and update the config to run it.
gcc -static -o rootfs/seccomp_test2 "${TESTDATA}/${TEST_NAME}.c"
update_config ".linux.seccomp = $(<"${TESTDATA}/${TEST_NAME}.json")"
update_config '.process.args = ["/seccomp_test2"]'
runc run test_busybox
[ "$status" -eq 0 ]
}
# TODO:
# - Test other actions like SCMP_ACT_TRAP, SCMP_ACT_TRACE, SCMP_ACT_LOG.
# - Test args (index, value, valueTwo, etc).
@test "runc run [seccomp] (SCMP_ACT_ERRNO default)" {
update_config ' .process.args = ["/bin/sh", "-c", "mkdir /dev/shm/foo"]
| .process.noNewPrivileges = false
| .linux.seccomp = {
"defaultAction":"SCMP_ACT_ALLOW",
"architectures":["SCMP_ARCH_X86","SCMP_ARCH_X32","SCMP_ARCH_X86_64","SCMP_ARCH_AARCH64","SCMP_ARCH_ARM"],
"syscalls":[{"names":["mkdir","mkdirat"], "action":"SCMP_ACT_ERRNO"}]
}'
runc run test_busybox
[ "$status" -ne 0 ]
[[ "$output" == *"mkdir:"*"/dev/shm/foo"*"Operation not permitted"* ]]
}
@test "runc run [seccomp] (SCMP_ACT_ERRNO explicit errno)" {
update_config ' .process.args = ["/bin/sh", "-c", "mkdir /dev/shm/foo"]
| .process.noNewPrivileges = false
| .linux.seccomp = {
"defaultAction":"SCMP_ACT_ALLOW",
"architectures":["SCMP_ARCH_X86","SCMP_ARCH_X32","SCMP_ARCH_X86_64","SCMP_ARCH_AARCH64","SCMP_ARCH_ARM"],
"syscalls":[{"names":["mkdir","mkdirat"], "action":"SCMP_ACT_ERRNO", "errnoRet": 100}]
}'
runc run test_busybox
[ "$status" -ne 0 ]
[[ "$output" == *"Network is down"* ]]
}
# Prints the numeric value of provided seccomp flags combination.
# The parameter is flags string, as supplied in OCI spec, for example
# '"SECCOMP_FILTER_FLAG_TSYNC","SECCOMP_FILTER_FLAG_LOG"'.
function flags_value() {
# Numeric values of seccomp flags.
declare -A values=(
['"SECCOMP_FILTER_FLAG_TSYNC"']=0 # Supported but ignored by runc, thus 0.
['"SECCOMP_FILTER_FLAG_LOG"']=2
['"SECCOMP_FILTER_FLAG_SPEC_ALLOW"']=4
# XXX: add new values above this line.
)
# Split the flags.
IFS=',' read -ra flags <<<"$1"
local flag v sum=0
for flag in "${flags[@]}"; do
# This will produce "values[$flag]: unbound variable"
# error for a new flag yet unknown to the test.
v=${values[$flag]}
((sum += v)) || true
done
echo $sum
}
@test "runc run [seccomp] (SECCOMP_FILTER_FLAG_*)" {
update_config ' .process.args = ["/bin/sh", "-c", "mkdir /dev/shm/foo"]
| .process.noNewPrivileges = false
| .linux.seccomp = {
"defaultAction":"SCMP_ACT_ALLOW",
"architectures":["SCMP_ARCH_X86","SCMP_ARCH_X32","SCMP_ARCH_X86_64","SCMP_ARCH_AARCH64","SCMP_ARCH_ARM"],
"syscalls":[{"names":["mkdir", "mkdirat"], "action":"SCMP_ACT_ERRNO"}]
}'
# Get the list of flags supported by runc/seccomp/kernel,
# or "null" if no flags are supported or runc is too old.
mapfile -t flags < <(__runc features | jq -c '.linux.seccomp.supportedFlags' |
tr -d '[]\n' | tr ',' '\n')
# This is a set of all possible flag combinations to test.
declare -A TEST_CASES=(
['EMPTY']=0 # Special value: empty set of flags.
['REMOVE']=0 # Special value: no flags set.
)
# If supported, runc should set SPEC_ALLOW if no flags are set.
if [[ " ${flags[*]} " == *' "SECCOMP_FILTER_FLAG_SPEC_ALLOW" '* ]]; then
TEST_CASES['REMOVE']=$(flags_value '"SECCOMP_FILTER_FLAG_SPEC_ALLOW"')
fi
# Add all possible combinations of seccomp flags
# and their expected numeric values to TEST_CASES.
if [ "${flags[0]}" != "null" ]; then
# Use shell {a,}{b,}{c,} to generate the powerset.
for fc in $(eval echo "$(printf "{'%s,',}" "${flags[@]}")"); do
# Remove the last comma.
fc="${fc/%,/}"
TEST_CASES[$fc]=$(flags_value "$fc")
done
fi
# Finally, run the tests.
for key in "${!TEST_CASES[@]}"; do
case "$key" in
'REMOVE')
update_config ' del(.linux.seccomp.flags)'
;;
'EMPTY')
update_config ' .linux.seccomp.flags = []'
;;
*)
update_config ' .linux.seccomp.flags = [ '"${key}"' ]'
;;
esac
runc --debug run test_busybox
[ "$status" -ne 0 ]
[[ "$output" == *"mkdir:"*"/dev/shm/foo"*"Operation not permitted"* ]]
# Check the numeric flags value, as printed in the debug log, is as expected.
exp="\"seccomp filter flags: ${TEST_CASES[$key]}\""
echo "flags $key, expecting $exp"
[[ "$output" == *"$exp"* ]]
done
}
@test "runc run [seccomp] (SCMP_ACT_KILL)" {
update_config ' .process.args = ["/bin/sh", "-c", "mkdir /dev/shm/foo"]
| .process.noNewPrivileges = false
| .linux.seccomp = {
"defaultAction":"SCMP_ACT_ALLOW",
"architectures":["SCMP_ARCH_X86","SCMP_ARCH_X32","SCMP_ARCH_X86_64","SCMP_ARCH_AARCH64","SCMP_ARCH_ARM"],
"syscalls":[{"names":["mkdir","mkdirat"], "action":"SCMP_ACT_KILL"}]
}'
runc run test_busybox
[ "$status" -ne 0 ]
}
# check that a startContainer hook is run with the seccomp filters applied
@test "runc run [seccomp] (startContainer hook)" {
update_config ' .process.args = ["/bin/true"]
| .linux.seccomp = {
"defaultAction":"SCMP_ACT_ALLOW",
"architectures":["SCMP_ARCH_X86","SCMP_ARCH_X32","SCMP_ARCH_X86_64","SCMP_ARCH_AARCH64","SCMP_ARCH_ARM"],
"syscalls":[{"names":["mkdir","mkdirat"], "action":"SCMP_ACT_KILL"}]
}
| .hooks = {
"startContainer": [ {
"path": "/bin/sh",
"args": ["sh", "-c", "mkdir /dev/shm/foo"]
} ]
}'
runc run test_busybox
[ "$status" -ne 0 ]
[[ "$output" == *"error running hook"* ]]
[[ "$output" == *"bad system call"* ]]
}