[packages/systemd] Rel 4. Security backports (CVE-2026-29111 CVE-2026-40225 CVE-2026-40226 CVE-2026-16742 CVE-2026-1505
arekm
arekm at pld-linux.org
Sat Oct 24 11:10:51 CEST 2026
commit 177924eae255485e247ed317cda1f5281f7bc1c3
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date: Sat Oct 24 11:10:24 2026 +0200
Rel 4. Security backports (CVE-2026-29111 CVE-2026-40225 CVE-2026-40226 CVE-2026-16742
CVE-2026-15059 CVE-2026-4105 GHSA-m8q3 GHSA-5rm9 GHSA-3jgj); fix build with kernel 7.x headers.
systemd-CVE-2026-15059.patch | 24 ++++
systemd-CVE-2026-16742.patch | 143 ++++++++++++++++++++
systemd-CVE-2026-29111.patch | 239 +++++++++++++++++++++++++++++++++
systemd-CVE-2026-40225.patch | 157 ++++++++++++++++++++++
systemd-CVE-2026-40226.patch | 60 +++++++++
systemd-CVE-2026-4105.patch | 21 +++
systemd-GHSA-3jgj-3phh-hx5j.patch | 274 ++++++++++++++++++++++++++++++++++++++
systemd-GHSA-5rm9-cc37-35gq.patch | 93 +++++++++++++
systemd-GHSA-m8q3-73v4-wvg7.patch | 44 ++++++
systemd-errno-list-kernel7.patch | 23 ++++
systemd.spec | 22 ++-
11 files changed, 1099 insertions(+), 1 deletion(-)
---
diff --git a/systemd.spec b/systemd.spec
index 1fafaa3..56c5b82 100644
--- a/systemd.spec
+++ b/systemd.spec
@@ -33,7 +33,7 @@ Summary(pl.UTF-8): systemd - zarządca systemu i usług dla Linuksa
Name: systemd
# Verify ChangeLog and NEWS when updating (since there are incompatible/breaking changes very often)
Version: 254.27
-Release: 3
+Release: 4
Epoch: 1
License: GPL v2+ (udev), LGPL v2.1+ (the rest)
Group: Base
@@ -84,6 +84,16 @@ Patch14: pld-pam-%{name}-user.patch
Patch15: %{name}-x32.patch
Patch16: rpm-macros.patch
Patch17: %{name}-glibc.patch
+Patch18: %{name}-CVE-2026-29111.patch
+Patch19: %{name}-CVE-2026-40225.patch
+Patch20: %{name}-GHSA-m8q3-73v4-wvg7.patch
+Patch21: %{name}-CVE-2026-40226.patch
+Patch22: %{name}-CVE-2026-16742.patch
+Patch23: %{name}-CVE-2026-15059.patch
+Patch24: %{name}-GHSA-5rm9-cc37-35gq.patch
+Patch25: %{name}-GHSA-3jgj-3phh-hx5j.patch
+Patch26: %{name}-CVE-2026-4105.patch
+Patch27: %{name}-errno-list-kernel7.patch
URL: https://www.freedesktop.org/wiki/Software/systemd/
BuildRequires: acl-devel
%{?with_audit:BuildRequires: audit-libs-devel}
@@ -828,6 +838,16 @@ Makra RPM-a definiujące ścieżki i skryptlety związane z systemd.
%patch -P15 -p1
%patch -P16 -p1
%patch -P17 -p1
+%patch -P18 -p1
+%patch -P19 -p1
+%patch -P20 -p1
+%patch -P21 -p1
+%patch -P22 -p1
+%patch -P23 -p1
+%patch -P24 -p1
+%patch -P25 -p1
+%patch -P26 -p1
+%patch -P27 -p1
cp -p %{SOURCE2} src/systemd_booted.c
diff --git a/systemd-CVE-2026-15059.patch b/systemd-CVE-2026-15059.patch
new file mode 100644
index 0000000..a767fdd
--- /dev/null
+++ b/systemd-CVE-2026-15059.patch
@@ -0,0 +1,24 @@
+CVE-2026-15059: oomd ReportManagedOOMCGroups accepts ../ in cgroup paths;
+unprivileged users can make oomd SIGKILL arbitrary processes.
+https://github.com/systemd/systemd/security/advisories/GHSA-652q-wxr6-h5j6
+Upstream v258.9: a8feb2f235; from Ubuntu noble 255.4-1ubuntu8.17.
+
+ src/oom/oomd-manager.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/src/oom/oomd-manager.c b/src/oom/oomd-manager.c
+index 49896ea..20757a8 100644
+--- a/src/oom/oomd-manager.c
++++ b/src/oom/oomd-manager.c
+@@ -79,6 +79,11 @@ static int process_managed_oom_message(Manager *m, uid_t uid, JsonVariant *param
+ if (r < 0)
+ continue;
+
++ if (!path_is_normalized(empty_to_root(message.path))) {
++ log_debug("Received non-normalized cgroup path '%s', ignoring.", message.path);
++ continue;
++ }
++
+ if (uid != 0) {
+ uid_t cg_uid;
+
diff --git a/systemd-CVE-2026-16742.patch b/systemd-CVE-2026-16742.patch
new file mode 100644
index 0000000..d3f3567
--- /dev/null
+++ b/systemd-CVE-2026-16742.patch
@@ -0,0 +1,143 @@
+CVE-2026-16742: homed stores worker-supplied user records without signature
+verification; a homed user can add itself to privileged groups.
+https://github.com/systemd/systemd/security/advisories/GHSA-jm29-p7hh-vjhv
+Upstream v258.10: 8735bb63b7 04cba0a78d; from Ubuntu noble 255.4-1ubuntu8.17.
+
+ src/home/homed-home.c | 19 ++++++++++
+ test/units/testsuite-46.sh | 71 ++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 90 insertions(+)
+
+diff --git a/src/home/homed-home.c b/src/home/homed-home.c
+index b0fee2a..58f4b7b 100644
+--- a/src/home/homed-home.c
++++ b/src/home/homed-home.c
+@@ -921,10 +921,18 @@ static void home_change_finish(Home *h, int ret, UserRecord *hr) {
+ }
+
+ if (hr) {
++ bool signed_locally = false;
++
++ r = home_verify_user_record(h, hr, &signed_locally, &error);
++ if (r < 0)
++ goto finish;
++
+ r = home_set_record(h, hr);
+ if (r < 0)
+ log_warning_errno(r, "Failed to update home record, ignoring: %m");
+ else {
++ h->signed_locally = signed_locally;
++
+ r = user_record_good_authentication(h->record);
+ if (r < 0)
+ log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
+@@ -1027,10 +1035,21 @@ static void home_authenticating_finish(Home *h, int ret, UserRecord *hr) {
+ }
+
+ if (hr) {
++ bool signed_locally = false, signed_locally_needs_update = false;
++
++ r = home_verify_user_record(h, hr, &signed_locally, /* ret_error= */ NULL);
++ if (r < 0)
++ hr = h->record;
++ else
++ signed_locally_needs_update = true;
++
+ r = home_set_record(h, hr);
+ if (r < 0)
+ log_warning_errno(r, "Failed to update home record, ignoring: %m");
+ else {
++ if (signed_locally_needs_update)
++ h->signed_locally = signed_locally;
++
+ r = user_record_good_authentication(h->record);
+ if (r < 0)
+ log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
+diff --git a/test/units/testsuite-46.sh b/test/units/testsuite-46.sh
+index 8c0b9ea..0e9f027 100755
+--- a/test/units/testsuite-46.sh
++++ b/test/units/testsuite-46.sh
+@@ -33,6 +33,75 @@ wait_for_state() {
+ done
+ }
+
++testcase_identity_groups() {
++ NEWPASSWORD=foobar homectl create idgrouptest --storage=directory --shell=/bin/bash --enforce-password-policy=no --rebalance-weight=off
++ PASSWORD=foobar homectl activate idgrouptest
++
++ machinectl shell idgrouptest@ /usr/bin/bash -euxo pipefail -c "jq '.memberOf = ((.memberOf // []) + [\"systemd-journal\"] | unique) | .lastChangeUSec = ((.lastChangeUSec // 0) + 3600000000)' /home/idgrouptest/.identity > /home/idgrouptest/.identity.new && mv -f /home/idgrouptest/.identity.new /home/idgrouptest/.identity"
++ jq -e '.memberOf | index("systemd-journal") != null' /home/idgrouptest/.identity
++
++ PASSWORD=foobar homectl authenticate idgrouptest
++
++ local groups
++ groups="$(machinectl shell idgrouptest@ /usr/bin/groups)"
++ (! grep systemd-journal <<<"$groups" >/dev/null)
++
++ homectl deactivate idgrouptest ||:
++ wait_for_state idgrouptest inactive
++ homectl remove idgrouptest
++
++ # Install a PK rule that allows 'idgrouptest2' user to update homed even
++ # though they are not on an fg console, just for testing
++ mkdir -p /etc/polkit-1/rules.d
++ cat >/etc/polkit-1/rules.d/updatehome.rules <<'EOF'
++polkit.addRule(function(action, subject) {
++ if (action.id == "org.freedesktop.home1.update-home-by-owner" &&
++ subject.user == "idgrouptest2") {
++ return polkit.Result.YES;
++ }
++});
++EOF
++ trap 'rm -f /etc/polkit-1/rules.d/updatehome.rules' RETURN ERR EXIT
++ systemctl try-reload-or-restart polkit.service
++
++ NEWPASSWORD=foobar homectl create idgrouptest2 --storage=directory --shell=/bin/bash --enforce-password-policy=no --rebalance-weight=off
++ PASSWORD=foobar homectl activate idgrouptest2
++
++ cat >/tmp/idgrouptest-add-group.sh <<'EOF'
++#!/bin/bash
++set -exuo pipefail
++
++loginctl show-session "${XDG_SESSION_ID:?}" -p Active --value | grep '^yes$' >/dev/null
++RECORD="$(busctl -j call org.freedesktop.home1 /org/freedesktop/home1 org.freedesktop.home1.Manager GetUserRecordByName s "$USER" | jq -r '.data[0]')"
++TS="$(printf '%s' "$RECORD" | jq '.lastChangeUSec')"
++NEW_TS=$((TS + 172800000000))
++jq --arg g systemd-journal --argjson ts "$NEW_TS" \
++ '.memberOf = ((.memberOf // []) + [$g]) | .lastChangeUSec = $ts' \
++ ~/.identity > ~/.identity.new
++mv -f ~/.identity.new ~/.identity
++# Ensure the identity update is persisted before UpdateHomeEx reads it.
++sync
++
++UPDATE_TS=$((TS + 1))
++UPDATE_RECORD="$(printf '%s' "$RECORD" | jq -c --argjson ts "$UPDATE_TS" --arg p foobar 'del(.binding, .status, .signature) | .lastChangeUSec = $ts | .secret = {password: [$p]}')"
++(! busctl call org.freedesktop.home1 /org/freedesktop/home1 org.freedesktop.home1.Manager UpdateHomeEx "sa{sh}t" "$UPDATE_RECORD" 0 0 )
++EOF
++ chmod +x /tmp/idgrouptest-add-group.sh
++ machinectl shell idgrouptest2@ /tmp/idgrouptest-add-group.sh
++ rm -f /tmp/idgrouptest-add-group.sh
++
++ PASSWORD=foobar homectl authenticate idgrouptest2
++
++ local groups
++ groups="$(machinectl shell idgrouptest2@ /usr/bin/groups)"
++ (! grep systemd-journal <<<"$groups" >/dev/null)
++
++ homectl deactivate idgrouptest2 ||:
++ wait_for_state idgrouptest2 inactive
++ homectl remove idgrouptest2
++}
++
++
+ systemd-analyze log-level debug
+ systemctl service-log-level systemd-homed debug
+
+@@ -170,6 +239,8 @@ if ! systemd-detect-virt -cq ; then
+ homectl remove test-user2
+ fi
+
++testcase_identity_groups
++
+ # userdbctl tests
+ export PAGER=
+
diff --git a/systemd-CVE-2026-29111.patch b/systemd-CVE-2026-29111.patch
new file mode 100644
index 0000000..313b2f6
--- /dev/null
+++ b/systemd-CVE-2026-29111.patch
@@ -0,0 +1,239 @@
+CVE-2026-29111: unprivileged GetUnitByControlGroup()/AttachProcesses call with
+oversized or unnormalized cgroup path asserts PID 1 (stack via strdupa/strjoina).
+https://github.com/systemd/systemd/security/advisories/GHSA-gx6q-6f99-m764
+Upstream v257.11: 20021e7686 7ac3220213 2116700657 54588d2ded; from Ubuntu noble
+255.4-1ubuntu8.17; core/cgroup.c part ported to 254 (no PidRef).
+
+ src/basic/fs-util.c | 2 +-
+ src/basic/mkdir.c | 2 +-
+ src/basic/path-util.c | 39 ++++++++++++++++++++++++++++-----------
+ src/basic/path-util.h | 10 ++++++++--
+ src/core/cgroup.c | 21 ++++++++++-----------
+ src/core/dbus-manager.c | 6 ++++++
+ src/test/test-path-util.c | 16 ++++++++++++++++
+ 7 files changed, 70 insertions(+), 26 deletions(-)
+
+diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
+index 479b036..e28d6a3 100644
+--- a/src/basic/fs-util.c
++++ b/src/basic/fs-util.c
+@@ -64,7 +64,7 @@ int rmdir_parents(const char *path, const char *stop) {
+ assert(*slash == '/');
+ *slash = '\0';
+
+- if (path_startswith_full(stop, p, /* accept_dot_dot= */ false))
++ if (path_startswith_full(stop, p, PATH_STARTSWITH_REFUSE_DOT_DOT))
+ return 0;
+
+ if (rmdir(p) < 0 && errno != ENOENT)
+diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c
+index 41af148..5ea7ceb 100644
+--- a/src/basic/mkdir.c
++++ b/src/basic/mkdir.c
+@@ -154,7 +154,7 @@ int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, ui
+ assert(_mkdirat != mkdirat);
+
+ if (prefix) {
+- p = path_startswith_full(path, prefix, /* accept_dot_dot= */ false);
++ p = path_startswith_full(path, prefix, PATH_STARTSWITH_REFUSE_DOT_DOT);
+ if (!p)
+ return -ENOTDIR;
+ } else
+diff --git a/src/basic/path-util.c b/src/basic/path-util.c
+index 216410e..b04125b 100644
+--- a/src/basic/path-util.c
++++ b/src/basic/path-util.c
+@@ -397,8 +397,8 @@ char* path_simplify_full(char *path, PathSimplifyFlags flags) {
+ return path;
+ }
+
+-char* path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) {
+- assert(path);
++char* path_startswith_full(const char *original_path, const char *prefix, PathStartWithFlags flags) {
++ assert(original_path);
+ assert(prefix);
+
+ /* Returns a pointer to the start of the first component after the parts matched by
+@@ -411,28 +411,45 @@ char* path_startswith_full(const char *path, const char *prefix, bool accept_dot
+ * Returns NULL otherwise.
+ */
+
++ const char *path = original_path;
++
+ if ((path[0] == '/') != (prefix[0] == '/'))
+ return NULL;
+
+ for (;;) {
+ const char *p, *q;
+- int r, k;
++ int m, n;
+
+- r = path_find_first_component(&path, accept_dot_dot, &p);
+- if (r < 0)
++ m = path_find_first_component(&path, !FLAGS_SET(flags, PATH_STARTSWITH_REFUSE_DOT_DOT), &p);
++ if (m < 0)
+ return NULL;
+
+- k = path_find_first_component(&prefix, accept_dot_dot, &q);
+- if (k < 0)
++ n = path_find_first_component(&prefix, !FLAGS_SET(flags, PATH_STARTSWITH_REFUSE_DOT_DOT), &q);
++ if (n < 0)
+ return NULL;
+
+- if (k == 0)
+- return (char*) (p ?: path);
++ if (n == 0) {
++ if (!p)
++ p = path;
++
++ if (FLAGS_SET(flags, PATH_STARTSWITH_RETURN_LEADING_SLASH)) {
++
++ if (p <= original_path)
++ return NULL;
++
++ p--;
++
++ if (*p != '/')
++ return NULL;
++ }
++
++ return (char*) p;
++ }
+
+- if (r != k)
++ if (m != n)
+ return NULL;
+
+- if (!strneq(p, q, r))
++ if (!strneq(p, q, m))
+ return NULL;
+ }
+ }
+diff --git a/src/basic/path-util.h b/src/basic/path-util.h
+index 6e7b493..924cf84 100644
+--- a/src/basic/path-util.h
++++ b/src/basic/path-util.h
+@@ -63,9 +63,15 @@ int safe_getcwd(char **ret);
+ int path_make_absolute_cwd(const char *p, char **ret);
+ int path_make_relative(const char *from, const char *to, char **ret);
+ int path_make_relative_parent(const char *from_child, const char *to, char **ret);
+-char* path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) _pure_;
++
++typedef enum PathStartWithFlags {
++ PATH_STARTSWITH_REFUSE_DOT_DOT = 1U << 0,
++ PATH_STARTSWITH_RETURN_LEADING_SLASH = 1U << 1,
++} PathStartWithFlags;
++
++char* path_startswith_full(const char *path, const char *prefix, PathStartWithFlags flags) _pure_;
+ static inline char* path_startswith(const char *path, const char *prefix) {
+- return path_startswith_full(path, prefix, true);
++ return path_startswith_full(path, prefix, 0);
+ }
+
+ int path_compare(const char *a, const char *b) _pure_;
+diff --git a/src/core/cgroup.c b/src/core/cgroup.c
+index 8fe9305..8291b0a 100644
+--- a/src/core/cgroup.c
++++ b/src/core/cgroup.c
+@@ -2404,12 +2404,13 @@ static int unit_update_cgroup(
+ return 0;
+ }
+
+-static int unit_attach_pid_to_cgroup_via_bus(Unit *u, pid_t pid, const char *suffix_path) {
++static int unit_attach_pid_to_cgroup_via_bus(Unit *u, const char *cgroup_path, pid_t pid) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+- char *pp;
+ int r;
+
+ assert(u);
++ assert(cgroup_path);
++ assert(pid_is_valid(pid));
+
+ if (MANAGER_IS_SYSTEM(u->manager))
+ return -EINVAL;
+@@ -2417,17 +2418,13 @@ static int unit_attach_pid_to_cgroup_via_bus(Unit *u, pid_t pid, const char *suf
+ if (!u->manager->system_bus)
+ return -EIO;
+
+- if (!u->cgroup_path)
+- return -EINVAL;
+-
+ /* Determine this unit's cgroup path relative to our cgroup root */
+- pp = path_startswith(u->cgroup_path, u->manager->cgroup_root);
++ const char *pp = path_startswith_full(cgroup_path,
++ u->manager->cgroup_root,
++ PATH_STARTSWITH_RETURN_LEADING_SLASH|PATH_STARTSWITH_REFUSE_DOT_DOT);
+ if (!pp)
+ return -EINVAL;
+
+- pp = strjoina("/", pp, suffix_path);
+- path_simplify(pp);
+-
+ r = bus_call_method(u->manager->system_bus,
+ bus_systemd_mgr,
+ "AttachProcessesToUnit",
+@@ -2466,8 +2463,10 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) {
+ return r;
+
+ if (isempty(suffix_path))
+- p = u->cgroup_path;
++ p = empty_to_root(u->cgroup_path);
+ else {
++ assert(path_is_absolute(suffix_path));
++
+ joined = path_join(u->cgroup_path, suffix_path);
+ if (!joined)
+ return -ENOMEM;
+@@ -2498,7 +2497,7 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) {
+ * Since it's more privileged it might be able to move the process across the
+ * leaves of a subtree whose top node is not owned by us. */
+
+- z = unit_attach_pid_to_cgroup_via_bus(u, pid, suffix_path);
++ z = unit_attach_pid_to_cgroup_via_bus(u, p, pid);
+ if (z < 0)
+ log_unit_info_errno(u, z, "Couldn't move process "PID_FMT" to requested cgroup '%s' (directly or via the system bus): %m", pid, empty_to_root(p));
+ else {
+diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
+index 91fe54b..1e2d45a 100644
+--- a/src/core/dbus-manager.c
++++ b/src/core/dbus-manager.c
+@@ -666,6 +666,12 @@ static int method_get_unit_by_control_group(sd_bus_message *message, void *userd
+ if (r < 0)
+ return r;
+
++ if (!path_is_absolute(cgroup))
++ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Control group path is not absolute: %s", cgroup);
++
++ if (!path_is_normalized(cgroup))
++ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Control group path is not normalized: %s", cgroup);
++
+ u = manager_get_unit_by_cgroup(m, cgroup);
+ if (!u)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT,
+diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c
+index 46a0c57..19cf9db 100644
+--- a/src/test/test-path-util.c
++++ b/src/test/test-path-util.c
+@@ -714,6 +714,22 @@ TEST(path_startswith) {
+ test_path_startswith_one("/foo/bar/barfoo/", "/fo", NULL, NULL);
+ }
+
++static void test_path_startswith_return_leading_slash_one(const char *path, const char *prefix, const char *expected) {
++ const char *p;
++
++ log_debug("/* %s(%s, %s) */", __func__, path, prefix);
++
++ p = path_startswith_full(path, prefix, PATH_STARTSWITH_RETURN_LEADING_SLASH);
++ assert_se(streq_ptr(p, expected));
++}
++
++TEST(path_startswith_return_leading_slash) {
++ test_path_startswith_return_leading_slash_one("/foo/bar", "/", "/foo/bar");
++ test_path_startswith_return_leading_slash_one("/foo/bar", "/foo", "/bar");
++ test_path_startswith_return_leading_slash_one("/foo/bar", "/foo/bar", NULL);
++ test_path_startswith_return_leading_slash_one("/foo/bar/", "/foo/bar", "/");
++}
++
+ static void test_prefix_root_one(const char *r, const char *p, const char *expected) {
+ _cleanup_free_ char *s = NULL;
+ const char *t;
diff --git a/systemd-CVE-2026-40225.patch b/systemd-CVE-2026-40225.patch
new file mode 100644
index 0000000..17159af
--- /dev/null
+++ b/systemd-CVE-2026-40225.patch
@@ -0,0 +1,157 @@
+CVE-2026-40225: scsi_id/v4l_id/net_id/dmi_memory_id print unsanitized device
+strings into udev properties; a crafted device can inject e.g. SYSTEMD_WANTS=.
+https://github.com/systemd/systemd/security/advisories/GHSA-vpfq-8p5f-jcqx
+Upstream v257.13: 03bb697b8d 5887e72ff8; from Ubuntu noble 255.4-1ubuntu8.17;
+net_id part ported to 254 (port name is checked where each name is built).
+
+ src/udev/dmi_memory_id/dmi_memory_id.c | 3 ++-
+ src/udev/scsi_id/scsi_id.c | 3 ++-
+ src/udev/udev-builtin-net_id.c | 26 ++++++++++++++++++++------
+ src/udev/v4l_id/v4l_id.c | 5 ++++-
+ 4 files changed, 28 insertions(+), 9 deletions(-)
+
+diff --git a/src/udev/dmi_memory_id/dmi_memory_id.c b/src/udev/dmi_memory_id/dmi_memory_id.c
+index 52ea250..4f2c21b 100644
+--- a/src/udev/dmi_memory_id/dmi_memory_id.c
++++ b/src/udev/dmi_memory_id/dmi_memory_id.c
+@@ -51,6 +51,7 @@
+ #include "string-util.h"
+ #include "udev-util.h"
+ #include "unaligned.h"
++#include "utf8.h"
+
+ #define SUPPORTED_SMBIOS_VER 0x030300
+
+@@ -185,7 +186,7 @@ static void dmi_memory_device_string(
+
+ str = strdupa_safe(dmi_string(h, s));
+ str = strstrip(str);
+- if (!isempty(str))
++ if (!isempty(str) && utf8_is_valid(str) && !string_has_cc(str, /* ok= */ NULL))
+ printf("MEMORY_DEVICE_%u_%s=%s\n", slot_num, attr_suffix, str);
+ }
+
+diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c
+index 6308c52..b2df8d9 100644
+--- a/src/udev/scsi_id/scsi_id.c
++++ b/src/udev/scsi_id/scsi_id.c
+@@ -27,6 +27,7 @@
+ #include "strv.h"
+ #include "strxcpyx.h"
+ #include "udev-util.h"
++#include "utf8.h"
+
+ static const struct option options[] = {
+ { "device", required_argument, NULL, 'd' },
+@@ -443,7 +444,7 @@ static int scsi_id(char *maj_min_dev) {
+ }
+ if (dev_scsi.tgpt_group[0] != '\0')
+ printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group);
+- if (dev_scsi.unit_serial_number[0] != '\0')
++ if (dev_scsi.unit_serial_number[0] != '\0' && utf8_is_valid(dev_scsi.unit_serial_number) && !string_has_cc(dev_scsi.unit_serial_number, /* ok= */ NULL))
+ printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number);
+ goto out;
+ }
+diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c
+index 01bfb78..f54e06d 100644
+--- a/src/udev/udev-builtin-net_id.c
++++ b/src/udev/udev-builtin-net_id.c
+@@ -39,6 +39,7 @@
+ #include "strv.h"
+ #include "strxcpyx.h"
+ #include "udev-builtin.h"
++#include "utf8.h"
+
+ #define ONBOARD_14BIT_INDEX_MAX ((1U << 14) - 1)
+ #define ONBOARD_16BIT_INDEX_MAX ((1U << 16) - 1)
+@@ -163,6 +164,14 @@ static bool is_valid_onboard_index(unsigned long idx) {
+ return idx <= (naming_scheme_has(NAMING_16BIT_INDEX) ? ONBOARD_16BIT_INDEX_MAX : ONBOARD_14BIT_INDEX_MAX);
+ }
+
++static bool phys_port_name_is_valid(sd_device *dev, const char *phys_port_name) {
++ if (utf8_is_valid(phys_port_name) && !string_has_cc(phys_port_name, /* ok= */ NULL))
++ return true;
++
++ log_device_debug(dev, "Invalid phys_port_name, not generating names using it.");
++ return false;
++}
++
+ /* retrieve on-board index number and label from firmware */
+ static int dev_pci_onboard(sd_device *dev, const LinkInfo *info, NetNames *names) {
+ unsigned long idx, dev_port = 0;
+@@ -213,7 +222,7 @@ static int dev_pci_onboard(sd_device *dev, const LinkInfo *info, NetNames *names
+ l = strpcpyf(&s, l, "r%d", info->vf_representor_id);
+ else if (!isempty(info->phys_port_name))
+ /* kernel provided front panel port name for multiple port PCI device */
+- l = strpcpyf(&s, l, "n%s", info->phys_port_name);
++ l = phys_port_name_is_valid(dev, info->phys_port_name) ? strpcpyf(&s, l, "n%s", info->phys_port_name) : 0;
+ else if (dev_port > 0)
+ l = strpcpyf(&s, l, "d%lu", dev_port);
+ if (l == 0)
+@@ -222,10 +231,13 @@ static int dev_pci_onboard(sd_device *dev, const LinkInfo *info, NetNames *names
+ idx, strempty(info->phys_port_name), dev_port,
+ special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), empty_to_na(names->pci_onboard));
+
+- if (sd_device_get_sysattr_value(names->pcidev, "label", &names->pci_onboard_label) >= 0)
+- log_device_debug(dev, "Onboard label from PCI device: %s", names->pci_onboard_label);
+- else
++ if (sd_device_get_sysattr_value(names->pcidev, "label", &names->pci_onboard_label) < 0)
+ names->pci_onboard_label = NULL;
++ else if (!utf8_is_valid(names->pci_onboard_label) || string_has_cc(names->pci_onboard_label, /* ok= */ NULL)) {
++ log_device_debug(dev, "Invalid onboard label from PCI device, ignoring.");
++ names->pci_onboard_label = NULL;
++ } else
++ log_device_debug(dev, "Onboard label from PCI device: %s", names->pci_onboard_label);
+
+ return 0;
+ }
+@@ -399,7 +411,7 @@ static int dev_pci_slot(sd_device *dev, const LinkInfo *info, NetNames *names) {
+ l = strpcpyf(&s, l, "r%d", info->vf_representor_id);
+ else if (!isempty(info->phys_port_name))
+ /* kernel provided front panel port name for multi-port PCI device */
+- l = strpcpyf(&s, l, "n%s", info->phys_port_name);
++ l = phys_port_name_is_valid(dev, info->phys_port_name) ? strpcpyf(&s, l, "n%s", info->phys_port_name) : 0;
+ else if (dev_port > 0)
+ l = strpcpyf(&s, l, "d%lu", dev_port);
+ if (l == 0)
+@@ -495,7 +507,7 @@ static int dev_pci_slot(sd_device *dev, const LinkInfo *info, NetNames *names) {
+ /* For VF representor append 'r<VF_NUM>' and not phys_port_name */
+ l = strpcpyf(&s, l, "r%d", info->vf_representor_id);
+ else if (!isempty(info->phys_port_name))
+- l = strpcpyf(&s, l, "n%s", info->phys_port_name);
++ l = phys_port_name_is_valid(dev, info->phys_port_name) ? strpcpyf(&s, l, "n%s", info->phys_port_name) : 0;
+ else if (dev_port > 0)
+ l = strpcpyf(&s, l, "d%lu", dev_port);
+ if (l == 0)
+@@ -970,6 +982,8 @@ static int names_netdevsim(sd_device *dev, const LinkInfo *info, NetNames *names
+
+ if (isempty(info->phys_port_name))
+ return -EINVAL;
++ if (!phys_port_name_is_valid(dev, info->phys_port_name))
++ return -EINVAL;
+
+ r = sd_device_get_parent_with_subsystem_devtype(dev, "netdevsim", NULL, &netdevsimdev);
+ if (r < 0)
+diff --git a/src/udev/v4l_id/v4l_id.c b/src/udev/v4l_id/v4l_id.c
+index 30527e9..2ec96d8 100644
+--- a/src/udev/v4l_id/v4l_id.c
++++ b/src/udev/v4l_id/v4l_id.c
+@@ -29,6 +29,8 @@
+ #include "build.h"
+ #include "fd-util.h"
+ #include "main-func.h"
++#include "string-util.h"
++#include "utf8.h"
+
+ static const char *arg_device = NULL;
+
+@@ -82,7 +84,8 @@ static int run(int argc, char *argv[]) {
+ int capabilities;
+
+ printf("ID_V4L_VERSION=2\n");
+- printf("ID_V4L_PRODUCT=%s\n", v2cap.card);
++ if (utf8_is_valid((char *)v2cap.card) && !string_has_cc((char *)v2cap.card, /* ok= */ NULL))
++ printf("ID_V4L_PRODUCT=%s\n", v2cap.card);
+ printf("ID_V4L_CAPABILITIES=:");
+
+ if (v2cap.capabilities & V4L2_CAP_DEVICE_CAPS)
diff --git a/systemd-CVE-2026-40226.patch b/systemd-CVE-2026-40226.patch
new file mode 100644
index 0000000..5725ef9
--- /dev/null
+++ b/systemd-CVE-2026-40226.patch
@@ -0,0 +1,60 @@
+CVE-2026-40226: malformed PivotRoot=/BindUser=/Ephemeral= in .nspawn files spawn
+the container on the host rootfs.
+https://github.com/systemd/systemd/security/advisories/GHSA-9mj4-rrc3-gjcx
+Upstream v257.12: 773fd3b6e7 bfa0a84282; from Ubuntu noble 255.4-1ubuntu8.17.
+
+ src/nspawn/nspawn-mount.c | 4 +++-
+ src/nspawn/nspawn.c | 18 ++++++++++++++----
+ 2 files changed, 17 insertions(+), 5 deletions(-)
+
+diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c
+index 0a85b5d..b73b9d6 100644
+--- a/src/nspawn/nspawn-mount.c
++++ b/src/nspawn/nspawn-mount.c
+@@ -1255,7 +1255,9 @@ int pivot_root_parse(char **pivot_root_new, char **pivot_root_old, const char *s
+
+ if (!path_is_absolute(root_new))
+ return -EINVAL;
+- if (root_old && !path_is_absolute(root_old))
++ if (!path_is_normalized(root_new))
++ return -EINVAL;
++ if (root_old && (!path_is_absolute(root_old) || !path_is_normalized(root_old)))
+ return -EINVAL;
+
+ free_and_replace(*pivot_root_new, root_new);
+diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
+index 7f6417b..073f1f7 100644
+--- a/src/nspawn/nspawn.c
++++ b/src/nspawn/nspawn.c
+@@ -4341,8 +4341,13 @@ static int merge_settings(Settings *settings, const char *path) {
+ }
+
+ if ((arg_settings_mask & SETTING_EPHEMERAL) == 0 &&
+- settings->ephemeral >= 0)
+- arg_ephemeral = settings->ephemeral;
++ settings->ephemeral >= 0) {
++
++ if (!arg_settings_trusted)
++ log_warning("Ignoring ephemeral setting, file %s is not trusted.", path);
++ else
++ arg_ephemeral = settings->ephemeral;
++ }
+
+ if ((arg_settings_mask & SETTING_DIRECTORY) == 0 &&
+ settings->root) {
+@@ -4510,8 +4515,13 @@ static int merge_settings(Settings *settings, const char *path) {
+ }
+
+ if ((arg_settings_mask & SETTING_BIND_USER) == 0 &&
+- !strv_isempty(settings->bind_user))
+- strv_free_and_replace(arg_bind_user, settings->bind_user);
++ !strv_isempty(settings->bind_user)) {
++
++ if (!arg_settings_trusted)
++ log_warning("Ignoring bind user setting, file %s is not trusted.", path);
++ else
++ strv_free_and_replace(arg_bind_user, settings->bind_user);
++ }
+
+ if ((arg_settings_mask & SETTING_NOTIFY_READY) == 0 &&
+ settings->notify_ready >= 0)
diff --git a/systemd-CVE-2026-4105.patch b/systemd-CVE-2026-4105.patch
new file mode 100644
index 0000000..13b2330
--- /dev/null
+++ b/systemd-CVE-2026-4105.patch
@@ -0,0 +1,21 @@
+CVE-2026-4105: machined RegisterMachine accepts class=host, leaving a usable
+privileged machine object (needs a polkit policy granting register-machine).
+https://github.com/systemd/systemd/security/advisories/GHSA-4h6x-r8vx-3862
+Upstream 6941d92dc2; from Debian bookworm 252.39-1~deb12u2.
+
+ src/machine/machined-dbus.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c
+index 2050bb2..58de076 100644
+--- a/src/machine/machined-dbus.c
++++ b/src/machine/machined-dbus.c
+@@ -270,7 +270,7 @@ static int method_create_or_register_machine(Manager *manager, sd_bus_message *m
+ c = _MACHINE_CLASS_INVALID;
+ else {
+ c = machine_class_from_string(class);
+- if (c < 0)
++ if (c < 0 || !IN_SET(c, MACHINE_CONTAINER, MACHINE_VM))
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter");
+ }
+
diff --git a/systemd-GHSA-3jgj-3phh-hx5j.patch b/systemd-GHSA-3jgj-3phh-hx5j.patch
new file mode 100644
index 0000000..9d7ad35
--- /dev/null
+++ b/systemd-GHSA-3jgj-3phh-hx5j.patch
@@ -0,0 +1,274 @@
+GHSA-3jgj-3phh-hx5j: unbounded lists in D-Bus/varlink calls to PID 1, resolved
+and userdb let unprivileged users exhaust memory/CPU.
+https://github.com/systemd/systemd/security/advisories/GHSA-3jgj-3phh-hx5j
+From Ubuntu noble 255.4-1ubuntu8.17 (7 of 9 upstream commits; 7671b43cb8 and
+1c534452e9 patch functions 254 does not have); manager.h context ported.
+
+ src/basic/env-util.h | 2 ++
+ src/core/dbus-manager.c | 40 ++++++++++++++++++++++++++---
+ src/core/manager.h | 6 ++++-
+ src/libsystemd/sd-bus/bus-message.c | 4 ++-
+ src/machine/machine-dbus.c | 4 +++
+ src/resolve/resolved-link-bus.c | 7 ++++-
+ src/resolve/resolved-link.h | 1 +
+ src/shared/json.c | 3 ++-
+ src/shared/user-record.c | 3 ++-
+ 9 files changed, 62 insertions(+), 8 deletions(-)
+
+diff --git a/src/basic/env-util.h b/src/basic/env-util.h
+index c1ea486..a3c1de5 100644
+--- a/src/basic/env-util.h
++++ b/src/basic/env-util.h
+@@ -15,6 +15,8 @@ static inline size_t sc_arg_max(void) {
+ return (size_t) l;
+ }
+
++#define ENVIRONMENT_ASSIGNMENTS_MAX 16384U
++
+ bool env_name_is_valid(const char *e);
+ bool env_value_is_valid(const char *e);
+ bool env_assignment_is_valid(const char *e);
+diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
+index 1e2d45a..8f5bfa1 100644
+--- a/src/core/dbus-manager.c
++++ b/src/core/dbus-manager.c
+@@ -967,6 +967,10 @@ static int method_list_units_by_names(sd_bus_message *message, void *userdata, s
+ if (r < 0)
+ return r;
+
++ if (strv_length(units) > MAX(hashmap_size(m->units), (unsigned) MANAGER_MAX_NAMES / 2))
++ return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
++ "Too many unit names requested.");
++
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+@@ -1251,9 +1255,13 @@ static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_e
+
+ /* Anyone can call this method */
+
+- r = mac_selinux_access_check(message, "status", error);
+- if (r < 0)
+- return r;
++ if (strv_length(states) > MANAGER_MAX_STATES_PER_CALL)
++ return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
++ "Too many states in a single query.");
++
++ if (strv_length(patterns) > MANAGER_MAX_PATTERNS_PER_CALL)
++ return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
++ "Too many patterns in a single query.");
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+@@ -1267,6 +1275,10 @@ static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_e
+ if (k != u->id)
+ continue;
+
++ _cleanup_(sd_bus_error_free) sd_bus_error unused_error = SD_BUS_ERROR_NULL;
++ r = mac_selinux_unit_access_check(u, message, "status", &unused_error);
++ if (r < 0)
++ continue; /* silently skip units the caller is not allowed to see */
+ if (!strv_isempty(states) &&
+ !strv_contains(states, unit_load_state_to_string(u->load_state)) &&
+ !strv_contains(states, unit_active_state_to_string(unit_active_state(u))) &&
+@@ -1441,6 +1453,10 @@ static int dump_impl(
+
+ assert(message);
+
++ if (strv_length(patterns) > MANAGER_MAX_PATTERNS_PER_CALL)
++ return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
++ "Too many patterns in a single query.");
++
+ /* 'status' access is the bare minimum always needed for this, as the policy might straight out
+ * forbid a client from querying any information from systemd, regardless of any rate limiting. */
+ r = mac_selinux_access_check(message, "status", error);
+@@ -1954,6 +1970,10 @@ static int method_set_environment(sd_bus_message *message, void *userdata, sd_bu
+ r = sd_bus_message_read_strv(message, &plus);
+ if (r < 0)
+ return r;
++
++ if (strv_length(plus) > ENVIRONMENT_ASSIGNMENTS_MAX)
++ return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
++ "Too many environment assignments in a single query.");
+ if (!strv_env_is_valid(plus))
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments");
+
+@@ -1985,6 +2005,9 @@ static int method_unset_environment(sd_bus_message *message, void *userdata, sd_
+ if (r < 0)
+ return r;
+
++ if (strv_length(minus) > ENVIRONMENT_ASSIGNMENTS_MAX)
++ return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
++ "Too many environment variable names in a single query.");
+ if (!strv_env_name_or_assignment_is_valid(minus))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid environment variable names or assignments");
+@@ -2021,6 +2044,9 @@ static int method_unset_and_set_environment(sd_bus_message *message, void *userd
+ if (r < 0)
+ return r;
+
++ if (strv_length(plus) > ENVIRONMENT_ASSIGNMENTS_MAX || strv_length(minus) > ENVIRONMENT_ASSIGNMENTS_MAX)
++ return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
++ "Too many environment variable names or assignments in a single query.");
+ if (!strv_env_name_or_assignment_is_valid(minus))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid environment variable names or assignments");
+@@ -2243,6 +2269,14 @@ static int list_unit_files_by_patterns(sd_bus_message *message, void *userdata,
+
+ /* Anyone can call this method */
+
++ if (strv_length(states) > MANAGER_MAX_STATES_PER_CALL)
++ return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
++ "Too many states in a single query.");
++
++ if (strv_length(patterns) > MANAGER_MAX_PATTERNS_PER_CALL)
++ return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
++ "Too many patterns in a single query.");
++
+ r = mac_selinux_access_check(message, "status", error);
+ if (r < 0)
+ return r;
+diff --git a/src/core/manager.h b/src/core/manager.h
+index a6c87dc..5b919b9 100644
+--- a/src/core/manager.h
++++ b/src/core/manager.h
+@@ -20,9 +20,13 @@
+ struct libmnt_monitor;
+ typedef struct Unit Unit;
+
+-/* Enforce upper limit how many names we allow */
++/* Enforce upper limit on how many names we allow */
+ #define MANAGER_MAX_NAMES 131072 /* 128K */
+
++/* Enforce upper limit on the number of patterns/states requested over IPC */
++#define MANAGER_MAX_PATTERNS_PER_CALL 4096U
++#define MANAGER_MAX_STATES_PER_CALL 256U
++
+ typedef struct Manager Manager;
+
+ /* An externally visible state. We don't actually maintain this as state variable, but derive it from various fields
+diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c
+index 7d75b67..f0747e3 100644
+--- a/src/libsystemd/sd-bus/bus-message.c
++++ b/src/libsystemd/sd-bus/bus-message.c
+@@ -4295,6 +4295,7 @@ int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz) {
+ _public_ int sd_bus_message_read_strv_extend(sd_bus_message *m, char ***l) {
+ char type;
+ const char *contents, *s;
++ size_t n;
+ int r;
+
+ assert(m);
+@@ -4311,9 +4312,10 @@ _public_ int sd_bus_message_read_strv_extend(sd_bus_message *m, char ***l) {
+ if (r <= 0)
+ return r;
+
++ n = strv_length(*l);
+ /* sd_bus_message_read_basic() does content validation for us. */
+ while ((r = sd_bus_message_read_basic(m, *contents, &s)) > 0) {
+- r = strv_extend(l, s);
++ r = strv_extend_with_size(l, &n, s);
+ if (r < 0)
+ return r;
+ }
+diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c
+index 5ecb570..7c3a14f 100644
+--- a/src/machine/machine-dbus.c
++++ b/src/machine/machine-dbus.c
+@@ -640,6 +640,10 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu
+ r = sd_bus_message_read_strv(message, &env);
+ if (r < 0)
+ return r;
++
++ if (strv_length(env) > ENVIRONMENT_ASSIGNMENTS_MAX)
++ return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
++ "Too many environment assignments in a single query.");
+ if (!strv_env_is_valid(env))
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments");
+
+diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c
+index 4f8f591..02f5e5a 100644
+--- a/src/resolve/resolved-link-bus.c
++++ b/src/resolve/resolved-link-bus.c
+@@ -333,7 +333,7 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_
+ if (r < 0)
+ return r;
+
+- for (;;) {
++ for (unsigned n_names = 0;; n_names++) {
+ _cleanup_free_ char *prefixed = NULL;
+ const char *name;
+ int route_only;
+@@ -351,6 +351,8 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search domain %s", name);
+ if (!route_only && dns_name_is_root(name))
+ return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Root domain is not suitable as search domain");
++ if (n_names >= LINK_SEARCH_DOMAINS_MAX)
++ return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many search domains per link");
+
+ if (route_only) {
+ prefixed = strjoin("~", name);
+@@ -682,6 +684,9 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v
+ if (r < 0)
+ return r;
+
++ if (strv_length(ntas) > LINK_NEGATIVE_TRUST_ANCHORS_MAX)
++ return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many negative trust anchors per link");
++
+ STRV_FOREACH(i, ntas) {
+ r = dns_name_is_valid(*i);
+ if (r < 0)
+diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h
+index e2cb2a2..d7cd153 100644
+--- a/src/resolve/resolved-link.h
++++ b/src/resolve/resolved-link.h
+@@ -20,6 +20,7 @@ typedef struct LinkAddress LinkAddress;
+
+ #define LINK_SEARCH_DOMAINS_MAX 256
+ #define LINK_DNS_SERVERS_MAX 256
++#define LINK_NEGATIVE_TRUST_ANCHORS_MAX 2048
+
+ struct LinkAddress {
+ Link *link;
+diff --git a/src/shared/json.c b/src/shared/json.c
+index d64daaf..b8b4aa8 100644
+--- a/src/shared/json.c
++++ b/src/shared/json.c
+@@ -4610,6 +4610,7 @@ int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags
+ _cleanup_strv_free_ char **l = NULL;
+ char ***s = ASSERT_PTR(userdata);
+ JsonVariant *e;
++ size_t n = 0;
+ int r;
+
+ assert(variant);
+@@ -4642,7 +4643,7 @@ int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags
+ if ((flags & JSON_SAFE) && !string_is_safe(json_variant_string(e)))
+ return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' contains unsafe characters, refusing.", strna(name));
+
+- r = strv_extend(&l, json_variant_string(e));
++ r = strv_extend_with_size(&l, &n, json_variant_string(e));
+ if (r < 0)
+ return json_log(e, flags, r, "Failed to append array element: %m");
+ }
+diff --git a/src/shared/user-record.c b/src/shared/user-record.c
+index b51d8ad..fad7d09 100644
+--- a/src/shared/user-record.c
++++ b/src/shared/user-record.c
+@@ -620,6 +620,7 @@ int json_dispatch_user_group_list(const char *name, JsonVariant *variant, JsonDi
+ _cleanup_strv_free_ char **l = NULL;
+ char ***list = userdata;
+ JsonVariant *e;
++ size_t s = 0;
+ int r;
+
+ if (!json_variant_is_array(variant))
+@@ -633,7 +634,7 @@ int json_dispatch_user_group_list(const char *name, JsonVariant *variant, JsonDi
+ if (!valid_user_group_name(json_variant_string(e), FLAGS_SET(flags, JSON_RELAX) ? VALID_USER_RELAX : 0))
+ return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a valid user/group name: %s", json_variant_string(e));
+
+- r = strv_extend(&l, json_variant_string(e));
++ r = strv_extend_with_size(&l, &s, json_variant_string(e));
+ if (r < 0)
+ return json_log(e, flags, r, "Failed to append array element: %m");
+ }
diff --git a/systemd-GHSA-5rm9-cc37-35gq.patch b/systemd-GHSA-5rm9-cc37-35gq.patch
new file mode 100644
index 0000000..b6f177a
--- /dev/null
+++ b/systemd-GHSA-5rm9-cc37-35gq.patch
@@ -0,0 +1,93 @@
+GHSA-5rm9-cc37-35gq: JSON parser stack grows without a nesting limit; a 16 MB
+varlink message makes services allocate ~640 MB.
+https://github.com/systemd/systemd/security/advisories/GHSA-5rm9-cc37-35gq
+Upstream v258.8: de6adcc29c; from Ubuntu noble 255.4-1ubuntu8.17.
+
+ src/shared/json.c | 12 ++++++++++++
+ src/test/test-json.c | 43 +++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 55 insertions(+)
+
+diff --git a/src/shared/json.c b/src/shared/json.c
+index aad79ba..d64daaf 100644
+--- a/src/shared/json.c
++++ b/src/shared/json.c
+@@ -2979,6 +2979,12 @@ static int json_parse_internal(
+ goto finish;
+ }
+
++ /* n_stack includes the top level entry, hence > instead of >= */
++ if (n_stack > DEPTH_MAX) {
++ r = -ELNRNG;
++ goto finish;
++ }
++
+ if (!GREEDY_REALLOC(stack, n_stack+1)) {
+ r = -ENOMEM;
+ goto finish;
+@@ -3030,6 +3036,12 @@ static int json_parse_internal(
+ goto finish;
+ }
+
++ /* n_stack includes the top level entry, hence > instead of >= */
++ if (n_stack > DEPTH_MAX) {
++ r = -ELNRNG;
++ goto finish;
++ }
++
+ if (!GREEDY_REALLOC(stack, n_stack+1)) {
+ r = -ENOMEM;
+ goto finish;
+diff --git a/src/test/test-json.c b/src/test/test-json.c
+index 3657075..64bef46 100644
+--- a/src/test/test-json.c
++++ b/src/test/test-json.c
+@@ -436,6 +436,49 @@ TEST(depth) {
+ fputs("\n", stdout);
+ }
+
++static char *prepare_nested_json(const char *open, unsigned depth) {
++ char *s, *p;
++ size_t olen;
++
++ assert_se(open);
++
++ olen = strlen(open);
++ s = p = new(char, olen * depth + 1);
++ if (!s)
++ return NULL;
++
++ for (unsigned i = 0; i < depth; i++)
++ p = mempcpy(p, open, olen);
++ *p = '\0';
++
++ return s;
++}
++
++TEST(parse_depth) {
++ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
++ _cleanup_free_ char *s = NULL;
++
++ /* Refuse parsing > DEPTH_MAX (currently 2048) levels of nested arrays */
++ s = prepare_nested_json("[", 2049);
++ assert_se(json_parse(s, 0, &v, NULL, NULL) == -ELNRNG);
++ s = mfree(s);
++
++ /* Same for nested objects */
++ s = prepare_nested_json("{\"a\":", 2049);
++ assert_se(json_parse(s, 0, &v, NULL, NULL) == -ELNRNG);
++ s = mfree(s);
++
++ /* <= DEPTH_MAX levels of nested arrays should be refused by EINVAL
++ * later in the parsing process */
++ s = prepare_nested_json("[", 2048);
++ assert_se(json_parse(s, 0, &v, NULL, NULL) == -EINVAL);
++ s = mfree(s);
++
++ /* And the same for nested objects */
++ s = prepare_nested_json("{\"a\":", 2048);
++ assert_se(json_parse(s, 0, &v, NULL, NULL) == -EINVAL);
++}
++
+ TEST(normalize) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL;
+ _cleanup_free_ char *t = NULL;
diff --git a/systemd-GHSA-m8q3-73v4-wvg7.patch b/systemd-GHSA-m8q3-73v4-wvg7.patch
new file mode 100644
index 0000000..2cd98ff
--- /dev/null
+++ b/systemd-GHSA-m8q3-73v4-wvg7.patch
@@ -0,0 +1,44 @@
+GHSA-m8q3-73v4-wvg7: scsi_id prints unsanitized ID_WWN* fields (rest of CVE-2026-40225).
+https://github.com/systemd/systemd/security/advisories/GHSA-m8q3-73v4-wvg7
+Upstream v258.8: d2d3aab7ab; from Ubuntu noble 255.4-1ubuntu8.17.
+
+ src/udev/scsi_id/scsi_id.c | 12 ++++++++----
+ 1 file changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c
+index b2df8d9..1f0d75f 100644
+--- a/src/udev/scsi_id/scsi_id.c
++++ b/src/udev/scsi_id/scsi_id.c
+@@ -391,6 +391,10 @@ static int set_inq_values(struct scsi_id_device *dev_scsi, const char *path) {
+ return 0;
+ }
+
++static bool scsi_string_is_valid(const char *s) {
++ return !isempty(s) && utf8_is_valid(s) && !string_has_cc(s, /* ok= */ NULL);
++}
++
+ /*
+ * scsi_id: try to get an id, if one is found, printf it to stdout.
+ * returns a value passed to exit() - 0 if printed an id, else 1.
+@@ -434,17 +438,17 @@ static int scsi_id(char *maj_min_dev) {
+ udev_replace_chars(serial_str, NULL);
+ printf("ID_SERIAL_SHORT=%s\n", serial_str);
+ }
+- if (dev_scsi.wwn[0] != '\0') {
++ if (scsi_string_is_valid(dev_scsi.wwn)) {
+ printf("ID_WWN=0x%s\n", dev_scsi.wwn);
+- if (dev_scsi.wwn_vendor_extension[0] != '\0') {
++ if (scsi_string_is_valid(dev_scsi.wwn_vendor_extension)) {
+ printf("ID_WWN_VENDOR_EXTENSION=0x%s\n", dev_scsi.wwn_vendor_extension);
+ printf("ID_WWN_WITH_EXTENSION=0x%s%s\n", dev_scsi.wwn, dev_scsi.wwn_vendor_extension);
+ } else
+ printf("ID_WWN_WITH_EXTENSION=0x%s\n", dev_scsi.wwn);
+ }
+- if (dev_scsi.tgpt_group[0] != '\0')
++ if (scsi_string_is_valid(dev_scsi.tgpt_group))
+ printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group);
+- if (dev_scsi.unit_serial_number[0] != '\0' && utf8_is_valid(dev_scsi.unit_serial_number) && !string_has_cc(dev_scsi.unit_serial_number, /* ok= */ NULL))
++ if (scsi_string_is_valid(dev_scsi.unit_serial_number))
+ printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number);
+ goto out;
+ }
diff --git a/systemd-errno-list-kernel7.patch b/systemd-errno-list-kernel7.patch
new file mode 100644
index 0000000..3637e6c
--- /dev/null
+++ b/systemd-errno-list-kernel7.patch
@@ -0,0 +1,23 @@
+linux-libc-headers 7.0+ define EFSBADCRC/EFSCORRUPTED as aliases of EBADMSG/EUCLEAN;
+the generated errno table then fails with -Werror=override-init.
+Upstream 3cfb16998808a6ec8012a6120d0a82f0e1a0c8bb, adapted to the 254 script.
+
+--- a/src/basic/generate-errno-list.sh
++++ b/src/basic/generate-errno-list.sh
+@@ -3,9 +3,13 @@
+ set -eu
+ set -o pipefail
+
+-# In kernel's arch/parisc/include/uapi/asm/errno.h, ECANCELLED and EREFUSED are defined as aliases of
+-# ECANCELED and ECONNREFUSED, respectively. Let's drop them.
++# In kernel's arch/parisc/include/uapi/asm/errno.h, The following aliases are defined:
++# ECANCELLED → ECANCELED
++# EREFUSED → ECONNREFUSED
++# EFSBADCRC → EBADMSG
++# EFSCORRUPTED → EUCLEAN
++# Let's drop them.
+
+ ${1:?} -dM -include errno.h - </dev/null | \
+- grep -Ev '^#define[[:space:]]+(ECANCELLED|EREFUSED)' | \
++ grep -Ev '^#define[[:space:]]+(ECANCELLED|EREFUSED|EFSBADCRC|EFSCORRUPTED)' | \
+ awk '/^#define[ \t]+E[^ _]+[ \t]+/ { print $2; }'
================================================================
---- gitweb:
http://git.pld-linux.org/gitweb.cgi/packages/systemd.git/commitdiff/177924eae255485e247ed317cda1f5281f7bc1c3
More information about the pld-cvs-commit
mailing list