Commit Graph
60814 Commits
Author SHA1 Message Date
dongshengyuan f2e30e42e3 resolved: keep parsing DNS delegate servers after duplicates
The DNS delegate parser tries to filter duplicate DNS= entries, but the
duplicate branch returned from the whole parser. This made a duplicate
server truncate the rest of the same DNS= line, silently dropping later
valid servers.

Reproducer:

  DNS=192.168.77.78 192.168.77.78 192.168.77.79

Before: only 192.168.77.78 appeared in the delegate server list.

Follow-up for: 7928c0e0a1

(cherry picked from commit 0cfa2d4f4c)
2026-09-09 10:08:20 +01:00
dongshengyuan 1fb609802c resolvectl: reject tlsa without a domain
The tlsa command accepts an optional service family before the domain list.
When the only argument was "tcp", "udp", or "sctp", we consumed it as the
family and returned success without issuing any query.

Reproducer:

  build/resolvectl tlsa tcp

Before: the command printed nothing and exited with status 0.

Follow-up for: ebbc70e511

(cherry picked from commit 060b0d2f70)
2026-09-09 10:08:20 +01:00
dongshengyuan bd3945a9f0 resolved: notice route-only domain updates
SetLinkDomains() tracks whether the configured search domain list changed
so it can skip redundant persistence. The route_only flag is part of the
D-Bus API and is persisted as a ~ prefix, but flipping an existing domain
between search and route-only only updated in-memory state and left
changed false. The method then skipped link_save_user() and
manager_write_resolv_conf(), leaving stale state until restart.

