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.
Currently with FileDescriptorStorePreserve=yes the FD store is kept
around
regardless of what happens to a unit, which is useful in many cases. But
in
some cases, for example when complex services crash horribly, it's hard
to
reason about what was in the intermediate state, and it's better to
start
fresh.
Add a new 'on-success' option for the FileDescriptorStorePreserve=
setting
that keeps it around only for as long as the unit doesn't go to a
persistently
failed state.
This is especially useful in combination with LUO, where we don't want
to
keep around LUO sessions created by units that then proceeded to crash
and
burn, and might be in a bad state afterwards.
Currently with FileDescriptorStorePreserve=yes the FD store is kept around
regardless of what happens to a unit, which is useful in many cases. But in
some cases, for example when complex services crash horribly, it's hard to
reason about what was in the intermediate state, and it's better to start
fresh.
Add a new 'on-success' option for the FileDescriptorStorePreserve= setting
that keeps it around only for as long as the unit doesn't go to a persistently
failed state.
This is especially useful in combination with LUO, where we don't want to
keep around LUO sessions created by units that then proceeded to crash and
burn, and might be in a bad state afterwards.
Drop the -fundamental suffix from src/fundamental/ headers in favor of names
that match their src/basic/ or src/shared/ counterparts (e.g.
macro-fundamental.h -> macro.h, assert-fundamental.h -> assert-util.h,
cleanup-fundamental.h -> cleanup-util.h). Rename src/basic/{btrfs,label}.{c,h}
to use the -util suffix to match the existing shared/btrfs-util and
shared/label-util siblings. Rename src/shared/mkdir-label.{c,h} to mkdir.{c,h}
and src/shared/tmpfile-util-label.{c,h} to tmpfile-util.{c,h} to match the
corresponding src/basic names.
This saves us from having to come up with separate names for files that do
the same thing across tiers, and it makes it easier to move stuff between
src/fundamental, src/basic and src/shared: consumers just #include "foo.h"
and pick up whichever tier their -I path resolves to first, so call sites
don't need to be updated when an API moves between layers.
Where a higher-tier wrapper exists (e.g. src/basic/macro.h wrapping
src/fundamental/macro.h), the wrapper uses an explicit "../fundamental/foo.h"
or "../basic/foo.h" relative include for the lower-tier header. We can't use
GCC's #include_next directive for this — when the wrapper is reachable both
via same-dir-as-source lookup and via -I (e.g. -Isrc/shared) for the
directory it lives in, #include_next advances by exactly one slot in libcpp's
internal directory chain and lands on the same physical directory it was
already in, never reaching the lower-tier sibling (see make_cpp_dir() in
gcc/libcpp/files.cc:1986).
To make sure the right headers are always picked up, the include directories
are reordered so that e.g. src/shared always takes priority over src/basic and
similar for the other directories.
Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
The current key sealing format has some less-than-ideal weaknesses:
- PBKDF2 with only 65k iterations, where recommendations are ~200k
- AES with null IV, relying on salt for uniqueness
- lack of AES MAC/AEAD
However improbable, it is at least theorically possible that with
a lot of resources an offline bruteforce could be attempted.
Add a v2 sealing format, keeping unsealing compatibility with
the current format:
v2:<iterations>:<salt>:<IV>:<ciphertext>:<aes tag>
and use 600k iterations for the PBKDF2 sha512
LUO sessions cannot be nested under other sessions. This means we need
to handle them explicitly, and held them open in the shutdown binary
like we do with our own internal session, to allow services to create
their own.
The requirement to support third party sessions comes from VMMs that
wish to preserve VM(s) state(s) across kexec, as some file descriptors
(KVM's vmfd from the KVM_CREATE_VM ioctl) cannot be transfered between
processes via SCM_RIGHTS, so they cannot be stashed in the FD Store
directly. Also some file descriptors have to be handled all together or
not at all, again to do with KVM and devices that are all part of the
same vm.
In order to allow FD Stores of user units to survive a user
session restart, propagate FDs received via the protocol up one
level from user to system manager via sd_notify.
And the other way around, propagate them down via LISTEN_FDS
tagging them with the unit name so that the child manager can
inject them in the appropriate unit.
Ensure units that are dead or not loaded can get FDs added to
their stores, and that they are correctly propagated once the
unit is started or loaded. When the unit is not loaded we don't
know what the FD max limit is, so simply increase it for each FD
injected, and then when the unit is realised prune it down to
match the unit's now available config in case the limit is lower
than the number of FDs in the store.
Each FD sent up or down is assigned a monotonic index, and the manager
also sends a JSON map that associates the index with the original
unit and FDNAME:
{
"unit-name.service": [
{ "name": "fdname1", "index": 1 },
{ "name": "fdname2", "index": 2 }
],
...
}
This allows the manager to assign back the FDs to the appropriate
unit using the appropriate name, given the FDNAMEs are not unique.
Add support for configuring cpuset partition type via the
CPUSetPartition= unit file setting. This controls the kernel's
cpuset.cpus.partition cgroup attribute.
The setting takes one of "member", "root", or "isolated". This is
useful for real-time workloads that require dedicated CPU resources
without interference from other processes.
When set, systemd will write the partition type to the
cpuset.cpus.partition cgroup file. If the kernel rejects the value
(e.g., due to partition hierarchy rules), a warning is logged and the
unit continues with the kernel's default partition type.
Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
UEFI firmware can report the currently-active keyboard layout via
EFI_HII_DATABASE_PROTOCOL.GetKeyboardLayout(). The layout descriptor
includes an RFC 4646 / BCP 47 language tag (e.g. "en-US"). Query this
from sd-boot/sd-stub and write it to a new LoaderKeyboardLayout EFI
variable, advertised through a new EFI_LOADER_FEATURE_KEYBOARD_LAYOUT
feature bit.
On the OS side, systemd-vconsole-setup reads the variable as a
lowest-priority fallback for the console keymap. To map the BCP 47
tag to a vconsole keymap we extend /usr/share/systemd/kbd-model-map
with an optional sixth column listing the comma-separated BCP 47 tags
each row covers; a new find_vconsole_keymap_for_bcp47() helper walks
the file, preferring an exact tag match and otherwise falling back to
the row whose tag matches the input's primary subtag. Credentials,
/etc/vconsole.conf, and vconsole.keymap= on the kernel command line
continue to take precedence.
bootctl status surfaces the new variable, printing the language tag
or "n/a (not reported by firmware)" when sd-boot advertises the
feature but the firmware HII database didn't expose a layout (common
on QEMU without a USB keyboard, since EDK2's PS/2 driver does not
register an HII keyboard layout).
Add options to vmspawn to configure journal-remote disk usage limits
when forwarding journal entries from the VM. These are passed through
as --max-use=, --keep-free=, --max-file-size=, and --max-files=
command-line arguments to systemd-journal-remote.
Add --max-use=, --keep-free=, --max-file-size=, and --max-files=
command-line options to systemd-journal-remote to allow overriding the
corresponding settings from the configuration file.
Add $SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE environment variable support
to systemd-journal-remote. When set, the specified file is used
instead of the default configuration file and drop-in directories.
When set to the empty string or /dev/null, configuration file parsing
is skipped entirely. vmspawn sets this to /dev/null in the child
process to avoid inheriting the host's journal-remote configuration.
Make fork_notify() argv parameter optional. When NULL is passed,
fork_notify() returns 0 in the child (with $NOTIFY_SOCKET set) and
lets the caller run custom code before exec. Returns 1 in the parent.
This allows vmspawn to set environment variables in the child without
polluting the parent process.
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Add guidance on when a field should use a proper varlink enum type
versus remaining a plain string: user-controlled/API fields should be
enums, engine-internal state fields may stay as strings.
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
With yeswehack.com suspended due to funding issues for triagers being
worked out, reports on GH are starting to pile up. Explicitly define
some ground rules to avoid noise and time wasting.
Fix two issues in WRITING_VM_AND_CONTAINER_MANAGERS.md:
1. The Host OS Integration section implied that -M switch and
machinectl shell/login work for VMs, but they currently only
work for containers. Add a note clarifying this limitation.
2. The Guest OS Integration section said "there's only one" VM
integration API (SMBIOS Product UUID), but VM_INTERFACE.md
documents five. Replace the outdated single-API description
with a reference to VM_INTERFACE.md listing all five.
Fixes#40935
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Markdown and HTML don't support mixing ordered and unordered items
within a single list. This means the previous syntax actually produced
three separate lists.
Also, markdown converters don't necesarrily respect the first number in
an ordered list, and may just overwrite it to one. This is the case for
the one that generates the systemd.io page. And even if that wasn't the
case, the numbering of the second ordered list would be off by one.
After analyzing all 218 CodeQL alerts across the project's history, the
workflow has not justified its CI cost:
- The most impactful query (PotentiallyDangerousFunction) was a custom
systemd-specific query that has already been replaced by clang-tidy's
bugprone-unsafe-functions check (6fb5ec3dd1).
- Of the remaining C++ queries, 6 never triggered at all
(bad-strncpy-size, unsafe-strcat, unsafe-strncat,
suspicious-pointer-scaling, suspicious-pointer-scaling-void,
inconsistent-null-check).
- Several high-value-sounding queries had extreme false positive rates:
toctou-race-condition (95% FP), use-after-free (88% FP),
cleartext-transmission (100% FP).
- Many queries that did trigger are already covered by compiler warnings
(-Wshadow, -Wformat, -Wunused-variable, -Wreturn-type,
-Wtautological-compare) or existing clang-tidy checks
(bugprone-sizeof-expression).
- Across all alerts, only 3 genuinely useful C++ fixes can be
attributed to CodeQL: 1 tainted-format-string, 2
incorrectly-checked-scanf. The rest were either false positives or
incidental fixes during refactoring that weren't prompted by CodeQL.
- The Python queries are largely superseded by ruff (already in CI) and
had an 89% false positive rate on the security-focused checks.
The workflow consumed significant CI resources (40+ minutes per run) and
the ongoing maintenance burden of triaging false positives outweighs the
marginal value of the 2-3 real findings it produced across its entire
lifetime.
Add a birthDate field to the JSON user record, stored internally as a
struct tm with INT_MIN/negative sentinels for unset fields. The field
is serialized as a YYYY-MM-DD string in JSON and validated via
parse_birth_date(), which shares its core logic with
parse_calendar_date() through a new parse_calendar_date_full()
function.
For birth dates, timegm() is called directly (rather than
mktime_or_timegm_usec) to support pre-epoch dates. The wday field is
used to distinguish timegm() failure from a valid (time_t) -1 return.
birthDate is excluded from user_record_self_modifiable_fields(), so
only administrators can set or change it via homectl. The field
remains in the regular (non-privileged) JSON section, keeping it
readable by the user and applications.
`max-width: 100%` keeps images from expanding beyond
their container and creating horizontal overflow scroll
on small screens.
`height: auto` ensures the image scales proportionally
when width is adjusted.