Commit Graph
9757 Commits
Author SHA1 Message Date
Daan De Meyer 74d392ed1b tree-wide: standardize header names across src/fundamental, src/basic and src/shared
Drop the -fundamental suffix from src/fundamental/ headers in favor of names
that match their src/basic/ or src/shared/ counterparts (e.g.
macro-fundamental.h -> macro.h, assert-fundamental.h -> assert-util.h,
cleanup-fundamental.h -> cleanup-util.h). Rename src/basic/{btrfs,label}.{c,h}
to use the -util suffix to match the existing shared/btrfs-util and
shared/label-util siblings. Rename src/shared/mkdir-label.{c,h} to mkdir.{c,h}
and src/shared/tmpfile-util-label.{c,h} to tmpfile-util.{c,h} to match the
corresponding src/basic names.

This saves us from having to come up with separate names for files that do
the same thing across tiers, and it makes it easier to move stuff between
src/fundamental, src/basic and src/shared: consumers just #include "foo.h"
and pick up whichever tier their -I path resolves to first, so call sites
don't need to be updated when an API moves between layers.

Where a higher-tier wrapper exists (e.g. src/basic/macro.h wrapping
src/fundamental/macro.h), the wrapper uses an explicit "../fundamental/foo.h"
or "../basic/foo.h" relative include for the lower-tier header. We can't use
GCC's #include_next directive for this — when the wrapper is reachable both
via same-dir-as-source lookup and via -I (e.g. -Isrc/shared) for the
directory it lives in, #include_next advances by exactly one slot in libcpp's
internal directory chain and lands on the same physical directory it was
already in, never reaching the lower-tier sibling (see make_cpp_dir() in
gcc/libcpp/files.cc:1986).

To make sure the right headers are always picked up, the include directories
are reordered so that e.g. src/shared always takes priority over src/basic and
similar for the other directories.

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 10:33:03 +09:00
Luca Boccassi e13e69de7c meson: move one source file from libcore to systemd array 2026-05-20 18:15:56 +01:00
Luca Boccassi fd16d4ac54 meson: reorder core sources alphabetically 2026-05-20 18:15:56 +01:00
Luca Boccassi 70b6dc7631 meson: reorder core sources array
To hint that sources should be added to the lowest common denominator
first
2026-05-20 18:15:56 +01:00
Michael Vogt ddedddffde core: tweak error handling around VARLINK_ERROR_UNIT_BAD_SETTING
This commit move the erros in StartTransient from
VARLINK_ERROR_UNIT_BAD_SETTING to SD_VARLINK_ERROR_INVALID_PARAMETER
and it also ensures we have the bad field in the error.

Thanks to Ivan Kruglov for suggesting this.
2026-05-20 16:11:45 +02:00
Michael Vogt ee7bd1d8a0 core: add User,Group,SupplementaryGroups,Nice to varlink Unit.StartTransient
This commit adds more writable fields to the io.systemd.Unit.StartTransient
varlink method. With this its possible to set:
User,Group,SupplementaryGroups,Nice values.

