Commit Graph
88780 Commits
Author SHA1 Message Date
noxiouz bb233dcb4b coredump: add JSON output support to coredumpctl info
Implement support for the --json= flag in the info subcommand
(issue #38844). Previously, coredumpctl info always produced
human-readable text output regardless of --json=.

Add a CoredumpFields struct that holds all journal fields extracted
for a coredump entry, along with coredump_fields_done() to release
member resources and coredump_fields_load() to populate the struct
from a journal entry. Both print_info() and the new print_info_json()
use this shared loader, eliminating the duplicate RETRIEVE loop.

print_info_json() builds a JSON object with the same fields shown by
print_info(). Missing fields are omitted via SD_JSON_BUILD_PAIR_CONDITION,
matching the tolerant behavior of print_info() rather than skipping the
entry entirely. Signal/Reason handling mirrors print_info(): normal
coredumps (MESSAGE_ID == SD_MESSAGE_COREDUMP_STR) emit a numeric Signal
field; non-normal entries (kernel oops, etc.) emit a Reason field with
the raw text from COREDUMP_SIGNAL.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 15:53:13 +01:00
Daan De Meyer 868223f78c btrfs: Beef up btrfs_subvol_make()
Let's make sure we handle AT_FDCWD and XAT_FDROOT
properly by using xopenat().
2026-05-14 23:40:25 +09:00
Daan De Meyer 6649a47eda nspawn: split boot parameters into env vars and argv
When the kernel hands the command line to PID 1, any KEY=VALUE assignment
whose KEY does not contain a '.' is exported as an environment variable
(with '-' replaced by '_') rather than passed as an argument. Mimic the
same split in --boot mode so kernel-cmdline-style arguments passed after
the container path behave as they would on a real boot.
2026-05-14 23:37:34 +09:00
Yu Watanabe d6c79c13ff Implement Socket Context/Runtime for io.systemd.Unit.List (#42057)
The PR implements the following objects + tests for
io.systemd.Unit.List:
- SocketContext
- Socket Runtime

It's a continuation of the following PRs:

* https://github.com/systemd/systemd/pull/37432
* https://github.com/systemd/systemd/pull/37646
* https://github.com/systemd/systemd/pull/38032
* https://github.com/systemd/systemd/pull/38212
* https://github.com/systemd/systemd/pull/39391
* https://github.com/systemd/systemd/pull/41980
2026-05-14 23:29:47 +09:00
Frantisek Sumsal 7d1b6b7b96 profile: bail out early if promptvars is disabled
We need promptvars, otherwise the prompt strings won't undergo parameter
expansion and we'd print them literally:

$ shopt -u promptvars
$ echo foo
$(__systemd_osc_context_ps0)foo

Resolves: #40620
2026-05-14 23:17:40 +09:00
Emanuele Rocca 7330e9c789 test-fs-util: check for CAP_DAC_OVERRIDE in xopenat_auto_rw_ro
When running test_xopenat_auto_rw_ro under a non-root user with the
CAP_DAC_OVERRIDE capability, the test currently fails.

As the comment already says, root bypasses mode bits via CAP_DAC_OVERRIDE so
let's check for that instead of the effective user ID.

Signed-off-by: Emanuele Rocca <emanuele.rocca@arm.com>
2026-05-14 23:16:27 +09:00
Luca Boccassi df630029c5 cgroup: Add CPUSetPartition= setting (#42013)
Add support for configuring cpuset partition type via the
CPUSetPartition= unit file setting. This controls the kernel's
cpuset.cpus.partition cgroup attribute.

The setting takes one of "member", "root", or "isolated". This is
useful for real-time workloads that require dedicated CPU resources
without interference from other processes.

When set, systemd will write the partition type to the
cpuset.cpus.partition cgroup file. If the kernel rejects the value
(e.g., due to partition hierarchy rules), a warning is logged and the
unit continues with the kernel's default partition type.

Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
2026-05-14 13:10:13 +01:00
Zbigniew Jędrzejewski-Szmek a1e6f4a15b Convert systemctl to option and verb macros (#42088)
This one was non-trivial, so it'd benefit from a close review.
2026-05-14 11:33:09 +02:00
Luca Boccassi eada7715cb export system memory and number of cpus in basic metrics (#42076) 2026-05-14 09:57:00 +01:00
Zbigniew Jędrzejewski-Szmek 440d701f3c fuzz-systemctl-parse-argv: add two corpus files to test compat parsers
Looking at the corpus examples, I'm not sure the fuzzer even went into
the compat parsers. None of the files have argv[0] that'd cause
invoked_as() to go into the compat paths. So add the files to provide
a quick test and possibly bias the fuzzer search into the right
direction.
2026-05-14 09:38:10 +02:00
Zbigniew Jędrzejewski-Szmek 4a30028c85 fuzz-systemctl-parse-argv: update suppression of logging and resetting of state
There's certainly more than one way to skin this particular cat,
so I'm keeping this as a separate commit.
2026-05-14 09:38:10 +02:00
Zbigniew Jędrzejewski-Szmek 0a33b37e93 shared/options: implement the equivalent of 'opterr'
All log messages during option parsing are emitted using log_full,
and the level is set as LOG_ERR + state->log_level_shift. The default
shift is 0, but if set to e.g. 4, we log at LOG_DEBUG, and if set
to 5 or higher, logging is effectively suppressed. (Unless compiled
with LOG_TRACE, when it'd be suppressed if the shift if set to 6
or higher.)  So this gives something like 'opterr', except that
without global state and potentially more flexible.
2026-05-14 09:15:48 +02:00
Zbigniew Jędrzejewski-Szmek 23ba690cca systemctl: convert shutdown_parse_argv to OPTION macros
Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 09:15:48 +02:00
Zbigniew Jędrzejewski-Szmek 45828705d6 systemctl: convert halt_parse_argv to OPTION macros
Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 09:15:48 +02:00
Zbigniew Jędrzejewski-Szmek 52185521f0 systemctl: convert verbs to VERB macros
systemctl_main() is moved to systemctl.c to allow fuzz-systemctl-parse-argv
to compile. It needs systemctl_help(), which needs the verb table, with the
expected groups. Once we provide that, the linker needs all the verb_*
functions. So add dummy implementations in fuzz-systemctl-parse-argv to
allow the link to happen.

The alternative would be to provide an empty option table, but that
seems to be more complicated, and also can simulate parsing of the whole
command line with the full verb set, so it seems better to test with the
real verb table.

$ nm build/fuzz-systemctl-parse-argv | rg 0000000000418885
0000000000418885 T verb_add_dependency
0000000000418885 T verb_bind
0000000000418885 T verb_cancel
0000000000418885 T verb_cat
0000000000418885 T verb_clean_or_freeze
0000000000418885 T verb_edit
0000000000418885 T verb_enable
0000000000418885 T verb_get_default
0000000000418885 T verb_import_environment
0000000000418885 T verb_is_active
0000000000418885 T verb_is_enabled
0000000000418885 T verb_is_failed
0000000000418885 T verb_is_system_running
0000000000418885 T verb_kill
0000000000418885 T verb_list_automounts
0000000000418885 T verb_list_dependencies
0000000000418885 T verb_list_jobs
0000000000418885 T verb_list_machines
0000000000418885 T verb_list_paths
0000000000418885 T verb_list_sockets
0000000000418885 T verb_list_timers
0000000000418885 T verb_list_unit_files
0000000000418885 T verb_list_units
0000000000418885 T verb_log_setting
0000000000418885 T verb_mount_image
0000000000418885 t verb_noop
0000000000418885 T verb_preset_all
0000000000418885 T verb_reset_failed
0000000000418885 T verb_service_log_setting
0000000000418885 T verb_service_watchdogs
0000000000418885 T verb_set_default
0000000000418885 T verb_set_environment
0000000000418885 T verb_set_property
0000000000418885 T verb_show
0000000000418885 T verb_show_environment
0000000000418885 T verb_start_special
0000000000418885 T verb_start_system_special
0000000000418885 T verb_switch_root
0000000000418885 T verb_trivial_method
0000000000418885 T verb_whoami
2026-05-14 09:13:20 +02:00
Zbigniew Jędrzejewski-Szmek 91249f8004 systemctl: convert parse_argv to OPTION macros
The verbs[] table still lives in systemctl-main.c — only the option parsing
side is migrated. systemctl_dispatch_parse_argv() gains a remaining_args
out-param so run() can pass the parsed positional args to systemctl_main(),
which dispatches via _dispatch_verb_with_args() instead of dispatch_verb().

The Options section of --help now renders from the OPTION declarations; the
verb sections still use raw printfs and will be converted alongside the
verbs[] migration.

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 09:03:13 +02:00
Zbigniew Jędrzejewski-Szmek 137736dd64 systemctl: reorder cases in parse_argv() to match order in --help
Compatibility-only options (--fail, --irreversible, --ignore-dependencies,
--no-legend) are grouped at the end alongside the '.' / '?' error handlers.
The case 'P': … _fallthrough_; case 'p': pair is kept intact and placed at
-p's slot in --help, so -P sits immediately before -p in the source.

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 09:03:13 +02:00
Zbigniew Jędrzejewski-Szmek f01b3122e3 systemctl: split out helper for --what and allow resetting
Analogous to parent commit.
2026-05-14 09:03:13 +02:00
Zbigniew Jędrzejewski-Szmek 04bf62b012 systemctl: split out helper for --type= and allow resetting
Analogous to grandparent commit.
2026-05-14 09:03:12 +02:00
Zbigniew Jędrzejewski-Szmek ae83ce85b9 systemctl: split out helper for --property=
We explicitly handled --property= in a specific way, so preserve
that behaviour.
2026-05-14 09:03:11 +02:00
Zbigniew Jędrzejewski-Szmek 979ecbb14e systemctl: split out helper for --state= and allow resetting
So far we'd reject --state=, but it seems nicer to make it reset the
setting as we generally do. The output variable is modified in place…
Option parsing isn't atomic anyway, so I think it's fine to to that.
2026-05-14 09:03:11 +02:00
glemco 50857801db cgroups: Refactor cgroup_apply_cpuset() argument order
Cgroups function have the name key first and then the value,
cgroup_apply_cpuset() has the opposite.

Swap the name and value (cpuset) arguments.
2026-05-14 08:55:44 +02:00
glemco 43b53679da cgroup: Add CPUSetPartition= setting
Add support for configuring cpuset partition type via the
CPUSetPartition= unit file setting. This controls the kernel's
cpuset.cpus.partition cgroup attribute.

The setting takes one of "member", "root", or "isolated". This is
useful for real-time workloads that require dedicated CPU resources
without interference from other processes.

When set, systemd will write the partition type to the
cpuset.cpus.partition cgroup file. If the kernel rejects the value
(e.g., due to partition hierarchy rules), a warning is logged and the
unit continues with the kernel's default partition type.

Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
2026-05-14 08:55:44 +02:00
Christian Brauner 8e6ad359e3 vmspawn: multifunction-pack pcie-root-ports on pcie.0 (#42077)
The pre-allocated pcie-root-port block in run_virtual_machine() places
every port directly on pcie.0 with an auto-assigned PCI address. A
minimal VM already costs 4 builtin + 10 hotplug spares = 14 pcie.0
slots, on top of 3 implicit virtio devices (virtio-rng-pci,
virtio-balloon, virtio-serial-pci) for another 3.

pcie.0 has 32 device-numbers; q35 reserves 0x00 (host bridge) and 0x1f
(ICH9 LPC), leaving ~30 auto-assignable slots. TEST-64-UDEV-STORAGE-
nvme_basic pushes 20 '-device nvme' lines through
$SYSTEMD_VMSPAWN_QEMU_EXTRA, which vmspawn does not see — total demand
14 + 3 + 20 = 37 > 30. Bus realization fails after QEMU's chardev has
already emitted the QMP greeting, and the monitor socket POLLHUPs while
we are mid-feature-probe, reported as 'QMP connection dropped during
feature probing'.

Pack the root ports as multifunction devices, 8 per pcie.0 device-
number (QEMU docs/pcie.txt:84, 117-120, 255-258). Function 0 of each
group carries multifunction=on; functions 1-7 ride the same slot via
addr=N.F. Each function remains independently hot-pluggable so vmspawn's
QMP device_add machinery is unaffected. 14 ports collapse to 2 pcie.0
slots; the nvme_basic budget becomes 2 + 3 + 20 = 25.

The chassis/slot properties (used for ACPI hotplug identity) stay as i+1
— they live in a uint8_t namespace independent of the PCI BDF and are
still unique. Base PCI slot 0x10 sits above the auto-assigned virtio
devices (which land at 0x01-0x03 in config order) and below the q35 LPC
reservation at 0x1f.

While here, rebuild the slot-count formula to match what
assign_pcie_ports() actually allocates. The +1 'SCSI controller' term
was bogus — virtio-scsi-pci comes from the hotplug-spares pool via
hotplug_port_owner[] in vmspawn-qmp.c, never from a builtin port (see
the comment in assign_pcie_ports()). The +1 'network' and +1 'vsock'
terms are now conditional on arg_network_stack and use_vsock. Bind
volumes were missing entirely. And the per-drive accounting now mirrors
assign_pcie_ports()'s skip-SCSI behaviour: non-SCSI drives (root +
extras + bind volumes) take one builtin port each, SCSI drives take none
— they share a controller drawn from the hotplug pool at device-add
time. Tighten the cap from UINT8_MAX to 192 (24 packed device-numbers ×
8) so we cannot claim more than 24 slots on pcie.0 regardless of how
many extras/runtime-mounts a caller asks for.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-05-14 08:13:00 +02:00
Lennart Poettering 29fbad2b7f update TODO 2026-05-14 08:12:28 +02:00
Lennart Poettering e5f27e51f9 report-basic: export PhysicalMemorybytes + CPUsOnline metrics 2026-05-14 08:12:28 +02:00
Lennart Poettering 94ef901b1c cpu-set-util: introduce cpus_online().
Add a helper that tries to determine the number of installed CPUs. This
borrows heavily from physical_memory(), i.e. uses the physical number,
but caps by per-container cpuset.
2026-05-14 08:12:28 +02:00
Lennart Poettering f78e7a0949 cpu-set-util: add cpu_set_count() helper
Let's add a minor, simplifying helper for getting number of CPUs in a
mask.
2026-05-14 08:12:28 +02:00
Daan De Meyer 95e7ead268 nsresourced: re-link GID delegation file after atomic UID file write
userns_registry_remove() restores a sub-delegated UID range by writing
the previous owner's data to u<UID>.delegate with WRITE_STRING_FILE_ATOMIC.
Atomic writes go via a temp file and rename, which replaces the directory
entry with a fresh inode and severs the hardlink to g<GID>.delegate. The
stale GID side then keeps pointing at the prior inode with outdated owner
and ancestor data, so subsequent lookups via GID return wrong results.

Re-create the hardlink after the atomic write so the two views stay in
sync, matching what userns_registry_store() already does after writing
a new delegation.
2026-05-13 23:51:41 +01:00
Daan De Meyer a442440315 blockdev-util: Drop name argument from BLKPG functions
We don't use it, the kernel ignores it, let's just drop
the argument. Saves callers from having to ensure the name
they pass in fits in the 64 char buffer.
2026-05-13 23:47:29 +01:00
Christian Brauner 0bd38f2258 vmspawn: multifunction-pack pcie-root-ports on pcie.0
The pre-allocated pcie-root-port block in run_virtual_machine() places
every port directly on pcie.0 with an auto-assigned PCI address. A
minimal VM already costs 4 builtin + 10 hotplug spares = 14 pcie.0
slots, on top of 3 implicit virtio devices (virtio-rng-pci,
virtio-balloon, virtio-serial-pci) for another 3.

pcie.0 has 32 device-numbers; q35 reserves 0x00 (host bridge) and 0x1f
(ICH9 LPC), leaving ~30 auto-assignable slots. TEST-64-UDEV-STORAGE-
nvme_basic pushes 20 '-device nvme' lines through
$SYSTEMD_VMSPAWN_QEMU_EXTRA, which vmspawn does not see — total demand
14 + 3 + 20 = 37 > 30. Bus realization fails after QEMU's chardev has
already emitted the QMP greeting, and the monitor socket POLLHUPs
while we are mid-feature-probe, reported as 'QMP connection dropped
during feature probing'.

Pack the root ports as multifunction devices, 8 per pcie.0 device-
number (QEMU docs/pcie.txt:84, 117-120, 255-258). Function 0 of each
group carries multifunction=on; functions 1-7 ride the same slot via
addr=N.F. Each function remains independently hot-pluggable so
vmspawn's QMP device_add machinery is unaffected. 14 ports collapse to
2 pcie.0 slots; the nvme_basic budget becomes 2 + 3 + 20 = 25.

The chassis/slot properties (used for ACPI hotplug identity) stay as
i+1 — they live in a uint8_t namespace independent of the PCI BDF and
are still unique. Base PCI slot 0x10 sits above the auto-assigned
virtio devices (which land at 0x01-0x03 in config order) and below
the q35 LPC reservation at 0x1f.

While here, rebuild the slot-count formula to match what
assign_pcie_ports() actually allocates. The +1 'SCSI controller' term
was bogus — virtio-scsi-pci comes from the hotplug-spares pool via
hotplug_port_owner[] in vmspawn-qmp.c, never from a builtin port (see
the comment in assign_pcie_ports()). The +1 'network' and +1 'vsock'
terms are now conditional on arg_network_stack and use_vsock. Bind
volumes were missing entirely. And the per-drive accounting now
mirrors assign_pcie_ports()'s skip-SCSI behaviour: non-SCSI drives
(root + extras + bind volumes) take one builtin port each, SCSI
drives take none — they share a controller drawn from the hotplug
pool at device-add time. Cap at 120 ports (15 device-numbers × 8) so
we cannot run off the end of the 5-bit PCI device-number space — the
usable range starting at 0x10 ends at 0x1e because ICH9 LPC sits at
0x1f.0 single-function, blocking the rest of that slot for
multifunction packing.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-05-13 23:48:53 +02:00
Lennart Poettering f8b73a3bae core: when figuring out whether to create orphanage units, consult vtable instead of allowlist
As per https://github.com/systemd/systemd/pull/41986#pullrequestreview-4281939586

This also corrects the list of unit types a bit:

1. this removes the mount/automount unit type from the list, since for these types
   we do not allow aliases/renaming anyway.

2. this adds socket + swap units to the list, since they can change
   name, and for both of them we actually do fork off processes hence
   track resources.

Follow-up for: #41986
2026-05-13 22:44:52 +01:00
Luca Boccassi e4c6c57c01 import: two minor debugability improvements (#42081)
TEST-13-NSPAWN.machined occasionally fails when importing, and it's hard
to debug, so try to make it better. eg:


https://github.com/systemd/systemd/actions/runs/25800895182/job/75790334230?pr=42071
2026-05-13 22:04:14 +01:00
Daan De Meyer bd7a152345 repart: Add debug logging for block_device_partition_add() 2026-05-13 22:25:55 +02:00
Luca Boccassi 6e232bb565 mkosi: update debian commit reference to 8b9ea8981eee267a2fa493435f2869f7b2479350
* 8b9ea8981e Install new files for upstream build
* b230cf0490 use dh-cruft to register & purge volatile files
* 8f9b9952e1 Install new files for upstream build
2026-05-13 20:33:00 +01:00
Luca Boccassi ff31c6db6b preset: enable cgroup metrics logic (#42075) 2026-05-13 20:12:47 +01:00
Luca Boccassi 148c34dd6a import: try to capture tar exit codes on failure
TEST-13-NSPAWN.machined occasionally fails with a tar error, and it's hard
to say what the problem is at the exit code is lost. Try to capture it.

[ 34.054] systemd-importd[504]: (transfer18) Imported 92%.
[ 34.118] systemd-importd[504]: (transfer18) Failed to decode and write: Broken pipe
[ 34.119] systemd-importd[504]: (transfer18) Exiting.
[ 34.121] systemd-importd[504]: (transfer18) Failed to allocate transient user namespace: Operation not permitted
[ 34.121] systemd-importd[504]: Transfer process failed with exit code 1.

Follow-up for b6e676ce41
2026-05-13 18:39:24 +01:00
Luca Boccassi b05d828b5c import: do not create foreign ns on cleanup if not needed
The user ns is only used if the appropriate flag is set, so avoid
creating it unless it is. This avoids a spurious EPERM error in
TEST-13-NSPAWN.machined that is confusing when debugging failures

[ 34.054] systemd-importd[504]: (transfer18) Imported 92%.
[ 34.118] systemd-importd[504]: (transfer18) Failed to decode and write: Broken pipe
[ 34.119] systemd-importd[504]: (transfer18) Exiting.
[ 34.121] systemd-importd[504]: (transfer18) Failed to allocate transient user namespace: Operation not permitted
[ 34.121] systemd-importd[504]: Transfer process failed with exit code 1.

Follow-up for 1be8caa6be
2026-05-13 18:31:47 +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
Lennart Poettering 94e05aca0d copy: retire splice use() for copying files on disk
Apparently splice() is quite problematic, hence just don't anymore. It's
also unnecessary these days since either copy_file_range() or sendfile()
nowadays typically work, the splice() fallback doesn't give us much
anymore.

(At least I am not aware of a combo of fds where splice() would work
where neither cfr nor sf would work).

This leaves one use of splice() in place, in
src/shared/socket-forward.c. We should probably kill that too, but
that'd require some reworking to use sendfile() I guess, and I am too
lazy for that right now. Moreover, in contrast to the other uses it's
probably even safe, since it uses an intermediary pipe always. But what
do I know...

Fixes: #29044
2026-05-13 15:29:36 +01:00
Zbigniew Jędrzejewski-Szmek 3061ec2707 meson: move systemd-sysupdate to /usr/bin/
Let's make systemd-sysupdate easy to call. It was added in 2021
and it's around to stay and not "experimental" in any way.
2026-05-13 15:09:27 +01:00
Lennart Poettering 78577ecd00 update TODO 2026-05-13 16:01:04 +02:00
Lennart Poettering ff32dd2474 preset: enable cgroup metrics logic
This stuff is so useful, and should work out of the box I am sure. Given
that the metrics are only generated on request this shouldn't create any
additional burden by default.

Yes, this might enlarge reports a bit, if generated with everything on,
but we really should solve that at the report generation level, not at
the point where we make the metrics available.

Follow-up for: 4409e52494
2026-05-13 16:00:58 +02:00
Lennart Poettering 0a9bbdcd33 update TODO 2026-05-13 15:00:56 +02:00
Chris Down c301966df7 core: do not leak resources when handling stale alias state on reload (#41986)
The fix for the corrupted state when units become aliased on reload
leaks the now-aliased unit's resources, which become untracked and
essentially lost.

While fixing the state corruption is of course necessary, leaking
processes/etc. is not ideal for a system and service manager, so
instead attempt to keep track of them by creating stub units
on-the-fly.
This way resources are not leaked, there are clear indications of
where they moved, and all state can be tracked as expected.
2026-05-13 21:25:08 +09: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
Daan De Meyer 450853c72c libc,shared: detect newer library symbols at runtime via weak references (#42065)
For libc syscall wrappers (pidfd_open, fsopen, openat2, etc.) we
previously
gated the calls behind build-time HAVE_* checks. Replace these with weak
external references, falling back to the raw syscall at runtime when the
loaded glibc lacks the symbol. Drop the corresponding cc.has_function()
loop
from meson.build and disable -Wredundant-decls /
readability-redundant-declaration
for src/libc/ via meson c_args and a local .clang-tidy.

For optional libraries (libcryptsetup, libdw, libarchive), drop the
per-symbol
HAVE_* checks. Always declare the prototypes, suppressing the
redundant-decl
warnings via DISABLE_WARNING_REDUNDANT_DECLS and NOLINT, and resolve the
symbols after the main dlopen via a new DLSYM_OPTIONAL() helper that
only
assigns on success. libarchive's *_is_set wrappers now use fallback
functions
as their pointer initializers, so call sites never need to NULL-check.

The same treatment applies to pidfd_spawn / posix_spawnattr_setcgroup_np
in
process-util.c and epoll_pwait2 in sd-event.c. coredump-config and
coredump-submit get a dlopen_dw_has_dwfl_set_sysroot() helper. The kexec
arch gate now uses defined(__NR_kexec_file_load) directly; pidfd.h uses
__has_include_next() to decide whether to pull in glibc's header.

This lets binaries built against newer glibc / libcryptsetup / libdw /
libarchive headers still load and run on older targets where these
symbols
are absent.
2026-05-13 13:06:35 +02:00
Daan De Meyer 2c9b5145a6 dhcp-message: introduce several more functions to parse/append DHCP options (#42063) 2026-05-13 11:51:18 +02:00
Zbigniew Jędrzejewski-Szmek a1f20a0746 Convert loginctl to option and verb macros (#42066) 2026-05-13 11:22:12 +02:00