7799 Commits
Author SHA1 Message Date
Lennart Poettering 6fb749e6aa pidfd-util: flush cached pidfd ID in child after fork()/clone()
pidfd_get_inode_id_self_cached() keys its cache on the PID only. This is
not sufficient to detect that we are in a different process: if we are
PID 1 of a PID namespace and fork off a child into a new PID namespace,
the child will be PID 1 too, and would then continue to use the parent's
cached pidfd ID. For example, a nested nspawn running as PID 1 would fill
the cache in sd_listen_fds() (which checks $LISTEN_PIDFDID), and then
pass its own ID to the payload, which would in turn drop the fds passed
to it.

Fix this by explicitly flushing the cache from reset_cached_pid(), i.e.
the pthread_atfork() child handler and from raw_clone(), the same way we
already flush the cached PID.

Also add a test that covers the PID 1 → PID 1 case.

(cherry picked from commit b7ab919eba)
(cherry picked from commit 69af528fed)
2026-11-05 14:48:10 +00:00
Lennart Poettering 05383201c4 sysctl-util: add sysctl_read_int() and friends
Quite a few places read an integer sysctl and parse it manually, each in
its own way: some via read_one_line_file() on a hand-built /proc/sys/
path, some via sysctl_read() followed by safe_atoX(), parse_boolean() or
even a plain string comparison, and parsing into various integer types
(int, unsigned, unsigned long, uint64_t).

