Commit Graph
129 Commits
Author SHA1 Message Date
Luca Boccassi 17e3db12ca test: coco credential transport on snp (#43827) 2026-09-25 13:33:03 +02:00
Paul Meyer 671206e266 test: split coco test per-platform
Grow TEST-94-COCO from a single inline check into the reusable structure
that can be used by multiple future subtests:

- lib.sh: code shared between subtests on the host side.
- guest-test.sh: the testcase_coco_* checks.
- guest-test-runner.sh: runs each testcase_coco_* check, ships guest
  side testcase results over vsock.

Helpers live under test/units/TEST-94-COCO/. The vsock records are
best-effort detail, the aggregate exit status stays the authoritative
pass/fail.

Split the test into subtests per platform/boot flow.

Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-09-25 08:15:24 +02:00
Ivan Kruglov 621df84009 test: give the D-Bus reconnect checks their own integration test
Killing the broker is destructive well beyond the units under test: nothing
reconnects to the new one, so logind for instance keeps running with
org.freedesktop.login1 unowned for the rest of the boot. As a subtest of
TEST-23-UNIT-FILE that leaked into everything running after it, and
statedir.sh, the first one to start user@0.service, then spent 25s per logind
property in user-runtime-dir and, where pam_systemd can't use Varlink, blocked
in CreateSession() past user@.service's 90s TimeoutStartSec and failed — with
the failure pointing at a subtest that has nothing to do with the bus.

In its own test the damage dies with the machine.
2026-09-17 04:51:29 -07:00
Christian Brauner 3674263071 test-bpf-restrict-fsaccess: add W^X and ptrace protection tests
Add tests for all new W^X protection mechanisms.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-08-31 23:33:40 +02:00
Zbigniew Jędrzejewski-Szmek 3d0ef4aefe various: mark files with the usual license tags
For .clang-format and pytest.ini, MIT-0 is used because people might want
to copy those.
2026-08-28 06:26:09 +09:00
Paul Meyer 5d8191da48 test: take extra mkosi options from TEST_MKOSI_ARGS
Add a TEST_MKOSI_ARGS environment variable whose contents are appended
to the mkosi invocation launching the test image, without affecting the
image build. This lets the environment adapt how tests are run, e.g.
pass --format=directory --output-dir=... to boot the test container
from a directory format output in environments that cannot dissect disk
images, such as unprivileged containers, where loop devices and udev
are not available.

Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-08-27 10:00:30 +02:00
Paul Meyer 3904661440 test: make boot mode work in unprivileged containers
In a mount namespace owned by a non-initial user namespace, the kernel
only allows mounting a fresh proc or sysfs if the namespace already
contains a fully visible mount of the same filesystem type
(mount_too_revealing()). systemd-nspawn's pivoted root contains no such
mount, so booting the test container inside an unprivileged container
(e.g. a containerized CI runner) fails with EPERM when mounting /proc.

When running in a non-initial user namespace, satisfy the check by
binding a read-only instance of each into the container under /work.
Skipping the binds on regular privileged runs keeps the container
isolated from the host's proc and sysfs. Binding non-recursively keeps
foreign submounts (e.g. lxcfs overmounts) out, which would spoil full
visibility where they end up locked, and which read-only would not
cover.

Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-08-27 10:00:30 +02:00
Paul Meyer b89729b16a test: add TEST-94-COCO integration test for confidential VMs
Teach the harness a --coco=tdx|sev-snp|any flag: it gates the test on
host coco capability (exit 77 otherwise), binds /dev/kvm and the coco
device(s) into the boot-mode container, and passes the resolved type to
the test via $COCO_TYPE.

TEST-94-COCO boots the mkosi image as a confidential guest with
systemd-vmspawn --coco= (using the /vm-images bind) and checks the guest
reports the expected type via systemd-detect-virt --cvm. Runs only on
coco-capable bare-metal hosts, skips elsewhere.

Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-08-12 15:57:07 +02:00
Paul Meyer 5f34079a6a test: bind built images into boot-mode tests via a --vm-images option
Integration tests that boot a nested VM (e.g. with systemd-vmspawn) need
the mkosi-built images, which are build outputs rather than installed
test artifacts. Add a --vm-images wrapper flag and 'vm-images' test
option that bind-mount the mkosi output directory read-only into the
boot-mode container at /work/vm-images, instead of exposing the whole
build tree via RuntimeBuildSources.

Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-08-12 15:57:07 +02:00
Yu Watanabe 8c3af1bc82 TEST-92-TPM2-SWTPM: set a higher priority
TEST-92-TPM2-SWTPM requires about 15 minutes on GitHub,
if it starts at the very end, the total CI run simply waits for
the test finished, and wastes the last 15 minutes.
Starting earlier should slightly improve the performance.
2026-08-07 11:56:44 +02:00
Chris Down 41c12e32ce TEST-80-NOTIFYACCESS: Add notify-reload signal handler validation test
Add a new integration test to verify the signal handler validation for
Type=notify-reload services, as introduced in the previous commit.

The test adds a shell harness and four service files to cover all scenarios:

1. `notify-reload-no-handler.service`: Verifies that the service fails to
   start with Result=protocol when the handler is missing at the first
   READY=1.

2. `notify-reload-sigstop.service`: Verifies that SIGSTOP is exempt from
   handler validation because it cannot be caught or blocked.

3. `notify-reload-toggle-handler.service`: Verifies that removing the
   handler at runtime results in a warning on reload, but the signal is still
   sent, causing the service to terminate from unhandled SIGHUP.

4. `notify-reload-well-behaved.service`: Verifies the happy path where a
   service with a handler starts and reloads successfully.

Use bounded FIFO operations and journal cursors so failures are prompt and log
assertions cannot race a relative time window.
2026-08-05 17:31:03 -07:00
Frantisek Sumsal f978a4a1e0 Revert "test: re-enable sync in TEST-25-IMPORT"
This shouldn't be necessary anymore, as we don't block openat2() when
running nspawn with --suppress-sync=yes.

This reverts commit c955e24916.
2026-07-24 14:10:58 +09:00
Simran Singh 104970a8bd clonesetup: add support to clone devices via /etc/clonetab
Adds dm-clone device setup at boot via a new /etc/clonetab config file,
following the crypttab/veritytab pattern.

- Add systemd-clonesetup-generator to parse /etc/clonetab and generate units.
- Add systemd-clonesetup binary to create/remove dm-clone devices via ioctl.
- Add clonesetup.target for ordering dm-clone activation at boot.
- Add region_size= option in clonetab to configure dm-clone hydration granularity.
- Add clonetab(5) and systemd-clonesetup-generator(8) man pages.

Fixes: https://github.com/systemd/systemd/issues/39500
2026-07-10 14:01:24 +02:00
Luca Boccassi 682a42891f test: use /run/ for temporary files in TEST-07-PID1.issue-14566
And delete the file before starting, to be safe against reruns

Follow-up for c1566ef0d2
2026-06-29 12:03:57 +01:00
Luca Boccassi e9482b9ebb test: make TEST-07-PID1.issue-14566 more robust
The test slept a fixed 4s after starting the service, then read the
child PID from /leakedtestpid. On a loaded host the executor had not
exec'd the script yet:

[ 1571.079978] TEST-07-PID1.sh[17329]: + systemctl start issue14566-repro
[ 1571.079978] TEST-07-PID1.sh[17329]: + sleep 4
[ 1571.079978] TEST-07-PID1.sh[17329]: + systemctl status issue14566-repro
[ 1562.350438] systemd[1]: init.scope: Child 17296 belongs to init.scope.
[ 1571.081084] TEST-07-PID1.sh[17333]: ● issue14566-repro.service - Issue 14566 Repro
[ 1571.081084] TEST-07-PID1.sh[17333]:      Loaded: loaded (/usr/lib/systemd/tests/testdata/TEST-07-PID1.units/issue14566-repro.service; static)
[ 1571.081084] TEST-07-PID1.sh[17333]:     Drop-In: /usr/lib/systemd/system/service.d
[ 1571.081084] TEST-07-PID1.sh[17333]:              └─10-timeout-abort.conf
[ 1571.081084] TEST-07-PID1.sh[17333]:      Active: active (running) since Mon 2026-06-29 06:19:07 UTC; 4s ago
[ 1571.081084] TEST-07-PID1.sh[17333]:  Invocation: b3356aa5fff24eee85e302250ecb06b1
[ 1571.081084] TEST-07-PID1.sh[17333]:    Main PID: 17331 (9)
[ 1571.081084] TEST-07-PID1.sh[17333]:       Tasks: 1 (limit: 4468)
[ 1571.081084] TEST-07-PID1.sh[17333]:      Memory: 768K (peak: 768K)
[ 1571.081084] TEST-07-PID1.sh[17333]:         CPU: 1ms
[ 1571.081084] TEST-07-PID1.sh[17333]:      CGroup: /system.slice/issue14566-repro.service
[ 1571.081084] TEST-07-PID1.sh[17333]:              └─17331 systemd-executor --deserialize 66 --log-level debug,console:info --log-target journal
[ 1562.350492] systemd[1]: Child 17297 ((sd-close)) died (code=exited, status=0/SUCCESS)
[ 1571.082379] TEST-07-PID1.sh[17334]: ++ cat /leakedtestpid
[ 1571.082379] TEST-07-PID1.sh[17334]: cat: /leakedtestpid: No such file or directory

Make the service Type=notify and notify readiness after writing the PID
file, and wait for the service to go inactive in a timeout loop instead
of fixed sleeps.

Follow-up for c1566ef0d2
2026-06-29 12:03:40 +01:00
Paul Meyer 1b1900a6f3 test: add TEST-92-TPM2-SWTPM for the software TPM fallback
Boot a VM in EFI mode without a hardware/firmware TPM and with
systemd.tpm2_software_fallback=yes, so systemd-tpm2-generator manufactures a
software TPM on the ESP in the initrd and chainloads swtpm. Assert the service
starts, the vtpm-proxy device shows up, and a systemd-creds TPM2 seal/unseal
round-trip works. Then reboot and confirm the sealed secret still unseals,
i.e. the TPM state persisted on the ESP across the reboot.

Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-06-25 09:39:55 +02:00
Zbigniew Jędrzejewski-Szmek 075036f205 test: drop whitespace after shell redirection operators 2026-06-20 01:41:46 +01:00
Luca Boccassi aac19fe468 Revert "test: pin stress-ng --vm-method to a portable scalar method in TEST-55-OOMD"
This reverts commit 881e4717c7.
2026-05-25 11:28:37 +01:00
Luca Boccassi 79ddf3c6f3 Revert "test: switch TEST-55-OOMD stress-ng --vm-method to lfsr32"
This reverts commit 4ac2369728.
2026-05-25 11:28:36 +01:00
Daan De Meyer ecaac1c0a2 TEST-08-INITRD: Set up exitrd on boot rather than including in image
Just copy the initramfs from the VM rather than doing it during
the image build.
2026-05-21 18:06:07 +02:00
Daan De Meyer 8533513aff Introduce support for running code in fibers
Traditionally, asynchronous programming in systemd has been achieved using
sd-event along with the asynchronous interfaces of sd-bus and sd-varlink.
This works well when the system is reacting to events and all code triggered
by those events can run without blocking. In these scenarios, the global
Manager object is passed as userdata to the callback, and the callback can
use the stack as usual, declaring local state and ensuring proper cleanup via
_cleanup_. Control flow structures, such as loops, work as expected, and
everything runs smoothly.

However, challenges arise when the code needs to perform long-running
operations within these callbacks. Since the system cannot block execution
within the callback, we can't directly invoke a long-running operation and
wait for its result without introducing complexities. Instead, we need to
initiate the long-running task, register for completion with sd-event,
sd-bus, or sd-varlink, and provide a callback to be invoked when the
operation completes.

This callback, however, only receives a single userdata pointer, which
forces us to bundle all local variables into a struct and pass it along as
part of the callback. On top of that, after queuing the asynchronous
operation, the caller continues executing. As the caller's stack unwinds
when the function exits, the resources and state within the local scope may
be prematurely cleaned up. Therefore, the struct must store copies of the
local variables or ensure proper reference counting to prevent premature
resource cleanup.

When multiple long-running operations need to be initiated within a loop,
the complexity grows further. We must introduce additional shared state to
track the completion of all operations before we can run any code that
depends on their results.

Furthermore, since the daemon may be shut down at any time, we must track
the lifecycle of each long-running operation in the global Manager struct,
ensuring proper cleanup even when stack unwinding can no longer manage the
resources for us.

Fibers, or green threads, provide a more natural way of handling
asynchronous operations. By enabling cooperative multitasking within a
single thread, fibers allow us to write code that looks like it’s running
synchronously, but with the ability to yield control at predefined points,
such as when waiting for long-running tasks to complete.

With fibers, we can simplify the control flow by running asynchronous
operations within a fiber, allowing us to "pause" execution while waiting
for the long-running operation to finish and then "resume" the operation once
it's complete. This eliminates the need for multiple callback chains,
extensive state tracking, and the potential pitfalls of stack unwinding.

This commit introduces the ability to execute long-running operations in a
non-blocking manner while maintaining the simplicity and readability of
synchronous code. The fiber-based approach will significantly improve the
handling of complex workflows, making the code easier to write and maintain.

The implementation is based on ucontext.h's makecontext() (with a fallback
to the venerable sigaltstack() approach on musl), sigsetjmp()/siglongjmp() 
and sd-event. ucontext.h provides us with alternate stacks that we can switch 
between. We use sigsetjmp()/siglongjmp() instead of swapcontext() because the
latter forcibly saves/restores a per context signal mask every time it is called.
Using sigsetjmp()/siglongjmp(), we can avoid the unnecessary syscall and maintain
a per thread signal mask, which makes much more sense than having a per fiber
signal mask.