Reproduced on Ubuntu with systemd 259:

  sudo resolvectl domain enp1s0 route-only-repro.test
  sudo resolvectl domain enp1s0 '~route-only-repro.test'
  resolvectl domain enp1s0
  grep '^DOMAINS=' /run/systemd/resolve/netif/$(cat /sys/class/net/enp1s0/ifindex)
  grep -H route-only-repro.test /run/systemd/resolve/*.conf

Before, resolvectl showed ~route-only-repro.test, but the persisted state
file still showed DOMAINS=route-only-repro.test, and the generated
resolv.conf files still contained search route-only-repro.test.

After this change, the state file is updated to
DOMAINS=~route-only-repro.test and the generated resolv.conf files are
refreshed so the route-only domain is not emitted as a search domain.

Follow-up for: cbf23f3853

(cherry picked from commit 628e8304cc)
2026-09-09 10:08:20 +01:00
zhaoshuang 8b4055c5e2 virt: detect Alibaba Cloud ECS as KVM
Alibaba Cloud ECS instances expose "Alibaba Cloud ECS" in DMI
product_name/bios_vendor and "Alibaba Cloud" in sys_vendor. None of
these match dmi_vendor_table[], so systemd-detect-virt --vm reports
"none" on these machines, even though they run on a KVM-based
hypervisor. This is especially visible on ARM64 instances, where the
CPUID fallback does not exist (x86 only) and /proc/device-tree is
not populated.

Add "Alibaba Cloud ECS" to dmi_vendor_table[], mapping to
VIRTUALIZATION_KVM. The narrower prefix avoids matching the bare
"Alibaba Cloud" sys_vendor value, reducing the false-positive
surface for Alibaba's bare-metal (X-Dragon) instances compared to
the original broader prefix. bios_vendor also carries the same
string on ECS VMs though, so the protection is not complete —
fully resolving the VM-vs-bare-metal ambiguity would need the
SMBIOS BIOS-Characteristics bit check that the Amazon EC2 entry
uses.

The existing "back to DMI" fallback path in detect_vm() reports
"kvm" on both x86 and ARM64.

(cherry picked from commit abffa868a8)
2026-09-09 10:08:20 +01:00
dongshengyuan e3e386e078 core: do not chown fd-passed TTYs
When stdio is passed as file descriptors, the provider of those
descriptors remains responsible for their ownership and access mode.

This keeps run0 in a pipeline from changing the caller's TTY ownership
to the target user, which can otherwise break later terminal access from
the original user.

Fixes #43336

(cherry picked from commit e98c68104b)
2026-09-09 10:08:20 +01:00
dongshengyuan be65853dba detect-virt: continue parsing after --cvm
--cvm is a mode switch, so it must not stop option parsing.
Keep parsing after it to honor later options and validate arguments.

Before: --cvm --quiet printed none; --cvm unexpected-argument printed none.
After: --cvm --quiet prints nothing; extra arguments are rejected.

Follow-up for: 5e0c61f64d

(cherry picked from commit e8454eb14c)
2026-09-09 10:08:20 +01:00
dongshengyuan 1247ecad40 vpick: terminate tries output with newline
tries should be line-oriented like the other --print= modes.
Terminate its counter output with a newline to avoid glued CLI output.

Before: output bytes were 2b 32 2d 33.
After: output bytes are 2b 32 2d 33 0a.

Follow-up for: 9e61ed1115

(cherry picked from commit f40aa8ea1c)
2026-09-09 10:08:20 +01:00
Kai Lüke 5cd131df69 pkcs7: Correct error handling for pkcs7_extract_signers
The lack of error handling for sym_i2d_X509_NAME/sym_i2d_ASN1_INTEGER
means that we get a NULL iovec that will cause an assertion failure
later. The use of regular free for OpenSSL is probably okay but since
the allocator can differ, one should use OPENSSL_free.
Add error handling and do an explicit copy instead of ownership transfer
between possibly different allocators.

(cherry picked from commit a257de2688)
2026-09-09 10:08:19 +01:00
Kakueeen e579d431a5 cgtop: use dynamic width for CPU% column (#43286)
On many-CPU hosts, the CPU% column in systemd-cgtop can exceed the
hardcoded %6.1f format width (e.g. 12769.4% on a 128-CPU host),
causing column overflow and table misalignment.

Follow the existing maxtcpu pattern for CPU Time: dynamically compute
the maximum width of CPU percentage values and use it for both the
header and data rows.

Fixes #39819

(cherry picked from commit 9b75d9bc66)
2026-09-09 10:08:19 +01:00
Daan De Meyer 9ed9c13e78 vmspawn: keep parsing options after the first argument
vmspawn's positional arguments are extra kernel command line arguments
rather than a command to execute, so unlike nspawn or run it has nothing
to shield from the option parser: a kernel command line argument never
begins with a dash. Stopping at the first one only means an option that
follows one is silently taken for another kernel command line argument,
with no diagnostic:

    systemd-vmspawn -i image.raw console=ttyS0 --set-credential=foo:bar

boots with "--set-credential=foo:bar" appended to the kernel command
line instead of setting a credential.

The stop was right when it was written. In 9de3cc1484, vmspawn's first
commit, the trailing arguments were appended to the QEMU command line
directly, so they really were options that had to reach QEMU unparsed.
4291f4461e turned them into extra kernel command line arguments passed
through SMBIOS three weeks later, and updated the man page to say so,
but left the "+" in the optstring behind; 4c778c51c0 and 5ff0ccaf8b then
carried it into the new option parser as
OPTION_PARSER_STOP_AT_FIRST_NONOPTION.

Use the default parser mode, which permutes options ahead of the
arguments; "--" still ends option parsing for an argument that does look
like an option. test-options gains the case this turns on, an option
after a positional argument in the default mode, beside the existing one
for the mode vmspawn no longer uses.

(cherry picked from commit 06cb8fbe61)
2026-09-09 10:08:19 +01:00
Frantisek Sumsal de7c98a4a8 sd-journal: prefer machine ID from the tail entry
compare_boot_ids() (from 262299dccb) skips
the tail entry realtime timestamp ordering when the compared locations
are from two different machines (and hence have two different machine
IDs) - in that case we fall through to a realtime comparison between the
two locations. This works fine except for journals that were imported
via systemd-journal-remote - the machine ID in the journal header is set
to the receiving machine's ID instead of the original one; but the
original machine ID is kept in the _MACHINE_ID= field.

This, however, defeats the check in compare_boot_ids() so we treat the
remote journals as local ones and try to compare the boots based on
their tail entry realtime timestamps, which results in grouping the
entries by their boot ID instead of interleaving them by their realtime
timestamp.

Given that we (transitively) call journal_file_read_tail_timestamp()
from compare_boot_ids(), where we both (re)set the newest_machine_id
field to the machine ID from the journal header and have the tail entry
at hand, let's try to get the _MACHINE_ID= field from the tail entry and
set newest_machine_id to that if it's present and valid. This way
compare_boot_ids() will correctly determine that the entries are from
two different machines and compare_locations() eventually falls through
to the entry realtime check, so we end up with properly interleaved
entries instead of entries grouped by boot ID.

The performance impact should be pretty negligible in vast majority of
cases, as we'd do this only when:
  - opening a new file
  - comparing entries across different boots
  - advancing to a next location

The first case is a one-time thing only per journal. The second and
third case might call this a bit more frequently, but we fall through to
the _MACHINE_ID= parsing only if the journal file got a new entry since
the last check. The most significant impact would be on journals that
are being actively written into, but even then iterating through the
handful of typical fields is negligible compared to what the rest of the
function does. Also, since 8f8ab4bf98 the
journal_file_read_tail_timestamp() calls are ratelimited when walking
through the journal, lessening the impact even further.

Resolves: #34169
(cherry picked from commit ae940730c6)
2026-09-09 10:08:19 +01:00
Daan De Meyer 0f302a879c import: allocate the tar hardlink db before dropping privileges
tar_c() keeps a database of already seen inodes so it can emit hardlinks
rather than storing the same content repeatedly. It prefers to keep that
database on a tmpfs superblock of its own, which is created lazily on the
first hardlinkable inode and which requires CAP_SYS_ADMIN in the caller's
user namespace. Failing that it falls back to a temporary directory under
/var/tmp/.

In the tar child forked off by import_fork_tar_c() the tmpfs branch can
never succeed: the child joins the user namespace, then drops all
capabilities but CAP_DAC_OVERRIDE, and only afterwards runs tar_c(), by
which point CAP_SYS_ADMIN is long gone. Every export therefore silently
took the /var/tmp/ fallback.

That fallback is a problem beyond being the slower path. The child runs
with the transient UID range assigned to the user namespace, so the
directory it creates is owned by a recyclable UID on a file system that
outlives the namespace, which is exactly what the nsresourced BPF-LSM
policy exists to prevent. With that policy in effect the mkdtemp() is
refused and the export fails with EPERM.

Create the database up front, in the window after the user namespace has
been joined and before the capabilities are dropped, and pass the
resulting fd into tar_c(). The database then lives on a superblock that
dies together with the namespace, no persistent file system is touched,
and the sandbox around tar is unchanged.

tar_c() keeps its lazy fallback for the callers that do not pass an fd,
i.e. systemd-dissect and the test, which do not run under a transient
range. The fd is borrowed, not consumed.

Signed-off-by: Daan De Meyer <daan@amutable.com>
(cherry picked from commit 5fd5cd8718)
2026-09-09 10:08:19 +01:00
dongshengyuan 03fc0f3755 bpf: probe generated vmlinux.h for LSM integrity types
Skip restrict-fsaccess when generated BTF lacks its LSM types.

Closes #42740

(cherry picked from commit 33be1e9232)
2026-09-09 10:08:19 +01:00
dongshengyuan a73afe3a26 analyze: skip SVG-only plot metadata for text output
pretty_boot_time() and acquire_host_info() only feed the SVG header.
Do not query them when plot output is rendered as table or JSON.

Follow-up for ff46b2f97c.

(cherry picked from commit 98b52f714c)
2026-09-09 10:08:19 +01:00
dongshengyuan 8c5b180f9f sysupdate: parse PartitionFlags= as hexadecimal
sysupdate.d(5) documents PartitionFlags= as a hexadecimal integer, and
sysupdate patterns format partition flags with PRIx64. The parser used
safe_atou64(), which rejected values such as abcdef.

Reproducer: set PartitionFlags=abcdef in a partition target transfer
and run systemd-sysupdate list with debug logging.

Before: sysupdate logged:
Failed to parse partition flags, ignoring: abcdef

After: the transfer parses cleanly and list emits no partition-flags
parse warning.

Follow-up for: 43cc7a3ef4

(cherry picked from commit 073965ab2f)
2026-09-09 10:08:19 +01:00
dongshengyuan 9e5e6eeb24 sysupdate: remove current partial instance names
48fff6a27a switched filesystem transfers to predictable
.sysupdate.partial.* names, but transfer_remove_temporary()
still only scanned the legacy .# prefix.

Remove the current partial prefix there too. Pending instances
are deliberately left for transfer_vacuum(), so ProtectVersion=
can still spare protected pending versions.

Follow-up for: 48fff6a27a

(cherry picked from commit 09f8b59ac3)
2026-09-09 10:08:19 +01:00
dongshengyuan 4746b8dfd2 sysupdate: honor default MatchPartitionType= for partitions
sysupdate.d(5) documents linux-generic as the default
MatchPartitionType= for partition targets. After 10eaca4159,
omitting the option left partition_type_set false and update failed
before acquiring the image.

Reproducer: create a partition target transfer without
MatchPartitionType= and run systemd-sysupdate update.

Before: update failed with:
Partition type must be set for partition targets.

After: update succeeds on a linux-generic slot and installs the
version, leaving the GPT label as part-v1.

Follow-up for: 10eaca4159

(cherry picked from commit a3ab9d2c1a)
2026-09-09 10:08:19 +01:00
Frantisek Sumsal 9490b3a3b1 test: verify cursor-seeking in the opposite direction as well
(cherry picked from commit 8e762e6b9f)
2026-09-09 10:08:19 +01:00
Frantisek Sumsal 9df40e25f2 sd-journal: prefer exact cursor match after seek
Since 262299dccb compare_locations() falls
through to a realtime-based comparison in compare_boot_ids() if the
compared entries have different boot IDs (and seqnum IDs). This can,
however, cause us to pick up the wrong entry/file on systems with
unreliable/missing RTC, where early-boot entries of a boot might have
earlier realtime timestamp compared to the previous boot.

For example, here's a minimal reproducer consisting of two journal files
containing three entries from two separate boot IDs:

$ journalctl --file one.journal -o export
__CURSOR=s=ba37c62fcacf406aad088166c4963cd1;i=1;b=c220efc91dd440deb89d82c98ddb11a5;m=186a0;t=1e8480;x=39d4323a8e5a248d
__REALTIME_TIMESTAMP=2000000
__MONOTONIC_TIMESTAMP=100000
__SEQNUM=1
__SEQNUM_ID=ba37c62fcacf406aad088166c4963cd1
_BOOT_ID=c220efc91dd440deb89d82c98ddb11a5
NUMBER=1

$ journalctl --file two.journal -o export
__CURSOR=s=9d15197efcc54d8ca5d6e7cce28373e2;i=1;b=686dc35e78f64b6ba126018ab6e8bae9;m=186a0;t=f4240;x=57db1c7bd38b37ed
__REALTIME_TIMESTAMP=1000000
__MONOTONIC_TIMESTAMP=100000
__SEQNUM=1
__SEQNUM_ID=9d15197efcc54d8ca5d6e7cce28373e2
_BOOT_ID=686dc35e78f64b6ba126018ab6e8bae9
NUMBER=2

__CURSOR=s=9d15197efcc54d8ca5d6e7cce28373e2;i=2;b=686dc35e78f64b6ba126018ab6e8bae9;m=30d40;t=2dc6c0;x=3513732159948113
__REALTIME_TIMESTAMP=3000000
__MONOTONIC_TIMESTAMP=200000
__SEQNUM=2
__SEQNUM_ID=9d15197efcc54d8ca5d6e7cce28373e2
_BOOT_ID=686dc35e78f64b6ba126018ab6e8bae9
NUMBER=3

The first file (one.journal) contains a single entry from the first boot
(c220efc91dd440deb89d82c98ddb11a5) and a realtime timestamp of 2s. The
second file (two.journal) contains two entries from the second boot
(686dc35e78f64b6ba126018ab6e8bae9) - the first entry has a realtime
timestamp of 1s (i.e. _earlier_ than the tail timestamp of the first
boot), and the second entry has a realtime timestamp of 3s (i.e.  after
a time correction).

This works fine for forward iteration, but seeking directly to the
second entry is when things go south:

First entry:
$ python3 <<EOF
c = "s=ba37c62fcacf406aad088166c4963cd1;i=1;b=c220efc91dd440deb89d82c98ddb11a5;m=186a0;t=1e8480;x=39d4323a8e5a248d"
from systemd import journal; j = journal.Reader(path="."); j.seek_cursor(c); j.get_next(); print(j.test_cursor(c))
EOF
True

Second entry:
$ python3 <<EOF
c = "s=9d15197efcc54d8ca5d6e7cce28373e2;i=1;b=686dc35e78f64b6ba126018ab6e8bae9;m=186a0;t=f4240;x=57db1c7bd38b37ed"
from systemd import journal; j = journal.Reader(path="."); j.seek_cursor(c); j.get_next(); print(j.test_cursor(c))
EOF
False

That's because:
  - we call sd_journal_seek_cursor(...) and parse it (seqnum, seqnum_id,
    boot_id, realtime, ...)
  - we call sd_journal_next() -> real_journal_next() which iterates over
    all open journal files to find all candidate entries and pick the
    best one
  - for each journal file we call next_beyond_location()
  - for next_beyond_location(one.journal) we get:
    - last_direction is _DIRECTION_INVALID, so we call find_location_with_matches()
    - we fall all the way through to journal_file_move_to_entry_by_realtime()
    - here we try to find the first entry with realtime >= 1s; this file
      has only one entry with realtime = 2s which matches
  - we got a match and new_file is unset, so we set it to one.journal
  - for next_beyond_location(two.journal) we get:
    - we fall through to find_location_with_matches()
    - here we have a matching seqnum, so we call journal_file_move_to_entry_by_seqnum()
  - we got another match but new_file is set, so we call compare_locations(two.journal, one.journal):
    - we fall through to compare_boot_ids()
    - we get the tail timestamps of both journal files and return
      CMP(second boot, first boot) -> CMP(3s, 2s) = 1
  - this bubbles up back to real_journal_next() where found = false, so
    new_file remains one.journal (the wrong journal file)
  - this is followed by set_location(one.journal), which makes the
    following sd_journal_test_cursor() call fail, as the
    boot_id/seqnum_id/... of the current position don't match with the
    cursor

To fix this, let's track the journal file that holds the exact cursor
match, and override the chosen candidate if needed once we process all
open journals.

Resolves: #31516
(cherry picked from commit 868d07d1d4)
2026-09-09 10:08:19 +01:00
dongshengyuan fc9e150b44 repart: fix shallow_join_strv() dedup scan
shallow_join_strv() shallow-copies the first strv into a temporary
array, then uses strv_contains() while appending entries from the
second strv. That helper requires a NUL-terminated strv.

Keep the temporary array terminated before each lookup. After an
append, the previous terminator is overwritten, so postponing the
NULL write until after the loop would let the next strv_contains()
scan past the initialized entries.

This is a correctness fix, not an optimization: it avoids a
potential out-of-bounds read when deduplicating the second strv.

(cherry picked from commit 756399c79e)
2026-09-09 10:08:19 +01:00
dongshengyuan 1ae8e57283 sd-netlink: append multipart messages in O(1)
Commit 52ceba53d3 made multipart replies append in receive
order, but each append walked the chain from the head.

Cache the tail on the chain head, so new fragments can be
linked directly. The visible next-pointer chain and order stay
unchanged.

Follow-up for: 52ceba53d3

(cherry picked from commit 1a43aec562)
2026-09-09 10:08:19 +01:00
Semyon Klintsov b70a242275 dbus: allow FreezeUnit and ThawUnit D-Bus requests (#43292)
This change adds explicit D-Bus policy rules to allow calling the
Freeze and Thaw methods on the systemd interfaces.

- Add D-Bus policy rules for Manager interface FreezeUnit/ThawUnit methods
- Add D-Bus policy rules for Unit interface Freeze/Thaw methods

(cherry picked from commit 0168430d91)
2026-09-09 10:08:19 +01:00
Ludwig Nussel 212a3d1180 pcrlock: honor pcr arg for filtering list-components
(cherry picked from commit 8ab0aaea3c)
2026-09-09 10:08:19 +01:00
Ludwig Nussel 0bad224bde pcrlock: ignore empty device path
Don't mark records with empty device path as invalid. When shim is in
use such records are normal :-/
https://github.com/rhboot/shim/issues/642

(cherry picked from commit 2e7c9a24a1)
2026-09-09 10:08:19 +01:00
Ludwig Nussel 6a42858df5 pcrlock: limit list view to selected PCRs
Makes it easier to inspect specific PCRs

Co-authored-by: Akarithos <277667353+Akarithos@users.noreply.github.com>
(cherry picked from commit 8f631ffe05)
2026-09-09 10:08:19 +01:00
Ludwig Nussel 40d121a533 pcrlock: don't add checkmark for missing component
Co-authored-by: Akarithos <277667353+Akarithos@users.noreply.github.com>
(cherry picked from commit dcde7c750d)
2026-09-09 10:08:19 +01:00
Daan De Meyer e462fc64d6 vmspawn: apply --grow-image= to the ephemeral copy
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.

(cherry picked from commit 2dd07068a3)
2026-09-09 10:08:19 +01:00
dongshengyuan e2bb7a258a userdb: apply UUID filters to group records
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

(cherry picked from commit 0b7c68eb3a)
2026-09-09 10:08:19 +01:00
dongshengyuan 37332cd1f1 test-nss-users: quote empty member lists
Use shell_maybe_quote() with SHELL_ESCAPE_EMPTY when
logging joined group and shadow group member vectors,
so empty vectors are rendered explicitly as "".

Follow-up for: f0d1266821

Follow-up for: 2eaca3ea5f

(cherry picked from commit fd828b4bd6)
2026-09-09 10:08:19 +01:00
Simon de Vlieger c4763e36a9 60-ukify: skip /proc/cmdline in containers
When running inside a container (during image builds or such),
`/proc/cmdline` belongs to the host and would leak the host's root= (or a
faked placeholder like root=/dev/osbuild) into the UKI.

Mirror the same guard that `90-loaderentry.install` has: check
`systemd-detect-virt --container` before falling back to `/proc/cmdline`,
and return an empty cmdline if we are in a container to ensure both
modes (`uki` and `bls`) work the same.

Signed-off-by: Simon de Vlieger <cmdr@supakeen.com>
(cherry picked from commit bdb5d53d4f)
2026-09-09 10:08:19 +01:00
Liu Zheng 1698d456aa repart: pass FileSystemSectorSize= to mkfs.erofs
The erofs branch in make_filesystem() never passed the sector size to
mkfs.erofs, so FileSystemSectorSize= was silently ignored for erofs
partitions. mkfs.erofs defaults to the page size (e.g. 16K on aarch64),
which is too large for some use cases such as UFS with 4K blocks.

Pass -b <sector_size> to mkfs.erofs, but only when it is smaller than the
page size, since mkfs.erofs defaults to the page size and rejects larger
values. This matches how the other supported filesystems (xfs, ext4,
btrfs, f2fs, vfat) already handle sector_size.

Fixes #43238

(cherry picked from commit 23058066e6)
2026-09-09 10:08:19 +01:00
Frantisek Sumsal 52ad118cc3 sysusers: gracefully handle existing group in /etc/gshadow
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.

(cherry picked from commit 36a9875c1d)
2026-09-09 10:08:19 +01:00
Andre Eikmeyer 6d1ba9b6f5 udev: derive path ID for PNP devices from ACPI firmware node
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.

(cherry picked from commit 6044c31038)
2026-09-09 10:08:19 +01:00
Eric Boucher af4f78ae07 sd-json: encode pidref fd_id as unsigned
PidRef.fd_id is uint64_t.

(cherry picked from commit bc9870a923)
2026-09-09 10:08:19 +01:00
Armaan Sandhu 893d65df6b string-util: don't miss ANSI sequence at the very end in previous_ansi_sequence()
The backwards scan started at offset length-3, so the last position at which a
sequence can begin, length-2, was never examined. CSI sequences are at least
three bytes long and were thus unaffected, but two byte Fe sequences (ESC
followed by 0x40…0x5F) terminating the examined slice were missed.

ellipsize_mem() calls this to figure out whether a sequence ends exactly at the
current position, so that it can be skipped over, which is precisely the case
that was broken: such a sequence was instead counted as two visible cells and
copied through as text, and the string was ellipsized more aggressively than
requested. For example ellipsize("🐱🐱\x1bM🐱🐱\x1bM", 5, 0) returned a three
cell wide string rather than the five cells asked for.

(cherry picked from commit 2bb179632f)
2026-09-09 10:08:19 +01:00
Michal Sekletar db1308b9e1 core/service: ignore SELinux label errors in permissive mode
Return -ENODATA instead of the raw error when SELinux is permissive, so
the caller falls back to the default label. This is needed to allow
relabeling service to start on systems where file contexts maybe
invalid.

(cherry picked from commit a3fee62707)
2026-09-09 10:08:19 +01:00
Yu Watanabe 21625647e7 core: quote each exec directory entry when serializing
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.

(cherry picked from commit 71a2af43f9)
2026-09-09 10:08:19 +01:00
Daniel28972897 1d59c57ed2 libudev: cache the errno for a failed parent lookup
udev_device_get_parent() caches the result of device_new_from_parent()
in udev_device->parent on first call. device_new_from_parent() sets
errno correctly via return_with_errno() when it fails, so a caller
gets the right errno on the first invocation. But on any subsequent
call for the same object, the cached NULL is returned directly without
recomputing anything, so errno reflects whatever happened in between
rather than the original failure reason.

Cache the errno alongside the parent pointer (new parent_errno field,
zero-initialized like the rest of the struct via udev_device_new()'s
compound literal) and restore it whenever the cached parent is NULL.

(cherry picked from commit b2cd050073)
2026-09-09 10:08:19 +01:00
Frantisek Sumsal bd5fd08cf9 alloc-util: make malloc_sizeof_safe() compatible with clang's _FORTIFY_SOURCE=3
Turns out that clang's interprocedural analysis is quite smart and can
look through our expand_to_usable() trick, which then causes crashes
with _FORTIFY_SOURCE=3.

clang's interprocedural analysis can see that expand_to_usable() simply
returns its first argument, so it replaces all uses of the return value
with that argument (the original realloc() result). This effectively
bypasses expand_to_usable()'s alloc_size attribute, causing the fortify
check to use the (smaller) size from realloc() instead, which eventually
leads to a false-positive buffer overflow:

$ build/test-varlink-idl
/* test_parse_format */
...
*** buffer overflow detected ***: terminated
Aborted                    (core dumped) build/test-varlink-idl

