1432 Commits
Author SHA1 Message Date
Daan De Meyer f301d3c01d install: turn off vendor enablement by masking it from /etc/
"systemctl disable" can't turn a unit off that the vendor enabled with a
dependency symlink below /usr/. Where a mask has been created by hand it removes
the mask, which silently enables the unit again. A preset policy of "disable"
can't turn such a unit off either.

/usr/ is frequently read-only and anything we did there would be undone by the
next image update, so let's shadow the symlink instead: disable writes a symlink
to /dev/null into the configuration directory, and PID 1 ignores a dependency
entry that resolves to /dev/null. Only the symlinks that enabling would create
are shadowed, i.e. the ones in the directories the [Install] section names. A
static dependency such as the symlink to remote-cryptsetup.target in
initrd-root-device.target.wants/ stays untouched. Enable removes the masks
again, those of the Also= units included, which is why the removal runs after
the symlinks have been applied. Creating a symlink replaces the mask at the same
path. Disabling a template masks every instance the vendor enabled, and enabling
the template only creates the symlink of its default instance, so the removal
afterwards catches the masks of the other instances.

Disabling a template also removes the symlinks of its instances.
"systemctl disable foo@.service" therefore deletes a foo@bar.service ->
/dev/null unit mask, which unmasks the instance. Disable now leaves all masks
alone, unit masks included.

Static dependencies are left alone throughout, so that a catch-all "disable *"
policy can't stop the likes of systemd-journald.service from starting at boot.
Nothing is masked where something else already exists in the dependency
directory: it already shadows the vendor symlink, and replacing it would destroy
it.

A preset policy of "disable" deliberately doesn't mask this way and leaves the
vendor enablement in place. Preset files never had to name the units the vendor
enabled below /usr/, and with a catch-all "disable *" rule a mask would stop
all of those units from starting at boot.

"systemctl --user disable" warns when a unit stays enabled in global scope, and
checks the global state for that. A vendor symlink in /usr/lib/systemd/user/ now
counts there, even though the mask the disable just created in the user's
configuration shadows it. Let's check the user scope's state instead. It sees
the mask, and it still reports a unit enabled in /etc/systemd/user/ as enabled.

The changes list gains two types for this, "mask-dependency" and
"unmask-dependency", so that callers can tell a mask from a plain unlink. The
bus API documents them as such, GetUnitFileLinks() reports the vendor symlink
rather than the mask that would shadow it, and so does "is-enabled --full".
2026-11-03 10:54:59 +01:00
Daan De Meyer 25f8fcab66 install: report units the vendor enabled below /usr/ as enabled
Distributions install .wants/, .requires/ and .upholds/ symlinks in
/usr/lib/systemd/system/, and PID 1 acts on those exactly like on the ones in
/etc/. The install layer doesn't know about that tier at all, so "systemctl
is-enabled" reports such units as "disabled" (or "static" for instances) even
though they start at boot. On a stock Arch install man-db.timer reports
"disabled" while being active.

Let's teach find_symlinks_in_scope() about that tier. A dependency symlink
below /usr/ now enables the unit, but only one that "systemctl enable" would
create, i.e. one in a directory that WantedBy=, RequiredBy= or UpheldBy= in the
[Install] section names. Any other such symlink is a static dependency rather
than enablement, and reporting the unit as "enabled" because of it would
suggest it can be disabled again. remote-cryptsetup.target has
WantedBy=multi-user.target, and we install a symlink to it in
initrd-root-device.target.wants/, which must not make it report as enabled.
Alias= and Also= don't count, as neither results in a dependency symlink.

