sysupdate: include default component for feature-all
--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
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.
test: add coverage for systemctl preset in test-systemctl-enable.sh
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.
sysupdate: don't double-prefix definitions with --root=
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.
veritysetup: keep parsing after ignored NvPCR options
tpm2-measure-nvpcr=no and invalid NvPCR names only affect the current
comma-separated option. They returned from parse_options(), so later
options were silently skipped.
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.
Eric Curtin [Thu, 2 Jul 2026 12:37:06 +0000 (13:37 +0100)]
shared/dropin: don't re-derive drop-in name candidates per lookup dir
unit_file_find_dirs() is called once for every (unit name or alias,
lookup directory, drop-in suffix) combination while enumerating units
at boot, to check whether that unit has a ".d", ".wants", ".requires"
or ".upholds" drop-in directory in that particular lookup path. On a
typical system with ~270 loaded units and ~12 directories in the unit
search path, this adds up to tens of thousands of calls.
For every one of those calls, the function used to independently
re-derive the full chain of candidate unit names to check for that one
directory: the name itself, its template if it is a template instance,
and its "-" prefix chain (e.g. for "foo-bar-waldo.service" also
"foo-bar-.service" and "foo-.service"), recursively expanding further
where applicable. That derivation only depends on the unit name itself
and does not involve the lookup directory at all, so it produces the
exact same list of candidate names regardless of which of the 12
lookup directories is currently being checked. Despite this, it was
being fully recomputed for every single directory, doing several small
allocations and unit-name parsing calls (unit_name_template(),
unit_name_to_prefix(), unit_name_build_from_type(), ...) each time.
Split the name-derivation logic out into its own function,
unit_file_expand_dropin_names(), and compute it once per unit
name/alias, then reuse the resulting candidate list across all lookup
directories instead of re-deriving it for each of them. The order in
which candidate directories end up being added is unchanged, so this
is not expected to alter drop-in resolution behaviour: I confirmed this
by comparing the sorted unit load state, fragment path and drop-in path
output of "systemd --test --system" before and after this change on the
same unit tree, which is byte-for-byte identical.
I measured the effect by instrumenting manager_enumerate() with
CLOCK_MONOTONIC timestamps and running systemd, built from this exact
tree, as actual PID 1 in a container with ~270 real units loaded, 50
runs each before and after this change:
before: mean 45.35ms (stddev 0.60ms)
after: mean 38.81ms (stddev 0.81ms)
a ~14% reduction with about 8 standard deviations of separation between
the two distributions, i.e. well outside of run-to-run noise.
unit_file_expand_dropin_names()'s out parameter is renamed from
ret_names to names, since it is appended to (including recursively)
rather than only being populated on success, matching the ret_ naming
convention used elsewhere for output-only parameters. Also, a failure
partway through expanding a name's candidates (e.g. OOM) no longer
discards the candidates already derived before the failure, keeping
unit_file_find_dirs() closer to the original recursive
implementation's error handling.
unit_file_add_dir_if_exists(), which builds the path to check for each
(lookup directory, candidate name) pair, is now the hottest remaining
part of this code: with the per-directory re-derivation gone, it is
called once for every directory/candidate combination instead of once
per candidate. It used to build that path with strjoin(name, suffix)
followed by path_join(unit_path, name_and_suffix), i.e. two heap
allocations plus path_join()'s normalization pass. Lookup paths are
already normalized (path_simplify() + strv_uniq()), so a single
strjoin(unit_path, "/", name, suffix) produces the same string while
halving the allocations and skipping the redundant normalization.
ask-password: refuse agent requests with unsafe characters in prompt fields
The message, icon and id fields are written verbatim into single-line
assignments of the [Ask] section of the agent request file, so a newline in
them lets the caller append arbitrary further assignments. Agents let a later
assignment override an earlier one, so an injected Socket= line redirects the
password to a path of the injector's choosing. Validate the fields and refuse
the request instead.
homed/fscrypt: default new homes to v2 policies (#18280) (#42397)
## Summary
Closes #18280.
`systemd-homed` currently writes fscrypt v1 policies, which bind the
master key to the calling process's keyring. This means files in a
homed-managed directory aren't readable when accessed through a
container bind mount, a different mount namespace, or by any process
other than the one that first unlocked the home. Reading the file from a
context that has the key first warms the page cache, but the underlying
problem remains.
v2 policies (Linux 5.4+) route the master key through the filesystem
keyring, via `FS_IOC_ADD_ENCRYPTION_KEY` /
`FS_IOC_REMOVE_ENCRYPTION_KEY`, so the key is visible to every process
accessing the filesystem.
This PR switches `systemd-homed` to v2 by default and keeps existing v1
homes working.
## Changes
**`shared/crypto-util`**: Add `kdf_hkdf_derive` (HKDF, RFC 5869) wrapper
around OpenSSL's `EVP_KDF` "HKDF", alongside the existing SSKDF/KBKDF
helpers.
**`homed/fscrypt`**:
- Read the existing policy via `FS_IOC_GET_ENCRYPTION_POLICY_EX`,
falling back to the legacy ioctl on pre-5.4 kernels.
- `HomeSetup` now carries a full `struct fscrypt_key_specifier` instead
of a bare 8-byte descriptor. Slot decryption derives either the v1
descriptor (double SHA-512) or the v2 identifier (HKDF-SHA512 with the
kernel's info string) from the unwrapped volume key and compares against
the policy.
- Install the master key the right way per version: `add_key("logon",
...)` to thread+user keyrings for v1, `FS_IOC_ADD_ENCRYPTION_KEY` for
v2.
- `home_flush_keyring_fscrypt` opens the image directory, reads the
policy version, and either calls `FS_IOC_REMOVE_ENCRYPTION_KEY` (v2) or
walks the user keyring (v1).
- New homes default to v2; fall back to v1 only when the kernel rejects
v2.
The on-disk slot xattr format is unchanged - the volume key is the same,
only how it binds to the directory changes. There is no v1 -> v2
migration; existing v1 homes continue to unlock, rekey, and deactivate
as before.
Tim Culverhouse [Sat, 18 Jul 2026 00:42:37 +0000 (00:42 +0000)]
core: support CollectMode in Varlink StartTransient
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.
format-table: give mixed cell types a stable sort order (#43129)
Mixed-type cells were compared by their insertion indices, while cells
of the same type were compared by value. Combining those rules
could make `cell_data_compare()` non-transitive.
Give different non-empty `TableDataType` values a stable order. Define
empty cells as greater than non-empty cells, so they appear last in
ascending sorts and first when the order is reversed. This ensures that
the comparator defines a consistent ordering.
password-cache= tracks both the cache mode and whether the option was
configured explicitly. The parser set arg_password_cache_set before
validating the value, so an invalid value was logged as ignored but
still blocked the PKCS#11 default no-cache policy.
Only mark password-cache= as configured after accepting read-only or a
valid boolean.
size= is specified in bits, but arg_key_size stores bytes after
parsing. The parser used arg_key_size as temporary storage before
validation. When the value was not divisible by 8, the warning said
the option was ignored, but the invalid bit count remained.
Parse into a local variable and update arg_key_size only after
validation.
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.
seccomp-util: allow openat2() with --suppress-sync=yes
When --suppress-sync=yes was introduced in 4a4654e0241fbeabecb8587fd3520b6b39264b9c it filtered out openat2()
completely, as we can't check its "flags" argument because it's hidden
in an indirect struct. This was perfectly fine at that time, as
openat2() was quite new and software shipped with a fallback to
open()/openat() if the syscall wasn't present.
However, today the situation is different and an increasing number of
software is moving to openat2() without any fallback - tar [0] being the
most common one in the recent reports and workarounds, and attr recently
fixed a CVE by switching to openat2() [1] as well, to name a few.
Given that we already block all the sync-family syscalls and calling
openat2() with O_SYNC is relatively rare, let's just blanket-enable it
in the --suppress-sync=yes mode. This means that we might issue a
synchronous write when something calls openat2() with O_SYNC, but not
breaking the apps here feels like a reasonable trade-off, at least until
a better solution pops up.
Note that the same situation is in seccomp_restrict_sxid(), but allowing
the openat2() syscall there could actually have some security-related
implications under certain circumstances.
core: tolerate kernels without POSIX mqueue support
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.
network: make VLAN= and friends take multiple names
Previously, specifying multiple stacked netdevs of the same type required
repeating the corresponding setting, e.g.:
```
[Network]
VLAN=vlan_10
VLAN=vlan_20
VLAN=vlan_30
```
With this change, the same configuration can be written as:
```
[Network]
VLAN=vlan_10 vlan_20 vlan_30
```
Specifying an empty string now also clears all previously assigned stacked
netdevs.
meson: stop registering .standalone in executables_by_name
af4c5730e524c972994232259e5051b951142485 tried to have meson stop
building all possible .standalone binaries in for tests. But it turns out
that those binaries are still built for meson-test-prereq, because they
are listed in executables_by_name, and we interate over
executables_by_name and add all exes found there as dependencies for two
tests: test-link-abi and libshared-unused-symbols. So we'd end up still
building all the .standalone binaries. To really fix this, define the
executables() for .standalone targets, but don't add them to this array.
A secondary change is to set build_by_default to true for .standalone
binaries if have_dlopen_tests is set. This means that we'll build more
binaries in the "build" phase, instead of only building them "on demand"
for tests. I think it is nicer to build everything that'll be used in one
step, and then only run the tests in the test target. For example, tests
may be run under root or in some special environment, and building thins
there is iffy.
The overall effect of this change should be that we stop building or
testing .standalone variants for binaries that we'll not later install,
unless -Ddlopen-tests=true is specified.
Also exclude .standalone binaries from check-help test. The test fails
for some binaries when the width is exceeded because of the ".standalone"
suffix in the name. The actual binary would be called without the
suffix, so the test is not testing a realistic scenario.
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.
user-record: validate JSON shell fields with valid_shell()
The user record loader currently uses a generic filename-or-path check
for shell and fallbackShell. This allows values that homectl rejects,
including relative names, control characters, colons, and trailing
slashes.
Use valid_shell() for all three record locations and cover the top-level,
matching per-machine, and status fallback fields at the loader boundary.
timer: clamp future calendar base after clock jumps back
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.
meson: disable dlopen tests by default, enable in gcc github workflow
1f76654f942893155bd42b0373b8130d2a9e1dcf 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.)
network: invalidate cached driver when an interface is renamed
During early boot, interfaces may be renamed frequently, which can lead to
caching the driver name of a different interface. Invalidate the cached
driver whenever an interface rename is detected so it can be re-read using
the new name.
Plymouth encodes the password prompt length in a single byte.
Reject prompts whose length cannot be represented instead of
wrapping the encoded length while sending the complete message.
strv: refuse invalid UTF-8 in strv_rebreak_lines()
The scan pointer was advanced with utf8_next_char(), which blindly skips
utf8_skip_data[lead byte] bytes, so a line ending in a truncated multibyte
sequence like "foo\xF0" stepped over the NUL and the loop read past the end
of the string. Decode each character with utf8_encoded_to_unichar() and
propagate the error instead of measuring broken characters.
Sven Joachim [Thu, 23 Jul 2026 08:53:17 +0000 (10:53 +0200)]
emacs: Fix warning when opening shell scripts
Opening a shell script, Emacs notified me in the echo area:
sh-indentation is obsolete (since 26.1); use ‘sh-basic-offset’ instead
Commit 0c40aef7ef14 ("emacs: drop obsolete emacs property") got it backwards,
keeping sh-indentation rather than sh-basic-offset. The latter is an obsolete
alias for the former.
tmpfiles: propagate copy errors after opening target
Opening the destination only proves that it exists. If copy_tree_at()
failed for any reason other than an existing destination, report that
copy error.
I noticed that the use of sed in the `80-systemd-osc-context.sh` was
unnecessary, and upon writing a replacement in pure bash and testing it,
actually buggy. While in there, I also spotted some other minor things
that might be worth doing.
Getting rid of fork+exec for doing sed is probably the biggest win, but
there are still quite a few subshell invocations (that only do fork, not
exec). I haven't measured the overhead of those, but it seems that we
could get rid of those as well if we want.
Another thing I wonder about is whether we should emit an end= marker on
shell exit. There's no guaranteed way of doing it. Just as everything
else, we could get killed before getting a chance to do it. But also,
unlike PROMPT_COMMANDS, there's no "at_exit" array to hook into. Still,
we could ask if there is a 'trap exit' hook installed, and if not,
install one ourselves; if the user's bashrc subsequently overrides that,
so be it (though .bash_logout would be more appropriate).
machined: refresh resolve hook addresses per machine
Track which machine the cached address list belongs to and refresh it
when ResolveRecord() advances to another machine. This keeps A/AAAA
reuse for the same machine while avoiding mixed name/address records.
Reproducer: register two machines with private addresses 10.88.1.1
and 10.88.2.1, then call io.systemd.Resolve.Hook.ResolveRecord
with both A questions in one request.
Before: the answer for the second machine used its own name but
reused the first machine's address, e.g. resolve-bug-m2 returned
10.88.1.1 instead of 10.88.2.1.
KERNEL_INSTALL_CONF_ROOT overrides kernel-install config lookup. When it
was set and cmdline was absent, 60-ukify still fell back to
/proc/cmdline and embedded host boot options into the target UKI.
Return an empty base cmdline in that case, matching the adjacent
90-loaderentry behavior of not searching outside the override root.
status with --static, --pretty, or --transient prints only the
selected hostname in regular output. JSON status output is built from
hostnamed's Describe data and remains a full status object.
Document this distinction so callers do not expect the name type
switches to turn JSON status into a single scalar value.
Workers may return a newer embedded identity while processing
an owner update. Accept self-modifiable changes, but require
privileged changes to carry a trusted signature.
rm-rf: downgrade root check log when directory doesn't exist
When a unit is stopped we try to destroy the credential mount point,
which might not exist. This logs at error level, which is noisy and
pointless. Downgrade to debug level on ENOENT.
systemd[1]: Failed to determine whether '/run/credentials/run-p119516-i119816.service' is the root file system: No such file or directory
Liu Zheng [Wed, 22 Jul 2026 07:31:24 +0000 (07:31 +0000)]
man: document that ExitType=cgroup is rejected for Type=oneshot
The ExitType= documentation stated that ExitType=main "cannot be used
with Type=oneshot", which is incorrect: ExitType=main is the default
and works fine with Type=oneshot. The restriction actually enforced by
the code is that ExitType=cgroup is refused for Type=oneshot services
(see service_verify() in src/core/service.c).
Move the "cannot be used with Type=oneshot" note from the ExitType=main
bullet to the ExitType=cgroup bullet where it belongs.
escape: reject UTF-16 surrogates in \u escapes in cunescape_one() (#43079)
A `\u` escape in `cunescape_one()` was accepted for any 16-bit value
except NUL. That range `0x0000`-`0xFFFF` includes the UTF-16 surrogates
`0xD800`-`0xDFFF`, which are not valid Unicode scalar values and cannot
be encoded as valid UTF-8.
GLib does not guarantee that check() is always called between two
consecutive prepare() invocations. When another thread attaches or
removes a source with poll fds while the main thread is inside
poll(), g_main_context_check_unlocked() returns immediately without
calling any source's check() callback.
Since the sd_event GSource adapter maps prepare/check/dispatch to
sd_event_prepare/sd_event_wait/sd_event_dispatch, a skipped check()
leaves sd_event in ARMED state. The next prepare() then hits
assertion "e->state == SD_EVENT_INITIAL".
The following program reproduces the faulty scenario by creating a
thread that attaches a new fd during the poll phase:
When the program uses the current glue code, it crashes with:
Assertion 'e->state == SD_EVENT_INITIAL' failed at src/libsystemd/sd-event/sd-event.c:4560, function sd_event_prepare(). Aborting.
Fix the problem by skipping sd_event_prepare() if the event is
already ARMED: it was already prepared in a previous iteration and
it can be polled immediately.
profile/systemd-osc-context: also escape $USER and $HOSTNAME
Claude pointed out that while rather unlikely, it is possible for the USER and
HOSTNAME environment variables to contain problematic characters that require
escaping. Now that that escaping no longer involves the fork+exec overhead of
calling sed, let's ensure those fields do get put through the escape routine.
Refactor __systemd_osc_context_escape so that it not only takes the value and
prints that, but also the format specifier used for emitting the field. That
way, we avoid an extra subshell (i.e. fork+pipe and all that overhead), and
combined with the previous commit, we now only spawn one subshell instead of
two per start= sequence.
That could be reduced to zero, if the "callers" were rewritten to something
like
but that will mean that the sequence is not emitted with a single write()
system call [which isn't really guaranteed currently either, just very likely],
so some background process could end up writing to the middle of the sequence,
thus losing that output and mangling the OSC3008 info. This in turn could be
overcome by building the whole sequence in a shell variable using 'printf -v'
and only printing at the end, but that would be a somewhat invasive change.
profile/systemd-osc-context: fold emitting cwd= field into __systemd_osc_context_common
Now that the %s specifier used to embed the common fields in the output appears
immediately before the cwd= field, we can just emit that cwd= field as part of
the common fields.
profile/systemd-osc-context: emit type= field first
Not because it is required by the specification, but it will make the following
patches simpler. Also, the fact that the spec explicitly does call out that
type= can appear at the end or in the middle suggests that people would
normally expect it to appear at the beginning.
profile/systemd-osc-context: don't do arithmetic expansion on systemd_exitstatus
There's really no point in having bash do arithmetic expansion on the
systemd_exitstatus variable, i.e. have it convert the string to a number, do no
actual arithmetic, then convert it back to a string to be used as the argument to
the printf, which will again convert it to a number for the %d specifier, and
finally emit it as a decimal. Also, it deviates for no obvious reason from how
it is passed to printf in the "probably died by signal" case just above.
profile/systemd-osc-context: do not zero-pad pid value
The %.20d specifier was introduced in 2d738a0aee ("profile/systemd-osc-context:
Enforce length limits"). But, for numeric conversions, the precision is not an
upper bound, but rather a lower bound on the output width, padding as necessary
with 0 on the left. So as-is, this does not in fact limit the output to at most
20 characters.
Of course, in practice, pids on linux are never greater than 2^22, and
certainly never larger than what would fit in a 20-digit decimal. On the other
hand, that more or less guarantees that the pid= field is always emitted with
12+ leadings zeroes, which is a bit silly. Moreover, a leading 0 can cause a
parser to treat it as octal.
Since $$ does expand to the PID in decimal, just print that as a string, with the
enforced 20 character limit.
profile/systemd-osc-context: do not use sed for escaping
Bash is perfectly capable of performing the simple substitutions needed for
escaping according to the OSC 3008 spec, so there is no need for the fork+exec
and other overhead of calling sed.
In fact, when writing a test for ensuring that this is a drop-in replacement, I
found out that the current sed method is flawed: If $PWD contains newline
characters, they are passed through unchanged, because sed obviously is
line-oriented.
I do not know which bash version started supporting
${foo//pattern/replacement}, but I ran the below on all Debian images from docker
hub going back to Debian 6 (EOL 2016), carrying BASH_VERSION =
4.1.5(1)-release, and they all succeeded. Since the logic otherwise relies on
PROMPT_COMMAND being an array variable, which happened in 5.1, this should be
all good.
do_test() {
local r0="$1"
local r1="$(using_sed "$r0")"
local r2="$(pure_bash "$r0")"
local s0="$(printf '%s' "$r0" | od -A x -t x1z -w40 | head -n1)"
local s1="$(printf '%s' "$r1" | od -A x -t x1z -w40 | head -n1)"
local s2="$(printf '%s' "$r2" | od -A x -t x1z -w40 | head -n1)"
if [ "$r1" != "$r2" ] || [ "$s1" != "$s2" ] ; then
echo "Input: $s0"
echo "sed: $s1"
echo "bash: $s2"
ret=1
fi
}
# The last cases show that the existing function doesn't actually work in
# the case of $PWD containing a newline character, because sed is
# line-oriented, so a newline character will never be replaced.
if [ "$1" = "all" ] ; then
do_test $'embedded \n newline'
do_test $'ending in newline\n'
fi
fscrypt v1 policies bind master keys to the calling process's keyring,
which means files in a homed-managed directory aren't readable when
accessed through a container bind mount, a different mount namespace,
or by any process other than the one that first unlocked the home.
Reading a file from outside such a context first warms the buffer cache
and papers over the symptom (#18280), but the underlying problem (the
key not flowing across keyrings) remains.
v2 policies (Linux 5.4+) route master keys through the filesystem
keyring via FS_IOC_ADD_ENCRYPTION_KEY / FS_IOC_REMOVE_ENCRYPTION_KEY,
so the key is visible to every process accessing the filesystem.
Switch homed to v2:
- Read the existing policy via FS_IOC_GET_ENCRYPTION_POLICY_EX, which
reports both v1 and v2 policies. The ioctl is available since Linux
5.4, i.e. on every kernel we support (our baseline is 5.10), so no
fallback to the v1-only FS_IOC_GET_ENCRYPTION_POLICY is needed.
- Drive slot matching off a full fscrypt_key_specifier (HomeSetup now
carries that instead of a bare 8-byte descriptor). Slot decryption
derives either the v1 descriptor (SHA-512 double hash) or the v2
identifier (HKDF-SHA512 with the kernel's info string), and compares
against the policy.
- Install the master key the right way per version: add_key("logon", ...)
to thread+user keyrings for v1, FS_IOC_ADD_ENCRYPTION_KEY for v2.
- home_flush_keyring_fscrypt opens the image directory, looks up the
policy, and either calls FS_IOC_REMOVE_ENCRYPTION_KEY (v2) or walks
the user keyring (v1).
- New homes default to v2; fall back to v1 only if the kernel rejects
FS_IOC_ADD_ENCRYPTION_KEY with ENOTTY/EOPNOTSUPP. The v2 create path
derives the identifier locally first, passes it to ADD_KEY as
expected_identifier, and cleans up via REMOVE_KEY if SET_POLICY then
fails, so the v1 fallback never sees a stranded key. Existing v1
homes continue to unlock, rekey, and deactivate as before.
- A v2 master key installed to work on an inactive home is always
removed again unless that home ends up activated. v2 keys persist in
the filesystem keyring until removed explicitly (v1 keys instead died
with the homework process' keyring), so a key left behind would leave
a home nobody activated readable until the next deactivation or reboot.
home_setup_fscrypt() and home_create_fscrypt() therefore arm a rollback
right after installing the key; the activation path disarms it once the
mount is in place (the live home owns the key), while every other path
-- create, and passwd/update/resize of an inactive home, plus all error
paths -- rolls it back via home_setup_done(). Activation reinstalls the
key.
The v1 and v2 on-disk policy formats differ (v1: 8-byte descriptor;
v2: 16-byte identifier) and fscrypt has no in-place upgrade path, so
a v1 home is always unlocked via the v1 code path and a v2 home is
always unlocked via the v2 code path, regardless of kernel version.
Slot xattr format is unchanged: the master key is the same, only how it
binds to the directory changes.
Mirrors the existing kdf_ss_derive / kdf_kb_hmac_derive helpers, wrapping
OpenSSL's "HKDF" EVP_KDF. Inputs and output are passed as struct iovec;
salt and info are optional (pass NULL or an empty iovec to omit). If
salt is omitted HKDF substitutes HashLen zero-bytes per RFC 5869.
The digest is a parameter now, so reimplement the existing SHA256-only
kdf_hkdf_sha256() as a thin wrapper around the new helper, keeping the
OpenSSL HKDF plumbing in one place.
Add test vectors against RFC 5869 Appendix A.1 and against the kernel's
fscrypt v2 master-key identifier construction (HKDF-SHA512 with empty
salt and info "fscrypt\x00\x01"), so future consumers can rely on the
helper matching that exact derivation.
hwdb: strip the root from filenames when generating hwdb.bin (#43062)
The modern hwdb.bin format contains the filenames of the input data that
makes up the database. This is useful but in offline builds where --root
is used, the filenames are the full build paths including the specified
root. This introduces build paths and thus information leakage and
non-reproducible data.
Solve this by stripping the root prefix off the original path when
passing to import_file.
hwdb: classify PlayStation controller audio as controller form-factor
Add Sony PlayStation controller entries to 70-sound-card.hwdb so that their
ALSA sound devices are tagged with SOUND_FORM_FACTOR=controller:
- DualSense (054c:0ce6)
- DualSense Edge (054c:0df2)
- DualShock 4 CUH-ZCT1x (054c:05c4)
- DualShock 4 CUH-ZCT2x (054c:09cc)
These controllers expose USB audio but are neither headsets nor speakers.
Pinning them in the hwdb ensures they are identified correctly before any
fallback matching occurs.
Ross Burton [Fri, 17 Jul 2026 16:25:31 +0000 (17:25 +0100)]
hwdb: strip the root from filenames when generating hwdb.bin
The modern hwdb.bin format contains the filenames of the input data that
makes up the database. This is useful but in offline builds where
--root is used, the filenames are the full build paths including the
specified root. This introduces build paths and thus information
leakage and non-reproducible data.
Solve this by stripping the root prefix off the original path when
passing to import_file.
Add TEST-17-UDEV.hwdb.sh to verify that hwdb.bin files contain the
path inside the root, but not the path of the root.
Nowadays, varlink is used to control systemd-udevd. Let's drop the
legacy socket.
Note, the existence of /run/udev/control socket is widely used in both
our code and external projects. Also, the dependency to
systemd-udevd-control.socket is widely used in many projects. Hence, we
need to create a symlink to the socket file and .socket unit file.
networkctl,networkd: add --no-reconfigure flag to networkctl reload
Add a new --no-reconfigure flag to 'networkctl reload' that reloads
.network and .netdev files from disk without reconfiguring any network
interfaces. This may be useful to avoid reconfiguring multiple interfaces
simultaneously when multiple .network files are updated, or when an updated
.network file is applied to multiple interfaces.
On the networkd side, manager_reload() gains a reconfigure_links parameter
that gates the per-link reconfiguration loop. A new io.systemd.Network.Reload
varlink method is added that exposes this as an optional reconfigureLinks
boolean (defaults to true). Both plain 'networkctl reload' and
'--no-reconfigure' now unconditionally call this method first. If an older
networkd returns MethodNotFound, plain reload falls back to
io.systemd.service.Reload for backward compatibility; '--no-reconfigure'
fails with a clear error in that case.
core/dbus: do not block the manager on GetId during bus (re-)connection
bus_init_api() issued a synchronous GetId call on every API bus
(re-)connection to decide whether saved subscription state could be
coldplugged onto the new connection.
If the D-Bus socket unit is listening while the message bus daemon
behind it is gone, connect() succeeds against the socket backlog but
nothing answers the authentication handshake. The synchronous call
then blocks PID 1 for BUS_AUTH_TIMEOUT (90 seconds by default), and
queued bus operations can trigger repeated reconnection attempts.
This was observed during shutdown as roughly 15 minutes of teardown
progressing only in 90-second intervals.
Query the instance ID asynchronously on every connection. Defer API
setup until the reply is processed, so saved subscriptions are
validated and coldplugged before new subscription requests can arrive.
If the query cannot be queued or its reply is invalid, discard the
unvalidated state and expose the API without blocking the manager.
Reset the live bus ID on every connection and serialize pending bus ID
and subscription state across reload and reexec. During daemon-reload,
preserve state that was already awaiting the asynchronous reply while
discarding the duplicate state produced by the reload itself.
Also remove the now-unused synchronous bus_get_instance_id() helper.
password-quality-util-passwdqc: restore password-quality-util.h include
suggest_passwords() references the N_SUGGESTIONS macro, which is defined in
password-quality-util.h. Commit ff33c8f87d ("Extend test-dlopen-so to also
cover cases when built without support") introduced the per-backend split
headers: for the pwquality backend it added the new
password-quality-util-pwquality.h include while keeping password-quality-util.h,
but for the passwdqc backend it replaced password-quality-util.h with
password-quality-util-passwdqc.h (which only pulls in shared-forward.h). As a
result N_SUGGESTIONS is no longer declared in the passwdqc translation unit and
the build fails when the passwdqc backend is enabled.
The passwdqc backend is not exercised by the default CI, so this went
unnoticed. Add the include back, matching the pwquality backend.