This is not an issue with gcc (at least not yet), since gcc sees
expand_to_usable() as an opaque user-defined allocation-like function
and simply trusts the alloc_size attribute that comes with it.

To fix this, let's add a simple no-op barrier to malloc_sizeof_safe()
that clobbers the input pointer, which causes
__builtin_dynamic_object_size() to return (size_t)-1 - this is
interpreted as an "unknown" size by the following fortify check which is
then skipped instead of triggering the assertion.

Similarly, test-alloc-util now doesn't call malloc_usable_size()
directly but instead goes through malloc_sizeof_safe(), so it's also
guarded by the barrier.

This follows the already established pile of similar workaround for the
same class of issues we encountered with gcc, namely [0], which prompted
[1], that was later reverted in [2], and then followed by another couple
of fixes in [3] and [4].

Resolves: #43178

[0] https://github.com/systemd/systemd/issues/22801
[1] https://github.com/systemd/systemd/commit/0bd292567a543d124cd303f7dd61169a209cae64
[2] https://github.com/systemd/systemd/commit/2cfb790391958ada34284290af1f9ab863a515c7
[3] https://github.com/systemd/systemd/commit/7929e180aa47a2692ad4f053afac2857d7198758
[4] https://github.com/systemd/systemd/commit/4f79f545b3c46c358666c9f5f2b384fe50aac4b4