The default stack size is the same as a regular thread. Because we 
use mmap() to allocate the stack, the memory won't actually be used until it 
is paged in by the kernel, so we don't actually use 8MB per fiber.

To integrate fibers with the event loop, each fiber is assigned a deferred
event source which resumes the fiber when enabled. The deferred event source
is oneshot by default so the fiber will run immediately until it yields or
suspends. If it yields, the deferred event source is enabled again (oneshot)
immediately. If it suspends, before it suspends, one or more event sources
are registered with sd-event that will enable the deferred event source
(oneshot) to resume the fiber once the operation it is waiting for completes.

Yielding or suspending the fiber is done by calling sd_fiber_yield() or
sd_fiber_suspend() respectively. Both of these return zero on success or any
error value from the async operation that caused the fiber to resume.

This is also how fiber cancellation is implemented. When a fiber is cancelled,
sd_fiber_yield() and sd_fiber_suspend() will return ECANCELED when the fiber
is resumed, allowing the fiber to unwind its stack (which allows cleanup to
happen automatically) and finish.

Instead of having applications work directly with fibers, we hide them behind
a generic futures interface to represent long-running operations, regardless of
whether those operations are running on a fiber or not. Aside from fibers, the
futures library (sd-future) will for example allow waiting for sd-event sources 
and doing sd-bus calls in the background as well. Fibers can suspend until a 
future is ready with sd_fiber_await() or by having the future wake up the fiber
explicitly in its callback. A future always defaults to waking up the current 
fiber.

