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>
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.
So far the constraints a file system declares about itself via the
user.validatefs.* xattrs were only checked by systemd-validatefs@.service
for file systems mounted via .mount units. Let's validate them also when
mounting partitions of DDIs, i.e. for systemd-dissect, systemd-nspawn,
portabled, sysext, RootImage= and mountfsd.
Any constraint that is set must be fulfilled in full: if a file system
declares GPT label or type constraints but isn't placed on a GPT
partition (for example because it is a bare file system image, or on an
MBR partition table) mounting is refused. If the partition is Verity
protected, the constraints must be fulfilled by the Verity hash
partition too, which matches the behaviour of systemd-validatefs.
Validation is done when the superblock is created, i.e. on the mountfsd
side if mountfsd is used. It can be turned off via
$SYSTEMD_DISSECT_VALIDATEFS=0. If validation fails, mounting returns
-EHWPOISON.
nss-systemd always passes USERDB_EXCLUDE_NSS to userdb. That flag skips
the io.systemd.NameServiceSwitch and io.systemd.Multiplexer services. A
sandbox can bind mount the host's userdb sockets into
/run/systemd/userdb/,
but nss-systemd in that sandbox still cannot resolve the host's regular
users and groups.
Add $SYSTEMD_NSS_SERVER_SIDE_NSS. If it is set, nss-systemd queries the
server-side NSS services too. Client-side NSS stays off, because
nss-systemd itself runs as a glibc NSS module.
nss-systemd always passes USERDB_EXCLUDE_NSS to userdb. That flag skips
the io.systemd.NameServiceSwitch and io.systemd.Multiplexer services. A
sandbox can bind mount the host's userdb sockets into /run/systemd/userdb/,
but nss-systemd in that sandbox still cannot resolve the host's regular
users and groups.
Add $SYSTEMD_NSS_SERVER_SIDE_NSS. If it is set, nss-systemd queries the
server-side NSS services too. Client-side NSS stays off, because
nss-systemd itself runs as a glibc NSS module.
The Trusted Computing Group has assigned the systemd project an NV index
range, let's document properly which one that is, and how we use it.
Thank you so much, TCG!
musl libc got a overhaul in it's locale support funded by NLnet,
as part the default search paths were set to search in
/etc/musl/locale, /usr/share/musl/locale, /share/musl/locale/.
Also change the behaviour on musl to search for multiple directories,
and include the old path as a fallback.
See https://www.openwall.com/lists/musl/2026/10/02/1
If the booted UKI carries no PCR public key or no signed policy with
the "initrd" reference, initialize NvPCRs in the initrd with a
PolicyPCR init policy bound to the current value of PCR 11 instead of
not initializing them at all.
The resulting NvPCR names depend on PCR 11 and hence change with every
UKI update, so this is only useful where PCR 11 can be predicted.
Signed-off-by: Paul Meyer <katexochen0@gmail.com>
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.
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.
The enablement has two levels:
- at compilation time, setting -Dexperimental=disabled disables the
features completely. The idea is that we can compile code out if
that is set. -Dexperimental=enabled disables the check completely.
-Dexperimental=envvar allows use if the variable is set.
-Dexperimental=auto is translated to envvar in developer mode, and
envvar otherwise.
- at runtime, if not disabled, the envvar is checked first
(always has priority over the compiled-in default), and then the
compile-time default is used.
If denied, the user gets an error message.
This is just meson and build.h plumbing + docs.
Code in the tree handles an error in an if (r < 0) block that returns, and
continues with the success path after it. CODING_STYLE.md does not mention
the pattern. Document it with an example.
Inspired by the text from https://github.com/jj-vcs/jj/blob/main/AI_POLICY.md.
- We add a section on not using LLM's to communicate.
- We relax the language on using AI to design/write code a bit. The main emphasis
is put on understanding what you're submitting. It's fine if AI designed it
and wrote it, but you as the author are supposed to fully understand and be able
to answer questions about it.
Let's maintain the list of subsystem maintainers
in CODEOWNERS, which github knows about and can do
useful stuff with like automatically adding those
usernames as reviewers.
When an administrator installed a custom keyring under /etc,
signature verification selected it instead of the vendor keyring
under /usr. That could make vendor-signed system updates stop
verifying after adding local keys.
Collect all readable default keyrings, including the current and
legacy /etc paths, and pass temporary copies to GnuPG with
separate --keyring options. Keep SYSTEMD_OPENPGP_KEYRING as an
explicit override, add regression coverage for keyring
coexistence, override behavior, and current-path signing, and
silence the expected shellcheck false positive in the test
cleanup helper.
Closes: #40702
NV indexes created in the storage hierarchy can be undefined and
redefined with TPM owner auth. Because of this, NvPCRs need some way
to prevent them from being redfined in a way that allows spoof
measurements to be replayed.
The current approach requires knowledge of a secret ("anchor secret")
in order to derive the initial NvPCR measurement and to derive a
measurement to an existing PCR (9). The credential is protected by the
TPM with a PCR policy. Without access to the credential, it's not
possible to replay measurements to a newly defined NvPCR without
breaking the binding with the measurement in PCR 9. However, this
approach has a couple of issues:
- The credential is currently only protected by PCR11. As it's not
protected by the rest of the boot chain, it's possible to boot other
operating systems in order to replay the PCR11 measurements and
recover the secret. Note that as the NvPCR anchoring happens in early
boot, the credential is stored in the ESP.
- Someone with privileged access to a system can just create a new
credential containing a known secret and store this in /var/lib and
the ESP. The NvPCRs are anchored with this known secret on subsequent
boots, and therefore the measurements can no longer be trusted.
Imagine the scenario where privileged access is theoretically possible
as a result of some vulnerability. After upgrading the system to fix
this vulnerability, the system should be able to attest that it is
now in a good state. However, if an adversary were able to use their
priviliges to replace the credential, they are able to obtain
persistence and the NvPCR measurements are no longer trustworthy.
This PR changes things to take a different approach. Instead of
requiring knowledge of a secret, the NvPCRs are now created in a way
that requires a policy to be satisfied for writing. The write policy has
2 branches:
- TPM2_PolicyNvWritten(true), which can be satisified without any
further authorization if the NvPCR has already been extended.
- TPM2_PolicyAuthorize(pcrPubKey, SHA256("nvpcr-init")) which can be
satisfied with a signed PCR policy, and must be used to perform the
initial extend to a NvPCR.
The intention here is that the signed PCR policy that can be used to
authorize the initial extend to the NvPCR can only be satisfied during
early boot. During later boot phases, this signed PCR policy must not be
valid. This means that if a NvPCR is undefined and redefined, it won't
be possible to satisfy its write policy in order to able to perform the
initial extend.
In order to anchor the NvPCRs and prevent them from being undefined and
then redefined with a different policy that does allow them to be
extended, the names of the NvPCRs are measured to PCR9. Verifiers must
check that the names of attested NvPCRs match the measurements in PCR9.
This uses the PCR signing key from the currently booted UKI to create
the NvPCRs. If this changes between boots, then tpm2-setup automatically
recreates new NvPCRs with an updated write policy to reflect this. I've
tried to be careful to not undefine arbitrary NV indexes in this case,
so it checks that the existing NV index looks like a NvPCR (ie, it has
the expected attributes) before undefining it.
I did originally try to preserve the old behaviour for existing systems,
but it makes things a lot more complicated. As the new implementation
already creates new NvPCRs when the PCR signing key changes, I ended up
just automatically upgrading the old NvPCRs as well. Again, I check here
that any existing NV index looks like an old style NvPCR (ie, it has the
expected attributes) before undefining it.
I did notice that the initial NvPCR measurement isn't going into the
log. I don't know if that was an intentional choice, but I've preserved
that behaviour in this PR.
This also adds a new option to ukify (--sign-initrd-pcrs) which creates
signed policies (one per PCR bank) that can only be satisfied from the
initrd. These policies are used for initializing the NvPCRs, but can also
be used for protecting TPM2 keyslots enrolled with systemd-cryptenroll
(by using the --tpm2-public-key-policyref=initrd option).
There is one outstanding issue. The NvPCR definitions support different
algorithms, but the use of PolicyAuthorize means that they can only support
SHA-256 for now. This is because the signed policy algorithm must match
the name algorithm, and some additional work is required to support
signed PCR policies for algorithms other than SHA256. I've left a note in
tpm2_nvpcr_initialize that details what's required, and I'll take a look
at that in a subsequent PR.
Removed note clarifying that portable services are only for system services and not user services, and changed comparisons to "system services" with just "services". With newer systemd versions, `systemd-portabled` can be run as a user service.
If the read-only FAT file attribute is set on /loader/random-seed,
don't update the seed file — and hence don't use it either, since a
seed we cannot update would be the same on every boot.
This gives users an explicit way to turn off random seed handling by
marking the file read-only, useful for example in pre-built OS images
that are replicated to many systems, where the baked-in seed is shared
and hence must not be credited.
The check is done upfront in process_random_seed(), before any other
work, mirroring the existing check for read-only volumes. This covers
both systemd-boot and systemd-stub, which share this code.
Inspired-by: #42979
When doing a userdb lookup we might end up issuing a lot of IPC calls in
parallel to backends and cause them all to do work, with us waiting for
it. Let's optimize this a bit, and indicate on the socket inodes via
xattrs hints which kind of records are provided by a backend. That way
we can suppress lookups to them and optimize runtime behaviour.
This only works on Linux 7.1 and newer where socket inodes gained
support for extended attributes.
This PR only sets the default to "relaxed" - I can change the default
to "tofu" if desired. But for that we will also need to update the NEWS
file to ensure everyone is aware of this new default.
---
This PR adds a new `systemd.credentials-boot=` kernel
commandline that allows to control if credentials with
a `null` key are accepted.
The possible options are:
* strict: always insist on tpm encryption
* tofu: allow null encryption in firstboot mode and when no tpm is
available
* relaxed: allow null encryption when sb is off, or no tpm is available
* off: allow null encryption always
The default is `relaxed` which is exactly the behavior we had before.
This replaces the initial idea of using plaintext credentials
at firstboot (thanks to Lennart for this nicer and simpler design).
---
With that we can drop `- firstboot: optionally accept credentials at
firstboot without authentication` from TODO.md
Adds dm-clone device setup at boot via a new /etc/clonetab config file,
following the crypttab/veritytab pattern.
- Add systemd-clonesetup-generator to parse /etc/clonetab and generate units.
- Add systemd-clonesetup binary to create/remove dm-clone devices via ioctl.
- Add clonesetup.target for ordering dm-clone activation at boot.
- Add region_size= option in clonetab to configure dm-clone hydration granularity.
- Add clonetab(5) and systemd-clonesetup-generator(8) man pages.
Fixes: https://github.com/systemd/systemd/issues/39500
This commit adds a new `systemd.credentials_boot_policy=` kernel
commandline that allows to control if credentials with
a `null` key are accepted.
The possible options are:
* strict: always insist on tpm encryption
* tofu: allow null encryption in firstboot mode and when no tpm is available
* relaxed: allow null encryption when sb is off, or no tpm is available
* off: allow null encryption always
The default is currently `relaxed` which is the same behavior
as before.
This replaces the initial idea of using plaintext credentials
at firstboot (thanks to Lennart for this nicer and simpler design).
Note that this also moves `in_first_boot()` to `basic/initrd-util`
which is a better fit now.
NVIndexes in TPMs can operate in two modes:
1. Backed by TPM RAM. In this case they are only written to NVRAM on an
orderly TPM shutdown when the system goes down. (TPMA_NV_ORDERLY flag
is on)
2. Backed by TPM NVRAM. In this case the nvindex value is written to NVRAM
on every write, and things are not delayed until orderly shutdown.
Normally mode 1 sounds like the obvious choice for NvPCRs, which reset
to zero anyway at boot. However, things are more complicated since
real-life TPMs tend to have a lot less RAM than NVRAM (both are
constrained but RAM even more than NVRAM). Hence there's value in using
NVRAM right-away. However, writing to NVRAM all the time means wearing
it out (since NVRAM is more vulnerable to that).
So far we unconditionally went for mode 1, but ran into space
constraints of RAM due to that.
Let's improve things a bit, and use orderly mode for NvPCRs we expect to
write many times, and non-orderly mode for those we expect to write only
a small, fixed number of times at boot, and not anymore during runtime.
Right now, this is only the "hardware" NvPCR, which measures hw identity
at boot.
Hopefully, this stretches available resources a bit further.
This also makes sure if the flag was set differently on allocation as
we'd set now, we accept it and won't complain, to make upgrades safe.
Suggested by Andreas Fuchs.
- import: Trust subkeys included in signature
With gpg sub keys one can rotate signing keys while having a stable
trust anchor. So far one still had to ship the sub key out of band but
a newer gpg has the option to include the sub key in the signature and
import it automatically. This is safe if we only allow importing a sub
key signed by the top key we already have in the key ring.
Add the --auto-key-import argument to gpg to import subkeys but also
set --import-options=merge-only,import-clean to restrict what we import
to only be sub keys signed by the top key we have in the keyring and
discard any irrelevant parts.
- import: Support env var to override gpg keyring
By default there is a fixed keyring in /usr or /etc. But when running
systemd-pull unprivileged in the user context or with a custom transfer
definition as in systemd-sysupdate --definitions=./... (e.g., for local
ParticleOS updates) it is limiting to require that all keys have to be
part of the OS keyring or otherwise no verification can be used. Also,
for testing it is valuable to point it at a different keyring.
Add a SYSTEMD_OPENPGP_KEYRING env var where the omission or empty
assignment sticks to the current behavior of the global OS keyrings but
a keyring path given will take precedence. While an env var can leak
down the process tree and is more difficult to secure for being the
trust anchor the advantage is that one can directly specify it in the
service unit as drop-in instead of having to patch the command
invocation. Anyway it's a niche use case and thus not part of the man
page.
As of GLib 2.90.0 (not yet released), GLib will fully support the spec,
including the `MEMORY_PRESSURE_WATCH` and `MEMORY_PRESSURE_WRITE`
environment variables, which it did not support previously.
See https://gitlab.gnome.org/GNOME/glib/-/merge_requests/5046
By default there is a fixed keyring in /usr or /etc. But when running
systemd-pull unprivileged in the user context or with a custom transfer
definition as in systemd-sysupdate --definitions=./... (e.g., for local
ParticleOS updates) it is limiting to require that all keys have to be
part of the OS keyring or otherwise no verification can be used. Also,
for testing it is valuable to point it at a different keyring.
Add a SYSTEMD_OPENPGP_KEYRING env var where the omission or empty
assignment sticks to the current behavior of the global OS keyrings but
a keyring path given will take precedence. While an env var can leak
down the process tree and is more difficult to secure for being the
trust anchor the advantage is that one can directly specify it in the
service unit as drop-in instead of having to patch the command
invocation. Anyway it's a niche use case and thus not part of the man
page.
Define a new io.systemd.SysUpdate.Notify Varlink interface with a single
OnCompletedUpdate() method, and after sysupdate successfully installs an
update, invoke that method on every socket linked into
/run/systemd/sysupdate/notify/ via varlink_execute_directory(). This
gives other components a hook to react to applied updates (e.g. recompute
a TPM policy, link a freshly downloaded kernel, refresh extensions).
The notification carries the component name, the installed version and the
list of updated resources (transfer id + on-disk path). Subscribers are
free to ignore the parameters and just treat the call as a trigger.
Setting SYSTEMD_SYSUPDATE_FORCE_NOTIFY=1 forces the notification to be sent
even when no update was applied (in which case no resource list is included),
so follow-up work can be triggered unconditionally.
Fixes: #35988
This commit adds support to /etc/hostname for substitution
of $ wordlists from {/etc,/run,/usr/lib}/systemd/hostname-wordlist.
The first $ will lookup hostname-wordlist/1, the next
hostname-wordlist/2 and so on.
With that we can do a petname [1] style hostname in systemd, e.g.
below a possible expansion for a hostname template:
$-$-$-???? -> wildly-happy-octopus-92a9
The substitution of words is stable (based on machine-id) but
not persisted, it is picked on every boot via a stable file
offset so the operation is cheap. But this means that if the
wordlist changes the hostname would change. The next commit
will add the pattern to the firstboot.hostname credential which
is persistet with the resolved names to avoid this issue.
This also includes a wordlist from the "petname" project
that can be optionally installed.
Thanks to Dustin Kirkland for this wonderful project.
[1] https://github.com/dustinkirkland/petname
The previous policy was primarily written from a standpoint
that AI models are not very good and we didn't wanna waste any
time reviewing PRs generated by AI. Now that AI models have become
actually good and their output is just as good as regular contributions,
let's stop requiring the disclosure as its pointless to still have it,
it doesn't really matter anymore whether a patch was written with or without
AI. It's up to the author to make sure they're not wasting our time by
submitting unreviewed, untested code upstream, regardless of whether that
code is written by an AI or not.
The new policy is inspired by https://github.com/lxc/incus/pull/3506, with
various removals to be less adverse to the usage of AI.
The measurement that systemd-pcrnvdone corresponds to
`src/pcrlock/pcrlock.d/770-nvpcr-separator.pcrlock`, and 770 is supposed to
happen in the initrd (which ends at 800).
The claude bot keeps getting this wrong again and again:
Claude: nit: systemd coding style requires braces on both branches of
an if/else when one branch uses them. Here the if branch
is a single statement without braces but the else branch
uses braces
Specifically mention this is not the case in the coding style doc
to hopefully make it stop hallucinating this rule
This allows multi-call binaries to be easily invoked with a different
name. After installation, the name is set by creating a symlink. But in
build directories, we don't create the symlinks. (There are also other
ways to achieve the same thing, e.g. zsh supports $ARGV0, and exec -a
can be used, but those are either non-portable or are more complicated
to use.) The primary use-case for me is to test --help output for
multicall binaries.
Also reorder the help for env vars to group the more generic ones near
the top.
This was initially proposed in https://github.com/systemd/systemd/pull/24054,
but there were some comments about the implementation. I had a branch
with the patch, but I don't think I ever actually submitted it as a
pull request.