This means "systemctl is-enabled" now exits 0 where it used to exit 1 for these
units. Distributions install such symlinks for a number of units, so packaging
scripts may notice. Turning them off is a separate matter: disable still can't,
which is no worse than before.
2026-11-03 10:54:36 +01:00
Lennart Poettering 0bfed33959 tree-wide: close-on-exec by default (#43692)
In any modern codebase `O_CLOEXEC` should be the default and file
descriptor inheritance must be explicitly enabled. Flip the switch and
port everyone to our internal open helpers as well.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-11-02 15:31:57 +01:00
Daan De Meyer 3a0caf9234 mountfsd: ask polkit if the client policy doesn't overlap
mountfsd intersects the client's image policy with its own one. For
images outside of the trusted directories, its own policy only allows
"signed" for the root and usr partitions. If the client's policy only
allows "verity", for example the policy that portabled pins for an image
without a signature, the intersection is empty. image_policy_intersect()
then fails with -ENAVAIL, and we return that error to the client,
without asking polkit whether the client may mount untrusted images. If
the client's policy also allows "signed", we ask polkit, and mount the
image if polkit allows it.

Let's handle -ENAVAIL from determine_image_policy() like a refusal by
the image policy, so that we ask polkit and retry with the policy for
trusted images. If the client's policy doesn't overlap with the policy
for trusted images either, we return DeniedByImagePolicy.
2026-11-02 09:27:41 +01:00
Daan De Meyer 186c14f300 mountfsd: ask polkit when Verity setup is denied by policy
If the image policy refuses an image during dissection, we ask polkit
whether the client may mount untrusted images and retry with the trusted
policy. dissected_image_decrypt() can refuse an image too, with -ERFKILL,
for example if the untrusted policy requires a signature and the
signature partition is invalid. We return that error to the client as a
plain errno, without asking polkit, so a polkit rule that allows
untrusted images doesn't help.

Let's handle -ERFKILL from dissected_image_decrypt() like -EDESTADDRREQ:
ask polkit, and retry with the trusted policy if polkit allows it. If
the client may use the trusted policy already, we return
io.systemd.MountFileSystem.DeniedByImagePolicy.
2026-11-02 09:27:41 +01:00
Christian Brauner 72e82cddb6 NEWS: mention that files are no longer created through dangling symlinks
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-11-02 09:21:12 +01:00
Daan De Meyer 9a8a4428bb veritysetup: make root-hash-signature=auto optional
systemd-gpt-auto-generator always passes root-hash-signature=auto for
the root and /usr partitions. The data device doesn't have the
ID_DISSECT_PART_ROOTHASH_SIG udev property if the image has no signature
partition. In that case, systemd-veritysetup fails, and the system can't
boot from a verity image that isn't signed. If a signature exists but
neither the kernel nor userspace validates it, we activate the device
without the signature.

The image builder controls whether the image has a signature partition.
Whether the boot requires a signature thus depends on the image
contents. The failure gives no protection, because an image with a
signature that doesn't validate boots. Anyone can avoid the failure by
adding a signature partition with any well-formed signature. Whether a
signature is required must come from the host.
systemd-gpt-auto-generator enables root-hash-signature-required= if
systemd.image_policy= requires a signature. With that option, activation
fails if the signature is missing or invalid.

Let's activate the device without a signature if the property is
missing and root-hash-signature-required= isn't enabled.
2026-11-01 21:04:58 +01:00
Daan De Meyer 1c6bcc4f51 veritysetup: add root-hash-signature-required= option
If the kernel can't validate the root hash signature of a verity device,
systemd-veritysetup activates the device with the root hash alone. With
systemd.image_policy=root=signed and root=dissect, the udev dissect
builtin only checks that the root partition has a signature partition.
Nothing checks that the signature is valid, even though the policy
requires a signature.

Let's add a root-hash-signature-required= option that refuses to
activate the device unless the kernel or userspace validates the
signature. Let's also make systemd-gpt-auto-generator enable it for the
root and /usr partitions if the image policy allows "signed" but not
"verity" for them.

Userspace validation uses the certificates in /etc/verity.d/, like
dissect-image does for disk images. A signature that is valid for a disk
image is thus valid at boot too. Like dissect-image, we try userspace
validation whenever the kernel rejects the signature, also if the
signature isn't required. That way, the signature is measured into the
NvPCR whenever the kernel or userspace validates it.
2026-11-01 21:04:58 +01:00
Yu Watanabe 1995a41d7c udev: detect USB smartcard readers through the usb-interface hwdb
Instead of setting ID_SMARTCARD_READER from a direct match on
ID_USB_INTERFACES in 60-fido-id.rules, set it for devices exposing a
smartcard (CCID) interface, i.e. interface class 0b, in the usb-interface
hwdb.

The hwdb lookup is done in 50-udev-default.rules.in, which is processed
before 60-fido-id.rules, so the subsequent rules that match on
ID_SMARTCARD_READER are not affected.

As the property can be set to either 0 or 1 in the hwdb, make the rules in
70-uaccess.rules.in and 99-systemd.rules.in match on "1" instead of any
non-empty value.

This makes the property, and the tags and the target depending on it,
require hwdb.bin, which matters when a smartcard reader is already used in
the initrd. Mention that in NEWS.
2026-10-31 23:07:06 +09:00
Christian Brauner f92f7eb809 seccomp: let LockPersonality= allow querying the personality (#44519)
Stacked on #44511.

LockPersonality= refuses every personality() call but the one setting
the locked personality. That includes personality(0xffffffff), which
only returns the current personality. Let the query through as well.

A libseccomp rule can't say "neither the personality nor the query",
so this needs masked compares on the lower 32 bits like the
READ_IMPLIES_EXEC filter in #44468. The first commit moves that
filter's rules into a helper that refuses everything but the values
matching a mask and the query. LockPersonality= then uses it with all
32 bits as the mask. test-seccomp checks the query and every value one
bit away from it and from the locked personality.
2026-10-31 14:23:32 +09:00
Yu Watanabe 88d39e7d3f Add /etc/machine-tags.d/ (#43851) 2026-10-31 14:20:52 +09:00
Michael Vogt 7321925477 units: add optional varlink-sftp socket and service
With the varlink protocol upgrade feature we can use provide
file system access via varlink easily by re-using the existing
sftp standard. SFTP is independant of ssh and provides a well
defined and widely used file transfer protocol.

So here we add two new (optional) varlink interfaces:
- io.systemd.Sftp.Root.Connect
- io.systemd.Sftp.RootReadOnly.Connect

They are behind polkit auth as io.systemd.sftp.root{,-read-only}.

Note that handing this is out is equivalent for giving root
access: even ReadOnly mode will allow to read secrets like
/etc/shadow so its inherently risky. The two actions mostly
exist to prevent accidents not as a (strong) protection
boundary. The unit description warns about this and the polkit
prompt also describes the full implications.

The use cases are:
- file access via the varlink-http-bridge [1]
- file access over AF_VSOCK

The install is optional and turned off by default.

[1] https://github.com/systemd/varlink-http-bridge
2026-10-30 20:06:11 +01:00
Christian Brauner 2eb940c952 man: mention that LockPersonality= allows querying the personality
Add a NEWS entry as well.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-30 20:02:05 +01:00
Zbigniew Jędrzejewski-Szmek dd410c02f8 NEWS: add entry about tag globs 2026-10-30 16:56:37 +01:00
Christian Brauner 975706f94b Refuse READ_IMPLIES_EXEC under RestrictFileSystemAccess (#44468)
Stacked on #44394.

Currently `RestrictFileSystemAccess` can still be talked into handing
out writable and executable memory. The W^X rules live in the
`mmap_file` and `file_mprotect` hooks but `brk()` never reaches them. If
the `READ_IMPLIES_EXEC` personality flag is set the kernel maps new heap
pages writable and executable. And setting that flag doesn't require any
privilege:

```c
personality(READ_IMPLIES_EXEC);
p = sbrk(4096);
memcpy(p, payload, len);
((void (*)(void))p)();
```

`PR_SET_MDWE` doesn't catch this either.

Nothing running under `RestrictFileSystemAccess` has any use for the
flag. It makes every anonymous mapping executable and the policy refuses
all of them anyway. So let PID 1 install a seccomp filter that refuses
to set it, for all processes and on all local architectures.

That doesn't cover processes that end up with the flag anyway. The
kernel sets it on its own for some legacy binaries and usermode helpers
never inherit PID 1's seccomp filters. But `brk()` does call into the
`mmap_addr` hook, so add a BPF program there that refuses any new
mapping to a task with the flag set.

Note that ELF binaries whose `PT_GNU_STACK` program header asks for an
executable stack still get one. The kernel sets that up without passing
through an LSM hook, so this is only documented for now.
2026-10-30 15:36:17 +09:00
Ian Pilcher dfe7e0cb07 cryptsetup: add friendly-name= /etc/crypttab option
When prompting a user for LUKS device passphrase or PKCS#11 token PIN,
systemd-cryptsetup generates a device description from the parent
device and the dm-crypt device name. Depending on the details of the
system configuration, this description can be quite long, and it may
not be very useful (e.g. "SAMSUNG MZVLC512HFJD-00BLL
(luks-f9ddd3cf-7655-4e57-9a08-87d392796cc6)").

Add a friendly-name= option, which can be set in the options field of
a device in /etc/crypttab (or on the kernel command line). The
"friendly name" is used in place of the default generated description
in prompts generated by systemd-cryptsetup.

The name is checked for invalid characters or excessive length (more
than 100 bytes). If it is invalid, a warning message is logged, and
the auto-generated description is used.
2026-10-30 14:50:56 +09:00
Christian Brauner 98b54b2ce4 man: document the READ_IMPLIES_EXEC refusal and executable stacks
Mention that processes that have READ_IMPLIES_EXEC anyway can't map
memory or grow their heap, and that ELF binaries asking for an
executable stack still get one. Add a NEWS entry for the
READ_IMPLIES_EXEC refusal.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-29 16:24:46 +01:00
Christian Brauner a0d4a01169 man: document SecureBits= for the service manager
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-29 13:43:41 +01:00
Christian Brauner d8a06551e2 RestrictFileSystemAccess=: answer execveat(AT_EXECVE_CHECK) (#44393)
Since Linux 6.14 execveat() takes AT_EXECVE_CHECK, which asks whether a
file could be executed without executing it. Interpreters use it so that
"sh script.sh" and "./script.sh" get the same answer from the kernel's
execution policy. The check returns right after the bprm_creds_for_exec
hook, before any binfmt handler runs, so our bprm_check_security program
never sees the file. Currently, we say "yes" to every such query, no
matter where the file lives.

So add a program on bprm_creds_for_exec that runs the same trust check
when bprm->is_check is set. Real execs stay with bprm_check_security,
which also sees the interpreters they pull in. The check is gated on
is_check so normal execs aren't evaluated twice, which is what IPE does
as well. is_check only exists since v6.14, so it's read through a CO-RE
flavor and the program is a no-op on older kernels, which don't know the
flag either. No new kernel requirement.

The tenth link changes the serialized link set, so the first patch
teaches PID 1 to accept the set of a v261 or v262 PID 1 as a prefix on
reexec. Without it a daemon-reexec or an initrd to root switch across
the upgrade kills PID 1.

Tested with TEST-90 (enforce, config, dm-verity keyring) on a 7.2
kernel.
2026-10-29 13:43:36 +01:00
Christian Brauner 694ac19a83 man: document AT_EXECVE_CHECK support in RestrictFileSystemAccess=
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-28 10:04:19 +01:00
Michael Vogt ced33dc3c0 varlinkctl: add new --polkit-action= to varlinkctl serve
When providing ad-hoc protocol upgrade support to existing programs
via the `varlinkctl serve` command it is often useful/desirable
to limit it to a certain polkit action. So we add a new flag called
`--polkit-action=` here that does exactly this. The protocol upgrade
is only allowed when the given polkit action is allowed for the
user.

Thanks to Lennart for suggesting this in PR#43994
2026-10-28 09:20:42 +01:00
Christian Brauner db8557d8a5 keyring-setup: add systemd-keyring-setup (#44114)
Lennart asked in #44114 to split the preparatory stuff out so it can
be merged in smaller PRs. So this is a stack of five, each based on
the one before it and each showing only its own commits:

1. #44323, the C helpers (merged)
2. #44324, support for programs written in Rust (merged)
3. #44420, bindings.h lists only the headers Rust code uses (merged)
4. #44325, the systemd_shared wrappers systemd-keyring-setup needs
(merged)
5. #44114, systemd-keyring-setup itself

This one is 5.

This adds systemd-keyring-setup which enrolls X.509 certificates from
the VOA hierarchy into the kernel's .dm-verity, .fs-verity and .bpf
keyrings at boot and seals them afterwards. It's the first program in
systemd written in Rust. The C prototype in #43549 was never merged, so
nothing is ported here.

The kernel has various keyrings that early userspace may choose to set
up. Right now it has the .fs-verity and .dm-verity keyrings and in the
next kernel it will also have a .bpf keyring.

All three keyrings accept X.509 certificates in DER form. The .dm-verity
and .bpf keyrings are inactive and restricted by default unless the
respective kernel command line options are passed that leave them
unrestricted. Both keyrings are ignored until they are restricted. IOW,
restricting them causes the kernel to use them for signature
verification. The .fs-verity keyring is always active and is taken into
account as soon as it contains a key.

The UAPI group published the File Hierarchy for the Verification of OS
Artifacts (VOA) specification (cf. [1]). systemd-keyring-setup reads the
certificates for each keyring from the kernel-keyring role with the
keyring as the context and the trust anchors from the
trust-anchor-kernel-keyring counterpart:

```
<load-path>/<os>/kernel-keyring/<context>/x509/<name>-certificate.pem
<load-path>/<os>/trust-anchor-kernel-keyring/<context>/x509/<name>-certificate.pem
```

The load paths in descending priority are /etc/voa, /run/voa,
/usr/local/share/voa and /usr/share/voa. `<os>` is the exact identifier
`ID[:VERSION_ID[:VARIANT_ID[:IMAGE_ID[:IMAGE_VERSION]]]]` and the bare
`ID`. `<context>` is dm-verity, fs-verity or bpf.

Examples for a vendor shipping verifiers on Debian:

```
/usr/share/voa/debian/kernel-keyring/dm-verity/x509/vendor-certificate.pem
/usr/share/voa/debian/kernel-keyring/fs-verity/x509/vendor-certificate.pem
/usr/share/voa/debian/kernel-keyring/bpf/x509/vendor-certificate.pem
```

A CA certificate as a trust anchor and an admin override in /etc for a
specific release:

```
/usr/share/voa/debian/trust-anchor-kernel-keyring/dm-verity/x509/vendor-ca-certificate.pem
/etc/voa/debian:13/kernel-keyring/dm-verity/x509/corp-certificate.pem
```

Masking works as everywhere in VOA via a /dev/null symlink under the
same file name in any load path:

```
/etc/voa/debian/kernel-keyring/dm-verity/x509/vendor-certificate.pem -> /dev/null
```

The lookup is anchored at a pinned root directory and drops verifiers
that somebody else could have tampered with. That's what CHASE_MAX_MODE
and the CONF_FILES_CHASE_* flags are for. The VOA lookup, the keyring
helpers and the X.509 helpers are all C in libsystemd-shared and the
Rust program calls into them.

After enrolling, the keyring is restricted to certificates signed by an
enrolled key and SetAttr is dropped from its permission mask. So neither
the restriction nor the mask can be changed anymore. Any keyring that's
still open is sealed at the end of a run, even when it's empty. The
binary may run in the initrd and on the host. A second run detects the
sealed keyring, skips certificates that are enrolled already and enrolls
the ones signed by an enrolled key. The service isn't enabled by
default.

Certificates can also be shipped as `keyring-setup.<keyring>.<name>`
system credentials (SMBIOS, ESP credential drop-ins, --set-credential
for the boot loader). This lets users avoid having to ship keys as part
of the initrd. We don't enroll them directly but follow the VOA spec
instead. They are placed into /run/voa first so masking, merging and
deduplication apply to them like to any other file:

```
/run/voa/debian/kernel-keyring/dm-verity/x509/corp-certificate.pem
```

A keyring-setup.os credential overrides the OS identifiers derived from
os-release. This is useful for generic initrds whose identity differs
from the host's.

The specification requires the extended key usage of a verifier to be
aligned with its purpose. The kernel has no concept of this. So
systemd-keyring-setup checks it.

TEST-98-KEYRING-SETUP boots with and without
dm_verity.keyring_unsealed=1 and the kernel verifies the signed test
image against the keyring that systemd-keyring-setup provisioned.
TEST-90 copes with a .dm-verity keyring that was sealed at boot.

[1]:
https://uapi-group.org/specifications/specs/file_hierarchy_for_the_verification_of_os_artifacts/

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-27 10:27:21 +01:00
Chris Down a7272145eb inhibit: Default delay locks to sleep:shutdown (#43717)
logind refuses delay locks for anything but sleep and shutdown, so the
old default doesn't make a whole lot of sense.
2026-10-27 03:19:43 +08:00
Christian Brauner 36fedadf4f docs: announce systemd-keyring-setup and its kernel requirements
Add a NEWS entry for systemd-keyring-setup and list what it needs from
the kernel in README. The .dm-verity keyring needs v7.0 and verifying
signed verity images and fs-verity files with the certificates it
enrolls needs CONFIG_KEYS and CONFIG_FS_VERITY_BUILTIN_SIGNATURES.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-26 18:55:54 +01:00
Daan De Meyer 5ddab5b8f4 portabled: authenticate extension images specified by path
GetImageMetadata() and GetImageMetadataWithExtensions() ask polkit before
inspecting an image that is specified by path, because inspecting
loop-mounts the image as root. Extension images aren't authenticated at
all, so any local user can make portabled set up loop devices for files
they choose, probe them and mount them, by passing them as extensions of
an installed image.

Let's ask polkit for the inspect-images action if any extension image is
specified by path, like we do for the image itself.
2026-10-26 17:39:48 +01:00
Chris Down 04693ada95 inhibit: Default delay locks to sleep:shutdown
logind refuses delay locks for anything but sleep and shutdown, so the
old default doesn't make a whole lot of sense.
2026-10-26 21:48:55 +08:00
Daan De Meyer a726e292c1 man: document when verity signatures are required
The man pages don't say what happens when an image policy allows
"verity" as well as "signed" and the signature can't be validated.
systemd.exec(5) even claims that RootHashSignature= only opens the
volume if the kernel validates the signature, which isn't true with the
default policy.

Let's document that only a policy that allows "signed" without "verity"
requires a valid signature, in systemd.image-policy(7),
systemd.exec(5), systemd-dissect(1) and for
$SYSTEMD_DISSECT_VERITY_SIGNATURE, and add a NEWS entry for the changed
behavior.
2026-10-26 14:06:54 +01:00
Daan De Meyer 55f7d28c36 mountfsd: restrict unprivileged Verity device reuse
Unprivileged mountfsd clients can select the root hash of a shared Verity
device and read its contents without opening the original image.

Let's require the same backing file when non-root clients reuse Verity
devices through mountfsd. Privileged clients and other callers keep
sharing by root hash without needing loop device metadata.
2026-10-25 19:46:21 +01:00
khromenokroman 18f3b466c6 network: introduce ClientIdentifier= setting in [DHCPServerStaticLease] section
Previously, a static lease could be configured only with MACAddress=, which matches a client by its
hardware address. Hosts that share a hardware address, e.g. ipvlan interfaces in L2 mode, but send
different client identifiers could not get different static addresses.

This introduces ClientIdentifier=, IAID=, DUIDType=, and DUIDRawData= settings, with the same
semantics as the ones in the [DHCPv4] section. With ClientIdentifier=mac, the default, the client
identifier is built from MACAddress= as before. With ClientIdentifier=duid, MACAddress= is ignored,
and the client identifier is built from IAID= and the DUID specified by DUIDType= and DUIDRawData=,
all of which must be specified.
2026-10-24 22:21:50 +02:00
Christian Brauner 021112961b rust: support programs written in Rust (#44324)
Lennart asked in #44114 to split the preparatory stuff out so it can
be merged in smaller PRs. So this is a stack of four, each based on
the one before it and each showing only its own commits:

1. #44323, the C helpers (merged)
2. #44324, support for programs written in Rust
3. #44325, the systemd_shared wrappers systemd-keyring-setup needs
4. #44114, systemd-keyring-setup itself

This one is 2.

This adds support for writing systemd programs in Rust. It replaces
#43551 and reworks it along the lines the maintainers agreed on at the
Berlin hackfest:

- Rust is only used for new leaf components. Existing C code isn't
  ported and nothing that other components depend on is written in
  Rust.
- No cargo. Everything is built with meson.
- Only the core, alloc and libc crates. Rust code is no_std and there
  are no other crates. The libc crate isn't needed yet, the bindings
  generated from the tree's own headers cover libc. And like the
  kernel we don't use alloc either. Its collections abort when memory
  runs out. systemd_shared has its own Box and Vec instead, which work
  like the kernel's: everything that allocates returns an error and
  there's no global allocator.
- C linkage only. libsystemd-shared and the dlopen machinery stay as
  they are. A program written in Rust links libsystemd-shared
  dynamically just like the C programs do and declares its dlopen()
  dependencies in .note.dlopen. The Rust crates are linked statically
  into the programs using them.

So nothing of the Rust standard library ends up in a program or in
libsystemd-shared. Per HACKING.md the stripped test-cli-rust binary is
32 KB with LTO against 20 KB for its C twin.

Rust support is optional via -Drust= and auto by default.

The bindings for libsystemd-shared are generated at build time by
bindgen from the same headers, flags and include stack the library is
built with. On top of that sits the systemd_shared crate which programs
are written against. It mirrors the C conventions: errno based errors,
the log_*() family, define_main!() and the command line framework of the
C programs. A Rust program prints the same --help, parses options the
same way and answers --introspect-cli exactly like a C program.
test-cli-parity.sh checks that byte for byte.

Secrets go into Vec<u8, Erasing>. Erasing allocates like malloc() but
frees with erase_and_free(). In C that's a convention every caller has
to remember. Here it's part of the type.

You need rustc >= 1.85, bindgen >= 0.71.1 and meson >= 1.4.0 which is
what Debian 13 ships. The tree keeps its meson floor and -Drust=auto
turns itself off on older meson. The mkosi builds of the distributions
that have the toolchain pass -Drust=enabled and the linter runs rustfmt,
the bindings header check and clippy. HACKING.md and CODING_STYLE.md
document how a program is written and what the rules are.

Note that the Arch and openSUSE OBS builds fail because their packaging
builds with --auto-features=enabled and doesn't pull in rustc and
bindgen. They need either -Drust=disabled or build dependencies on both.

It was based on #44323 only because bindings.h lists every header in
src/basic/ and src/shared/ and so has to know voa-util.h. That's merged
now and this PR targets main. The first program written against it is
systemd-keyring-setup in #44114, with the wrappers it needs in
#44325.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-24 08:21:59 +02:00
Yu Watanabe 0f0682b773 network: enable IPv6 link-local addressing on WireGuard interfaces (#44367)
Fixes #44005.
 
Previously, IPv6 link-local addressing was always disabled for WireGuard
interfaces, even with LinkLocalAddressing=ipv6, and an IPv6LL address
could only be assigned with Address=.

To answer the question in #44149 about kernel support:
- The kernel generates IPv6LL addresses for ARPHRD_NONE interfaces
(which WireGuard is) since v4.5 (cc9da6cc4f56, "ipv6: addrconf: use
stable address generator for ARPHRD_NONE"). As there is no hardware
address, the address is generated as in the random mode.
- However, the WireGuard driver sets addr_gen_mode to none in wg_open(),
i.e. every time the interface is brought up, since the driver
was introduced. So the mode set by networkd before the interface is
brought up is overwritten, and simply dropping "wireguard" from the
  list in link_ipv6ll_enabled() is not enough.
- Writing addr_gen_mode by sysctl after the interface is up generates
the address, as the kernel calls addrconf_init_auto_addrs() on
  change since v4.11 (d35a00b8e33d).
 
Tested on kernel 6.1:
ip link add wgtest type wireguard
ip link set wgtest addrgenmode random -> addr_gen_mode is 3
ip link set wgtest up -> addr_gen_mode is 1, no fe80 address
echo 3 > /proc/sys/net/ipv6/conf/wgtest/addr_gen_mode ->
fe80::a6b8:44e1:db02:94e9/64 scope link stable-privacy
                                            
With this change, IPv6 link-local addressing is enabled for WireGuard
interfaces only when LinkLocalAddressing= is explicitly set, so the
default is unchanged, and the address generation mode is set again when
an interface is brought up, like the IPv6 MTU.
2026-10-24 07:22:11 +09:00
Ivan Kruglov 5ea7c0416a core/varlink: give SetProperties a dedicated input type (#44370)
`io.systemd.Unit.SetProperties`, introduced in #40356, uses
`UnitRuntime` as its input type. The D-Bus equivalent,
`org.freedesktop.systemd1.Manager.SetUnitProperties()`, supports
properties split between `UnitContext` and `UnitRuntime`. Neither type
describes the writable subset: `UnitRuntime` excludes configuration,
while `UnitContext` includes unsupported settings and lacks `Markers`.

1. Introduce `UnitProperties`, initially containing only `Markers`, for
future setters to extend. Preserve the `Markers` JSON representation and
update semantics, and restore required fields in the now output-only
`UnitRuntime`.
2. Reject `runtime=false` because `Markers` cannot be persisted as unit
configuration.
3. Document the compatibility implications in NEWS: callers should pass
`runtime=true`, and generated client bindings may need updates.
2026-10-23 17:08:05 +02:00
Christian Brauner 66412c31bb docs: document writing programs in Rust
HACKING.md describes how a program is declared and written with the
command line framework of the C programs, what the toolchain
requirements and the per-distribution packages are, what a Rust program
costs and what it may not do (std, external crates), how the build
treats Rust code (lint set, overflow checks, panic=abort, remapped
paths, no CET markers) and where the tests, clippy, rustdoc and the
rust-analyzer project file are.

CODING_STYLE.md gets the rules for Rust code:

- rustfmt and the enforced lint set
- define_main!()
- the systemd_shared crate as the only way to talk to the rest of
  systemd
- names mirroring the C names
- SAFETY comments and Safety sections
- BorrowedFd for file descriptors
- no_std
- the error handling conventions
- #[expect] over #[allow]
- allocation failure never panicking or aborting, like in the kernel
- no pointless copies of strings

ARCHITECTURE.md explains where the Rust code lives. README lists rustc
and bindgen among the optional build dependencies, and NEWS tells
packagers that builds with --auto-features=enabled now need them or
-Drust=disabled.

CODING_STYLE.md also spells out the rules the systemd maintainers
agreed on for Rust: it is only used for new leaf components and
existing C code isn't ported, it's built with meson and without cargo,
only the core, alloc and libc crates are permitted, and Rust code only
talks to the rest of systemd through C linkage.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-23 16:34:47 +02:00
khromenokroman a065f9a059 network: enable IPv6 link-local addressing on WireGuard interfaces
Previously, IPv6 link-local addressing was always disabled for WireGuard
interfaces, even when LinkLocalAddressing=ipv6 was explicitly specified,
and an IPv6LL address could only be assigned with Address=.

The kernel has been able to generate IPv6LL addresses for interfaces
without a hardware address since v4.5 (cc9da6cc4f56, "ipv6: addrconf: use
stable address generator for ARPHRD_NONE"), which uses the random mode
instead of eui64 for such interfaces. However, the WireGuard driver sets
the address generation mode to none in wg_open(), i.e. every time the
interface is brought up, so the mode set by networkd before the interface
is brought up does not take effect. Setting the mode by sysctl after the
interface is up generates the address, as the kernel calls
addrconf_init_auto_addrs() on change since v4.11 (d35a00b8e33d).

So, let's not disable IPv6 link-local addressing for WireGuard interfaces
anymore, and set the address generation mode again every time an interface
is brought up, including when it is brought up by other programs, e.g.
"ip link set up". Now an IPv6LL address is generated on WireGuard
interfaces by default, as on other interfaces. LinkLocalAddressing=no
restores the previous behavior.

Fixes #44005.
2026-10-23 16:00:56 +02:00
Ivan Kruglov 541cff4516 NEWS: document Unit.SetProperties compatibility changes
Record the dedicated input type and the rejection of runtime=false,
including the implications for generated client bindings.
2026-10-23 10:29:58 +02:00
Yu Watanabe 55cf186454 network: show why a link is still in the configuring state (#44168)
Currently, the only way to find out why a link is stuck in the
configuring state is to enable debug logging and restart
systemd-networkd. This exposes the reason that link_check_ready()
already computes in the new ConfiguringReason field of the interface
description, and shows it in "networkctl status":

     State: routable (configuring)
     Configuring: static routes are not configured
           
Fixes #18222.
2026-10-23 08:39:54 +09:00
khromenokroman 0b69c340e0 networkctl: show why a link is still configuring
Currently, the only way to find out why a link is stuck in the configuring state is to enable debug
logging and restart systemd-networkd. Show the reason in "networkctl status" instead.

Fixes #18222.
2026-10-22 19:03:15 +02:00
khromenokroman ac9d0ce62e network: request DHCPv6 information again on "networkctl renew"
When the DHCPv6 client runs in the information request mode, the client is
stopped after a reply is received. Hence, previously "networkctl renew"
tried to restart the client. That did nothing when the client was started
by RA, and otherwise the new request was refused until the information
refresh time passed. Let's request the information again in that case.

As the hostname is not sent in the information request mode, a hostname
change does not trigger a new Information-Request message.
2026-10-22 09:21:18 +02:00
khromenokroman 9c6e9578ee network: renew DHCPv6 lease on "networkctl renew"
Previously, "networkctl renew" and the RenewLink() D-Bus and Varlink
methods renewed only the DHCPv4 lease. Let's renew the DHCPv6 lease too,
now that sd-dhcp6-client supports renewal on request. As with DHCPv4,
the client is restarted if it has been stopped, unless it is started by
RA.
2026-10-21 16:48:33 +09:00
khromenokroman a28fbedfe2 network: renew DHCPv6 lease when system hostname changes
Previously, the new hostname was sent to the DHCPv6 server only on the
next renewal. Now, the lease is renewed immediately, as is done for
DHCPv4. As with DHCPv4, this is skipped when the new hostname is the
one provided in the current lease, as the server already knows it.

Unlike DHCPv4, the hostname is set from the DHCPv6 lease on every
reply, including the ones for renewals. Hence, when the hostname is
set from the lease of this link, the renewal is also skipped if the
hostname was set from the lease of another link, otherwise two links
whose servers provide different hostnames would renew their leases
endlessly.

Also, skip updating the hostname when SendHostname=no, to avoid a
pointless renewal.
2026-10-21 16:48:33 +09:00
Martin Pitt 1592d554e5 condition: make ConditionCredential= also search the credential stores
Credentials can reach a system through the credential stores. E.g.
`systemd-imds --import` writes cloud provided credentials to
`/run/credstore/` in the initrd. `ImportCredential=` already finds them
there, but `ConditionCredential=` and `AssertCredential=` only checked
the credentials passed into the service manager itself. Hence, a
credential in the store could configure a service, but not decide
whether a unit runs.

Make the credential condition search the same credential store
directories as `ImportCredential=`. Only the existence of a credential
is checked, encrypted ones are not decrypted.

This is a behaviour change, see NEWS. As before, non-root callers which
can't access a credential store treat these as absent, otherwise this
would break running e.g. `systemd-analyze` or `sysetmd-sysupdate` as
user.

Signed-off-by: Martin Pitt <martin@amutable.com>
2026-10-20 06:55:47 +02:00
Paul Meyer a923ab5ed1 sd-varlink-idl: add SD_VARLINK_EXPERIMENTAL marker (#44287)
Allow marking Varlink interfaces, methods, errors, types, fields and
  enum values as experimental, for documentation and discoverability.
  Calls are not refused.

  - One SD_VARLINK_EXPERIMENTAL flag works for interface, symbol and
    field flags; _FULL variants of the definition macros take flags.
    Interface and symbol flags are now validated, and the internal
    _SD_VARLINK_*_FLAGS_MAX are replaced by _SD_VARLINK_*_FLAGS_ALL.
  - The IDL output shows experimental items with a "# [Experimental]"
comment line, and sd_varlink_idl_parse() turns it back into the flag,
    so the information survives GetInterfaceDescription.
- varlink_idl_consistent() refuses non-experimental fields referring to
    experimental types, non-nullable experimental fields in
    non-experimental symbols, and experimental values in
    non-experimental enums. These rules are deliberately strict, so that
    they can be relaxed later without breaking anything.
  - varlinkctl highlights experimental methods in list-methods and shows
    a notice when calling one (only when on a terminal).
  - docs/PORTABILITY_AND_STABILITY.md now excludes experimental parts of
    Varlink interfaces from the stability promise.
- Fixes resolution of named types within anonymous types in the parser,
    which the new checks ran into.
  - io.systemd.PCRLock is marked experimental as a first user; other
    experimental components will follow once the design is settled.

  Open questions:
  - Should experimental values in non-experimental enums be permitted in
    some cases, e.g. for enums only used as input?
- Should experimental errors be allowed in non-experimental interfaces?
    The IDL does not link errors to methods, so this is documentation
    only.
2026-10-19 15:27:04 +02:00
Zbigniew Jędrzejewski-Szmek 2d19c1734c sysext: IMAGE_ID, IMAGE_VERSION in extension-release (#43339)
Extend extension_release_validate() to optionally match IMAGE_ID and
IMAGE_VERSION fields from the extension's extension-release file against
the host's os-release if they are provided.

Previously, extensions could only be gated on ID + VERSION_ID (or
SYSEXT_LEVEL), which ties them to a distro release. IMAGE_ID and
IMAGE_VERSION allow extensions to target a specific image (e.g. a
specific appliance or OS image) rather than just the underlying distro.

These valuse are checked before checking the VERSION_ID so they can
return earlier (as they are more specific).

While it is possible to do this matching through SYSEXT_LEVEL I'd just
be putting those values into there concatenated. It feels nicer to be
able to match them separately if defined.

The two fields are checked independently of each other and act as
additional constraints on top of the existing validation thus they
should not affect the loading of pre-existing sysexts that do *not*
define these fields.

If they do define these fields then things will break when they are
loaded on systems where the host also defines IMAGE_ID/IMAGE_VERSION.
2026-10-19 11:41:30 +02:00
Paul Meyer 931a744b6c NEWS, docs: document SD_VARLINK_EXPERIMENTAL
Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-10-19 11:28:33 +02:00
Zbigniew Jędrzejewski-Szmek cca9a12e5c core,shared: support the ConfigurationDirectory= flags field over D-Bus (#43633) 2026-10-19 10:13:46 +02:00
Simon de Vlieger 70449239f8 sysext: IMAGE_ID, IMAGE_VERSION in extension-release
Extend extension_release_validate() to optionally match IMAGE_ID and
IMAGE_VERSION fields from the extension's extension-release file against
the host's os-release if they are provided.

Previously, extensions could only be gated on ID + VERSION_ID (or
SYSEXT_LEVEL), which ties them to a distro release. IMAGE_ID and
IMAGE_VERSION allow extensions to target a specific image (e.g. a
specific appliance or OS image) rather than just the underlying distro.

These valuse are checked before checking the VERSION_ID so they can
return earlier (as they are more specific).

While it is possible to do this matching through SYSEXT_LEVEL I'd just
be putting those values into there concatenated. It feels nicer to be
able to match them separately if defined.

The two fields are checked independently of each other and act as
additional constraints on top of the existing validation thus they
should not affect the loading of pre-existing sysexts that do *not*
define these fiels.

If they do define these fields then things will break when they are
loaded on systems where the host also defines IMAGE_ID/IMAGE_VERSION.
2026-10-19 08:38:42 +02:00
Lennart Poettering 80aefb24ef man,NEWS: document validatefs enforcement when mounting DDIs 2026-10-17 17:44:29 +02:00
umutcagand ef23a1e3fd core,shared: support ConfigurationDirectory flags over D-Bus
Expose ConfigurationDirectorySymlink with the same a(sst) signature as
the other directory types and route transient ConfigurationDirectory=
assignments through the tuple parser. Keep the name and signature for
uniform client handling; its destination is always empty because
configuration directories do not support symlinks.

Preserve the plain as property for ordinary names and empty assignments.
Emit empty arrays for resets of all five directory types, including the
four types whose client previously discarded empty assignments.

Show tuple flags in systemctl, retaining unknown 64-bit values numerically
for compatibility with newer managers. Document literal-colon escaping,
which now needs an extra level for transient ConfigurationDirectory=,
and the changed output/reset behavior in NEWS.

Read client messages back to check property names, signatures, fields and
empty arrays. Cover flags, raw D-Bus validation and reload/restart path
round-trips in TEST-23, resetting failed transient units during cleanup.

Fixes #43611.
2026-10-17 11:24:21 +03:00
umutcagand a03e1e3124 core: reject reserved private paths in directory tuples
The plain D-Bus directory properties and fragment parser already reject
private/ source paths. Apply the same restriction to tuple sources and
to symlink destinations in both the tuple handler and fragment parser.
Otherwise a destination can create the DynamicUser private boundary with
permissions that prevent later DynamicUser units from starting.

This narrows the four existing *DirectorySymlink interfaces as well as the
new configuration property. Record the compatibility change in NEWS and
exercise the rejected source and destination paths in both manager passes.
2026-10-17 11:24:21 +03:00
umutcagand 03ff2801dd core: preserve transient directory paths and empty destinations
Escape colon separators and double quotes in plain directory lists, and
escape whitespace and quotes in tuple assignments, before writing the
transient drop-in. Empty tuple destinations must serialize as empty
strings, never as (null).

Keep flag validation in the check pass and serialize only public flags.
Prepare the tuple handler for ConfigurationDirectory while rejecting its
unsupported symlink destination. Test both check and apply passes and
round-trip the resulting settings through the fragment parser.
2026-10-17 11:24:21 +03:00