1923 Commits
Author SHA1 Message Date
Martin Pitt cec6bc4ea6 imds: warn if net.ipv4.conf.all.rp_filter breaks locked mode
Make that caveat from the previous commit more obvious with a logged
warning, and document the workaround (or really, the fix) of disabling
`net.ipv4.conf.all.rp_filter` globally.

Signed-off-by: Martin Pitt <martin@amutable.com>
2026-11-03 12:05:59 +01:00
Luca Boccassi b6cdaf4a3d units: let systemd user/capsule manage its own CPU/I/O pressure handling
With CPU/I/O accounting enabled, inherited pressure parameters prevent the
user manager from setting its own threshold, producing:

systemd[807]: Failed to adjust io pressure threshold, ignoring: Device or resource busy

Use CPU/IOPressureWatch=skip, as for memory pressure, so the user manager
and capsule manager configure monitoring for their own init.scope.

Follow-up for 9d03637404
2026-11-01 00:58:10 +00: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
Zbigniew Jędrzejewski-Szmek 0a43fdc495 systemd-machine-tags.service: apply machine-tags.d/ configuration at boot
The service runs "hostnamectl tags --apply" before sysinit.target, so that
the tags declared in the machine-tags.d/ directories are in place before
most units are started and may be matched by ConditionMachineTag=. It is
skipped if no configuration exists.
2026-10-30 16:56:37 +01:00
Lennart Poettering 4d041137e9 factory-reset: add systemd-efivars-clear.service
This is a small service that hooks into factory-reset.target, similar to
systemd-tpm2-clear.service, and sets the LoaderFactoryResetVariables EFI
variable, asking systemd-boot to erase its EFI variables on the next
boot. Or in other words: a factory reset now resets the TPM *and* the EFI
variables owned by systemd.

The operation can be turned off via systemd.efivars_allow_clear= on the
kernel command line or $SYSTEMD_EFIVARS_ALLOW_CLEAR. With --graceful the
tool exits cleanly if the boot loader doesn't support the request.

"systemd-factory-reset cancel" now also withdraws the request, and
"bootctl remove" removes the variable too.
2026-10-29 19:53:35 +01:00
Christian Brauner b98913f4b8 keyring-setup: accept certificates via system credentials
Allow the use of credentials (SMBIOS, ESP credential drop-ins,
--set-credential for the boot loader) to ship keys to enroll into
keyrings. This lets users avoid having to ship keys as part of the
initrd.

So accept keyring-setup.<keyring>.<name> credentials holding one PEM
certificate each. We don't enroll them directly but follow the VOA spec
instead. Before enrolling they are placed into the ephemeral load path
as:

  /run/voa/<os>/kernel-keyring/<context>/x509/<name>-certificate.pem

This also makes masking, merging and deduplication apply to them like
to any other file. Only the certificate is written there, encoded anew,
so a private key that came along in the credential doesn't end up
world-readable. A credential that isn't a single certificate is bad
input.

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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-26 18:55:53 +01:00
Christian Brauner 24548be0bf keyring-setup: add systemd-keyring-setup
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 has slightly different semantics. It is always
active and is taken into account as soon as it contains a key.

Add systemd-keyring-setup which provisions the keyrings based on the
VOA spec from the kernel-keyring role with the keyring as the context:

  .dm-verity -> kernel-keyring/dm-verity/
  .fs-verity -> kernel-keyring/fs-verity/
  .bpf       -> kernel-keyring/bpf/

Trust anchors are read from the trust-anchor-kernel-keyring/
counterpart. Note that the "trust-anchor-*" prefix is mandated by the
spec.

