From 8c199d4f1a3bb35615afdab838c9c54021ebec6c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 30 Jul 2026 10:41:42 +0200 Subject: [PATCH] test: cover overlayfs in TEST-90-RESTRICT-FSACCESS Have the attach helper print whether the loader enabled union filesystem resolution and assert that this agrees with the kernel BTF. Then mount the signed minimal image as an overlayfs lower layer with a tmpfs upper. On kernels with bpf_real_data_inode() executing through the overlay works while a file created in the upper layer or copied up into it is denied. Without the kfunc execution through the overlay is denied outright. The overlay uses metacopy=off so that the copy-up test really does move the data over to the untrusted upper layer. Signed-off-by: Christian Brauner (Amutable) --- src/test/test-bpf-restrict-fsaccess.c | 1 + .../TEST-90-RESTRICT-FSACCESS.enforce.sh | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/src/test/test-bpf-restrict-fsaccess.c b/src/test/test-bpf-restrict-fsaccess.c index df07222478..51c8722c68 100644 --- a/src/test/test-bpf-restrict-fsaccess.c +++ b/src/test/test-bpf-restrict-fsaccess.c @@ -137,6 +137,7 @@ static int do_attach(void) { printf("VERITY_MAP_ID=%u\n", (unsigned) obj->bss->protected_map_id_verity); printf("BSS_MAP_ID=%u\n", (unsigned) obj->bss->protected_map_id_bss); + printf("HAVE_REAL_DATA_INODE=%u\n", (unsigned) obj->rodata->have_real_data_inode); /* Print comma-separated prog and link IDs for guard tests */ printf("PROG_IDS=\""); diff --git a/test/units/TEST-90-RESTRICT-FSACCESS.enforce.sh b/test/units/TEST-90-RESTRICT-FSACCESS.enforce.sh index ad1093f44f..f5398f62d2 100755 --- a/test/units/TEST-90-RESTRICT-FSACCESS.enforce.sh +++ b/test/units/TEST-90-RESTRICT-FSACCESS.enforce.sh @@ -79,6 +79,11 @@ at_exit() { rm -rf /tmp/restrict-fsaccess-test umount /tmp/restrict-fsaccess-baseline 2>/dev/null || true rm -rf /tmp/restrict-fsaccess-baseline + # Clean up overlay test mounts + umount /tmp/restrict-fsaccess-overlay/merged 2>/dev/null || true + umount /tmp/restrict-fsaccess-overlay/rw 2>/dev/null || true + systemd-dissect --umount /tmp/restrict-fsaccess-overlay/lower 2>/dev/null || true + rm -rf /tmp/restrict-fsaccess-overlay # Clean up background processes [[ -n "${SLEEP_PID:-}" ]] && kill "$SLEEP_PID" 2>/dev/null || true rm -rf /tmp/restrict-fsaccess-attach.out @@ -195,6 +200,81 @@ else echo "Verity keyring trust not available, skipping positive verity test" fi +# ------ Test: overlayfs over a signed dm-verity lower layer ------ +# Kernels with the bpf_real_data_inode() kfunc (v7.2+) resolve files on +# union filesystems to the layer hosting the data: execution through an +# overlay whose lower layer sits on a signed verity device is permitted, +# while files created in (or copied up to) an untrusted upper layer are +# denied. Without the kfunc, everything on the overlay is denied. + +if command -v bpftool >/dev/null 2>&1; then + KERNEL_HAS_REAL_DATA_INODE=0 + if bpftool btf dump file /sys/kernel/btf/vmlinux 2>/dev/null | grep "FUNC 'bpf_real_data_inode'" >/dev/null; then + KERNEL_HAS_REAL_DATA_INODE=1 + fi + + # The loader's BTF probe must agree with the kernel BTF + HAVE_RDI="$(sed -n 's/^HAVE_REAL_DATA_INODE=//p' /tmp/restrict-fsaccess-attach.out)" + if [[ "$HAVE_RDI" != "$KERNEL_HAS_REAL_DATA_INODE" ]]; then + echo "ERROR: Loader probed have_real_data_inode=$HAVE_RDI but kernel BTF says $KERNEL_HAS_REAL_DATA_INODE!" >&2 + exit 1 + fi + + if machine_supports_verity_keyring; then + OVL=/tmp/restrict-fsaccess-overlay + mkdir -p "$OVL"/{lower,rw,merged} + systemd-dissect --mount "$MINIMAL.raw" "$OVL/lower" + mount -t tmpfs tmpfs "$OVL/rw" + mkdir -p "$OVL/rw/upper" "$OVL/rw/work" + # metacopy=off: a metadata-only copy-up would keep the data — and + # hence the verdict — on the lower layer, breaking the copy-up test + mount -t overlay overlay \ + -o "lowerdir=$OVL/lower/usr,upperdir=$OVL/rw/upper,workdir=$OVL/rw/work,metacopy=off" \ + "$OVL/merged" + + if [[ "$KERNEL_HAS_REAL_DATA_INODE" == 1 ]]; then + # Resolution follows the data to the signed verity lower layer + "$OVL/merged/bin/bash" -c 'exit 0' + echo "Execution through overlay from signed verity lower: OK" + + # A file created in the (tmpfs) upper layer is untrusted. + # Basename must stay "true" for multicall coreutils binaries — + # see the baseline comment above. + cp /usr/bin/true "$OVL/merged/true" + if "$OVL/merged/true" 2>/dev/null; then + echo "ERROR: Execution of upper-layer file should have been blocked!" >&2 + exit 1 + fi + echo "Execution from overlay upper layer blocked: OK" + + # Copy-up moves the data to the untrusted upper layer + chmod 700 "$OVL/merged/bin/bash" + if "$OVL/merged/bin/bash" -c 'exit 0' 2>/dev/null; then + echo "ERROR: Execution of copied-up file should have been blocked!" >&2 + exit 1 + fi + echo "Execution of copied-up file blocked: OK" + else + # Without the kfunc all the program sees is the overlay's + # anonymous device: deny, even though the lower is trusted + if "$OVL/merged/bin/bash" -c 'exit 0' 2>/dev/null; then + echo "ERROR: Execution through overlay should have been blocked without bpf_real_data_inode!" >&2 + exit 1 + fi + echo "Execution through overlay blocked (no bpf_real_data_inode): OK" + fi + + umount "$OVL/merged" + umount "$OVL/rw" + systemd-dissect --umount "$OVL/lower" + rm -rf "$OVL" + else + echo "Verity keyring trust not available, skipping overlay tests" + fi +else + echo "bpftool not available, skipping overlay tests" +fi + # ------ Test: Guard blocks non-PID1 from obtaining BPF object FDs by ID ------ if command -v bpftool >/dev/null 2>&1 && [[ -n "${VERITY_MAP_ID:-}" ]]; then