(cherry picked from commit a56d7db48f)
2026-09-09 10:08:19 +01:00
r-vdp 8a1063e5ae core: postpone D-Bus queue dispatch until the API bus is set up
Since 1166f4472d ("core/dbus: do not block the manager on GetId during
bus (re-)connection") 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 re-added, so bus_foreach_bus() skipped the
API bus and queued messages were lost for subscribers. In particular
the one-shot Reloading(false) signal was never sent, and clients that
wait for it to detect that a reexec finished timed out.

Track whether bus_setup_api() has run for the current API bus
connection, and postpone dispatching the queue until then.

(cherry picked from commit 266b3e5021)
2026-09-09 10:08:19 +01:00
Eric Curtin 630e384bb5 test-bpf-restrict-fs: skip if manager startup fails due to lack of privileges (#43202)
test-bpf-restrict-fs.c creates a Manager with RUNTIME_SCOPE_SYSTEM, which
tries to set up the real system runtime directory hierarchy (e.g. create
/run/systemd/), and that requires privileges the test process may not
have (e.g. unprivileged sandboxed builders such as OBS).

Previously this was masked because bpf_restrict_fs_supported() did a
trial open/load/attach of the BPF program itself, which also requires
elevated privileges and so failed first, causing the test to skip
before ever reaching manager_new()/manager_startup(). Since
bpf_restrict_fs_supported() no longer does that trial load, the test
now reaches manager_new()/manager_startup() in these unprivileged
environments and hard-fails instead of skipping, e.g.:

  Assertion failed: Expected "manager_startup(m, NULL, NULL, NULL, NULL)"
  to succeed, but got error: -13/EACCES

Use the same manager_errno_skip_test() pattern already used by other
tests (test-engine.c, test-execute.c, test-path.c, ...) to skip
gracefully when manager_new() or manager_startup() fail due to missing
privileges, instead of asserting.

Follow-up for c99678eeda.

(cherry picked from commit d47b07d05f)
2026-09-09 10:08:19 +01:00
Markus Boehme 40d1c21160 update-utmp: shorten comm on boot/shutdown
audit_log_user_comm_message from libaudit 4.2 rejects comm arguments
that exceed the kernel's comm limit of 15 characters with EINVAL. The
hard-coded "systemd-update-utmp" exceeds this by 4 characters. Shorten
it to "update-utmp" instead.

(cherry picked from commit b8968c4921)
2026-09-09 10:08:19 +01:00
dongshengyuan cbe4daf842 test: cover DeviceAllow parsing
Add unit coverage for config_parse_device_allow().

Verify valid device paths and subsystem patterns, invalid
specifiers and rights, default permissions, and reset handling.

Follow-up for: 20d52ab60e

(cherry picked from commit 385623fee3)
2026-09-09 10:08:19 +01:00
Eric Curtin a0d90c77d4 core/bpf-restrict-fs: avoid loading the LSM BPF program twice at boot
bpf_restrict_fs_setup() is always called right after a successful
bpf_restrict_fs_supported(true) probe (see manager_setup() in
manager.c). Previously the probe independently opened, sized and
kernel-verifier-loaded the BPF object via prepare_restrict_fs_bpf(),
then did a trial LSM attach/detach via bpf_can_link_lsm_program() to
confirm the program *could* attach, and threw the whole object away
-- only for bpf_restrict_fs_setup() to build and verifier-load an
identical one from scratch again for the real, permanent attach.

The trial attach in the probe is redundant: if BPF_LSM_MAC attach
isn't actually usable (e.g. no BPF trampoline support on the running
architecture/kernel), bpf_restrict_fs_setup()'s own
sym_bpf_program__attach_lsm() call will simply fail, and that failure
is already logged and handled gracefully by its caller in manager.c
(logged as a warning, systemd continues without RestrictFileSystems=
enforcement). So bpf_restrict_fs_supported() only needs to check
whether the BPF LSM hook is enabled in the kernel at all
(lsm_supported("bpf")); it doesn't need to open/load/attach the BPF
program itself. Drop that from the probe, so the program is opened,
sized and verified by the kernel exactly once per boot instead of
twice.

Since the probe no longer verifies that the LSM BPF program can
actually attach, test-bpf-restrict-fs.c can no longer rely on
bpf_restrict_fs_supported(true) alone to skip on kernels/architectures
where the hook is listed but the real attach fails (e.g. missing BPF
trampoline support). Have the test also check m->restrict_fs after
manager_startup() and skip if the program never got attached, instead
of proceeding to hard-fail the enforcement assertions.

(cherry picked from commit c99678eeda)
2026-09-09 10:08:19 +01:00
Yu Watanabe e18d814db6 locale: update Context only on success
Do not clear Context.vc, Context.x11_from_vc, and Context.x11_from_xorg
on errors like ENOMEM or so.

This does not change behavior on success.

(cherry picked from commit f2718dee52)
2026-09-09 10:08:19 +01:00
Liu Zheng 66681b7ff3 localed: normalize empty X11 option values to NULL after parsing
x11_read_data() parses an 'Option "XkbVariant" ""' line in
00-keyboard.conf with strv_split_full(..., EXTRACT_UNQUOTE), which turns
the empty quoted value into a non-NULL empty string rather than NULL.
Since 812aa57d2c ("string-util: beef up string_is_safe()") an empty
string is rejected by string_is_safe() unless STRING_ALLOW_EMPTY is
passed, so x11_context_is_safe() now refuses such a context and
x11_context_verify() discards the whole thing. As a result "localectl
status" reports "X11 Layout: (unset)" even though the file names a valid
layout, and compositors reading org.freedesktop.locale1 (e.g. the SDDM
greeter) fall back to the us layout.

Introduce x11_context_normalize(), suggested by @lionheartyu, which
converts empty strings to NULL while freeing the heap allocation —
unlike x11_context_empty_to_null() which only NULLs the pointer without
freeing. Call it at the end of x11_read_data(), before
x11_context_verify(), so empty option values are treated as unset. This
also keeps x11_context_equal() comparisons consistent with the setter
path (method_set_x11_keyboard) and vconsole_read_data(), which both
store NULL for empty values.

Fixes #43007

(cherry picked from commit 0d92898e85)
2026-09-09 10:08:19 +01:00
Daniel28972897 0e0606b5e1 udev: probe_superblocks: return a real negative errno on failure
Previously, probe_superblocks() forwarded blkid_do_fullprobe()'s and
blkid_do_safeprobe()'s raw return code on error, which is just -1 with
no errno attached. The caller passes this value straight to
log_device_debug_errno() with %m, so a generic probing failure always
printed strerror(-1) regardless of what actually went wrong.

Convert the -1 error case to a proper negative errno via
errno_or_else(), matching the pattern used elsewhere in this file. The
'nothing found' (1) and success (0) return values are unchanged.

(cherry picked from commit ecc8c69eca)
2026-09-09 10:08:19 +01:00
Eric Curtin cef8da246d mount-util: don't trigger automounts when cloning submounts
get_sub_mounts() clones each submount of the given prefix with
OPEN_TREE_CLONE. The kernel resolves the path of an OPEN_TREE_CLONE
with LOOKUP_AUTOMOUNT, i.e. if the submount is an autofs automount
point that has not been triggered yet, cloning it forces the automount
to trigger, and open_tree() blocks until the automount request has
been served.

This is particularly problematic during boot: setting up a private
/proc for the first sandboxed service (e.g. systemd-userdbd.service,
which uses ProtectProc=invisible) clones the submounts of /proc, which
include PID 1's own /proc/sys/fs/binfmt_misc automount point. The
executor then blocks until PID 1 gets around to dispatching the
resulting proc-sys-fs-binfmt_misc.mount job, which competes with the
ongoing boot transaction. On a Fedora 44 VM this delayed
systemd-userdbd.service by ~0.9s, and with it every early-boot NSS
user/group lookup that ends up in nss-systemd's varlink queries — most
importantly systemd-tmpfiles-setup-dev-early.service, which
systemd-udevd.service is ordered after, stalling the whole boot
critical path:

  [2.131846] proc-sys-fs-binfmt_misc.automount: Got automount request
             for /proc/sys/fs/binfmt_misc, triggered by 323 ((systemd-userd))
  [2.943574] Mounting proc-sys-fs-binfmt_misc.mount...
  [2.968507] Mounted proc-sys-fs-binfmt_misc.mount.

Triggering the automount here also defeats its purpose, since
binfmt_misc ends up mounted on every boot even if nothing ever
accesses it.

Pass AT_NO_AUTOMOUNT so that untriggered automount points are cloned
as they are instead.

Before (Fedora 44 VM, 4 vCPUs):
  Startup finished in ... + 2.559s (userspace)
    1.058s systemd-tmpfiles-setup-dev-early.service
     938ms systemd-userdbd.service

After:
  Startup finished in ... + 1.582s (userspace)
     137ms systemd-tmpfiles-setup-dev-early.service
      22ms systemd-userdbd.service
(cherry picked from commit 0494ced5a9)
2026-09-09 10:08:19 +01:00
Mike Yuan 15ef9c2022 iovec-util: enclose macro arg in ()
Follow-up for 56f3ae9292

(cherry picked from commit 5306dca35f)
2026-09-09 10:08:19 +01:00
dongshengyuan 59aabb1889 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.

Repro:
build/systemd-veritysetup attach testvol /dev/null /no/such \
    0000000000000000000000000000000000000000000000000000000000000000 \
    tpm2-measure-nvpcr=no,root-hash-signature=relative

Before: root-hash-signature=relative was skipped, and execution continued
to the missing block-device error.

After: root-hash-signature=relative is parsed and rejected. Invalid NvPCR
names take the same continue path.

Follow-up for: 85d7fb2247

(cherry picked from commit 99c6f01321)
2026-09-09 10:08:19 +01:00