We currently blindly send the configured reload signal (e.g. SIGHUP or
SIGUSR1) to the main service PID for Type=notify-reload units, even if
the service hasn't installed a userspace handler. This can lead to
invoking unintended default behavior (typically process termination) in
daemons which later deprecate and remove their reload handler.
This is a real problem we have seen in production on multiple occasions.
In one particularly egregious instance, a production distributed storage
service had a large percentage of its nodes all terminate at once when
sent a reload signal. In this case a signal handler had been removed,
but another place still sending the signal was missed.
To mitigate this, introduce two new checks for Type=notify-reload
services:
1. On READY=1: When the service first sends READY=1 during initial
startup, we check for the handler. If it's missing, the service startup
is failed with SERVICE_FAILURE_PROTOCOL. This enforces the contract that
services *must* install their handler before signalling readiness.
2. On reload: Immediately before sending the reload signal, we check
again. If the handler is now missing, we log a warning but still send
the signal. This preserves the requested operator action while making
the missing handler visible.
The startup check catches definite static misconfiguration before the
service is accepted as ready. The reload check is advisory because the
service has already started successfully, but warns if its handler later
disappears. Together these provide a best-effort safety net while
preserving the agreed reload semantics.
Add a new integration test to verify the signal handler validation for
Type=notify-reload services, as introduced in the previous commit.
The test adds a shell harness and four service files to cover all scenarios:
1. `notify-reload-no-handler.service`: Verifies that the service fails to
start with Result=protocol when the handler is missing at the first
READY=1.
2. `notify-reload-sigstop.service`: Verifies that SIGSTOP is exempt from
handler validation because it cannot be caught or blocked.
3. `notify-reload-toggle-handler.service`: Verifies that removing the
handler at runtime results in a warning on reload, but the signal is still
sent, causing the service to terminate from unhandled SIGHUP.
4. `notify-reload-well-behaved.service`: Verifies the happy path where a
service with a handler starts and reloads successfully.
Use bounded FIFO operations and journal cursors so failures are prompt and log
assertions cannot race a relative time window.
- --strict=/--full moved to the OPTION_LONG() parser framework that
replaced getopt in the meantime
- the is-supported commit is dropped, main grew that verb independently.
That also gets rid of the early tpm2_support() gate that broke CI back
then, and TEST-70-TPM2 runs with a software TPM these days anyway
- special_glyph() → glyph() renames
- the log table PCR filter needs an EVENT_LOG_RECORD_IS_PCR() guard now
that the event log has NV-index records
Combining --ephemeral with --grow-image= truncated the file passed to
--image= to the requested size before the VM was started, so a mode whose
entire purpose is to leave the original image untouched modified it on
disk.
Skip growing the original image when running ephemeral, and instead create
the qcow2 overlay with the requested virtual size, so the guest sees the
larger disk while the base image stays read-only. As only the overlay is
sized in that case, the base image format no longer matters, so allow
--grow-image= together with qcow2 images when running ephemeral. The round
up to a multiple of 4096 moves to option parsing so both paths apply the
same value.
The vmspawn drive test grows an ephemeral VM and checks that the image
passed to --image= keeps its size, and that the overlay QEMU runs off is
created at the requested one.
group_record_match() handled the other filters, but ignored match->uuid,
so userdbctl group --uuid=... could still return non-matching groups.
Add the same UUID check used by user_record_match().
Reproduced locally:
build/userdbctl --uuid=aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa \
--output=json group root
Before result:
The command succeeded and printed the root group record.
Follow-up for: 466562c69b
systemd-sysusers currently skips entries for groups that already exists.
However, if a group exists only in /etc/gshadow and not in /etc/group,
systemd-sysusers panics:
~# groupdel sgx
~# systemd-sysusers; echo $?
Creating group 'sgx' with GID 106.
0
~# systemd-sysusers; echo $?
0
~# sed -i '/sgx/d' /etc/group
~# systemd-sysusers; echo $?
Creating group 'sgx' with GID 106.
/etc/gshadow: Group "sgx" already exists.
1
To fix this, let's mirror the logic from the user path
(write_temporary_shadow()) where we simply drop the user from the "todo"
queue if it already exists in /etc/shadow instead of throwing an error.
The openpgp and tlsa verbs rejected --json= even though they
already determine the RR type they query. Allow JSON output for
these shortcut commands.
Accept matching explicit --type= values as well, but reject
mismatched types so openpgp only queries OPENPGPKEY records and
tlsa only queries TLSA records.
Refuse unauthenticated key records in JSON format so scripts do
not accidentally consume cryptographic DNS data that would only
produce a warning in human-readable output.
Fixes#43245
Introduce Slice.ActivatingConcurrencyMax to limit how many units
within a slice hierarchy may be in activating state concurrently.
Expose the setting over D-Bus, support transient/property parsing,
enforce it during unit start dispatch, and re-check queued starts when
units leave activating state.
Document the new slice option and add a PID1 concurrency test covering
queued startup behavior.
PNP devices may represent ACPI-enumerated hardware but do not have a
parent type supported by path_id. Consequently, importing path_id fails
even when the PNP device exposes a stable ACPI firmware_node. This also
prevents later assignments in rules such as the systemd-backlight
activation rule from taking effect.
Resolve the PNP device's firmware_node and use its ACPI sysname for the
path component. This gives PNP-backed devices the same stable identity
as their firmware representation.
Add a regression test using an RTC device below a PNP parent with an
ACPI firmware node.
Implement the TODO item for `udevadm info -q symlink`: keep the
default space-separated output pager-free, and make `--value` print
one symlink per line with an empty separator line between devices.
The RETURN traps in the browse helpers only stopped the transient
varlinkctl unit; the mktemp'd output/error/scratch files were never
removed and leaked on every invocation. Remove them from the same trap,
after the unit has been stopped so that nothing is still writing to
them, and make that stop best-effort like in
testcase_browse_ifindex_zero_no_flap: if varlinkctl exited on its own,
the transient unit is already gone, and a failing stop would otherwise
abort the testcase under errexit and skip the removal.
testcase_browse_ifindex_zero_no_flap cleans up its output file from the
trap it already arms for the dummy link, which is an EXIT trap since
run_testcases runs each testcase in its own subshell.
While at it, tidy up the helpers' variable scoping: error_file was
accidentally a global, and i/svc were declared in the wrong functions
(they are used by check_both/check_first, via dynamic scoping).
This updates systemd-tmpfiles in two small areas:
- Reject non-empty argument fields for tmpfiles.d line types that do not
consume the argument field, instead of warning and silently ignoring
them.
- Let `r` and `R` tmpfiles.d entries honor the `Age` field when
`systemd-tmpfiles --clean` is used. The existing `--remove` behavior
remains unconditional.
The completed TODO entries are removed, and NEWS/man page documentation
is updated for the visible behavior changes.
Quote each serialized exec directory entry, and use extract flags
compatible with config_parse_exec_directories() when deserializing.
This allows paths containing spaces and escaped characters to round-trip
correctly.
Fixes#41853.
Replaces #42686.
Retry units that still exist and clear stale state for units removed by
a downgrade, as packages might be old and not have new units that were
added in the latest version.
Let r and R lines participate in --clean when they specify an age. The
target itself is removed only after its selected file or directory
timestamps have aged enough; --remove remains unconditional.
Follow-up for: beca6b6e6b
Since 1166f4472d the API bus setup
and the subscriber coldplug happen only once the asynchronous GetId
reply is processed by the event loop. After a daemon-reexec,
manager_dispatch_dbus_queue() runs before subscribers were registered
and consumed send_reloading_done, so the one-shot Reloading(false)
signal was never sent.
Clients that wait for this signal to detect that a reexec finished time out.
Track the pending setup and hold the flag until the reply handler has
re-added the subscriptions.
Also affects v261.2 via backport 26f3717e27f6f08347adf851c605d5fe1f52ac57.
Check that the manager broadcasts Reloading(true/false) on the API bus
for daemon-reload, and the one-shot Reloading(false) after a
daemon-reexec, which requires the subscribers of the previous instance
to be coldplugged before the D-Bus queue is dispatched.
Line types which do not use the argument field used to warn and ignore
a non-empty field. Treat that as invalid configuration instead, so typos
are not silently accepted.
Follow-up for: 614cc34f3a
Motivated by
https://github.com/systemd/systemd/pull/43041#discussion_r3595022610.
Switch systemd-cryptsetup's volume-key and keyslot measurements from
driving the TPM directly (tpm2-util) to the io.systemd.PCRExtend Varlink
service, aligning it with how the verity and imds measurements already
work.
Some notes on decisions taken:
- The volume key is sent over the wire. systemd-pcrextend will do the
hmac. The socket is root only. Otherwise we would need to do bank
negotiation via varlink and pollute the interface with it.
- `tpm2-measure-bank=` deprecated/dropped. Same reason as above.
- `tpm2-device=` now only affects unlocking. The device for measurements
is selected by pcrextend.
- Measuring requires the presence of `systemd-pcrextend.socket` in the
initrd, should be already given as systemd-veritysetup relies on it,
too.
- Logs are done on the pcrextend side.
Accept -n as a short option for --dry-run in bootctl,
systemd-oomd, systemd-sysusers, and systemd-tmpfiles.
For systemd-repart, make -n equivalent to --dry-run=yes,
while keeping --dry-run=BOOL available.
Follow-up for: 2479f0bb09
Measure the volume key and unlock keyslot through io.systemd.PCRExtend
instead of driving the TPM directly via tpm2-util, matching how the
verity and imds measurements already work.
Bank selection and the TPM context now live entirely in
systemd-pcrextend. As a result the tpm2-measure-bank= crypttab option
can no longer be honored per volume and is now a deprecated no-op.
Signed-off-by: Paul Meyer <katexochen0@gmail.com>
- sysupdate: In the auto-enable service, don't enable all features
The auto-enable service should activate suggested components and
features but enabled all features which includes the default components
unsuggested features and any unsuggested features of the suggested
components. This is unexpected behavior and we rather want this service
to be limited to suggested features.
Switch the service flag to suggested and make the wording more explicit
in the man page. Also fix the wrong statement that it operates on
enabled components, it operates on all components, also explicitly
disabled ones.
- sysupdate: Change disabling with
--component-suggested/--feature-suggested
The disabling of features or components with the flag
--component-suggested/--feature-suggested didn't disable the suggested
ones but instead disabled all other ones. This is rather unintuitive due
to how the flags are named and also not really needed because the
intended reconciliation outcome can instead be done by first disabling
everything and then enabling the suggested ones again which is easier to
reason about. For components the tricky part is that they default to
enabled and thus it's better to have the disable/enable commands with
--component-suggested operate only on suggested ones instead of touching
others like "legacy" components that don't explicity say whether they
are enabled and suggested or not.
Make running disablement of components/features with
--component-suggested/--feature-suggested undo a previous enablement
with the same flags. Document how one can align the system to only use
suggested components/features and not anything else by doing it in two
steps, first disabling everything and then enabling suggested ones. This
also makes it clearer now that all components that are not explicitly
enabled nor suggested will be disabled then.
The disabling of features or components with the flag
--component-suggested/--feature-suggested didn't disable the suggested
ones but instead disabled all other ones. This is rather unintuitive due
to how the flags are named and also not really needed because the
intended reconciliation outcome can instead be done by first disabling
everything and then enabling the suggested ones again which is easier to
reason about. For components the tricky part is that they default to
enabled and thus it's better to have the disable/enable commands with
--component-suggested operate only on suggested ones instead of touching
others like "legacy" components that don't explicity say whether they
are enabled and suggested or not.
Make running disablement of components/features with
--component-suggested/--feature-suggested undo a previous enablement
with the same flags. Document how one can align the system to only use
suggested components/features and not anything else by doing it in two
steps, first disabling everything and then enabling suggested ones. This
also makes it clearer now that all components that are not explicitly
enabled nor suggested will be disabled then.
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.
--component-all is documented to include the default component-less
installation. Do not drop it merely because the context operates on a
root/image, or because all its transfers are currently disabled by
features.
This lets --component-all --feature-all enable-feature write the
default component feature drop-ins instead of succeeding with no
components selected.
TEST-72-SYSUPDATE covers both all transfers disabled by features and
the same default component feature operation under --root=.
Repro: create a default feata.feature plus a transfer gated by feata,
then run:
build/systemd-sysupdate --root="$root" --component-all --feature-all enable-feature
Before: no drop-in was written.
Follow-up for: 4481661a75
When recording installdb entries under --root=, keep the leading slash
after stripping the root. Compare current transfer target paths in the
same root-relative form during cleanup.
This prevents cleanup from treating still-owned resources below --root=
as orphaned.
TEST-72-SYSUPDATE covers --root= cleanup keeping a still-owned file and
its matching installdb entry.
Repro: create a rooted transfer for /target/foo-@v.bin, add a
matching installdb entry for /target/./foo-@v.bin, then run:
build/systemd-sysupdate --root="$root" --verify=no cleanup
Before: foo-1.bin and the installdb entry were removed.
Follow-up for: d82e256bb9
Repeats the enable/disable specifier-expansion check with 'systemctl
preset' instead. preset-all is intentionally not exercised here, since
$root accumulates unit files from earlier sections that are
deliberately invalid, and preset-all would trip on those unrelated
units.
Definitions enumerated under --root= are already rooted. Passing those
paths to the config parsers with the same root prefixes the root again,
so feature and transfer files are parsed from the wrong path.
Repro: create root/etc/sysupdate.d/rootfeat.feature and
01-root.transfer, then run:
build/systemd-sysupdate --root="$root" --verify=no --offline features rootfeat
Before: parsing failed at line 1 with a bogus Source Type= error.
Fixes#42783.
Follow-up for: e1384cfb09
systemd-escape currently only processes strings passed as
command line arguments. This is awkward for callers that already
have a generated list of strings, because they need to loop around
the tool or use xargs and carefully preserve whitespace and other
special characters.
Add --stdin to read one string per line from standard input and
write one escaped result per output line. Keep command line strings
mutually exclusive with --stdin so the input source remains
unambiguous.
Use an explicit option instead of treating '-' specially, since '-'
is itself a valid string to escape. The existing escape, unescape,
mangle, path, suffix, and template rules are reused unchanged.
The io.systemd.Unit context exposes CollectMode, but StartTransient
rejects it as unsupported. This prevents Varlink clients from selecting
whether failed transient units should be garbage-collected.
Accept CollectMode when creating transient units and persist the setting
in the runtime unit configuration. Extend the integration test to verify
the value in both the Varlink response and the resulting unit.
X entries inherit the cleanup age from the closest parent directory rule.
Inherit the age-by fields too, so parent rules such as m:1d keep their
full cleanup policy.
Reproducer:
mkdir -p /tmp/tmpfiles-ageby/parent/{child,other}
printf old >/tmp/tmpfiles-ageby/parent/child/file
printf old >/tmp/tmpfiles-ageby/parent/other/file
touch -d '3 days ago' /tmp/tmpfiles-ageby/parent/{child,other}/file
systemd-tmpfiles --clean - <<'EOT'
d /tmp/tmpfiles-ageby/parent - - - m:1d
X /tmp/tmpfiles-ageby/parent/child - - - -
EOT
Before:
child/file remained because X used the default age-by set.
other/file was removed by the parent m: rule.
Follow-up for 7f7a50dd15.
Resolve /etc/machine-id before checking the mount state and before
writing back, so symlinked targets are committed correctly.
Fixes: #43104
Follow-up for: efabf4e044
PrivateIPC= sets up a private mqueue mount for the service. When
CONFIG_POSIX_MQUEUE is disabled, mounting mqueue fails with ENODEV and
prevents the service from starting.
Treat ENODEV as an unavailable optional kernel feature while preserving
the private IPC namespace. Keep all other mount errors fatal.
Add coverage verifying that PrivateIPC= still creates a separate IPC
namespace when POSIX message queues are unavailable.
Fixes#42582
The option is generalized from a simple boolean switch that enables
a fixed list to a list-of-patterns.
The old value works, but is deprecated: -Dstandalone-binaries=true is translated
to -Dstandalone-binaries=repart,report,tmpfiles,sysusers,shutdown.
We could already build all normal executables as .standalone, but the
installation only supported a small fixes subset. This wasn't flexible
enough:
- packagers might want to provide additional standalone binaries then the
small subset that was already enabled
- but at the same time, whenever new binaries were added to this subset,
downstream packaging had to be adjusted in sync, at least in the case of
Fedora, because otherwise we'd get a complaint about unexpected .standalone
binaries in the temporary install root.
With the new option, downstreams can just specify the binaries that they
want to be installed in the .standalone variant.
As usual with this type of change, the build dir must be reprovisioned.
A calendar timer that is already waiting keeps the base timestamp it was
last armed from. When the wall clock is set backwards, that base can end
up in the future relative to the new realtime, and passing it to
calendar_spec_next_usec() schedules the next elapse relative to the old
future time instead of recalculating from now. systemctl list-timers
then keeps showing the stale pre-adjustment elapse (e.g. "3 years left"
after the clock moved back two years) and the timer never catches up.
Clamp the selected calendar base to the current realtime before asking
calendar_spec_next_usec() for the next occurrence. The clamp only kicks
in when the base is genuinely in the future, so Persistent=yes timers
whose last trigger is still in the past keep their catch-up behaviour and
we don't reintroduce the missed-run regression seen after suspend.
Fixes#6036.
1f76654f94 added support to build .standalone
variants of most binaries. test-dlopen-note.py is hooked in the test suite for
everything that can do .standalone, but this means that the .standalone
variants become a prerequisite for tests. This means that meson will build the
.standalone variants for all binaries before running tests, which takes quite a
bit of time in some situations. (Arguably, the dlopen test suite was excluded
by default, so maybe meson could be smart and figure out that those tests are
not enabled by default. But it doesn't seem to make this distinction.)
C lines copy symlinks as symlinks. The post-copy type check should
inspect the source symlink itself, not follow its target.
Reproducer:
src=/tmp/tmpfiles-src-link.$$
dst=/tmp/tmpfiles-dst-link.$$
conf=/tmp/tmpfiles-conf.$$
ln -s missing-target "$src"
printf 'C %s - - - - %s\n' "$dst" "$src" >"$conf"
systemd-tmpfiles --create "$conf"
Before:
Failed to stat($src): No such file or directory
status=73
$dst -> missing-target already existed
Follow-up for 8f6fb95cd0.