Each future kind plugs into the library by providing an sd_future_ops vtable
(alloc, free, cancel, set_priority). The library treats the impl pointer
returned by alloc() as a black box. Future Implementations retrieve it via 
sd_future_get_private().

A future starts in SD_FUTURE_PENDING and transitions exactly once to
SD_FUTURE_RESOLVED, carrying an integer result. Consumers can react to that
transition either by installing a one-shot callback with
sd_future_set_callback() (callback-style code) or by waiting on it from a
fiber via sd_fiber_await() (synchronous-looking fiber code). sd_fiber_await()
is itself built on a "wait future" that resolves when its target resolves;
sd_future_new_wait() exposes the same primitive directly so non-fiber callers
can chain futures without involving a fiber.

Cancellation is cooperative: sd_future_cancel() invokes the future impl's 
cancel callback, which is responsible for tearing down its work and ultimately
resolving the promise with -ECANCELED. For fiber futures this is what
surfaces as the ECANCELED return from sd_fiber_yield()/sd_fiber_suspend()
mentioned above.

Fire-and-forget fibers — created by passing a NULL ret to sd_fiber_new() —
take a self-reference on their future so they outlive the caller's scope.
The self-ref is dropped when the fiber resolves. This floating mechanism
(sd_fiber_set_floating()) is restricted to fiber futures because they
uniquely guarantee resolution; allowing it for arbitrary future kinds would
risk silent leaks for kinds that may never resolve.