Plus tests for them.
2026-05-20 15:27:20 +02:00
Michael Vogt 4f0271e923 core: report unsupported service fields in varlink calls
Just like for the unsupported/bad exec_fields we should show
a message about what field is bad for service parameters. This
commit adds it using the same pattern. The JSON parser works in
fail-fast mode so we only display the first bad field (and
it depends on the parser what it finds first).
2026-05-20 11:37:43 +02:00
Yu Watanabe 27e0bba966 core: introduce varlink io.systemd.Job interface (#42104)
Methods:

- `io.systemd.Job.List` — list all queued jobs or look up by `id`/`unit`
name, with streaming support. Uses context/runtime split: `JobContext`
(Unit, JobType) and `JobRuntime` (Id, State, Result,
ActivationDetails). Follows the same SELinux and parameter-conflict
patterns as `io.systemd.Unit.List`.
- `io.systemd.Job.Cancel` — cancel a specific job by ID, with SELinux
and polkit authorization.
- `io.systemd.Job.ClearAll` — cancel all pending jobs, with SELinux and
polkit authorization.
2026-05-20 17:22:18 +09:00
Michael Vogt ae169c99fa core: improve errors from varlink io.systemd.Unit.StartTransient
The existing error reporting for the varlink `StartTransient` code
was converting all errors into `VARLINK_ERROR_UNIT_BAD_SETTING`.

This is not correct in some cases, we need to have a more targted
pattern here, i.e. only convert EINVAL to VARLINK_ERROR_UNIT_BAD_SETTING
and otherwise return the matching varlink error from the errno instead.

This commit fixes this issue. Thanks to Ivan Kruglov for raising
this.
2026-05-20 10:10:15 +09:00
Ivan Kruglov 3941dff8b4 core: introduce io.systemd.Job interface with List, Cancel, and ClearAll methods
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-19 11:05:55 +02:00
Ivan Kruglov 7416bdf8f1 shared: extend Job varlink type with Unit and ActivationDetails fields
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-19 11:05:55 +02:00
Daan De Meyer bdbcff6530 Various dlopen/linking cleanups from #42100 (#42166)
- **bpf-util: rename from bpf-dlopen, unify version-specific symbol
handling**
- **cryptsetup: dlopen libcryptsetup in tokens**
- **tree-wide: dlopen libpam in pam plugins**
- **test-bus-marshal: dlopen() glib and libdbus instead of linking
directly**
- **lock-util: Simplify timeout for lock_generic_with_timeout()**
- **color-util: simplify hsv_to_rgb, fix rgb_to_hsv negative-hue wrap**
- **tree-wide: Use our own macros instead of fabs()/fmax()/fmin()**
- **locale-util: dlopen() libintl instead of linking against it**
- **home: Use log2u64() over log2()**
- **meson: drop libdl, threads, and librt dependencies**
- **libc: Use dlsym() from a constructor instead of weak symbols**
- **libc: Make sure C23 versions of strtol(), sscanf() are not used**
2026-05-19 08:54:27 +02:00
Daan De Meyer a14adac423 meson: drop libdl, threads, and librt dependencies
Our baseline glibc is 2.34, which merged libdl, libpthread (the
dependency('threads') target), and librt into libc. Empty .so/.a stubs
remain for backward compatibility with old binaries, but new builds
resolve dl_*, pthread_*, mq_*, timer_*, etc. directly from libc.
On musl the same libraries are likewise empty stubs.

Drop the libdl, threads, and librt entries from every meson.build, and
remove the now-stale 'Libs.private: -lrt -pthread' from libudev.pc.in
since both flags resolve to empty link-time stubs on glibc 2.34+ and
musl.

Verified with readelf -d that libsystemd.so, libudev.so, and systemd no
longer carry DT_NEEDED entries for libdl/libpthread/librt.
2026-05-18 21:17:38 +00:00
Daan De Meyer 7d822ca8af bpf-util: rename from bpf-dlopen, unify version-specific symbol handling
Renames src/shared/bpf-dlopen.{c,h} to src/shared/bpf-util.{c,h} and
folds the former src/shared/bpf-compat.h (struct forward decl and
compat_bpf_map_create() helper) into the new header.

Aligns dlopen_bpf() with the standard wrapper pattern: drops the
manual dlopen_safe()/dlsym_many_or_warn()/TAKE_PTR(dl) plumbing and
the bespoke 'cached' int in favor of dlopen_many_sym_or_warn() inside
a FOREACH_STRING() soname-fallback loop.

Unifies declaration of the version-specific symbols (bpf_create_map,
bpf_map_create, bpf_object__next_map, bpf_token_create) into a single
DISABLE_WARNING_REDUNDANT_DECLS block in the header, and alphabetically
merges the DLSYM_PROTOTYPE list. DLSYM_OPTIONAL is used to load each
one — call sites already handle NULL (compat_bpf_map_create() and the
sym_bpf_object__next_map guard in userns-restrict.c). bpf_token_create
additionally defaults to a missing_bpf_token_create() stub returning
-ENOSYS, so callers can branch on the errno instead of NULL-checking
the pointer.

Updates test-bpf-token to match: drops the compile-time
LIBBPF_MAJOR_VERSION ≥ 1.5 gate and the direct <bpf/bpf.h> include in
favor of dlopen_bpf() + sym_bpf_token_create(), and treats -ENOSYS as
the test-skip path (covering both 'libbpf too old' and 'kernel lacks
BPF_TOKEN_CREATE support').
2026-05-18 21:17:38 +00:00
Matteo Croce 15783e4657 oomd: ruleset parsing
Add to oomd the capability to define rulesets in /etc/systemd/oomd/rules.d/
and then reference them with a new config option OOMRule=
2026-05-18 21:42:56 +02:00
Zbigniew Jędrzejewski-Szmek be8b41eafa Fixes for claude review report (#42152) 2026-05-18 15:13:24 +02:00
Zbigniew Jędrzejewski-Szmek fd12cc9a52 core/manager: fix (theoretical) fd leak on invalid messages
We'd leak fds on early return. We are sending a message to ourself,
so it's unlikely to fail. But let's keep the logic correct.

Reported by qarmin (Rafał Mikrut).
2026-05-18 12:33:37 +02:00
Zbigniew Jędrzejewski-Szmek e305f552ec core/dbus-execute: propagate oom in property_get_cpu_affinity
The function already returns errors, so I'm not sure why we ignored
the error in the second call, potentially leaving variables unitialized.
It seems easiest to propagate the error.

Reported by qarmin (Rafał Mikrut).
2026-05-18 12:29:12 +02:00
Lennart Poettering cd6609499a tree-wide: make use of sd_json_parse_fd() where appropriate 2026-05-18 10:39:04 +01:00
Yu Watanabe c54decc406 ruff: use single quotes for multiline strings 2026-05-18 02:32:39 +09:00
Luca Boccassi c10c2674f3 LUO: add support for preserving third party sessions
LUO sessions cannot be nested under other sessions. This means we need
to handle them explicitly, and held them open in the shutdown binary
like we do with our own internal session, to allow services to create
their own.

The requirement to support third party sessions comes from VMMs that
wish to preserve VM(s) state(s) across kexec, as some file descriptors
(KVM's vmfd from the KVM_CREATE_VM ioctl) cannot be transfered between
processes via SCM_RIGHTS, so they cannot be stashed in the FD Store
directly. Also some file descriptors have to be handled all together or
not at all, again to do with KVM and devices that are all part of the
same vm.
2026-05-15 13:46:08 +01:00
Luca Boccassi 257c35c1a3 core: support FD Store preservation through kexec via LUO
The kernel Live Update Orchestrator (LUO) exposes /dev/liveupdate, which
allows userspace to hand a set of "preservable" kernel objects to the
new kernel across a kexec-based reboot. For now it only supports memfds,
with more object types (virtio devices, etc.) expected to be added later.

This is a natural fit for systemd's FD Store feature: services hand
memfds (containing serialized state or other service data) to PID 1 via
FDSTORE=1 sd_notify() messages, and get them back on their next start.
Today this works across service restarts, soft reboots and
initrd→rootfs transitions. With LUO we can extend the same mechanism to
work across kexec, too.

The protocol on the PID 1 side works roughly as follows:

 * All preservable fds are collected into a single LUO session named
   "systemd". Each FD gets uploaded with a token. Token 0 in that session
   is reserved for a "mapping" memfd, which carries a JSON object
   describing how to dispatch the other tokens back to units on the next
   boot:

       {
         "foo.service": [
           { "type": "fd", "name": "stateA", "token": 1 },
           { "type": "fd", "name": "stateB", "token": 2 }
         ],
         ...
       }

   unit IDs are used as the unit identifier, as they're stable
   across daemon-reexec, switch-root and kexec. token refers to the
   LUO token assigned to the object in the session.

 * On shutdown for MANAGER_KEXEC, just before manager_free(), systemd
   walks all services and serializes their persistent fd store contents
   (fds + FDNAMEs + unit IDs) into a JSON memfd. The FDs themselves are
   gathered into a FDSet to be kept around. The fdset and the
   serialization memfd are passed to systemd-shutdown via the
   SYSTEMD_LUO_SERIALIZE_FD environment variable providing the fd number,
   so the actual LUO session creation and ioctls can happen as the very
   last step before kexec (shutdown implementation is the next commit).

 * On boot, manager_luo_restore_fd_stores() opens /dev/liveupdate,
   tries to retrieve the "systemd" session, reads the mapping memfd,
   then for each entry retrieves the fd from the session and attempts
   to attach it to the matching unit's fd store.

 * The FDs are injected in the appropriate unit's FD stores using the
   same mechanism as the LISTEN_FDS propagation that was set up earlier.

Non-kexec shutdown paths are unaffected: if MANAGER_KEXEC is not the
final objective, no serialization file is produced and no LUO session
is ever created. Likewise if /dev/liveupdate does not exist, nothing
happens.
2026-05-15 13:46:08 +01:00
Luca Boccassi 5608575e01 core: propagate FDs from store from user to system manager
In order to allow FD Stores of user units to survive a user
session restart, propagate FDs received via the protocol up one
level from user to system manager via sd_notify.

And the other way around, propagate them down via LISTEN_FDS
tagging them with the unit name so that the child manager can
inject them in the appropriate unit.

Ensure units that are dead or not loaded can get FDs added to
their stores, and that they are correctly propagated once the
unit is started or loaded. When the unit is not loaded we don't
know what the FD max limit is, so simply increase it for each FD
injected, and then when the unit is realised prune it down to
match the unit's now available config in case the limit is lower
than the number of FDs in the store.

Each FD sent up or down is assigned a monotonic index, and the manager
also sends a JSON map that associates the index with the original
unit and FDNAME:

 {
   "unit-name.service": [
     { "name": "fdname1", "index": 1 },
     { "name": "fdname2", "index": 2 }
   ],
   ...
 }

This allows the manager to assign back the FDs to the appropriate
unit using the appropriate name, given the FDNAMEs are not unique.
2026-05-15 13:46:08 +01:00
Zbigniew Jędrzejewski-Szmek dca9ab5b92 Convert PID1 to option macros (#42099) 2026-05-15 13:39:36 +02:00
Zbigniew Jędrzejewski-Szmek dad6c71728 core: add WorkingDirectory, Environment and SetCredential{,Encrypted} to io.systemd.Unit.StartTransient (#41874)
This PR adds some more properties to the io.systemd.Unit.StartTransient
varlink interface: WorkingDirectory, Environment and
SetCredential{,Encrypted}. Its also hopefully a useful starting point to
establish a pattern to add even more.
2026-05-15 11:29:19 +02:00
Luca Boccassi f33fa0910d Implement Service Context/Runtime for io.systemd.Unit.List (#42098)
The PR implements the following objects + tests for
io.systemd.Unit.List:
- ServiceContext
- ServiceRuntime

It's hopefully the last PR of the long sequence of:

* 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
* https://github.com/systemd/systemd/pull/42057
2026-05-15 10:20:56 +01:00
Zbigniew Jędrzejewski-Szmek 09f107c811 manager: skip reopening of console and signal reset when running as normal program
We want to reopen the console used for logging when running as PID1, but
also when running a user manager (c.f. 48a601fe5d
and 2a646b1d62). But this can cause
problems when the binary is invoked directly, e.g. to print --help.
E.g. if we ignore SIGPIPE, we'd remain running briefly after
'/usr/lib/systemd/systemd --help | head -n1'.

Previusly, the getopt machinery would print to stderr unconditionally.
But after the rework of option parsing, which means that we use the
log_* functions to repor errors, the test that checks if we print errors
to stderr started failing.

So let's skip some more of the setup if !invoked_by_systemd().
2026-05-15 10:57:04 +02:00
Zbigniew Jędrzejewski-Szmek 4bfdb3c62f basic: fold getopt-defs.h into proc-cmdline.c
It'd be nice if we could not repeat the information about the option
list a second time. But I don't see a nice way to do this, since
(by design) with the macro approach, the macros must be intertwined
with the parse_argv() code. But that code in turn refers to a bunch
of variables, so lifting out the function is not immediately possible.
So I think it's best to keep the existing approach where we provide
a list of options, without additional context, and skip them using
a custom routine.
2026-05-15 09:26:48 +02:00
Zbigniew Jędrzejewski-Szmek 63fc9f1696 core: tweak handling of unknown options
099663ff8c added "support" for
-b/-s/-z ARG with a comment of
> /* Just to eat away the sysvinit kernel cmdline args without getopt()
>  * error messages that we'll parse in parse_proc_cmdline_word() or
>  * ignore. */
And for PID1 those was valid. But when not running as PID1, those
options would be parsed as valid but then help() would immediately
return -EINVAL:
  $ build-old/systemd -b; echo $?
  1

At the same time, when running as PID1, if we encounter an error,
we shouldn't opine about the rest of the command line. So continuing
with the loop and the checks after the loop were iffy.

Later, cd57038a30 made a big refactoring,
and the 'break' (i.e. continuation of the loop) was changed to 'return 0',
making things even more confusing, since now we'd just silently stop in
the middle of the command line if -b/-s/-z were encountered.

So be more careful: when running as PID1, stop parsing on error
and return from the function. We didn't parse the full command line,
so the later checks are not useful. Silently ignore -b/-s/-z.
When not running as PID1, explicitly say that -b/-s/-z are not
supported, and propogate the error if parsing fails, e.g. with
an unknown option.
2026-05-15 09:26:48 +02:00
Zbigniew Jędrzejewski-Szmek f51393716f core: add --crash-vt= for compat with --help
Fixes b9e74c3994.
2026-05-15 09:26:48 +02:00
Zbigniew Jędrzejewski-Szmek 5597a78df2 core: convert PID 1 parse_argv to OPTION macros
parse_argv() uses FOREACH_OPTION (not _OR_RETURN) so we can preserve the
existing PID 1 tolerance: an unknown option, or one of the sysvinit-style
-b/-s/-z catch-alls, returns 0 instead of -EINVAL when getpid_cached() == 1.

The docs documented --crash-vt=, but the code implemented "crash-chvt",
as introduced in b9e74c3994.
The output from --help is now modified to match code.

getopt-defs.h is intentionally left in place since
proc_cmdline_filter_pid1_args() in src/basic/proc-cmdline.c still uses
its COMMON_/SYSTEMD_/SHUTDOWN_GETOPT_* macros to walk the kernel
command line.

Previously, opterr=0 was used to suppress error messages about option
parsing in PID1. They are now logged at debug level (if debug logging
is enabled.)

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 09:26:41 +02:00
Ivan Kruglov 69b5c5db10 core: expand ServiceContext and add ServiceRuntime for io.systemd.Unit.List
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 00:02:01 -07:00
Ivan Kruglov 5305da009a shared: add exec_command_status_build_json() and ExecCommandStatus varlink type to common
Add exec_command_status_build_json() and exec_command_status_list_build_json() to varlink-common, alongside exec_command_build_json() and exec_command_list_build_json(). The status list function is the runtime counterpart of the command list function — the two arrays are positionally aligned so index N in the status array corresponds to index N in the command array. Commands that have not yet run produce null entries to preserve alignment.

Add the ExecCommandStatus varlink struct type to varlink-idl-common next to ExecCommand. It contains PID, timestamps, and mutually exclusive ExitStatus (int, for normal exit) / ExitSignal (string, for signal kill).
2026-05-15 00:02:01 -07:00
Michal Sekletar e1c63fd4c0 core: make manager event loop rate limit configurable
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 14:01:57 +09:00
Zbigniew Jędrzejewski-Szmek 953ff3c83e core: reorder cases in parse_argv() to match order in --help
The hidden-from-help options (--crash-reboot, --service-watchdogs,
--deserialize, --switched-root, --machine-id, -D, -b/-s/-z, ?)
move to the bottom. The 'b'/'s'/'z' → '?' fall-through is preserved.

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 19:59:21 +02:00
Ivan Kruglov 59203dca3a core: move service_context_build_json() to varlink-service.c
Move the existing (partial) service context builder from varlink-unit.c into its own varlink-service.c file, following the pattern established by other unit type context builders (varlink-path.c, varlink-scope.c, etc.). No functional change.
2026-05-14 09:53:03 -07: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
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
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
Michael Vogt de2ad965c2 core: add SetCredentials{,Encrypted} to varlink Unit.StartTransient
This commit adds support to set `SetCredentials` and
`SetCredentialsEncrypted` in the `io.systemd.Unit.StartTransient`
varlink call.
2026-05-13 19:28:35 +02:00
Michael Vogt 045d08e417 core: extract exec_context_apply_set_credential() helper
Extract the SetCredential{,Encrypted} logic out of
bus_exec_context_set_transient_property() into a new helper.
No functional changes.

This will be used in the varlink Unit.StartTransient SetCredential
implementation.
2026-05-13 19:28:35 +02:00
Michael Vogt 27fa92aa3c core: add support for Environment in io.systemd.Unit.StartTransient
This commit adds support to set `Environment` in the
`io.systemd.Unit.StartTransient` varlink call.

The behavior is similar to D-Bus, i.e. a `null` or `[]` clears the
environment. This is not needed for StartTransient() as there the env
always starts empty but it seems a good property to have if this is
reused.
2026-05-13 19:28:35 +02:00
Michael Vogt ed6b2b9c30 core: extract exec_context_apply_environment() helper
Extract a small helper exec_context_apply_environment() from the
dbus-execute.c file so that it can be re-used in the coming varlink
version.
2026-05-13 19:28:35 +02:00
Michael Vogt a2aa3a7450 core: add WorkingDirectory to Unit.StartTransient
This commit adds setting the WorkingDirectory to the
`io.systemd.Unit.StartTransient` varlink call. This is a
first step towards more complete StartTransient in varlink.

The goal is to be as close as possible to the D-Bus parameters.
The exception is WorkingDirectory which is an object here so that
we avoid the `-` prefixes and use a more type-safe approach by
making it an explicit `missingOK` parameter.

The key names stay the same as the D-Bus properties (PascalCase).
If there are no equivalent D-Bus properties the native varlink
convention of camelCase is used.
2026-05-13 19:28:35 +02:00
Michael Vogt eb435b8326 core: tweak pattern of applying varlink properties for StartTransient
So far we had the pattern to check first if any property needs setting
and then have a function to set it. The downside is that when we add
a new property two different places need to change (once the `if`
in vl_method_start_transient_unit() and once in the specific helper
to do the actual setting). So instead this commit moves everything to
the helpers and tweaks the code so that we can always call the function.
2026-05-13 19:28:35 +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
Christian Brauner 4684df8f97 core: work around btf_ctx_access() rejection of const void * in BPF LSM
Kernels before v6.16 (missing commit 1271a40eeafa "bpf: Allow access to
const void pointer arguments in tracing programs") have a bug in
btf_ctx_access() where const void * parameters in LSM hook signatures
are not recognized as void pointers. The function checks t->type == 0
to detect void *, but for const void * the BTF chain is PTR -> CONST ->
void, so t->type points to the CONST node rather than directly to
type_id 0. This causes the verifier to reject any BPF program that
reads the const void *value argument of bdev_setintegrity:

  func 'bpf_lsm_bdev_setintegrity' arg2 type UNKNOWN is not a struct
  invalid bpf_context access off=16 size=8

Work around this by providing a compat variant of the
bdev_setintegrity BPF program that avoids reading the const void *value
argument entirely. Instead it reads the size argument (a scalar integer)
directly from the raw BPF context (ctx[3]), which is not subject to the
broken type check. This is safe because dm-verity guarantees that value
and size are always in lockstep: both NULL/0 for unsigned devices, both
non-zero for signed devices.

The loader tries the full version first (which reads both value and size
for defense-in-depth) and falls back to the compat variant if loading
fails. bpf_program__set_autoload(false) disables whichever variant is
not needed so the verifier never sees it.

This compat logic can be removed once the minimum kernel baseline
includes the 1271a40eeafa fix.

Signed-off-by: Christian Brauner <brauner@kernel.org>
2026-05-13 10:36:13 +02:00
Christian Brauner ad5eb75e60 core: expose internal helpers for test-bpf-restrict-fsaccess
Make dm_verity_require_signatures() non-static and declare it in the
header so the test helper binary can exercise the same precondition
checks that PID1 uses.

Signed-off-by: Christian Brauner <brauner@kernel.org>
2026-05-13 10:36:12 +02:00