From 937f609b41b9e27eba69c5ddbab4df2232e5a37b Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 9 Oct 2025 23:08:19 +0200 Subject: [PATCH 1/4] test: build the crashing test binary outside of the test So we don't have to pull in gcc and other stuff into it. Also, make the test itself a bit more robust and debug-able. --- src/test/meson.build | 11 ++++ src/test/test-coredump-stacktrace.c | 29 +++++++++ test/units/TEST-87-AUX-UTILS-VM.coredump.sh | 72 +++++++++++++++------ 3 files changed, 93 insertions(+), 19 deletions(-) create mode 100644 src/test/test-coredump-stacktrace.c diff --git a/src/test/meson.build b/src/test/meson.build index e25637dcc6..d5f133bca7 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -275,6 +275,17 @@ executables += [ 'sources' : files('test-compress.c'), 'link_with' : [libshared], }, + test_template + { + 'sources' : files('test-coredump-stacktrace.c'), + 'type' : 'manual', + # This test intentionally crashes with SIGSEGV by dereferencing a NULL pointer + # to generate a coredump with a predictable stack trace. To prevent sanitizers + # from catching the error first let's disable them explicitly, and also always + # build with minimal optimizations to make the stack trace predictable no matter + # what we build the rest of systemd with + 'override_options' : ['b_sanitize=none', 'strip=false', 'debug=true'], + 'c_args' : ['-fno-sanitize=all', '-fno-optimize-sibling-calls', '-O1'], + }, test_template + { 'sources' : files('test-cryptolib.c'), 'dependencies' : libopenssl, diff --git a/src/test/test-coredump-stacktrace.c b/src/test/test-coredump-stacktrace.c new file mode 100644 index 0000000000..334a155a9c --- /dev/null +++ b/src/test/test-coredump-stacktrace.c @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* This is a test program that intentionally segfaults so we can generate a + * predictable-ish stack trace in tests. */ + +#include + +__attribute__((noinline)) +static void baz(int *x) { + *x = rand(); +} + +__attribute__((noinline)) +static void bar(void) { + int * volatile x = NULL; + + baz(x); +} + +__attribute__((noinline)) +static void foo(void) { + bar(); +} + +int main(void) { + foo(); + + return 0; +} diff --git a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh index 0ea1a0260f..2dfa6eccf1 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh @@ -8,15 +8,13 @@ set -o pipefail # Make sure the binary name fits into 15 characters CORE_TEST_BIN="/tmp/test-dump" -CORE_STACKTRACE_TEST_BIN="/tmp/test-stacktrace-dump" -MAKE_STACKTRACE_DUMP="/tmp/make-stacktrace-dump" CORE_TEST_UNPRIV_BIN="/tmp/test-usr-dump" MAKE_DUMP_SCRIPT="/tmp/make-dump" # Unset $PAGER so we don't have to use --no-pager everywhere export PAGER= at_exit() { - rm -fv -- "$CORE_TEST_BIN" "$CORE_TEST_UNPRIV_BIN" "$MAKE_DUMP_SCRIPT" "$MAKE_STACKTRACE_DUMP" + rm -fv -- "$CORE_TEST_BIN" "$CORE_TEST_UNPRIV_BIN" "$MAKE_DUMP_SCRIPT" } (! systemd-detect-virt -cq) @@ -251,30 +249,66 @@ systemd-run -t --property CoredumpFilter=default ls /tmp (! coredumpctl debug --debugger=/bin/true --debugger-arguments='"') # Test for EnterNamespace= feature -if pkgconf --atleast-version 0.192 libdw ; then - # dwfl_set_sysroot() is supported only in libdw-0.192 or newer. - cat >"$MAKE_STACKTRACE_DUMP" <"$MAKE_STACKTRACE_DUMP" <<\EOF +#!/usr/bin/bash -eux + +TARGET="/tmp/${1:?}" +EC=0 + +# "Unhide" debuginfo in the namespace (see the comment below) +test -d /usr/lib/debug/ && umount /usr/lib/debug/ + +mount -t tmpfs tmpfs /tmp/ +cp /usr/lib/systemd/tests/unit-tests/manual/test-coredump-stacktrace "$TARGET" + +$TARGET || EC=$? +if [[ $EC -ne 139 ]]; then + echo >&2 "$TARGET didn't crash, this shouldn't happen" + exit 1 +fi + +exit 0 EOF -$CORE_STACKTRACE_TEST_BIN -END chmod +x "$MAKE_STACKTRACE_DUMP" + # Since the test-coredump-stacktrace binary is built together with rest of the systemd its debug symbols + # might be part of debuginfo packages (if supported & built), and libdw will then use them to symbolize + # the stacktrace even if it doesn't have access to the original crashing binary. Let's make the test + # simpler and just "hide" the debuginfo data, so libdw is forced to access the target namespace to get + # the necessary symbols + test -d /usr/lib/debug/ && mount -t tmpfs tmpfs /usr/lib/debug/ + mkdir -p /run/systemd/coredump.conf.d/ printf '[Coredump]\nEnterNamespace=no' >/run/systemd/coredump.conf.d/99-enter-namespace.conf - unshare --pid --fork --mount-proc --mount --uts --ipc --net bash -c "$MAKE_STACKTRACE_DUMP" || : - timeout 30 bash -c "until coredumpctl -1 info $CORE_STACKTRACE_TEST_BIN | grep -zvqE 'baz.*bar.*foo'; do sleep .2; done" + unshare --pid --fork --mount-proc --mount --uts --ipc --net "$MAKE_STACKTRACE_DUMP" "test-stacktrace-not-symbolized" + timeout 30 bash -c "until coredumpctl list -q --no-legend /tmp/test-stacktrace-not-symbolized; do sleep .2; done" + coredumpctl info /tmp/test-stacktrace-not-symbolized | tee /tmp/not-symbolized.log + (! grep -E "#[0-9]+ .* main " /tmp/not-symbolized.log) + (! grep -E "#[0-9]+ .* foo " /tmp/not-symbolized.log) + (! grep -E "#[0-9]+ .* bar " /tmp/not-symbolized.log) + (! grep -E "#[0-9]+ .* baz " /tmp/not-symbolized.log) printf '[Coredump]\nEnterNamespace=yes' >/run/systemd/coredump.conf.d/99-enter-namespace.conf - unshare --pid --fork --mount-proc --mount --uts --ipc --net bash -c "$MAKE_STACKTRACE_DUMP" || : - timeout 30 bash -c "until coredumpctl -1 info $CORE_STACKTRACE_TEST_BIN | grep -zqE 'baz.*bar.*foo'; do sleep .2; done" + unshare --pid --fork --mount-proc --mount --uts --ipc --net "$MAKE_STACKTRACE_DUMP" "test-stacktrace-symbolized" + timeout 30 bash -c "until coredumpctl list -q --no-legend /tmp/test-stacktrace-symbolized; do sleep .2; done" + coredumpctl info /tmp/test-stacktrace-symbolized | tee /tmp/symbolized.log + grep -E "#[0-9]+ .* main " /tmp/symbolized.log + grep -E "#[0-9]+ .* foo " /tmp/symbolized.log + grep -E "#[0-9]+ .* bar " /tmp/symbolized.log + grep -E "#[0-9]+ .* baz " /tmp/symbolized.log + + test -d /usr/lib/debug/ && umount /usr/lib/debug/ + rm -f "$MAKE_STACKTRACE_DUMP" /run/systemd/coredump.conf.d/99-enter-namespace.conf /tmp/{not-,}symbolized.log else echo "libdw doesn't not support setting sysroot, skipping EnterNamespace= test" fi From cfb604f8f7c83912648d69bd3ad89c2436b4b8ef Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 9 Oct 2025 17:57:25 +0200 Subject: [PATCH 2/4] test: exclude test-stacktrace(-not)?-symbolized from the coredump check As they are expected coredumps from the EnterNamespace= feature test. --- test/integration-tests/TEST-87-AUX-UTILS-VM/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration-tests/TEST-87-AUX-UTILS-VM/meson.build b/test/integration-tests/TEST-87-AUX-UTILS-VM/meson.build index d82172729f..e469a78c5f 100644 --- a/test/integration-tests/TEST-87-AUX-UTILS-VM/meson.build +++ b/test/integration-tests/TEST-87-AUX-UTILS-VM/meson.build @@ -5,7 +5,7 @@ integration_tests += [ integration_test_template + { 'name' : fs.name(meson.current_source_dir()), 'storage': 'persistent', - 'coredump-exclude-regex' : '/(test-usr-dump|test-dump|bash)$', + 'coredump-exclude-regex' : '/(test-usr-dump|test-dump|test-stacktrace(-not)?-symbolized|bash)$', 'vm' : true, 'firmware' : 'auto', }, From 4d8e8d44ab3f6f99102faf0dcb53ca4de4d517ae Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Thu, 9 Oct 2025 17:54:58 +0200 Subject: [PATCH 3/4] mkosi: install test dependencies for EnterNamespace= test The test for the EnterNamespace= feature [0] has been both broken and disabled since the migration to the mkosi framework, as it's missing the libdw.pc file for pkg-config, so the test is skipped completely, and it's also missing gcc to actually build the test binary. [0] Part of TEST-87-AUX-UTILS-VM.coredump.sh --- mkosi/mkosi.conf.d/arch/mkosi.conf | 2 ++ mkosi/mkosi.conf.d/centos-fedora/mkosi.conf | 3 +++ mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf | 3 +++ mkosi/mkosi.conf.d/opensuse/mkosi.conf | 3 +++ 4 files changed, 11 insertions(+) diff --git a/mkosi/mkosi.conf.d/arch/mkosi.conf b/mkosi/mkosi.conf.d/arch/mkosi.conf index a06b84371b..5807dcdef0 100644 --- a/mkosi/mkosi.conf.d/arch/mkosi.conf +++ b/mkosi/mkosi.conf.d/arch/mkosi.conf @@ -22,6 +22,7 @@ Packages= dbus-broker dbus-broker-units dhcp + elfutils erofs-utils f2fs-tools git @@ -39,6 +40,7 @@ Packages= openssl pacman perf + pkgconf polkit procps-ng psmisc diff --git a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf index 0de00d82ac..a898c03067 100644 --- a/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf +++ b/mkosi/mkosi.conf.d/centos-fedora/mkosi.conf @@ -28,6 +28,8 @@ Packages= device-mapper-event device-mapper-multipath dfuzzer + elfutils-devel + elfutils-libs erofs-utils git-core glibc-langpack-de @@ -49,6 +51,7 @@ Packages= pam passwd perf + pkgconf policycoreutils polkit procps-ng diff --git a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf index 694b8a5165..511d000384 100644 --- a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf +++ b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf @@ -53,6 +53,8 @@ Packages= isc-dhcp-server knot libcap-ng-utils + libdw-dev + libdw1 locales login man-db @@ -63,6 +65,7 @@ Packages= openssh-server passwd polkitd + pkgconf procps psmisc python3-pexpect diff --git a/mkosi/mkosi.conf.d/opensuse/mkosi.conf b/mkosi/mkosi.conf.d/opensuse/mkosi.conf index 1b2f80fd4d..98c7364a00 100644 --- a/mkosi/mkosi.conf.d/opensuse/mkosi.conf +++ b/mkosi/mkosi.conf.d/opensuse/mkosi.conf @@ -58,6 +58,8 @@ Packages= knot libapparmor1 libcap-progs + libdw-devel + libdw1 libtss2-tcti-device0 man multipath-tools @@ -68,6 +70,7 @@ Packages= pam patterns-base-minimal_base perf + pkgconf procps4 psmisc python3-pefile From 80b4cacf1b121c8b1ec444970cc566dc4efd1837 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 10 Oct 2025 20:09:51 +0200 Subject: [PATCH 4/4] test: temporarily skip the EnterNamespace= test w/o embedded debuginfo The EnterNamespace= feature currently doesn't work if the debuginfo is separated from the crashing binary. Until that's resolved, let's run the test only if the test binary has embedded debuginfo (.debug_info section; e.g. when systemd is built without WITH_DEBUG=1) or it contains MiniDebugInfo (.gnu_debugdata section; default on Fedora and CentOS). See: https://github.com/systemd/systemd/pull/39268#issuecomment-3390745718 --- test/units/TEST-87-AUX-UTILS-VM.coredump.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh index 2dfa6eccf1..4f2bbf40c8 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh @@ -251,7 +251,11 @@ systemd-run -t --property CoredumpFilter=default ls /tmp # Test for EnterNamespace= feature # # dwfl_set_sysroot() is supported only in libdw-0.192 or newer. -if pkgconf --atleast-version 0.192 libdw; then +# +# FIXME: drop the objdump call once https://github.com/systemd/systemd/pull/39268#issuecomment-3390745718 is +# addressed +if pkgconf --atleast-version 0.192 libdw && + objdump -h -j .gnu_debugdata -j .debug_info /usr/lib/systemd/tests/unit-tests/manual/test-coredump-stacktrace; then MAKE_STACKTRACE_DUMP="/tmp/make-stacktrace-dump" # Simple script that mounts tmpfs on /tmp/ and copies the crashing test binary there, which in