Yu Watanabe [Tue, 11 Nov 2025 22:30:01 +0000 (07:30 +0900)]
musl: locale-util: explicitly check existence of locale file
musl's newlocale() always provides a locale object even the requested
locale does not exist. Let's explicitly check the existence of the
requested locale file.
Yu Watanabe [Tue, 11 Nov 2025 23:59:06 +0000 (08:59 +0900)]
systemctl: fix edit and cat verbs with --global flag (#39606)
The --global flag has been broken since commit d77d42ed3ae95ee035dce4707777b077d1a9bf8b, which added a
blanket restriction on acquiring D-Bus connections when
arg_runtime_scope is RUNTIME_SCOPE_GLOBAL. This was done to prevent
crashes, but inadvertently broke legitimate use cases like 'systemctl
edit --global' and 'systemctl cat --global'.
The issue is that verb_edit() and verb_cat() were unconditionally
calling acquire_bus(), which triggers the restriction and fails with
"--global is not supported for this operation."
This commit fixes the issue by making bus acquisition conditional,
following the same pattern used in verb_enable():
- Only acquire the bus when install_client_side() returns NO (i.e., for
system and user scopes)
- For client-side operations (--global, --root, etc.), skip bus
acquisition and use mangle_names() instead of expand_unit_names()
- Update find_paths_to_edit() and verb_cat() to handle NULL bus by
forcing client-side path lookups
- Skip bus-dependent checks (unit_is_masked, need_daemon_reload) when
bus is NULL
This allows both 'systemctl edit --global' and 'systemctl cat --global'
to work correctly by performing all operations client-side without
requiring a connection to the system or user manager.
Yu Watanabe [Tue, 11 Nov 2025 23:54:09 +0000 (08:54 +0900)]
sd-path: add new type SD_PATH_SEARCH_SYSCTL (#38680)
Aim of this patches set, is to add a new type SD_PATH_SEARCH_SYSCTL for
sd_path_lookup() and sd_path_lookup_strv(). This new type is used to get the
directories list used by systemd-sysctl:
bootctl: calculate secureboot state taking MokSBStateRT into account (#39298)
shim is often used as part of the EFI boot chain with Linux kernels.
shim has an option to disable all verification of binaries it loads.
This can be performed by end-user using mokutil / mokmanager EFI app,
which set BootServices only variable MokSBState. shim honors that, and
mirrors it as readonly MokSBStateRT for the post-ExitBootService access.
Thus presense of MokSBStateRT is an indicator that shim was used during
boot chain.
Some OEM vendors are known to set MokSBState variable, without user
having done so.
When verification is disabled, one should assume secureboot is insecure,
because any EFI binary was allowed to run, including but not limited to
unsigned or revoked:
- grub
- systemd-boot
- UKI
- linux kernel
Linux kernel also has code to check this variable, and correctly report
that Secure Boot is disabled, see:
-
https://github.com/torvalds/linux/blob/3a8660878839faadb4f1a6dd72c3179c1df56787/drivers/firmware/efi/libstub/secureboot.c#L57
With this change bootctl output changes like this:
```diff
System:
Firmware: n/a (n/a)
Firmware Arch: x64
- Secure Boot: enabled (user)
+ Secure Boot: disabled (insecure)
TPM2 Support: yes
Measured UKI: no
Boot into FW: supported
```
This implementation is trying to mimic mokutil behaviour like this one:
```
$ mokutil --sb-state
SecureBoot enabled
SecureBoot validation is disabled in shim
```
As well as the linux kernel behavior of:
```
$ journalctl -b | grep 'Secure boot disabled'
kernel: Secure boot disabled
```
Note that MokSBState is extended into PCR7 as well as also into PCR14.
For more details see https://github.com/rhboot/shim/blob/main/README.tpm
Luca Boccassi [Mon, 10 Nov 2025 02:01:57 +0000 (02:01 +0000)]
dissect: check that roothash in signature matches before selecting partition
If there are multiple verity sig partitions (e.g.: sysupdate and A/B
scheme), dissection will simply pick the last sig partition it encounters,
as no checks are done on the content (like for the usr/root and verity data).
Check that the JSON content matches the requested roothash, if any.
Before:
sda: /usr/lib/udev/rules.d/90-image-dissect.rules:34 IMPORT{builtin}="dissect_image probe": Importing properties from results of builtin command "dissect_image probe".
Dissecting esp partition with label esp and UUID b80070bd-ea4f-49ea-94ab-41a4e4125f80.
Dissecting usr-verity-sig partition with label ParticleOS_27.178_verity_sig and UUID a6d47959-39f7-4686-99b0-660b301d1488.
Dissecting usr-verity partition with label ParticleOS_27.178_verity and UUID d7acad57-995d-297d-bf6c-a58821dcd28a.
Dissecting usr partition with label ParticleOS_27.178 and UUID f5b6aff5-945d-946e-faf4-d482c07f9968.
Dissecting usr-verity-sig partition with label ParticleOS_118.26_verity_sig and UUID c9151ec9-3264-434a-8f42-7b125432d676.
Dissecting usr-verity partition with label ParticleOS_118.26_verity and UUID 88fa8c85-8161-ea32-bf4a-fc8df18d27ae.
Partition UUID '88fa8c85-8161-ea32-bf4a-fc8df18d27ae' does not match expected UUID 'f5b6aff5-945d-946e-faf4-d482c07f9968' derived from usr verity hash, ignoring.
Dissecting usr partition with label ParticleOS_118.26 and UUID 52df1859-e144-348d-2cb1-8d6440254719.
Partition UUID '52df1859-e144-348d-2cb1-8d6440254719' does not match expected UUID 'f5b6aff5-945d-946e-faf4-d482c07f9968' derived from usr verity hash, ignoring.
Dissecting swap partition with label ParticleOS-swap and UUID 7fe77f77-32fb-4957-8c1e-6c04bd2e435f.
Dissecting root partition with label ParticleOS-root and UUID a5c89fc4-e92c-4e83-913f-8c866b94592e.
Dissecting home partition with label ParticleOS-home and UUID 25885d07-baa2-4992-b6aa-56813aa70cef.
Found for designator root: encrypted+unprotected+unused.
Found for designator usr: verity+signed+encrypted+unprotected+unused.
Found for designator home: encrypted+unprotected+unused.
Found for designator srv: absent.
Found for designator esp: encrypted+unprotected+unused.
Found for designator xbootldr: absent.
Found for designator swap: encrypted+unprotected+unused.
Found for designator root-verity: absent.
Found for designator usr-verity: encrypted+unprotected+unused.
Found for designator root-verity-sig: absent.
Found for designator usr-verity-sig: encrypted+unprotected+unused.
Found for designator tmp: absent.
Found for designator var: absent.
Probed fstype 'btrfs' on partition /dev/sda9.
Probed fstype 'erofs' on partition /dev/sda4.
Probed fstype 'btrfs' on partition /dev/sda10.
Probed fstype 'swap' on partition /dev/sda8.
Root hash in signature JSON data (52df1859e144348d2cb18d644025471988fa8c858161ea32bf4afc8df18d27ae) doesn't match configured hash (f5b6aff5945d946efaf4d482c07f9968d7acad57995d297dbf6ca58821dcd28a).
sda: Failed to load verity signature data from image: Invalid argument
After:
Dissecting usr-verity-sig partition with label ParticleOS_27.178_verity_sig and UUID a6d47959-39f7-4686-99b0-660b301d1488.
Dissecting usr-verity partition with label ParticleOS_27.178_verity and UUID d7acad57-995d-297d-bf6c-a58821dcd28a.
Dissecting usr partition with label ParticleOS_27.178 and UUID f5b6aff5-945d-946e-faf4-d482c07f9968.
Dissecting usr-verity-sig partition with label ParticleOS_118.26_verity_sig and UUID c9151ec9-3264-434a-8f42-7b125432d676.
Root hash in signature JSON data (52df1859e144348d2cb18d644025471988fa8c858161ea32bf4afc8df18d27ae) doesn't match configured hash (f5b6aff5945d946efaf4d482c07f9968d7acad57995d297dbf6ca58821dcd28a).
Dissecting usr-verity partition with label ParticleOS_118.26_verity and UUID 88fa8c85-8161-ea32-bf4a-fc8df18d27ae.
Partition UUID '88fa8c85-8161-ea32-bf4a-fc8df18d27ae' does not match expected UUID 'f5b6aff5-945d-946e-faf4-d482c07f9968' derived from usr verity hash, ignoring.
Dissecting usr partition with label ParticleOS_118.26 and UUID 52df1859-e144-348d-2cb1-8d6440254719.
Partition UUID '52df1859-e144-348d-2cb1-8d6440254719' does not match expected UUID 'f5b6aff5-945d-946e-faf4-d482c07f9968' derived from usr verity hash, ignoring.
<...>
ID_DISSECT_PART2_DESIGNATOR=usr-verity-sig
ID_DISSECT_PART3_ARCHITECTURE=x86-64
ID_DISSECT_PART3_DESIGNATOR=usr-verity
ID_DISSECT_PART4_ARCHITECTURE=x86-64
ID_DISSECT_PART4_DESIGNATOR=usr
ID_DISSECT_PART4_HAS_VERITY=1
ID_DISSECT_PART4_HAS_VERITY_SIG=1
ID_DISSECT_PART4_ROOTHASH=f5b6aff5945d946efaf4d482c07f9968d7acad57995d297dbf6ca58821dcd28a
ID_DISSECT_PART4_ROOTHASH_SIG=<...>
ID_DISSECT_PART4_VERITY_DEVICE=/dev/disk/by-diskseq/9-part3
ID_DISSECT_PART4_VERITY_SIG_DEVICE=/dev/disk/by-diskseq/9-part2
gvenugo3 [Tue, 11 Nov 2025 17:00:29 +0000 (10:00 -0700)]
systemctl: support --global and --root in edit and cat
Make bus acquisition conditional in verb_edit() and verb_cat(), following
the same pattern used in verb_enable(). When install_client_side() returns
non-zero (indicating --global, --root, offline, or similar scenarios), skip
acquiring a D-Bus connection and perform all operations client-side.
Changes:
- Only acquire bus when install_client_side() returns NO
- Use mangle_names() instead of expand_unit_names() in client-side mode
- Pass force_client_side flag based on bus availability
- Skip bus-dependent operations (need_daemon_reload, etc.) when bus is NULL
This allows 'systemctl edit --global' and 'systemctl cat --global' to work
correctly, fixing the regression introduced by commit d77d42ed3a.
Test cases added to verify:
- Creating and editing global user units with --runtime
- Reading global units with cat --global
- Proper detection and rejection of masked units in client-side mode
- Tests use /run/ instead of /etc/ for safer temporary testing
gvenugo3 [Tue, 11 Nov 2025 16:55:59 +0000 (09:55 -0700)]
systemctl: check if unit is masked in unit_find_paths()
When operating in client-side mode (force_client_side=true), unit_find_paths()
now checks if the unit file is masked (symlinked to /dev/null or empty) and
returns -ERFKILL, matching the behavior of the server-side path.
This centralizes masked unit detection in one place, making it consistent
across both client-side and server-side operations.
libsystemd: add new type SD_PATH_SEARCH_SYSCTL for sd_path_lookup*
Add the new type SD_PATH_SEARCH_SYSCTL to libsystemd.
With this new type sd_path_lookup() and sd_path_lookup_strv() will
return the paths used by systemd-sysctl(1) to search the .conf files:
Frantisek Sumsal [Mon, 10 Nov 2025 18:26:43 +0000 (19:26 +0100)]
test: ignore EC from the second `systemctl status -a` as well
There is a TOCTOU in the `systemctl status` where a unit might change
its state during the initial ListUnitsByPatterns call and the subsequent
individual GetAll calls, which then makes the systemctl call fail even
if the unit that was originally pulled in was active/running:
Frantisek Sumsal [Mon, 10 Nov 2025 16:42:06 +0000 (17:42 +0100)]
test: don't register short-living containers with machined
As registering the container creates a scope which might not be cleaned
up completely before we run a next command in the same container,
causing intermittent test fails:
[ 63.424739] TEST-13-NSPAWN.sh[4231]: + systemd-nspawn --directory=/var/lib/machines/TEST-13-NSPAWN.sanity.zH2 bash -xec '[[ $USER == root ]]'
[ 63.427504] systemd-nspawn[4381]: ░ Spawning container TEST-13-NSPAWN.sanity.zH2 on /var/lib/machines/TEST-13-NSPAWN.sanity.zH2.
[ 63.437154] systemd[1]: Started TEST-13-NSPAWN.sanity.zH2.scope - Container TEST-13-NSPAWN.sanity.zH2.
[ 63.437765] systemd-machined[1164]: New machine TEST-13-NSPAWN.sanity.zH2.
[ 63.440311] TEST-13-NSPAWN.sh[4381]: + [[ root == root ]]
[ 63.442046] systemd[1]: TEST-13-NSPAWN.sanity.zH2.scope: Killed unit cgroup '/machine.slice/TEST-13-NSPAWN.sanity.zH2.scope' with SIGKILL on client request.
[ 63.442583] systemd-nspawn[4381]: Container TEST-13-NSPAWN.sanity.zH2 exited successfully.
[ 63.443073] systemd-machined[1164]: Machine TEST-13-NSPAWN.sanity.zH2 terminated.
[ 63.448728] TEST-13-NSPAWN.sh[4231]: + systemd-nspawn --directory=/var/lib/machines/TEST-13-NSPAWN.sanity.zH2 --user=testuser bash -xec '[[ $USER == testuser ]]'
[ 63.451209] systemd-nspawn[4385]: ░ Spawning container TEST-13-NSPAWN.sanity.zH2 on /var/lib/machines/TEST-13-NSPAWN.sanity.zH2.
[ 63.455295] systemd-nspawn[4385]: Failed to allocate scope: Unit TEST-13-NSPAWN.sanity.zH2.scope was already loaded or has a fragment file.
[ 63.456139] systemd[1]: TEST-13-NSPAWN.sanity.zH2.scope: Deactivated successfully.
[ 63.461292] TEST-13-NSPAWN.sh[2839]: + at_exit
Since even systemd-nspawn's man page suggests not to register containers
with systemd-machined if they don't run a service manager, let's do just
that to mitigate the race.
Gero Schwäricke [Fri, 7 Nov 2025 15:09:17 +0000 (16:09 +0100)]
rules: add rule to generate unique symlinks for gpio devices
Regular generated paths make it hard to identify individual GPIO
devices. This is a challenge when using multiple USB-to-GPIO adapters
like Diolan DLN2.
The unique symlinks from this rule can be used, e.g., with gpiod tools.
Yu Watanabe [Mon, 10 Nov 2025 10:01:32 +0000 (19:01 +0900)]
test: avoid service name collision
The same service name was accidentally used for two invocations:
```
[ 1801.197993] H TEST-04-JOURNAL.sh[20563]: + assert_rc 0 journalctl -q -D /run/log/journal/e30adae55e664d328af442bf5df694c8/ -u test-23833.service --grep service=test-23833.service
[ 1801.198527] H TEST-04-JOURNAL.sh[20685]: + set +ex
[ 1801.222676] H TEST-04-JOURNAL.sh[20686]: Nov 10 03:18:51 H systemd[1]: test-23833.service: About to execute: /usr/bin/bash -c "echo service=test-23833.service invocation=\$INVOCATION_ID; journalctl --sync"
[ 1801.222676] H TEST-04-JOURNAL.sh[20686]: Nov 10 03:18:51 H systemd[1]: Started test-23833.service - [systemd-run] /usr/bin/bash -c "echo service=test-23833.service invocation=\$INVOCATION_ID; journalctl --sync".
[ 1801.222676] H TEST-04-JOURNAL.sh[20686]: Nov 10 03:18:51 H (bash)[20681]: test-23833.service: Executing: /usr/bin/bash -c "echo service=test-23833.service invocation=\$INVOCATION_ID; journalctl --sync"
[ 1801.222676] H TEST-04-JOURNAL.sh[20686]: Nov 10 03:18:51 H bash[20681]: service=test-23833.service invocation=1866f15e95924a688dcecde72bf345f6
[ 1801.227878] H TEST-04-JOURNAL.sh[20563]: + assert_rc 1 journalctl -q -D /var/log/journal/e30adae55e664d328af442bf5df694c8/ -u test-23833.service --grep service=test-23833.service
[ 1801.228265] H TEST-04-JOURNAL.sh[20689]: + set +ex
[ 1801.253412] H TEST-04-JOURNAL.sh[20690]: Nov 10 03:18:49 H systemd[1]: test-23833.service: About to execute: /usr/bin/bash -c "echo service=test-23833.service invocation=\$INVOCATION_ID; journalctl --sync"
[ 1801.253412] H TEST-04-JOURNAL.sh[20690]: Nov 10 03:18:49 H systemd[1]: Started test-23833.service - [systemd-run] /usr/bin/bash -c "echo service=test-23833.service invocation=\$INVOCATION_ID; journalctl --sync".
[ 1801.253412] H TEST-04-JOURNAL.sh[20690]: Nov 10 03:18:49 H (bash)[20581]: test-23833.service: Executing: /usr/bin/bash -c "echo service=test-23833.service invocation=\$INVOCATION_ID; journalctl --sync"
[ 1801.253412] H TEST-04-JOURNAL.sh[20690]: Nov 10 03:18:49 H bash[20581]: service=test-23833.service invocation=a3089a62b5624d21bac0a75a3995d8b5
[ 1801.258158] H TEST-04-JOURNAL.sh[20692]: FAIL: expected: '1' actual: '0'
```
This gives access to credentials within ExecCondition=. As described in
ticket #35788, I do have a use-case for this and as noted in the
commit that dropped this[1], this is OK to be revisited if there are
use-cases.
systemd-repart is incorrectly choosing the loop-mount
code path to copy files after formatting, instead of using the --rootdir
path, which is required by mkfs.btrfs to apply compression (since it's
on files, not the fs).
So two fixes (and an integ test):
1. If Btrfs compression is requested without a root directory (e.g.,
Compression= without CopyFiles=), we now log a warning and skip the
--compress flag. This prevents the mkfs.btrfs failure, and it's
meaningless anyway without any files.
2. The logic in repart now uses the --rootdir code path whenever the
partition is btrfs and compression is requested. Otherwise it still
won't work even in the legitimate case because use the loop mounting
code, which is too late to use --compress.
Chris Down [Thu, 6 Nov 2025 15:36:19 +0000 (23:36 +0800)]
test: Add integration test for btrfs compression in repart
Add testcase_btrfs_compression() to verify that btrfs partitions with
Compression= and CopyFiles= directives work correctly.
The test verifies the fix for issue #39584, where mkfs.btrfs would fail
with "ERROR: --compression must be used with --rootdir" when repart
tried to create compressed btrfs filesystems.
The test creates a partition definition with Format=btrfs,
Compression=zstd, and CopyFiles=, then validates:
1. systemd-repart output shows "Rootdir from:" and "Compress:",
confirming that the --rootdir code path is used
2. mkfs.btrfs is invoked with both --compress and --rootdir options
3. The file is successfully copied to the filesystem
4. Compression is actually applied (verified via compsize output
containing "zstd")
Yu Watanabe [Sat, 8 Nov 2025 23:44:25 +0000 (08:44 +0900)]
libarchive-util: several cleanups
- use loop for checking existence of functions,
- rename HAVE_LIBARCHIVE_XYZ -> HAVE_ARCHIVE_XYZ to make them match with
the function name,
- do not conditionally include user-util.h in libarchive-util.h,
- sort library function symbols.
nsresource: allow multiple userns from the same process in parallel
When generating a name for a transient userns automatically we so far
just included our PID to make it unique. That doens't really work if
multiple userns shall be kept in parallel by a single process. Let's hence
include a counter as well.
pull: there's no need to keep the downloaded image in memory, except for the sha256sums/gpg file
This seems to be a mistake, in place since the first commit: we only
want the downloaded data in memory if this is a sha256sums or gpg file,
which we need to prorcess ourselves.
pull: now that PullJob can verify expected digests, let's rely on it for tar/raw pulling
Instead of authenticating the downloaded image explicity in the tar and
in the raw downloader, we can now rely on the checksum checking in the
generic PullJob code. Hence do so: drop tep the checksum field from
TarPull and RawPull, and just initialized the ->expected_checksum in the
relevant PullJob instead.
import: rework pull logic to store download digests in binary form rather than string
We generally want to store data in parsed form, not formatted form,
hence let's follow our own rules on this, and store the message digest
as "struct iovec" rather than as string. This is generally more
efficient and safer, simply because of case issues.
pull-job: always implicitly NUL terminate downloaded payload stored in memory
Just as a safety measure, let's always NUL terminate what we are
downloading, maybe future code will parse it as string, and is sloppy by
accident.
(We have similar logic in read_full_file(), and I think it's a really
good rule, to always implicitly NUL terminate blobs we acquire that
might very well be used as text later on)
After the commit, the functions are only used to determine
whether journals shall be forwarded to selected targets,
hence rename as such and remove effectively unused condition
on EXEC_OUTPUT_TTY.
test: move the system time to exactly the timer's elapse time
When we moved the time to 1 minute after the timer would've elapsed,
systemd could pick RandomizedDelaySec= <= 1 minute which would then
cause the timer to elapse immediately and the InactiveExitTimestamp=
to get recalculated including a new next elapse time that would be for
the next "window":
systemd[1]: timer-RandomizedDelaySec-30785.timer: Adding 3.634672s random time.
systemd[1]: timer-RandomizedDelaySec-30785.timer: Realtime timer elapses at Fri 2025-11-07 00:10:03 UTC.
systemd[1]: timer-RandomizedDelaySec-30785.timer: Timer elapsed.
systemd[1]: timer-RandomizedDelaySec-30785.timer: Changed waiting -> running
systemd[1]: Found unit timer-RandomizedDelaySec-30785.timer at /run/systemd/system/timer-RandomizedDelaySec-30785.timer (regular file)
systemd[1]: Preset files say disable timer-RandomizedDelaySec-30785.timer.
systemd[1]: timer-RandomizedDelaySec-30785.timer: Got notified about unit deactivation.
systemd[1]: timer-RandomizedDelaySec-30785.timer: Adding 8h 39min 26.166418s random time.
systemd[1]: timer-RandomizedDelaySec-30785.timer: Realtime timer elapses at Sat 2025-11-08 08:49:26 UTC.
systemd[1]: timer-RandomizedDelaySec-30785.timer: Changed running -> waiting
...
TEST-53-TIMER.sh[1008]: InactiveExitTimestamp=Thu 2025-11-06 23:00:00 UTC
TEST-53-TIMER.sh[1010]: ++ systemctl show -P NextElapseUSecRealtime timer-RandomizedDelaySec-30785.timer
TEST-53-TIMER.sh[905]: + NEXT_ELAPSE_REALTIME='Sat 2025-11-08 08:49:26 UTC'
TEST-53-TIMER.sh[1011]: ++ date '--date=Sat 2025-11-08 08:49:26 UTC' +%s
TEST-53-TIMER.sh[905]: + NEXT_ELAPSE_REALTIME_S=1762591766
TEST-53-TIMER.sh[905]: + : 'Next elapse timestamp should be Fri 2025-11-07 00:10:00 UTC <= Sat 2025-11-08 08:49:26 UTC <= Fri 2025-11-07 22:10:00 UTC'
TEST-53-TIMER.sh[905]: + assert_ge 17625917661762474200
TEST-53-TIMER.sh[1012]: + set +ex
TEST-53-TIMER.sh[905]: + assert_le 17625917661762553400
TEST-53-TIMER.sh[1013]: + set +ex
TEST-53-TIMER.sh[1013]: FAIL: '1762591766' > '1762553400'
Technically, the race is still there, but the window for it should be
_much_ smaller now (< 1s on a reasonably fast system). Let's hope that's
enough.
profile/osc-context: move and extend check for TERM=dumb
Let's do the check early and skip most of the file if appropriate. Also, treat
missing $TERM same as "dumb". We're almost certainly at a dump terminal in that
case.