The systemd-keyring-setup binary reads
<os>/kernel-keyring/<context>/x509/*-certificate.pem and the
trust-anchor-kernel-keyring/ counterpart from all VOA load paths and
enrolls the certificates into the corresponding keyring. Afterwards it
restricts the keyring to certificates signed by an enrolled key and
drops the SetAttr permission. Hence, neither the restriction nor the
mask can be changed anymore.

The binary may run in the initrd and on the host. A second run detects
a sealed keyring by its permission mask, skips certificates that are
enrolled already and enrolls certificates that are signed by an already
enrolled key.

Any unprovisioned keyrings are sealed at the end of a run even when
empty. Failing to enroll a certificate still causes the keyring to be
sealed. We can make this more flexible in the future but I don't think
leaving a keyring open just because enrolling a key failed makes sense.

Note that a file holding more than one certificate is refused.

Note that on a second call to add_key() for a certificate with the same
subject and key the kernel silently replaces the first. We can detect
this by reading the keyring's members before and after enrollment.

The binary exits with EX_DATAERR for input that can be fixed and
EXIT_FAILURE for kernel refusals. --dry-run only shows what would be
enrolled, optionally as JSON. The service isn't enabled by default and
the tool is marked experimental for now.

systemd-keyring-setup is written in Rust on top of the systemd_shared
crate. The VOA lookup, the keyring and X.509 helpers and everything
else it uses stay C in libsystemd-shared. All lookups are anchored at
the root directory which is opened once. The program, its unit and its
man page are only built with Rust support.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-26 18:55:53 +01:00
Luca Boccassi 7795f9c66d meson: add feature flag for veritysetup
veritysetup builds and installs binaries, docs, units, etc. Add a
feature flag to make it easier to avoid shipping it.
2026-10-25 09:34:00 +09:00
Luca Boccassi e2a70c2cec meson: add feature flag for validatefs
validatefs builds and installs binaries, docs, units, etc. Add a
feature flag to make it easier to avoid shipping it.
2026-10-25 09:34:00 +09:00
Luca Boccassi 48493bc9d3 meson: add feature flag for storage
storage builds and installs binaries, docs, units, etc. Add a
feature flag to make it easier to avoid shipping it.
2026-10-25 09:34:00 +09:00
Luca Boccassi 2ce176286a meson: add feature flag for sleep
sleep builds and installs binaries, docs, units, etc. Add a
feature flag to make it easier to avoid shipping it.
2026-10-25 09:34:00 +09:00
Luca Boccassi 25f505f0ec meson: add feature flag for integritysetup
integritysetup builds and installs binaries, docs, units, etc. Add a
feature flag to make it easier to avoid shipping it.
2026-10-25 09:34:00 +09:00
Luca Boccassi 5534e9ec66 meson: add feature flag for growfs
growfs builds and installs binaries, docs, units, etc. Add a
feature flag to make it easier to avoid shipping it.
2026-10-25 09:34:00 +09:00
Luca Boccassi c5018d9dbf meson: add feature flag for clonesetup
clonesetup builds and installs binaries, docs, units, etc. Add a
feature flag to make it easier to avoid shipping it.
2026-10-25 09:34:00 +09:00
Luca Boccassi 0bc7a465e7 meson: add feature flag for battery-check
battery-check builds and installs binaries, docs, units, etc. Add a
feature flag to make it easier to avoid shipping it.
2026-10-25 09:34:00 +09:00
Michael Vogt e7af4000ba units: add /run/varlink/registry/ symlink to systemd-report.socket
The symlink for the varlink registry in /run/varlink/registry is
missing for systemd-report.socket. This looks like an oversight
(per 382f141fea it seems to qualify) so add it so that
consumers like the varlink-http-bridge can expose it automatically.
2026-10-22 21:08:10 +02:00
Zbigniew Jędrzejewski-Szmek 3b0652d1e6 journald: use common_parse_argv()
Describe the program with COMMAND() and parse the command line with the
shared helper, which adds -h/--help, --version, and --introspect-cli. The
optional namespace argument is unchanged. Superfluous arguments are now
rejected with "Too many arguments." instead of "This program takes one or
no arguments.".

The template unit is adjusted to use '-- %i' to pass the namespace name.
I'm not convinced that using a namespace name starting with a dash is a
good idea, but using '--' to disambiguate seems good in any case, so let's
do that. We might want to adjust log_namespace_name_valid() separately.
2026-10-21 22:08:12 +02:00
Lennart Poettering 492a35af37 ptybroker: add new component that allocates pty on client request 2026-10-21 13:19:49 +02:00
Lennart Poettering db34d7f479 report: add a simple geoip source 2026-10-17 07:52:31 +02:00
Lennart Poettering e0c648653b report: add metrics source that reports disk health data
This adds systemd-report-diskhealth, which reports the key health
parameters of NVMe and ATA disks, plus basic identity information about
all disks.

For all hardware backed disks (including USB ones) size, logical and
physical sector size are reported from sysfs, and model, vendor and serial
from the udev database, preferring hwdb data, and falling back to the PCI
or USB device if the block device itself carries no model/vendor
information.

For NVMe disks the SMART / Health Information log page is read via the
NVMe admin command ioctl, and the critical warning bitmask, available
spare, percentage used and media error counter are reported. For ATA
disks with SMART enabled the overall SMART status is read via SG_IO ATA
pass-through, and for rotating ones also the reallocated, pending and
offline uncorrectable sector counts (SMART attributes 5, 197, 198), since
various SSD vendors reused these attributes with incompatible semantics.
Power-on hours and power cycle counts are reported for both
under a common metric name, for ATA disks taken from the standardized
Device Statistics log rather than from the vendor specific SMART
attributes. ATA disks in standby are not queried, in order not to spin
them up, and neither are ATA disks behind USB bridges.

The temperature is deliberately not reported, since systemd-report-hwmon
covers that already.
2026-10-16 16:10:23 +02:00
Sebastian Wick 05d309f852 varlink-monitord: Add BPF-based varlink traffic monitor daemon
Add systemd-varlink-monitord, a daemon that uses BPF LSM and fentry
hooks to capture varlink protocol traffic on AF_UNIX sockets. Sockets
are classified as varlink by reading the user.varlink xattr. The daemon
exposes a varlink API with Setup/Start/Stop methods and passes a ring
buffer fd to the client for zero-copy packet delivery. Unprivileged
callers can only see their own traffic; polkit authorization grants full
access.
2026-10-14 13:53:03 +02:00
Lennart Poettering d4a70ecc31 dissect-image: unlock LUKS partitions in DDIs via enrolled TPM2 tokens (#44132) 2026-10-14 09:36:29 +02:00
Paul Meyer 33776d483d tpm2-setup: treat NvPCR init outside initrd as error
systemd-tpm2-setup-early.service also runs after the switch to the
root file system if the initrd doesn't include it. NvPCRs can't be
initialized there, and so far we silently skipped them. With the PCR
policy fallback, this is the only remaining reason for NvPCRs not
being initialized, and it means the initrd is incomplete. Log an
error pointing to the missing service instead.

Signed-off-by: Paul Meyer <katexochen0@gmail.com>
2026-10-13 20:41:53 +02:00
Lennart Poettering 82e307483b cryptenroll-varlink: Allow access from all users (#43786)
Since the varlink interface for cryptenroll uses polkit it makes sense
to allow connections from all users not just root and user running the
server.
2026-10-13 16:09:28 +02:00
Adrian Reber 600fcc6dcb dissect,nspawn,sysext: enable TPM2 unlocking of DDIs, document it
Set DISSECT_IMAGE_TPM2_UNLOCK where unlocking an image's LUKS partitions
via the local TPM2 is wanted and the caller is trusted:

 * systemd-dissect, an admin tool operating on an image the invoking user
   can read anyway;
 * systemd-nspawn --image=, which already prompts for a passphrase
   interactivly, so TPM2 first and passphrase second is the natural
   order;
 * systemd-sysext/systemd-confext, which decrypt without any passphrase,
   so extension images sealed to the host can be activated without user
   interaction.

For sysext/confext setting the flag at merge time is not enough: before
merging, the extension-release metadata is read via image_read_metadata()
in discover-image.c, which uses its own flag set and would already fail
with -ENOKEY on an encrypted image. Give it an extra_flags parameter and
pass the flag from there, too; the other callers (machined, portabled)
pass 0 and keep their behaviour.

Two units need adjusting for this to work when started by the service
manager: systemd-nspawn@.service has DevicePolicy=closed, so allow
/dev/tpmrm0 there, and the sysext/confext services are not ordered
against the TPM2 device showing up, so order them after tpm2.target
like the pcr* services. The initrd variants cannot be ordered the same
way: they run before local-fs-pre.target, while the software TPM fallback
only becomes available after the early ESP mount, which is ordered after
local-fs-pre.target, so a static After=tpm2.target would be an ordering
cycle with systemd.tpm2_software_fallback=1. Hence systemd-tpm2-generator
adds the ordering as a drop-in instead, unless the fallback is in use.
That drop-in also orders them after systemd-pcrphase-initrd.service:
nothing else guarantees the "enter-initrd" phase has been measured into
PCR 11 before the initrd extension units merge their images (the barrier's
Before=systemd-sysext.service is a leftover from when that unit was also
used in the initrd), so tokens bound to a signed PCR 11 policy or a
pcrlock policy would race the measurement.

Deliberately not enabled for now: RootImage=/ExtensionImages=/MountImages=
in units, portabled and gpt-auto. For those an unconditional default is
probably not wanted but a per-unit or per-image switch, which is left for
a follow-up. systemd-mountfsd, which unprivileged systemd-dissect and
systemd-nspawn delegate to, keeps its own flag set and does not honour
the flag, hence TPM2 unlocking is only available when running privileged;
the man pages say so.

Document the behaviour and $SYSTEMD_DISSECT_TPM2_UNLOCK= in the dissect,
nspawn and sysext man pages and in docs/ENVIRONMENT.md.
2026-10-13 11:05:48 +00:00
Julian Sparber 069198a4f4 cryptenroll-varlink: allow access from all users
Since the varlink interface for cryptenroll uses polkit it makes sense
to allow connections from all users not just root and user running the
server.
2026-10-13 11:33:06 +02:00
Lennart Poettering af8916b105 units: run systemd-sysupdate-update.service with --loop=16
Let's make sure that updates that make further updates available are
fully applied in a single run of the automatic update service.
2026-10-13 09:34:28 +02:00
Chris Coulson e846bbad04 tpm2: Don't fail NvPCR initialization if there is no initial write policy available
Currently systemd-tpm2-setup will fail if NvPCR initialization doesn't
succeed, either because the currently booted UKI carries no PCR public
key, or it carries no appropriate signed policy (one with the "initrd"
policy reference). In addition to this, systemd-pcrextend will
subsequently fail because it also fails to initialize the NvPCR it is
requested to extend.

This makes systemd-tpm2-setup return EX_CONFIG in these scenarios, with
this marked as a successful exit status in the service unit file. In
addition to this, systemd-pcrextend is updated to only attempt NvPCR
initialization from the initrd (the only place it will succceed), and
when called with --graceful, to not exit with an error code if the
target NvPCR is uninitialized, and can't be initialized because of a
missing PCR public key or appropriate signed policy. When called via
Varlink, it will return the existing "NoSuchNvPCR" error.

Note that I also removed some NvPCR initialization specific exit status
codes from systemd-tpm2-setup.service.in, as these can only be returned
via systemd-tpm2-setup-early.service.

This should address the issues in
https://github.com/systemd/systemd/issues/43848.
2026-10-12 10:54:09 +02:00
Yu Watanabe 6d8af5c208 udev-watch: reimplement with fanotify
Replace the inotify + /run/udev/watch/ symlink machinery with fanotify inode
marks (FAN_CLOSE_WRITE). The point is not merely a different backend; fanotify
removes several structural costs that inotify imposed on udevd:

- An inotify event only identifies the watch by a watch descriptor, which udevd
  had to translate back to a device. That mapping was maintained as a table of
  symlinks under /run/udev/watch/. A fanotify event instead carries the watched
  inode's file handle (FAN_REPORT_FID), which is resolved to the device directly
  via open_by_handle_at()+fstat(). The wd->device translation, and with it
  /run/udev/watch/ entirely, is no longer needed.

- Without that shared symlink store to coordinate, a worker can add and remove
  its own device marks directly on the fanotify group fd. Previously the watch
  state was owned by the manager, and workers had to request (un)registration
  over the notification socket (sd_notify()+SIGUSR1). Dropping that round-trip
  both simplifies the code and is expected to be faster.

- A fanotify mark lives on the inode and is dropped by the kernel once the inode
  is gone, so the special handling for removed device nodes is no longer needed.

The group fd is handed over across restarts via the service manager's fd store
and the kernel keeps the marks, so nothing is rebuilt in-process; the fd is also
propagated to each worker. Watches left behind by an older, inotify-based udevd
under /run/udev/watch/ are migrated to fanotify marks on first start, then the
directory is removed.

Device watch falls back to inotify on kernels < v5.13, when CONFIG_SHMEM=n
(devtmpfs backed by ramfs), or when CONFIG_FANOTIFY is disabled. This fallback
can also be forced via $SYSTEMD_UDEV_USE_INOTIFY.
2026-10-12 04:04:37 +09:00
Lennart Poettering 5d7cb3878c logind: expose session and suspend metrics
logind now implements io.systemd.Metrics on
/run/systemd/report/io.systemd.Login, via the new
systemd-logind-metrics.socket, and reports:

- io.systemd.Login.SessionsStarted: counter of sessions started since
  boot, by session class (field class=)
- io.systemd.Login.CurrentSessions: gauge of current sessions, including
  those in closing state, by session class (field class=)
- io.systemd.Login.SuspendCounter: counter of suspend cycles, as reported
  by the kernel in /sys/power/suspend_stats/ (field result=success|fail)
2026-10-10 10:40:03 +02:00
Lennart Poettering f0f2bf15b8 journald: expose message counters as metrics
journald now implements io.systemd.Metrics on
/run/systemd/report/io.systemd.JournalDaemon, via the new
systemd-journald-metrics.socket, and reports the message counters as
io.systemd.JournalDaemon.MessagesByPriority (field priority=) and
io.systemd.JournalDaemon.MessagesByTransport (field transport=).

Metrics requests are processed at a lower event loop priority than
incoming log messages. For now only the main journald instance provides
metrics, not the namespace instances.

Like the audit socket, the metrics socket is not listed in Sockets= of
the service, so that it can be disabled.
2026-10-10 10:38:15 +02:00
Lennart Poettering 0c2366a159 report: Add network statistics metrics generator
One of the fundamental counters we need to cover in metrics are
packet/byte counters on network interfaces. Let's add this.

We do this separately from networkd, to not unnecessarily affect
networkd operations.
2026-10-09 07:55:32 +02:00
Lennart Poettering 4da00a0432 report: suppress some metrics of hw owned by the host on containers 2026-10-08 17:30:21 +02:00
Zbigniew Jędrzejewski-Szmek e008bd7781 meson: add config switch for systemd-report
This prog is still labelled as experimental and we didn't
have a way to disable its build. With the addition of this option,
all experimental components can be disabled.
2026-10-07 15:49:45 +02:00
Kai Lüke 7197b862b2 sysext: Order sysext unit after confext to provide guarantees
Up to now there was no clear ordering of confext and sysext units.
Normally, configuration should be able to influence the behavior of
services (config files or unit drop-ins), so it makes sense to order
confext before sysext. The mounting from the initrd for the final system
makes this less important together with the fact that live merges/
refreshes can encode unit restarts now. Yet I think it's better to know
what gets merged first and if wanted one can avoid encoding restarts if
one doesn't care about later live content changes. Where it matters more
for me is consistent behavior for the refresh check during boot for
non-verity images because they get compared by file handle and mount ID
which means whether the overlay was set up or not determines the
identity and decides if we do a refresh or not and this should not
vary by boot merely due to timing or other dependencies impacting the
ordering. (Note that one extension type shipping another extension type
is not the reason for the ordering and these cycles should be avoided
because they never free the mounts and backing loop devices.)
2026-10-02 10:27:56 +02:00
Lennart Poettering c25226b820 report: add metrics source that reports current disk space data 2026-09-25 16:43:13 +02:00
Lennart Poettering 186a8486fc report: add backend for reporting temperature sensors 2026-09-23 16:31:41 +02:00
Lennart Poettering 6a16394cd6 units: use --relax in systemd-loop@.service
Our primary usecase for this service is to deal with sector issues
around CDROMs. For that insisting on proper DDI layout and a root
partition is overkill. Let's hence relax the logic for this.

Fixes: #43605
2026-09-22 16:04:26 +02:00
Zbigniew Jędrzejewski-Szmek 9c665ee7aa units: allow disabling systemd-coredumpd.service
The new unit is nice, but it might not be appropriate in all scenarios. It
requires a new kernel, which might be known to not be available, and also
changes sysctl kernel.core_pattern at runtime, which might not be desired.
Replace static enablement by a normal [Install] section and add the unit
to presets to enable it by default.

The awkward instructions in the man page are simplified and adjusted.
2026-09-08 14:31:45 +09:00
Totoro 7fa7ff87cd sysupdate: Permit mount namespaces in Varlink service
The Varlink service enumerates image targets and needs a private mount
namespace while dissecting images. Keep its sandbox in sync with
systemd-sysupdated.service.

Follow-up to 31616d00ef

Fixes #43604
2026-09-05 08:22:45 +02:00
Arian van Putten c7837840f5 firstboot: apply hostname via hostnamed varlink API
Previously we just wrote /etc/hostname and returned. The kernel hostname
was left at whatever PID 1 sethostname()'d very early in boot (before
/etc/hostname existed), which meant hostnamectl reported the transient
hostname instead of the static one until the next reboot.

Call io.systemd.Hostname.SetStaticHostname on the running system so
hostnamed writes /etc/hostname and updates the kernel hostname in one
step. If hostnamed is unreachable (masked or absent on minimal images)
fall back to writing /etc/hostname directly. --root=/--image= also keeps
writing the file directly, since hostnamed only manages the running
system.
2026-08-28 06:29:58 +09:00
Luca Boccassi 2de150d7b7 Add experimental manpage marker, use it for sd-sysupdate and move it back to libexecdir (#43533) 2026-08-27 10:46:01 +01:00
Yu Watanabe 264ca8f827 coredump: support kernel coredump socket protocol
Since kernel v6.17, we can set a path to unix socket to core_pattern
(with request mode). See also:
https://lore.kernel.org/all/20250603-work-coredump-socket-protocol-v2-0-05a5f0c18ecc@kernel.org/

This introduces a tiny service that prepares and listens on a UNIX
socket to be passed to the kernel core pattern. When a process is
crashed, the kernel connects to the socket, and sends information
about the crashed process and passes coredump through the socket.
The service accepts the connection, and forks a worker process that
receives coredump from the kernel.
2026-08-27 05:52:49 +09:00
Luca Boccassi 35530fcf94 sysupdate: move back to libexecdir to mark as experimental
Fixes https://github.com/systemd/systemd/issues/43498
2026-08-26 19:51:31 +01:00
Luca Boccassi cd4379bb54 nsresourced: harden the transient UID/GID write restriction (#43196)
nsresourced hands out transient, recyclable UID/GID ranges to
unprivileged user namespaces, and a BPF-LSM policy is what keeps a
client from leaving inodes owned by its transient range on a file system
that outlives the namespace, so that a later client which gets the same
range can't inherit them.

The old policy decided based on the mount a write went through, which is
forgeable: unshare(CLONE_NEWNS) gives you clones of every mount with
fresh mount IDs but the same superblock, and the mount namespace's owner
is yours to pick. overlayfs sidesteps it entirely. So this reworks the
check to look at where the write actually lands on disk.

The first commit (Christian Brauner's) replaces the per-mount allowlist
with two questions answered from fields the client can't forge and that
survive mount cloning: does the file system die together with the user
namespace (sb->s_user_ns at or below the managed one), and does the
transient ID actually reach the disk, or does an idmapped mount
translate it into something else first. It walks the userns chain
upwards so a nested namespace can't escape it. The map layout changed,
so the pins move to userns-restrict.v2 and the old ones are removed on
install.

The second commit handles overlayfs. A write through an overlay creates
the file on the upper layer via vfs_create(), which only trips the inode
LSM hooks, not the path hooks we attach to, and the one path hook that
does fire sees the overlay mount, which belongs to the managed namespace
and so looks like it dies with it even though the upper may be
persistent. So we judge the upper at mount time in sb_kern_mount and
refuse a writable overlay unless its upper dies with the namespace too.
Reading overlayfs' private ovl_fs/ovl_layer uses CO-RE type flavors
guarded by bpf_core_type_exists(), and the service now orders after
modprobe@overlay.service.

The third commit deals with restarts. The managed set only lives in the
pinned maps, so an upgrade that changes the map layout comes up with
empty maps and every already-running namespace silently loses its
restriction. manager_startup() now re-adds the live registry entries to
the maps in the same pass that reaps the dead ones. The setgroups()
denial is restored from a flag we now store in the registry.

One consequence: a managed namespace can only mount a writable overlay
whose upper dies with it, e.g. a tmpfs it set up itself. If you need a
writable persistent upperdir (mkosi does), use a plain unprivileged user
namespace for that, not a transient nsresourced range.
2026-08-21 22:05:25 +01:00
Chris Coulson 768b6f21ec report: Add a TPM2 signing backend
This adds an initial TPM2 implementation of the io.systemd.Report.Signer
interface, which returns a set of signed TPM attestations that also
includes the supplied report digest.

The returned report consists of multiple components:
- A signed PCR quote generated with TPM2_Quote.
- Signed NvPCR attestations generated with TPM2_NV_Certify.
- A signed audit session attestation generated with
  TPM2_GetSessionAuditDigest.

Each signed report component includes the JSON encoded TPMS_ATTEST
attestation structure and corresponding signature, in both PEM encoded
form and as a JSON encoded TPMT_SIGNATURE structure.

NvPCR report components also include the readable name of the NvPCR, the
NV index public area as the JSON encoded TPMS_NV_PUBLIC structure, and some
additional authenticated data that is digested and supplied as the
qualifying data to the TPM2_NV_Certify command. This authenticated data
includes the NvPCR name and priority.

The report digest that is supplied via varlink is supplied as the
qualifying data to the audit session attestation.

The returned report also includes the event log which is retrieved from
pcrlock's varlink interface, and the public area of the signing key in
both PEM encoded form and as a JSON encoded TPMT_PUBLIC structure.

The audit session attestation is always the last component. The
preceding components are all executed with the audit session, and the
signer requires the audit session to remain exclusive whilst generating
the report. What this does is provide evidence to a verifier that the
overall report is a single, consistent snapshot of the system (ie, no
other TPM commands executed whilst generating the report) because the
audit session attestation contains a field that indicates that the
session was exclusive at the time it was attested.

The set of attested PCRs and NvPCRs are currently hardcoded. It includes
all firmware PCRs with the exception of PCR6, and all other PCRs with
the exception of the IMA one (which isn't useful on its own without the
IMA log). All defined NvPCRs are included.

A verifier can verify the signed attestation components individually by
verifying their signatures against the supplied attestation data, using
the supplied public key.

For the PCR report component, a verifier can replay the measurements in
the log in order to reconstruct the PCR values, and then use this and
the PCR selection from the attestation in order to calculate the PCR
digest, and then check that this matches the one in the attestation.

For the NvPCR report components, a verifier checks that the NV index
name in the attestation matches one that is anchored in PCR9 to be sure
that the attestation is for a valid NvPCR. A verifier can then replay
the measurements in the log to ensure that the resulting NvPCR value
matches the one supplied in the attestation. The authenticated data is a
JSON blob containing the NvPCR name and priority. A verifier can digest
this and ensure it matches the qualifying data in the attestation.

The audit session attestation includes the audit session digest and
whether the audit session was still exclusive when the attestation was
generated. A verifier should confirm exclusivity. The digest includes a
record of all TPM commands executed with the audit session, as each
command results in it being extended with the command parameter hash and
the result parameter hash. A verifier should replay the preceding report
components to compute an expected audit digest and make sure that this
digest matches the one in the attestation in order to be sure that the
overall report represents a single, consistent snapshot. The other
report components provide sufficient information for a verifier to
reconstruct the cpHash and rpHash.

When reconstructing the cpHash for the PCR and NvPCR components, the
signing scheme is the NULL scheme (they use the attestation key's
signing scheme). The required signing key public area is supplied in
the report. The supplied qualifying data is in the extra data field of
the attestation.

The PCR selection is also required to reconstruct the cpHash for the
PCR component. The PCR selection is available in the attestation.

The name of the NV index is also required to reconstruct the cpHash for
the NvPCR components. The name is available in the attestation.

The rpHash for each component can be reconstructed from the supplied
attestation blob and signature.

In the future, we might want to add more report component types. Eg, we
could add report components for ordinary NV indexes. Using the audit
session means that we could also add additional components that the TPM
can't attest directly, such as the values of interesting TPM properties
(as the TPM2_GetCapability calls will appear in the audit session digest).

The attestation signing key is stored as a JSON file in the report
signer's state directory. Different types of keys are supported:
- Ordinary keys that are protected by an existing persistent TPM key
  such as the SRK or EK.
- Persistent TPM keys.
- Primary keys, which are recreated using a template on use.

When an ordinary or primary key is used, the report signer saves the
object context in its runtime directory. This context is used on future
invocations. This has the benefit that where a primary key is used, it
doesn't need to be recreated on each use. When an ordinary key is used,
we don't need to run TPM2_Load, which requires authorization with the
parent key, on each This authorization currently requires a policy
session for low-range EKs.

Regardless of the key type, it must be a restricted signing key. If no
key exists, an ordinary key is generated automatically.

The attestation key can also have an associated credential which is
attached to the signed report. The credential can be issued by a CA or
some other party during onboarding, and is presented to a verifier as
proof that the attestation key is authentic. It is considered an opaque
blob in order to not require it to be a specific format, such as a X.509
certificate. I've called this a "voucher" though because "cred" or
"credential" has other meanings in systemd.

As a follow-up to this, I plan to add a varlink interface for creating
attestation keys that can be used for report signing.
2026-08-20 15:30:20 +02:00
Clayton Craft 6279910be7 timesyncd: order after systemd-resolved
Our first resolve happens way before sockets.target, and
systemd-resolved-varlink.socket has no ordering of its own, so there's
nothing making sure the socket is listening by then. Without this the
connect fails and we quietly fall back to getaddrinfo(), which is exactly
the case the varlink path is there for.

Order after both the service and the socket since either one is enough
on its own but neither covers every install situation. For example
resolved binds the socket itself when nothing hands it one, and on
systems where only the socket is enabled the service isn't started at
boot at all.
2026-08-15 13:12:50 -07:00
Clayton Craft 98e7cc36f0 timesyncd: resolve NTP hostnames through resolved's varlink interface
With DNSSEC on, resolved can't validate anything until the clock is
right, and without an RTC the only thing that fixes the clock is talking
to the NTP servers we're trying to resolve. On glibc the timesyncd unit
sets SYSTEMD_NSS_RESOLVE_VALIDATE=0 and nss-resolve turns that into a
per-query flag, which breaks the chicken/egg. musl (and other systems
not using nss-resolve) have no NSS, so nothing reads it there and we're
stuck.

So talk to resolved directly when it's there, and pass
SD_RESOLVED_NO_VALIDATE until we've synced the clock ourselves.
NTP-related lookups after that are validated normally, which is better
than what we get from nss. If resolved isn't around we just keep using
getaddrinfo().

The reply handler duplicates the tail of the getaddrinfo handler, since
the two differ in how they report failures. Varlink discards what the
callback returns, so it has to move on to the next host itself.

This also drops SYSTEMD_NSS_RESOLVE_VALIDATE=0 from the unit because
nss-resolve talks to resolved over the same socket we do, so the
getaddrinfo() path is only ever taken when resolved is unreachable, and
then nothing validates anyway.
2026-08-15 13:12:50 -07:00
Daan De Meyer e50ec930c2 nsresourced: refuse writable overlayfs whose upper outlives the namespace
A write through an overlay mount does not create the object on the overlay
itself: overlayfs creates it on the upper layer via vfs_create(), which
reaches only the *inode* LSM hooks, not the *path* hooks the transient
range policy attaches to. The one path hook that does fire sees the overlay
mount, whose superblock belongs to the (managed) mounting namespace and so
looks like it dies with it -- while the upper may be a persistent file
system on which the transient IDs then persist.

  Regular file system -- caught by the path hook:

    openat(dir_on_persistent_fs, "f", O_CREAT)
      path_openat -> ... -> security_path_mknod(path{mnt = persistent mount})   << path hook (attached)
        userns_restrict_path_mknod -> validate_mount()
          sb->s_user_ns = init   -> not below managed   (outlives the ns)
          mnt_idmap does not remap the transient id      (reaches disk)
        => -EPERM                                        BLOCKED

  Overlay write -- slips past the path hook (this is the hole):

    openat(overlay_mnt/"f", O_CREAT)
      path_openat -> ... -> security_path_mknod(path{mnt = OVERLAY mount})       << path hook (attached)
        validate_mount(): overlay sb->s_user_ns == managed
        => "dies with the namespace"                     ALLOW
      ovl_create_or_link -> vfs_create(UPPER dentry)
        security_inode_create(UPPER inode)               << inode hook (NOT attached)
        => no check
      inode created on the UPPER fs, owned by the transient uid   PERSISTS

Judge the upper at mount time instead, in lsm/sb_kern_mount:

    mount("overlay", ..., upperdir=<X>, workdir=<Y>)
      do_new_mount -> vfs_get_tree
        ovl_fill_super
          stamps work/index onto the upper via vfs_mkdir, owned by the
          transient mounter -> security_inode_mkdir only (not a path hook)
        security_sb_kern_mount(sb)                        << hook (attached)
          userns_restrict_sb_kern_mount:
            sb->s_magic != OVERLAYFS_SUPER_MAGIC       -> return 0   (not an overlay)
            find_managed_userns(sb->s_user_ns) == none -> return 0   (unmanaged mounter)
            upper = ((struct ovl_fs*)sb->s_fs_info)->layers[0].mnt
            upper == NULL                              -> return 0   (read-only, no upper)
            fs_userns = upper->mnt_sb->s_user_ns
              upper dies with the managed ns (e.g. a tmpfs)         => return 0  ALLOW
              upper persistent, idmap records no transient id       => return 0  ALLOW
              upper persistent, an idmap extent records a transient => -EPERM   REFUSE

The upper has to be judged at mount time because overlayfs performs the
actual upper creations (both the mount-time work/index dirs and every later
copy-up) with internal vfs_create()/vfs_mkdir() calls that reach only the
inode hooks, on the upper's own superblock, where neither the overlay's
ovl_fs nor the mount idmapping is available. Refusing the mount stops every
such write. It cannot undo the work/index dirs ovl_fill_super() already
stamped onto the upper before this hook runs. Reaching those takes an
upperdir and a workdir that already exist on the same persistent,
non-idmapped file system and that the transient IDs may write to: the client
cannot create them itself, since that is what the path hooks deny.

The idmap check walks the upper mount's uid_map and gid_map and permits the
overlay only when no extent records a transient id on disk (ids the idmap
does not map fail the write, so they are fine too). This is what lets a
client back a writable overlay with a persistent but id-mapped upper, e.g.
one handed out by mountfsd, while a plain persistent upper stays refused.

The check keys on whether a writable upper exists (layers[0].mnt is
non-NULL), which is fixed for the superblock's lifetime, not on the mutable
SB_RDONLY flag: an overlay mounted read-only but with an upper can be
remounted read-write later without re-entering sb_kern_mount (remount uses a
different LSM hook), so it must be judged now. A genuinely read-only overlay
has no upper and is left alone. Reading the upper layer needs overlayfs'
private struct ovl_fs/ovl_layer, which are absent from vmlinux.h when
overlayfs is built as a module; they are declared as CO-RE type flavors and
guarded by bpf_core_type_exists(), and the service now orders itself after
modprobe@overlay.service so the module's BTF is present by the time the
program is loaded.

A managed (nsresourced-provisioned) user namespace can therefore only mount
a writable overlay whose upper dies with it (e.g. a tmpfs it set up
itself). Callers that need a writable, persistent (non-tmpfs) upperdir --
mkosi, for one -- should use a regular unprivileged user namespace for that
rather than a transient nsresourced range, since such an overlay is refused
for a managed namespace by design.

test-userns-restrict now mounts real overlays: a read-only overlay, a
writable overlay with a tmpfs upper, and a writable overlay whose upper is
an init-owned but idmapped mount are all allowed, while a writable overlay
with a plain init-owned upper is refused with EPERM. The kernel permits that
last mount without the LSM loaded, so the EPERM is the policy's doing.
2026-08-12 10:49:34 +02:00