Note that fiber cleanup depends on the runtime operating normally. Each
fiber's _cleanup_-style cleanups live on the fiber's own stack and run
only when the fiber is resumed and allowed to unwind, which requires a
working event loop to drive it to completion. The exit event source
registered for top-level fibers ensures unwind on a normal sd_event_exit(),
but if the event loop itself terminates abnormally (e.g. an unrecoverable
allocation failure mid-dispatch) before all fibers have resolved, their
stacks never unwind and any resources they own leak.

The code lives in libsystemd as sd-future (not exported) for the following reasons:
- We may want to make this a public libsystemd API in the future
- The code can't live in src/basic as it makes heavy use of sd-event
- The code can't live in src/shared as sd-bus and sd-event make use of it

The log and log-context headers are updated with functions to allow
fibers to have their own log prefix and log context.
2026-05-21 09:55:04 +00:00
Luca Boccassi 4ac2369728 test: switch TEST-55-OOMD stress-ng --vm-method to lfsr32
Commit 881e4717c7 ("test: pin stress-ng --vm-method to a portable
scalar method in TEST-55-OOMD") pinned --vm-method=zero-one with the
rationale that it is "a long-standing scalar method". That rationale is
wrong: stress_vm_zero_one() in stress-ng's stress-vm.c is declared

    static size_t TARGET_CLONES stress_vm_zero_one(...)

i.e. it carries the exact same TARGET_CLONES attribute as 33 of the 35
other vm methods. On x86_64 with GCC >=5, TARGET_CLONES expands (see
core-target-clones.h in stress-ng) to a target_clones attribute
including "arch=skylake-avx512", "arch=cooperlake", "arch=tigerlake",
"arch=sapphirerapids", and several other AVX-512-bearing arch variants,
plus "default". GCC generates AVX-512 clones of stress_vm_zero_one() and
the IFUNC resolver picks them on any CPU that advertises AVX-512.

The only vm methods in stress-ng's registry whose function definitions
omit TARGET_CLONES entirely (and are therefore guaranteed not to
dispatch to an AVX-512 clone) are lfsr32 (portable, always registered)
and write64ds (x86_64-only, gated on HAVE_ASM_X86_MOVDIRI, i.e. Intel
Tremont / Tiger Lake+ MOVDIRI instruction).

Switch the four stress-ng --vm invocations in TEST-55-OOMD to
--vm-method=lfsr32 so the AVX-512 SIGILL on CPUs without AVX-512 (e.g.
AMD Zen 1-3) can no longer occur regardless of compiler version,
optimization level, or stress-ng package build.

Follow-up for 881e4717c7

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 22:53:59 +01:00
Luca Boccassi 881e4717c7 test: pin stress-ng --vm-method to a portable scalar method in TEST-55-OOMD
The stress-ng "vm" stressor's default --vm-method=all cycles through every
VM stress method, including newer ones that use AVX-512 instructions. On
CPUs without AVX-512 support (e.g. AMD Zen 1 to 3) those methods crash with
SIGILL. In testcase_oom_rulesets_lasting_sec all 10 stress-ng workers die
within ~2.34 seconds, so by the time the 6 second sleep elapses the unit
is already in failed/exit-code state and the assert_eq for
ActiveState=active trips.

Pin --vm-method=zero-one, a long-standing scalar method, on all four
stress-ng --vm invocations in this test (the two transient services in
testcase_oom_rulesets and testcase_oom_rulesets_lasting_sec, plus
TEST-55-OOMD-testbloat.service and TEST-55-OOMD-testmunch.service) so the
workers do not crash on AVX-512-less CPUs. testbloat, testmunch and
testcase_oom_rulesets have not been observed failing because they get
OOM-killed by systemd-oomd within ~1 to 2 seconds, before stress-ng cycles
into an AVX-512 method, but they share the same latent flake.

Journal excerpts from the failing run, TEST-55-OOMD-slowrule.service in
testcase_oom_rulesets_lasting_sec (journalctl -o short-monotonic):

[   58.018676] stress-ng[1015]: invoked with '/usr/bin/stress-ng --timeout 15s --vm 10 --vm-bytes 50M --vm-keep' by user 0 'root'
[   59.866072] stress-ng[1030]: stress-ng: debug: [1030] caught SIGILL, address 0x000055bd8d609140 (ILL_ILLOPN)
[   59.921050] stress-ng[1030]: stress-ng: debug: [1030] stress-ng: info: 0x000055bd8d609140:<62>71 fd 48 6f 2d 36 14 1c 00 c5 d1 ef ed 49 29
[   59.929310] stress-ng[1015]: stress-ng: error: [1015] vm: [1021] terminated with an error, exit status=2 (stressor failed)
[   60.364111] stress-ng[1015]: stress-ng: info:  [1015] failed: 10: vm (10)
[   60.364493] stress-ng[1015]: stress-ng: info:  [1015] unsuccessful run completed in 2.34 secs
[   60.371290] systemd[1]: TEST-55-OOMD-slowrule.service: Main process exited, code=exited, status=2/INVALIDARGUMENT
[   60.371396] systemd[1]: TEST-55-OOMD-slowrule.service: Failed with result 'exit-code'.
[   64.017061] TEST-55-OOMD.sh[1010]: + assert_eq failed active
[   64.018167] TEST-55-OOMD.sh[1039]: FAIL: expected: 'active' actual: 'failed'

The faulting bytes marked by stress-ng with <62> (the byte at the
instruction pointer) decode unambiguously to an AVX-512 VMOVDQA64 using
the 512-bit zmm13 register, confirmed independently by two disassemblers:

  $ printf '\x62\x71\xfd\x48\x6f\x2d\x36\x14\x1c\x00' | ndisasm -b 64 -
  00000000  6271FD486F2D3614  vmovdqa64 zmm13,zword [rel 0x1c1440]
           -1C00

  $ echo '0x62, 0x71, 0xfd, 0x48, 0x6f, 0x2d, 0x36, 0x14, 0x1c, 0x00' | \
        llvm-mc -disassemble -triple=x86_64 -mattr=+avx512f
          .text
          vmovdqa64       1840182(%rip), %zmm13

The leading 0x62 is the EVEX prefix (exclusive to AVX-512 on this target),
zmm13 is a 512-bit register that only exists when AVX-512 is implemented,
and VMOVDQA64 requires the AVX512F (Foundation) CPUID feature (Intel SDM
Vol 2C). Executing this on a CPU without AVX-512 raises #UD, delivered by
the kernel as SIGILL/ILL_ILLOPN, matching the journal entry above. The
same journal shows the kernel reporting "kvm_amd: TSC scaling supported",
i.e. the guest is on AMD KVM, and AMD did not ship AVX-512 before Zen 4.

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 15:07:25 +01:00
Yu Watanabe 4c333638ad proxy-echo: apply "ruff check --fix" 2026-05-18 02:51:35 +09:00
Yu Watanabe 06961b7c5f fake-report-server: apply "ruff format" and "ruff check --fix" 2026-05-18 02:49:29 +09:00
Yu Watanabe 2a0eaff3a7 fake-imds: apply "ruff format" and "ruff check --fix" 2026-05-18 02:48:35 +09:00
Yu Watanabe f6b2a27e59 TEST-04-JOURNAL: apply "ruff format" 2026-05-18 02:44:46 +09:00
Yu Watanabe 75f7304512 ruff: do not warn about too long line and lambda assignment 2026-05-18 02:32:42 +09:00
Yu Watanabe c54decc406 ruff: use single quotes for multiline strings 2026-05-18 02:32:39 +09:00
Luca Boccassi 14f773ce5e test: add integration test for LUO 2026-05-15 13:46:08 +01:00
Daan De Meyer 74d742fbfc TEST-64-UDEV-STORAGE: Drop number of nvme devices to 12
qemu by default only has 30 PCI slots and with vmspawn now reserving
some of those for its hotplug features, we go over the limit for the 
nvme test.

Let's drop the number of nvme devices to 12 to fix the conflict.
2026-05-13 18:13:37 +02:00
Christian Brauner e28aab8840 RestrictFileSystemAccess= — dm-verity filesystem access enforcement via BPF LSM (#41340)
This series adds a new `RestrictFileSystemAccess=` setting in the
`[Manager]` section of `system.conf` that enforces a deny-default
execution policy: only binaries residing on signed dm-verity block
devices (and the initramfs during early boot) are permitted to execute.
Everything else — tmpfs, procfs, sysfs, anonymous executable mappings,
unsigned dm-verity devices — is denied.

The directive takes the values `no` (default), `exec` (lock down
execution), and accepts `yes` as an alias for `exec`. The name is
deliberately broader than what the initial values cover so the same
setting can grow to restrict other filesystem access categories in the
future (e.g. `any` to deny all access from untrusted filesystems, not
just execution).

### How it works

The BPF program is entirely self-contained; PID1 loads it and the kernel
does the rest. When dm-verity brings up a device, the kernel calls
`security_bdev_setintegrity()` twice during `verity_preresume()`: once
with the root hash and once with the signature validity status. Our
`lsm/bdev_setintegrity` hook captures the second call and records the
device number in a BPF hash map if the signature is valid. When a device
is torn down, `lsm/bdev_free_security` cleans up the map entry. No
userspace map population is needed at any point.

The enforcement side hooks `bprm_check_security` (execve), `mmap_file`
(PROT_EXEC mappings including shared libraries), and `file_mprotect`
(W→X transitions like JIT and libffi). Each hook resolves the file's
backing device via `file->f_inode->i_sb->s_dev` and looks it up in the
verity device map. For block-backed filesystems, `s_dev` equals
`s_bdev->bd_dev`, which avoids an extra pointer chase and NULL check on
`s_bdev` — non-block filesystems simply miss in the map and get denied
by the default policy.

During early boot the initramfs needs to be trusted as well, since it
runs before any dm-verity volume is mounted. PID1 writes the initramfs
superblock's device number into a BPF global before attaching the
programs, and clears it after `switch_root` to close the trust window.
As a prerequisite, PID1 also verifies that
`dm_verity.require_signatures=1` is active — without it, unsigned
dm-verity devices could be created, which would weaken the security
model even though the BPF program would correctly deny execution from
them.

### Surviving daemon-reexec

The BPF programs and their verity device map must survive PID1
re-execution (daemon-reexec, switch_root, soft-reboot). Without
preservation, `manager_free()` would destroy the skeleton, the link FDs
would close, programs would detach, and the map would be freed. After
exec, a fresh skeleton would have an empty map — but existing dm-verity
devices have already signaled their integrity and won't do so again. A
deny-default policy plus an empty map means all execution denied and the
system is bricked.

We solve this by serializing the raw BPF link FDs and the `.bss` map FD
across exec using systemd's existing `serialize_fd` / `fdset_cloexec` /
`deserialize_fd` infrastructure. The kernel reference chain (link FD →
`struct bpf_link` → `struct bpf_prog` → `struct bpf_map`) keeps programs
attached and map data intact as long as the dup'd FDs survive. After
exec, PID1 detects the deserialized FDs and skips skeleton re-creation
entirely. If switching root, it uses the deserialized `.bss` map FD to
clear `initramfs_s_dev` via a targeted `mmap()` write, preserving the
other guard globals in `.bss`.

We intentionally avoid bpffs pinning. Pinned objects are discoverable
and manipulable by any process with sufficient privileges
(`BPF_OBJ_GET`, unlink). FD serialization keeps everything private to
PID1 with no external attack surface.

### Self-protection

BPF LSM programs attached via the tracing trampoline (`BPF_LSM_MAC`) are
inherently tamper-resistant — `bpf_tracing_link_lops` has no
`.update_prog` and no `.detach` callbacks, so the kernel rejects
`BPF_LINK_UPDATE` with `-EINVAL` and `BPF_LINK_DETACH` with
`-EOPNOTSUPP`. Once attached, our programs cannot be modified or
detached through the `bpf()` syscall.

The remaining attack vector is map injection: `BPF_MAP_GET_FD_BY_ID` to
obtain an FD to `verity_devices`, then `BPF_MAP_UPDATE_ELEM` to insert a
fake trusted device. The self-protection guard blocks this with three
hooks. `lsm/bpf_map` fires inside `bpf_map_new_fd()`, the chokepoint for
all code paths that produce a map FD, and denies access to our map IDs
from any process other than PID1 (identified via `tgid == 1`, which is
unspoofable — `bpf_get_current_pid_tgid()` reads `current->tgid` from
`pid->numbers[0].nr`, the init-namespace PID). `lsm/bpf_prog` provides
analogous protection for program FDs as defense-in-depth. `lsm/bpf`
handles `BPF_LINK_GET_FD_BY_ID` at the command level since there is no
`security_bpf_link()` hook in the kernel.

The guard starts inactive — all protected IDs default to 0 in `.bss`,
and no real BPF object has ID 0 — so there is no window where it
interferes with PID1's own setup. After attaching all programs, PID1
queries the kernel-assigned IDs via `bpf_obj_get_info_by_fd()` and
writes them into the guard's globals. From that point on, the guard is
active. The guard has zero collateral damage: it only denies access to
our specific object IDs, leaving bpftrace, bpftool,
`RestrictFileSystems=`, and all other BPF usage completely unaffected.

Additionally, a ptrace guard (`lsm/ptrace_access_check`) blocks
`PTRACE_MODE_ATTACH` to PID1 from other processes, preventing extraction
of sensitive state from PID1's address space via ptrace, `/proc/1/mem`,
`process_vm_readv()`, or `pidfd_getfd()`. `PTRACE_MODE_READ` is allowed
so that monitoring tools and `systemctl` continue to work normally.

### Limitations

- The enforcement hooks resolve trust by looking at
`file->f_inode->i_sb->s_dev` — the device number of the superblock that
owns the file's inode. This works correctly for files directly on a
dm-verity block device, but it does not see through overlayfs. When a
file is accessed on an overlay mount, `f_inode` points to the overlay
inode, and `i_sb->s_dev` is the overlay superblock's anonymous device
number — not the underlying dm-verity device. The overlay superblock has
no backing block device, so the lookup misses in the verity map and
execution is denied by the default policy.

This means that overlayfs mounts whose lower layers are on
dm-verity-protected volumes will currently have execution blocked, even
though the actual data is integrity-protected. The correct fix requires
a kernel extension that allows the BPF program to call something like
`d_real_inode()` to resolve through the overlay to the real inode on the
underlying filesystem, and then check that inode's superblock device
number against the verity map. I plan to add a BPF kfunc exposing this
functionality in a follow-up kernel series.

- Multi-device filesystems such as btrfs use entirely synthetic device
numbers and there is no way to reach the actual device backing the inode
from the inode itself. So `RestrictFileSystemAccess=` only works
reliably with a subset of filesystems. In practice this isn't a problem
because the feature is tailored to erofs; using it on arbitrary
filesystems requires careful vetting of the actual filesystem behaviour.

- The initial implementation also blocks JIT-style execution that relies
on memory mapped executable. This is part of `exec` semantics today and
can be loosened later by introducing finer-grained values (a common
pattern in systemd — following the precedent of `ProtectSystem=`, which
started as a boolean and later grew `auto`/`yes`/`full`/`strict`
semantics).

- The configuration is a system-wide setting with no per-unit opt-out.
This is intentional for the initial implementation: a global invariant
is easier to reason about and harder to accidentally weaken. Per-unit
relaxation can be added later if a concrete need arises.

### Testing

The series includes unit tests and integration tests covering both the
core enforcement logic and the self-protection guard. The unit test
loads the skeleton, attaches programs, populates guard globals, and
verifies that protected IDs are set correctly. The integration tests
exercise the guard by attempting `BPF_MAP_GET_FD_BY_ID` and
`BPF_PROG_GET_FD_BY_ID` from a non-PID1 process and verifying that
access is denied.

What we cannot currently test end-to-end is actual execution enforcement
against a dm-verity-signed root filesystem. The systemd test suite does
not yet have infrastructure for booting a VM with a signed dm-verity
rootfs image — the existing mkosi-based test framework lacks the ability
to produce and boot such images. This will hopefully change soon when
Daan integrates barrage into the test suite.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-05-13 13:58:46 +02:00
Daan De Meyer 7cc5d161e6 test: Modernize btrfs tests
Convert test-btrfs to use the test framework and
assertions, merge the physical offset test into it
and beef it up to include what TEST-83-BTRFS does and
finally get rid of TEST-83-BTRFS as it is unneeded now.
2026-05-13 13:47:18 +02:00
Christian Brauner 5439911f59 test: add integration tests for RestrictFileSystemAccess= BPF LSM
Add TEST-90-RESTRICT-FSACCESS with two subtests:

config subtest — Tests PID1's RestrictFileSystemAccess= configuration parsing and
failure modes via system.conf drop-ins and daemon-reexec:
 - Default RestrictFileSystemAccess=no produces no log messages
 - RestrictFileSystemAccess=yes without BPF LSM logs appropriate warning
 - RestrictFileSystemAccess=yes without require_signatures is correctly rejected
   by the test helper binary's precondition check

enforce subtest — Tests actual BPF LSM enforcement using a test helper
binary (test-bpf-restrict-fsaccess) that loads the BPF skeleton with
initramfs_s_dev set to the rootfs s_dev, pins BPF links, and exits:
 - Execution from rootfs continues to work (trusted via initramfs_s_dev)
 - Execution from tmpfs is blocked with EPERM
 - Execution from a signed dm-verity device is allowed, driven via
   systemd-run -p RootImage= against the pre-built signed minimal_0
   images that mkosi ships and signs at image build time (no on-the-fly
   squashfs / verity hash tree / signature build required)
 - After BPF detach, enforcement is lifted

All tests skip gracefully when prerequisites are not met (BPF LSM, BPF
framework, dm-verity tools, signing keys).

Signed-off-by: Christian Brauner <brauner@kernel.org>
2026-05-13 10:36:12 +02:00
Zbigniew Jędrzejewski-Szmek 937129d1f6 Merge facts into metrics and add systemd-report generate/upload (#41688) 2026-05-05 20:55:36 +02:00
Luca Boccassi 8fbc93345e test: bump device timeout to 300s for TPM2 tests
Booting with TPM2 has become slower recently so tests are randomly
failing, try to bump the default device timeout in those test VMs
2026-05-03 15:06:51 +01:00
Zbigniew Jędrzejewski-Szmek e5687f689f report: absorb "facts" into "metrics"
This gets rid of the duality in the cmdline interface and various
APIs. The general plan is collect both "facts" and "metrics" in a
single list. We have various producers which respond on the
io.systemd.Facts endpoint. Those will need to be adjusted to respond
to io.systemd.Metrics.

Cmdline interface:
  'metrics' (unchanged)
  'describe-metrics' → 'describe'
  'facts' → merged into 'metrics'
  'describe-facts' → merged into 'describe'
2026-04-30 11:37:58 +02:00
Frantisek Sumsal 445f980548 test: temporarily ignore sanitizer warning about blocked ptrace()
LLVM 22 introduced an additional check [0] for ptrace() syscall when
invoking sanitizers [0] which currently produces a false-positive
warning when running some of our units under sanitizers:

[   47.524680] systemd-timedated[740]: ==740==WARNING: ptrace appears to be blocked (is seccomp enabled?). LeakSanitizer may hang.
[   47.524680] systemd-timedated[740]: ==740==Child exited with signal 15.
...
[ 1555.734223] systemd-oomd[93]: ==93==WARNING: ptrace appears to be blocked (is seccomp enabled?). LeakSanitizer may hang.
[ 1555.734223] systemd-oomd[93]: ==93==Child exited with signal 15.
...

It is a false positive because we disable the seccomp filters
system-wide for our units in the sanitizer jobs.

Now, from what I've seen so far this happens only in
Type=notify(-reload) units that also utilize bus_event_loop_with_idle().
This, combined with the fact that the ptrace()-check child process from
[0] checks only if the child process was killed by _any_ signal, means
that if the systemd unit exits on its own after becoming idle and then
something sends it SIGTERM (either via explicit `systemctl stop` or
during system shutdown), this SIGTERM might hit the ptrace()-check child
process from the sanitizer handler (as we also send the signal to all
processes in the target cgroup), which the parent process then
mistakenly evaluates as a blocked ptrace() syscall, even though the
check process wasn't killed by SIGSYS.

I filed this as [1] to the LLVM project, but let's also temporarily
ignore the warning in the sanitizer report processing, as it currently
causes annoying test fails.

[0] https://github.com/llvm/llvm-project/commit/a708b4bf21d7c2298224cdacf7d424abc3c8fed4
[1] https://github.com/llvm/llvm-project/issues/193714
2026-04-25 18:49:40 +02:00
Frantisek Sumsal c955e24916 test: re-enable sync in TEST-25-IMPORT
Newer tar started using openat2() via open_subdir() to address
CVE-2025-45582 [0]. Now, gnulib, that tar uses, provides the openat2()
syscall in two ways [1]:

1) If glibc doesn't provide openat2(), it provides its own version in
   openat2.c, that tries to call openat2() syscall first, and if it
   returns ENOSYS, it emulates the function in userspace.

2) If glibc provides openat2(), it uses that directly, without providing
   any fallback on ENOSYS.

Quite recently our test suite started calling nspawn with
--suppress-sync=yes. This means that we call seccomp_suppress_sync(),
which eventually calls block_open_flag(), that blocks the openat2()
syscall completely and refuses it with ENOSYS as this syscall can't be
sensibly filtered (see the openat2()-relevant comments in
block_open_flag() and seccomp_restrict_sxid()). And when glibc provides
openat2(), there's no fallback, so the ENOSYS bubbles up to the user as:

TEST-25-IMPORT.sh[163]: + tar xzf /var/tmp/scratch.tar.gz
TEST-25-IMPORT.sh[163]: tar: ./adirectory/athirdfile: Cannot open: Function not implemented
TEST-25-IMPORT.sh[163]: tar: Exiting with failure status due to previous errors

Let's mitigate this by re-enabling sync for TEST-25-IMPORT, at least for
now.

[0] https://cgit.git.savannah.gnu.org/cgit/tar.git/commit/?id=75b03fdff48916bd0654677ed21379bdb0db016d
[1] https://cgit.git.savannah.gnu.org/cgit/gnulib.git/commit/?id=0b97ffdf32bdab909d02449043447237273df75e
2026-04-20 14:33:36 +01:00
Zbigniew Jędrzejewski-Szmek b7ac32c2c3 test: add HTTPS upload test for systemd-report
Extend fake-report-server.py with optional --cert, --key, --port
arguments for TLS support. Add a test case that generates a
self-signed certificate and tests HTTPS upload of metrics and facts.

Also exercise the --header param.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 21:12:20 +02:00
Zbigniew Jędrzejewski-Szmek 0f3bd778e0 test: add HTTP upload test for systemd-report
Add a fake HTTP server (fake-report-server.py) that accepts JSON POST
requests and validates the report structure, and test cases in
TEST-74-AUX-UTILS.report.sh that exercise plain HTTP upload of both
metrics and facts.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 21:12:20 +02:00
Daan De Meyer 0d57260976 TEST-75-RESOLVED: Make sure --suppress-sync is not used 2026-04-14 20:35:24 +02:00
Daan De Meyer 72cfcfa0ec TEST-06-SELINUX: Relabel in the initrd rather than at image build time
This gets rid of the requirement to run the image build as root.
2026-04-14 20:35:24 +02:00
Daan De Meyer 014a4d93e0 TEST-64-UDEV-STORAGE: Add missing scsi controllers 2026-04-14 20:35:24 +02:00
Daan De Meyer a8416614b0 TEST-24-CRYPTSETUP: Use virtio-blk-pci
Doesn't require a controller.
2026-04-14 20:35:24 +02:00
Daan De Meyer 316d17fcbd core: Add support for CPU pressure notifications
Works the same way as memory pressure notifications. Code is refactored
to work on enum arrays to reduce duplication.
2026-04-09 22:47:10 +02:00
Luca Boccassi abe3d570f8 test: add a mock ModemManager for basic coverage of sd-networkd's integration
Just the minimal setup and test case required to cover
https://github.com/systemd/systemd/issues/41389 for now, can be
expanded in the future

Boring boilerplate is bot-made, don't @ me

Co-developed-by: Claude Opus 4.6 noreply@anthropic.com
2026-03-28 15:19:21 +00:00
Luca Boccassi f737b38977 test: exclude gnusleep from coredumps parsing
In Ubuntu 26.04 the actual binary is called gnusleep, and sleep is a symlink,
so fix the regex exclusion for the coredump checks
2026-03-27 22:40:09 +00:00
Lennart Poettering 41dc0dc7b6 test: add simple integration test for systemd-imdsd 2026-03-26 10:54:15 +01:00
Daan De Meyer 53d5f5c02f ci: Drop codeql workflow
After analyzing all 218 CodeQL alerts across the project's history, the
workflow has not justified its CI cost:

- The most impactful query (PotentiallyDangerousFunction) was a custom
  systemd-specific query that has already been replaced by clang-tidy's
  bugprone-unsafe-functions check (6fb5ec3dd1).

- Of the remaining C++ queries, 6 never triggered at all
  (bad-strncpy-size, unsafe-strcat, unsafe-strncat,
  suspicious-pointer-scaling, suspicious-pointer-scaling-void,
  inconsistent-null-check).

- Several high-value-sounding queries had extreme false positive rates:
  toctou-race-condition (95% FP), use-after-free (88% FP),
  cleartext-transmission (100% FP).

- Many queries that did trigger are already covered by compiler warnings
  (-Wshadow, -Wformat, -Wunused-variable, -Wreturn-type,
  -Wtautological-compare) or existing clang-tidy checks
  (bugprone-sizeof-expression).

- Across all alerts, only 3 genuinely useful C++ fixes can be
  attributed to CodeQL: 1 tainted-format-string, 2
  incorrectly-checked-scanf. The rest were either false positives or
  incidental fixes during refactoring that weren't prompted by CodeQL.

- The Python queries are largely superseded by ruff (already in CI) and
  had an 89% false positive rate on the security-focused checks.

The workflow consumed significant CI resources (40+ minutes per run) and
the ongoing maintenance burden of triaging false positives outweighs the
marginal value of the 2-3 real findings it produced across its entire
lifetime.
2026-03-24 17:55:26 +01:00