Add three helpers, sysctl_read_int(), sysctl_read_unsigned() and
sysctl_read_uint64(), plus a sysctl_read_uint32() alias for
sysctl_read_unsigned() for places where the value is stored in a
strictly 32-bit type (such as uid_t/gid_t), and a sysctl_read_lu()
wrapper that maps to sysctl_read_uint32() or sysctl_read_uint64(),
depending on the size of unsigned long. Then port all these users over:

  - kernel.cap_last_cap (cap_last_cap() and test-capability-util)
  - kernel.random.poolsize (random_pool_size())
  - fs.nr_open (read_nr_open())
  - kernel.pid_max and kernel.threads-max (procfs_get_pid_max(),
    procfs_get_threads_max())
  - net.unix.max_dgram_qlen (PID 1's bump_unix_max_dgram_qlen())
  - kernel.overflowuid and kernel.overflowgid (PID 1's taint logic)
  - fs.protected_hardlinks (tmpfiles)
  - kernel.apparmor_restrict_unprivileged_userns (test-execute)

Note that fs.protected_hardlinks is read as integer instead of boolean
now, since the kernel exposes it as such (even if it only accepts 0 and
1 for now). This matters even more for its siblings fs.protected_regular
and fs.protected_fifos which accept 0, 1 and 2, and hence cannot be
parsed via parse_boolean().

PID 1 keeps tainting itself if kernel.overflowuid/kernel.overflowgid
cannot be parsed, as before, but not if they cannot be read at all.

(cherry picked from commit 9ccd43072e)
(cherry picked from commit 334c06a1ba)
2026-11-05 14:48:10 +00:00
Nayyar ccf8bb6ed0 device-nodes: always NUL-terminate encode_devnode_name() output
When the encoded string does not fit, encode_devnode_name() returned
-EINVAL without terminating the output buffer. The callers in usb_id,
ata_id and scsi_id ignore the return value and add the buffer as a
property. With a USB product or manufacturer string that fills the
256-byte buffer in usb_id exactly (e.g. 85 three-byte UTF-8 characters
followed by one ASCII letter) udevd then reads past the end of the stack
array and copies adjacent stack bytes into ID_MODEL_ENC/ID_VENDOR_ENC.

Leave room for the NUL in each length check and terminate the truncated
output on failure. Output for strings that fit is unchanged.

(cherry picked from commit b2501ee485)
(cherry picked from commit 2385bdb61c)
2026-11-05 14:48:10 +00:00
Michael Vogt 2201147451 fd-util: dup regular files on stdout instead of reopening them
ptyfwd and varlinkctl reopen stdin/stdout to set O_NONBLOCK on
the then private open file description. The issue with this
code is that it does not work for the following scenario:

   { echo header; systemd-run --pty echo hello; echo trailer; } > out

Because the shell's file offset is not advanced (only the one of
our private open file description is) this ends up with just
"header" and "trailer" in the "out" file.

So we add a new fd_dup_or_reopen_stdio_nonblock() that simply dups
regular files and block devices on stdout, which shares the file
position (and O_APPEND) with the shell. Those never block, so we
don't need O_NONBLOCK on them. Everything else, and stdin in
particular, is still reopened privately via the existing
fd_reopen_propagate_append_and_position(): we read ahead from stdin
whether or not the child consumes the data, so a shared offset would
eat the input the shell still expects to read itself, as in

   while read -r l; do systemd-run --pty ...; done < list

This also consolidates the flags O_CLOEXEC|O_NOCTTY|O_NONBLOCK into
the helper instead of passing them from every caller. As all callers
now go through fd_dup_or_reopen_stdio_nonblock(),
fd_reopen_propagate_append_and_position() becomes private to
fd-util.c.

This also adds a unit test for the helper and integration tests for
systemd-run --pty and varlinkctl call --upgrade with a regular file
on stdout, and for systemd-run --pty inside a shell read loop.

Follow-up for a33034e714

Addresses:
https://github.com/systemd/systemd/pull/44374#discussion_r4364836266
(cherry picked from commit 34d8764144)
(cherry picked from commit 4257987646)
2026-11-05 14:48:10 +00:00
khromenokroman fa70856e77 build-path: do not run broken callout binaries via /bin/sh
invoke_callout_binary() falls back to execvp(), which runs a truncated
(e.g. empty) callout binary as an empty shell script that exits
successfully, instead of failing with ENOEXEC. For systemd-userdbd this
means that every worker exits immediately with success, and the actual
reason is never logged.

Let's use execvp_safe() instead, so that ENOEXEC is propagated to the
caller and logged.

(cherry picked from commit e7df404c1f)
(cherry picked from commit 0d17230836)
2026-11-05 14:48:09 +00:00
khromenokroman 8e49974e6c process-util: add execvp_safe() and execvpe_safe()
When execve() fails with ENOEXEC, glibc's execvp() and execvpe() retry the
file as a shell script via /bin/sh. Hence a truncated (e.g. empty) binary
is run as an empty shell script and exits successfully, instead of
failing with ENOEXEC. musl doesn't do this.

Let's add variants that look up the file in $PATH via find_executable()
and then call execve(), so that ENOEXEC is propagated to the caller.

(cherry picked from commit b3ec1fec6d)
(cherry picked from commit 68fa520bf5)
2026-11-05 14:48:09 +00:00
Semyon ee436659bf chase: don't force a sync with the server in chase_statx() unless needed
chase_statx() asks for STATX_MODE and STATX_UID of every path component. For the
root of a mounted network file system such as NFS, the kernel has to make sure
these are up to date, i.e. it asks the server. If the server is unreachable,
statx() blocks.

Add a new CHASE_NO_SYNC flag. When set, chase() passes AT_STATX_DONT_SYNC to
statx(), preventing the kernel from querying the server. This is safe for
callers that only care about the canonical path and do not base security
decisions on the freshness of st_uid or st_mode.

Use this flag in fstab-generator, as it only needs to resolve the path and
does not inspect the attributes of the mount point.

libmount made the same change for the same reason:
https://github.com/util-linux/util-linux/commit/42e141d20505a0deb969c2e583a463c26aadc62f

Fixes: #44396
(cherry picked from commit 36344cd1f1)
(cherry picked from commit 3d7e6e1388)
2026-11-05 14:30:22 +00:00
Jeremy Linton 4298e24072 confidential-virt: Update CCA detection to use new sys entry
Upstream (*) linux has now settled on /sys/firmware/cca/realm_guest
as the detection point for CCA guests and has removed the platform
device previously used to trigger this.

This patch adds the additional realm_guest check while leaving the
arm-cca-dev check in place for the time being. At some point in the
near future it should be safe to drop that check.

* https://lkml.org/lkml/2026/9/23/2061

Signed-off-by: Jeremy Linton <jeremy.linton@arm.com>
(cherry picked from commit 2336319d6a)
(cherry picked from commit 30a864df19)
2026-11-05 14:30:22 +00:00
Daan De Meyer fc6aff1c1c chase: keep the fd of a "." result on a trailing ".."
With CHASE_PARENT, chase.h says that the fd refers to the result itself if
the result is "." or the root. chaseat() does that for "." itself, but not
when the path ends in ".." and resolves to the starting directory, for
example "b/.." with dir_fd pointing to "a". Then it goes up two levels and
returns an fd for the parent of "a", while the path is ".". So
chase_and_open_parent_at() returns the filename "." with an fd for the
parent of "a". A caller that opens the filename in that fd ends up in the
parent of "a" instead of in "a".

Let's return fd_parent as is when the result is "." or "/", the same way we
do when fd_parent is the root directory. The CHASE_MAX_MODE test used
"c/.." from "b" to reach a grandparent that the walk never entered. We
switch it to ".." from "c", which still goes up two levels.

(cherry picked from commit 154df43621)
(cherry picked from commit 640e213778)
2026-11-05 14:30:22 +00:00
Daan De Meyer 6453f42f83 chase: don't leave the root on a trailing ".."
When a path ends in "..", chaseat() with CHASE_PARENT goes up one more
level, because its fd at that point refers to the resolved path and not to
its parent. It opens ".." of the parent without checking whether the parent
is the root directory, unlike the ".." handling right above it. So if the
path resolves to the root directory, for example "a/.." or a symlink "a/up"
pointing to "..", chaseat() returns an fd for the directory above the root.
systemd-firstboot --root= resolves /etc/hostname this way, so an image where
/etc/hostname points to ".." gives it a directory fd outside the image.

Let's treat the root directory as its own parent in that case, the same way
we already do for "/..". We move the inode and mount comparison with the
root into a helper, so that both ".." checks use the same test.

(cherry picked from commit 74cfd2dc55)
(cherry picked from commit ebe4c9cbb2)
2026-11-05 14:30:22 +00:00
Zbigniew Jędrzejewski-Szmek 3e3e562585 basic/assert-util: add _unused_ annotation to silence a warning
clang-23.1.3-1.fc45.x86_64 says:
[18/2253] Compiling C object src/basic/libbasic.a.p/assert-util.c.o
../src/basic/assert-util.c:13:21: warning: variable 'log_abort_msg' set but not used [-Wunused-but-set-global]
   13 | static char _used_ *log_abort_msg = NULL;
      |                     ^
1 warning generated.

The variable is intended to be used from outside of the program, so
annotate it appropriately. Also add an explanatory message.

(cherry picked from commit aac30c4f78)
(cherry picked from commit 572f70cc02)
2026-11-05 14:30:22 +00:00
Zbigniew Jędrzejewski-Szmek f534479afa basic/dlfch-util: adjust DLSYM_PROTOTYPE to avoid issues with _const_
clang-23.1.3-1.fc45.x86_64 doesn't like the attribute:
[316/2253] Compiling C object systemd-resolved.p/src_resolve_resolved-util.c.o
In file included from ../src/resolve/resolved-util.c:8:
../src/shared/idn-util.h:17:49: warning: '__const__' attribute only applies to functions [-Wignored-attributes]
   17 | extern const char *(*sym_idn2_strerror)(int rc) _const_;
      |                                                 ^
../src/basic/../fundamental/macro.h:81:32: note: expanded from macro '_const_'
   81 | #define _const_ __attribute__((__const__))
      |                                ^
1 warning generated.

OTOH, if it is dropped, gcc fails without it. Adjust the DLSYM_PROTOTYPE
macro to use & instead of *, and then DLSYM_PROTOTYPE can be also used in
for one more function in shared/idn-util.[ch]. Both compilers accept the
new form.

(cherry picked from commit b31b557ecc)
(cherry picked from commit 98b1869308)
2026-11-05 14:30:22 +00:00
Christian Brauner 129a0649c3 chase: refuse O_CREAT on a path that resolves to a directory
chase_and_openat() ends up with an empty name to open when the caller
asks for the parent with CHASE_PARENT, or when the path resolves to the
root, a symlink to it in an untrusted tree for example. chase_and_open()
does for CHASE_PARENT. xopenat_full() then reopens the directory through
fd_reopen() which asserts that O_CREAT isn't set. So a caller passing
O_CREAT aborts instead of getting an error. open(2) refuses to create a
directory with EISDIR. Do the same before we get to the assert.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
(cherry picked from commit 1f2c6ec8b5)
(cherry picked from commit 23f92c138c)
2026-11-05 14:30:22 +00:00
Skye Soss 5e86b30826 cgroup-util: add cg_pidref_get_path_shifted helper
Completes the cgroup utility pidref API.

(cherry picked from commit b013da92d2)
(cherry picked from commit ac80373b25)
2026-11-05 13:59:45 +00:00
Luca Boccassi 0d41212518 namespace-util: handle missing nsfs compat ioctls
Recognize our own namespace by inode when NS_GET_NSTYPE returns ENOTTY
on pre-6.3 compat kernels, while preserving errors for other namespaces.

Fixes test-namespace-util failure with 32bit userland on 64bit kernel:

/* test_namespace_enter_nothing */
Successfully forked off 'test-ns-enter-nothing' as PID 31761.
Skipping PR_SET_MM, as we don't have privileges.
src/test/test-namespace-util.c:72: Assertion failed: Expected "namespace_enter(-EBADF, -EBADF, -EBADF, userns_fd, -EBADF)" to succeed, but got error: -25/ENOTTY
'test-ns-enter-nothing' terminated by signal ABRT.
src/test/test-namespace-util.c:62: Assertion failed: Expected "pidref_safe_fork( "test-ns-enter-nothing", FORK_LOG|FORK_WAIT|FORK_DEATHSIG_SIGKILL, NULL)" to succeed, but got error: -71/EPROTO

https://salsa.debian.org/systemd-team/systemd/-/jobs/10491555
(cherry picked from commit d7d86164a4)
(cherry picked from commit 2fe14ab846)
2026-11-05 13:59:44 +00:00
umutcagand 2525d89372 shared: share exec directory property names and format flags
Move the symlink property lookup alongside the directory type lookup so
both the manager and clients use the same mapping. Cover it in test-tables.

Add a flag formatter taking the full 64-bit D-Bus value: zero has no
suffix, the public read-only flag is ro, and other bits return NULL so
display callers can retain their numeric value. Test high and private
bits without a live bus connection.

(cherry picked from commit ea389fd77d)
(cherry picked from commit 4eb8b7d8c7)
2026-11-05 13:58:25 +00:00
Achill Gilgenast 0c4fc1405f locale-util: Update musl's default locale search paths
musl libc got a overhaul in it's locale support funded by NLnet,
as part the default search paths were set to search in
/etc/musl/locale, /usr/share/musl/locale, /share/musl/locale/.

Also change the behaviour on musl to search for multiple directories,
and include the old path as a fallback.

See https://www.openwall.com/lists/musl/2026/10/02/1

(cherry picked from commit 0f655ffc7b)
(cherry picked from commit 934e31fac0)
2026-10-17 12:01:02 +01:00
Skye Soss ebeab96de9 namespace-util: don't require privileges when there's nothing to enter
When namespace_enter_full() is called with namespaces that the current
process is already a part of, skip the setns() call on the user
namespace. This allows the function to succeed without CAP_SYS_ADMIN.

Follow-up for 211c3c9d41

(cherry picked from commit 27b1fd3a1e)
(cherry picked from commit 5cfe544a1a)
2026-10-17 12:01:02 +01:00
Lennart Poettering e2912d2663 compress: don't treat Z_BUF_ERROR from inflate() as fatal
Since dac2b39405 ("compress: strictly check if stream finished on
decompress") the gzip branch of decompressor_push() no longer loops
"while (avail_in > 0)", but loops until inflate() can make no further
progress, so that we can properly detect truncated streams and trailing
garbage. This means inflate() may now be called with no input left.

If a previous iteration happened to fill the output buffer exactly
(avail_out == 0) while also consuming the last input byte (avail_in ==
0), we go around once more, and inflate() then has nothing to do and
returns Z_BUF_ERROR. This is documented zlib behaviour and not an error
condition: it merely indicates that no progress was possible. However,
we treated anything other than Z_OK/Z_STREAM_END as a corrupted stream
and returned -EBADMSG, hence valid gzip data was refused whenever the
input chunk boundaries happened to line up with the output buffer size
in this way.

Accept Z_BUF_ERROR if all input has been consumed, and let the existing
avail_in/avail_out check that follows decide whether we are done with
the current input, or whether the stream is truncated (in the size == 0
finalization case). If input is left, Z_BUF_ERROR still results in
-EBADMSG, so that we never loop forever without making progress.

Whether the boundary condition is hit depends on the data being
decompressed and on how it is chunked. This showed up in CI here:

https://github.com/systemd/systemd/actions/runs/38428217767/job/115403151687?pr=44163

Add a test that decompresses incompressible data split at every offset
around COMPRESS_PIPE_BUFFER_SIZE, which reliably triggers the condition
for gzip, and checks the other algorithms the same way for good measure.

Follow-up for: dac2b39405

(cherry picked from commit 4128feb9f2)
(cherry picked from commit 99a31af26e)
2026-10-17 12:01:01 +01:00
Yu Watanabe 53ff86df8c memfd-util: introduce memfd_set_sealed_gracefully()
(cherry picked from commit d2810b5c79)
(cherry picked from commit b39f5cfa2e)
2026-10-17 12:01:01 +01:00
Yu Watanabe 10feb4bcea memfd-util: mention that F_SEAL_EXEC is supported since kernel v6.3
(cherry picked from commit f183651070)
(cherry picked from commit 9ad97c1f2e)
2026-10-17 12:01:01 +01:00
Daan De Meyer b2f73ebade tree-wide: use pwrite_full() for positioned writes
Several places write a buffer at an offset with a single pwrite() or
pwritev(), and fail if the call writes less than the whole buffer. The
lock file of a dynamic user is then truncated to the bytes written, so
it keeps a cut-off user name. memfd_new_and_seal() fails for data larger
than one write() call accepts, which is about 2 GiB on Linux. The other
callers return -EIO, and lose the error that another write would report,
such as ENOSPC.

Use pwrite_full(), which keeps writing until the whole buffer is written
or a write fails. The lock file of a dynamic user is written from two
buffers and uses pwritev_full() instead.

repart writes the verity signature with lseek() and loop_write(). It
uses pwrite_full() as well, which does not move the file offset.

(cherry picked from commit 2fd17cb9ea)
(cherry picked from commit ef565d78af)
2026-10-17 12:01:01 +01:00
Daan De Meyer a9b309fc26 io-util: add pwritev_full() and pwrite_full()
loop_write() writes one buffer at the file position. To write at an
offset, callers call pwrite() or pwritev() directly and handle short
writes themselves.

Add pwritev_full(), which writes all iovecs at an offset, retries on
EINTR and short writes, splits batches larger than IOV_MAX, and reports
the number of bytes written, also on failure. Add pwrite_full(), which
does the same for one buffer.

(cherry picked from commit 4d36be491a)
(cherry picked from commit 8b9fb1bbec)
2026-10-17 12:01:01 +01:00
Daan De Meyer 4807a13545 stat-util: fix null_or_empty_path_with_root() below a root
null_or_empty_path_with_root() passes a path that already includes the root
to chase_and_stat() with CHASE_PREFIX_ROOT, so the root is added twice. It
also only recognizes /dev/null when the path itself is /dev/null. Below a
root, a symlink to an empty file therefore fails with ENOENT. A symlink to
/dev/null fails the same way when the image has no /dev/null. Resolve the
symlink inside the root without requiring the target to exist, and stat the
target through the O_PATH fd that chase() returns.

chase() accepts ret_fd together with CHASE_NONEXISTENT and sets the fd to
-EBADF when the target is missing. Its comment says that ret_fd cannot be
combined with CHASE_NONEXISTENT. Fix the comment.

(cherry picked from commit f2b0c29597)
(cherry picked from commit b7ae93f093)
2026-10-17 12:01:00 +01:00
Tobias Hunger 8d72554b27 fix(xattr): Handle labels without value
I stumbled over this with the very first image I tried:

> sudo systemd-pull oci --system quay.io/toolbx/arch-toolbox:latest

That failed with a label "user.overlay.origin" with a value of NULL and size 0.

This one-line change allows value == NULL if size == 0 when setting xattrs.

(cherry picked from commit 885fe07ee3)
(cherry picked from commit c74f6f85ed)
2026-10-17 12:01:00 +01:00
Robert Beckett c930a914f8 terminal-util: don't query terminal size on virtual consoles
terminal_fix_size() queries the terminal for its dimensions with CSI 18
and DSR. On a virtual console that is pointless: the kernel renders the
screen itself, so TIOCGWINSZ is already authoritative.

It is also slow. PID 1 queries /dev/console from console_setup(), and
writes there go through redirected_tty_write(), so while plymouth holds a
TIOCCONS redirect the query never reaches the VT and no reply is ever
generated. Each query then waits out CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC.
Previously the write failed immediately with EAGAIN — the kernel forces
O_NONBLOCK on /dev/console — so the query was abandoned at no cost; since
ba10d789a8 it is retried until the timeout instead. On a Steam Deck
booting with console=tty1 and plymouth the two queries cost 667ms, about a
quarter of userspace boot time, and no log level shows it because main()
runs console_setup() between log_close() and log_open().

So skip the query on a virtual console and keep the dimensions the kernel
gave us, reporting success rather than an error: declining to ask is not a
failure, and the "dimensions already match" path below returns 0 for the
same reason.

Note this also skips the query on a /dev/ttyN that would have answered,
and does not help a serial console behind a TIOCCONS redirect.

Fixes: ba10d789a8
Fixes: d91a184509
(cherry picked from commit 4ca7ac0275)
(cherry picked from commit ffb5f4bf45)
2026-10-17 12:01:00 +01:00
Daan De Meyer c1f005ebaa namespace-util: fail if we cannot become root when entering a userns
namespace_enter() tried to become root after joining a user namespace
but ignored failures, so if root wasn't mapped we'd just carry on with
our original UID/GID. Let's make that an error instead.

Dropping supplementary groups stays best effort, as setgroups() might
be denied (e.g. by the nsresourced BPF LSM) even when root is mapped.

Some callers do need to enter namespaces without root, so add
NAMESPACE_ENTER_KEEP_UID_GID_IF_ROOT_UNMAPPED for those. It's used by
systemd-analyze unit-shell and by the credential refresh in the user
manager. Everything else enters containers or namespaces we allocated
ourselves, where root is always mapped.

(cherry picked from commit cfd2f7c73e)
2026-10-17 12:00:59 +01:00
Lennart Poettering fe752ec918 mountpoint-util: improve logging when kernel lacks support for a certain fs type
Inspired by #43752

(cherry picked from commit e2c84823ed)
2026-10-17 12:00:59 +01:00
Armaan Sandhu f75dc55c3d parse-util: fix ineffective overflow guards in store_loadavg_fixed_point()
(cherry picked from commit efdc4b0cc7)
2026-10-17 12:00:59 +01:00
Yu Watanabe e6e0ec36a4 compress: avoid shifting null pointer
Otherwise, UBSAN complains when src is NULL even though the shift is zero:

../src/basic/compress.c:2074:29: runtime error: applying zero offset to null pointer

(cherry picked from commit 6a68191199)
2026-10-17 12:00:58 +01:00
Yu Watanabe 6b668b7aab filesystem: correctly recognize failfs
The kernel v7.3-rc1 introduces failfs:
https://github.com/torvalds/linux/commit/fa0d6d945e5ce96cff14b114eec7527f13d7f23a

(cherry picked from commit 8220fcf4fe)
2026-09-09 10:08:21 +01:00
Yu Watanabe 7e904e3043 pidref: introduce pidref_set_pid_full() and friends
By passing the PIDFD_THREAD flag, we can get pidfd of a worker thread.
The flag is supported since kernel v6.9.

(cherry picked from commit 918f1462d7)
2026-09-09 10:08:21 +01:00
Christian Glombek 1ffb9a06f6 dns-packet: keep unreferenceable names out of the compression map
dns_packet_append_name() added every name it appends to the packet's
compression map, including names written at offsets >= 0x4000 that an
RFC 1035 compression pointer (14 bits) can never reference. Such
entries are not just useless -- when the same name occurs again, the
lookup finds it, the pointer cannot be emitted, the code falls through
to appending the labels again, and the re-insertion into the map then
fails the whole packet build with hashmap_ensure_put() returning
-EEXIST.

This is easily triggered with mDNS: an unsolicited DNS-SD announcement
covering a zone with a few hundred services exceeds 16KiB, and
dns_scope_announce() then fails with "Failed to build reply packet:
File exists".

This lifts the failure only: dns_scope_announce() still builds the whole
zone into one packet and hands it to the socket as a single datagram,
fragmented as the link requires. Splitting announcements across
MTU-sized packets, as RFC 6762 section 17 asks, is done separately.

Only insert a name into the map if its offset is pointer-expressible,
making "mapped implies referenceable" a map invariant. Re-occurrences of
out-of-range names are emitted as labels again -- the only encoding RFC
1035 section 4.1.4 permits in that case -- still compressed from the
first suffix that is mapped in range.

The lookup restates that range as control flow rather than asserting it.
Under -Db_ndebug=true, which CI builds, assert() is
__builtin_unreachable(), so an out-of-range entry would put 0xC000 | n
truncated to 14 bits on the wire -- a pointer to an arbitrary earlier
offset -- where falling through emits the labels instead. This layer is
shared with unicast DNS, DoT and LLMNR.

The pointer's tag bits and its offset bound are now named once, in
dns-def.h next to the other wire-format constants rather than in
dns-packet.h, since the format is RFC 1035's and not this packet
abstraction's -- dhcp-message.c open-codes the same two bits to parse
compressed names out of DHCP options. Compile-time checks pin that the
two partition the 16-bit field and that the tag is its top two bits,
which is what lets the reader take it from the first octet alone.

The offset's other bound becomes control flow along with it: it was an
assert(), which under -Db_ndebug=true is no check at all, for the bound
whose violation puts a pointer to the wrong bytes on the wire. It is
also tightened to the start of the name being appended rather than the
end of the packet, which is the "prior occurrence" RFC 1035 section
4.1.4 allows a pointer to address and what dns_packet_read_name()
enforces on the way in.

Add regression tests, including one through dns_packet_append_rr() --
the path the reported failure came from, where the name append is nested
inside the rdlength backpatch and two further rollbacks -- and one for
the reader's label-type dispatch, which the renamed constants touch.

(cherry picked from commit a95358da6f)
2026-09-09 10:08:21 +01:00
Armaan Sandhu ade1a9afb6 compress: don't grow the output buffer forever on a truncated stream
decompress_startswith() decodes just enough of a blob to compare its leading
bytes against a prefix, and grows the output buffer whenever a round of
decoding came back with less than prefix_len + 1 bytes decoded. That growth is
only the right answer while the decoder actually wants more output room. If it
instead ran out of input, i.e. the stream is truncated, then no amount of extra
room will help, and the gzip and bzip2 paths double their allocation on every
iteration until the process is out of memory.

Neither decoder lets us tell the two cases apart by return code alone:
inflate() reports the stalled stream as Z_BUF_ERROR, which we cannot simply
refuse since that is also how it asks for a bigger output buffer, and
BZ2_bzDecompress() does not distinguish them at all, it just keeps saying
BZ_OK. The output space left over does tell us, though: a decoder that stopped
without filling the buffer we gave it is waiting for input that is not coming,
so report the stream as corrupt instead of growing.

A 10 byte gzip header, or a 4 byte bzip2 header, with nothing behind it was
enough to reach a single 4 GiB allocation this way. The xz and zstd paths
already terminate, the former because liblzma turns a stalled stream into
LZMA_BUF_ERROR.

Note that no caller can reach this today: the only non-test caller is
decompress_startswith_journal(), and compression_supported_journal() admits
only NONE/XZ/LZ4/ZSTD. It is a trap for whoever wires up the other two, so
let's close it now.

(cherry picked from commit 87d3c04672)
2026-09-09 10:08:21 +01:00
Chris Down 116bf0502f core: Add SERVICE_RUNNING_REVALIDATING state
For a Type=dbus service we sometimes want to keep treating it as running
while its bus name has briefly gone away (for example, due to a broker
restart).

SERVICE_RUNNING_REVALIDATING is active and keeps its main process like
SERVICE_RUNNING, but it's deliberately not part of the runtime timer
set, so RuntimeMaxSec= is paused while we sit in it.

(cherry picked from commit 00cf56abbd)
2026-09-09 10:08:21 +01:00
Christian Brauner b0ab425c91 keyring-util: pass the buffer size to KEYCTL_DESCRIBE
While keyring_describe() allocates a 64 byte buffer it still passes the
value of c as the buffer length. It's -1 on the first iteration. So
keyctl_describe_key() copies the whole description whenever buflen >=
its length. So any key whose description exceeding 64 bytes overflows.
Pass the actual buffer size.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>

[zjs: adjust comment]

(cherry picked from commit 1783f8c3de)
2026-09-09 10:08:21 +01:00
Yu Watanabe 67f766c420 compress: strictly check if stream finished on decompress
Previously, even when the compressed data was truncated, or had
trailing garbage, the decompressor might not detect it.

To fix the issue, update the check if stream is finished.
With this change, now decompressor_push() must be called with
size == 0 to finalize the stream.

(cherry picked from commit dac2b39405)
2026-09-09 10:08:21 +01:00
Yu Watanabe 487067ded5 compress: check cursor position in should_sparse()
Otherwise, the result will be broken, as skipped area by lseek() is not
cleared.

(cherry picked from commit d9b6e8663c)
2026-09-09 10:08:21 +01:00
Yu Watanabe 6c3624fa92 compress: fix error handling for lzma_lzma_preset()
Follow-up for 91d6f1ee53.

(cherry picked from commit d365c4cd52)
2026-09-09 10:08:21 +01:00
wyu 214c7b3f72 extract-word: also handle EXTRACT_UNESCAPE_RELAX in the escape branch
extract_first_word() handled a backslash followed by another character in the
escape-processing branch only when EXTRACT_CUNESCAPE or EXTRACT_UNESCAPE_SEPARATORS
was set. With EXTRACT_UNESCAPE_RELAX alone, the backslash was silently dropped and
only the following character kept, which is inconsistent with the documented "keep
invalid escapes verbatim" intent of RELAX. Let RELAX also enter that branch, so an
invalid escape like "\x" is preserved verbatim as "\x" rather than collapsing to
"x".

EXTRACT_UNESCAPE_RELAX is not used on its own anywhere outside the test cases —
the non-test callers always combine it with EXTRACT_CUNESCAPE, which already enters
this branch — so only the test cases that use RELAX by itself are affected, and
their expectations are updated accordingly.

(cherry picked from commit 609f8d0214)
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
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
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
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
Armaan Sandhu 70cc4f3d72 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.

(cherry picked from commit eb03267070)
2026-07-23 10:54:52 +02:00
Syed Mohammed Nayyar f4c528cb06 string-util: add strnspn()
Like strspn(), but reads at most 'n' bytes from the input. strspn()
is bounded only by a non-matching byte or a NUL, so it over-reads a
buffer that is all matching bytes and not NUL terminated within 'n'.

(cherry picked from commit 0af6caff42)
2026-07-19 22:34:06 +01:00
Luca Boccassi cb25da37eb log: add upper bound to journal iovec accounting
Use checked arithmetic before clamping the journal iovec length.
Stop copying input iovecs once the fixed journal vector is full.

Follow-up for e69cc9eb36

(cherry picked from commit f5e35f72cf)
2026-07-19 22:34:06 +01:00
Luca Boccassi a09c3c523d string-util: add upper bound to ellipsize_mem UTF-8 walks
Validate UTF-8 characters against the caller-provided byte slice.
This stops truncated sequences from borrowing bytes past old_length.

Follow-up for e69cc9eb36

(cherry picked from commit 66600e15c9)
2026-07-19 22:34:06 +01:00
Lennart Poettering 1b23cedff3 string-util: introduce STRING_FILENAME_PART flag for string_is_safe()
Whenever we are validating a string that shall appear in a filename
eventually we want to use filename_part_is_valid() rather than file
filename_is_valid(). Let's add explicit support for that to
string_is_safe(), since it's actually a really common case.

(cherry picked from commit dbd35fdc81)
2026-07-19 22:34:06 +01:00
Luca Boccassi 87983793db string-util: add STRING_DISALLOW_WHITESPACE for string_is_safe
Co-developed-by: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 9404fa70df)
2026-07-19 22:34:06 +01:00