From 68b0488120866073552d0421f1763c37fbc9ae07 Mon Sep 17 00:00:00 2001 From: Andrew Lukoshko Date: Fri, 15 Aug 2025 21:40:05 +0000 Subject: [PATCH] core: reorder systemd arguments on reexe coredump: use %d in kernel core pattern - CVE-2025-4598 Debrand for AlmaLinux --- ...-add-helper-to-parse-bounded-unsigne.patch | 97 +++++++++++++++ ...edump-get-rid-of-_META_MANDATORY_MAX.patch | 88 ++++++++++++++ ...oredump-use-d-in-kernel-core-pattern.patch | 113 ++++++++++++++++++ SPECS/systemd.spec | 8 +- 4 files changed, 304 insertions(+), 2 deletions(-) create mode 100644 SOURCES/CVE-2025-4598-0001-basic-parse-util-add-helper-to-parse-bounded-unsigne.patch create mode 100644 SOURCES/CVE-2025-4598-0002-coredump-get-rid-of-_META_MANDATORY_MAX.patch create mode 100644 SOURCES/CVE-2025-4598-0003-coredump-use-d-in-kernel-core-pattern.patch diff --git a/SOURCES/CVE-2025-4598-0001-basic-parse-util-add-helper-to-parse-bounded-unsigne.patch b/SOURCES/CVE-2025-4598-0001-basic-parse-util-add-helper-to-parse-bounded-unsigne.patch new file mode 100644 index 0000000..0425fdd --- /dev/null +++ b/SOURCES/CVE-2025-4598-0001-basic-parse-util-add-helper-to-parse-bounded-unsigne.patch @@ -0,0 +1,97 @@ +From d9281e6450d2cca3ea5e7eed61d95b8ff0fcca0b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Sat, 16 Sep 2023 10:08:12 +0200 +Subject: [PATCH] basic/parse-util: add helper to parse bounded unsigned values + +"parse_range" is already used for stuff like "a-b", so use "bounded" here to +avoid confusion. +--- + src/basic/parse-util.c | 15 +++++++++++++++ + src/basic/parse-util.h | 3 ++- + src/test/test-parse-util.c | 26 ++++++++++++++++++++++++++ + 3 files changed, 43 insertions(+), 1 deletion(-) + +diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c +index 3b3efb0..c1c25fd 100644 +--- a/src/basic/parse-util.c ++++ b/src/basic/parse-util.c +@@ -390,6 +390,21 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) { + return 0; + } + ++int safe_atou_bounded(const char *s, unsigned min, unsigned max, unsigned *ret) { ++ unsigned v; ++ int r; ++ ++ r = safe_atou(s, &v); ++ if (r < 0) ++ return r; ++ ++ if (v < min || v > max) ++ return -ERANGE; ++ ++ *ret = v; ++ return 0; ++} ++ + int safe_atoi(const char *s, int *ret_i) { + unsigned base = 0; + char *x = NULL; +diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h +index 8d8d523..7e1517c 100644 +--- a/src/basic/parse-util.h ++++ b/src/basic/parse-util.h +@@ -28,11 +28,12 @@ int parse_errno(const char *t); + #define SAFE_ATO_MASK_FLAGS(base) ((base) & ~SAFE_ATO_ALL_FLAGS) + + int safe_atou_full(const char *s, unsigned base, unsigned *ret_u); +- + static inline int safe_atou(const char *s, unsigned *ret_u) { + return safe_atou_full(s, 0, ret_u); + } + ++int safe_atou_bounded(const char *s, unsigned min, unsigned max, unsigned *ret); ++ + int safe_atoi(const char *s, int *ret_i); + int safe_atolli(const char *s, long long int *ret_i); + +diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c +index 388d0fe..3bf237b 100644 +--- a/src/test/test-parse-util.c ++++ b/src/test/test-parse-util.c +@@ -417,6 +417,32 @@ TEST(parse_range) { + assert_se(upper == 9999); + } + ++TEST(safe_atou_bounded) { ++ int r; ++ unsigned x; ++ ++ r = safe_atou_bounded("12345", 12, 20000, &x); ++ assert_se(r == 0); ++ assert_se(x == 12345); ++ ++ r = safe_atou_bounded("12", 12, 20000, &x); ++ assert_se(r == 0); ++ assert_se(x == 12); ++ ++ r = safe_atou_bounded("20000", 12, 20000, &x); ++ assert_se(r == 0); ++ assert_se(x == 20000); ++ ++ r = safe_atou_bounded("-1", 12, 20000, &x); ++ assert_se(r == -ERANGE); ++ ++ r = safe_atou_bounded("11", 12, 20000, &x); ++ assert_se(r == -ERANGE); ++ ++ r = safe_atou_bounded("20001", 12, 20000, &x); ++ assert_se(r == -ERANGE); ++} ++ + TEST(safe_atolli) { + int r; + long long l; +-- +2.47.1 + diff --git a/SOURCES/CVE-2025-4598-0002-coredump-get-rid-of-_META_MANDATORY_MAX.patch b/SOURCES/CVE-2025-4598-0002-coredump-get-rid-of-_META_MANDATORY_MAX.patch new file mode 100644 index 0000000..3a24c97 --- /dev/null +++ b/SOURCES/CVE-2025-4598-0002-coredump-get-rid-of-_META_MANDATORY_MAX.patch @@ -0,0 +1,88 @@ +From a18061cee0313d9425cad46c3b7c771cb74a22d0 Mon Sep 17 00:00:00 2001 +From: Alex Burmashev +Date: Tue, 27 May 2025 10:44:20 +0000 +Subject: [PATCH 1/2] coredump: get rid of _META_MANDATORY_MAX + +No functional change. This change is done in preparation for future changes. +Currently, the list of fields which are received on the command line is a +strict subset of the fields which are always expected to be received on a +socket. But when we add new kernel args in the future, we'll have two +non-overlapping sets and this approach will not work. Get rid of the variable +and enumerate the required fields. This set will never change, so this is +actually more maintainable. + +The message with the hint where to add new fields is switched with +_META_ARGV_MAX. The new order is more correct. + +CVE-2025-4598 + +Modified-by: Alex Burmashev +Signed-off-by: Alex Burmashev +--- + src/coredump/coredump.c | 28 +++++++++++++++++++--------- + 1 file changed, 19 insertions(+), 9 deletions(-) + +diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c +index dca78fa..539bc83 100644 +--- a/src/coredump/coredump.c ++++ b/src/coredump/coredump.c +@@ -81,7 +81,7 @@ + * size. See DATA_SIZE_MAX in journal-importer.h. */ + assert_cc(JOURNAL_SIZE_MAX <= DATA_SIZE_MAX); + +-enum { ++typedef enum { + /* We use these as array indexes for our process metadata cache. + * + * The first indices of the cache stores the same metadata as the ones passed by +@@ -103,16 +103,14 @@ enum { + * environment. */ + + META_COMM = _META_ARGV_MAX, +- _META_MANDATORY_MAX, + + /* The rest are similar to the previous ones except that we won't fail if one of + * them is missing. */ +- +- META_EXE = _META_MANDATORY_MAX, ++ META_EXE, + META_UNIT, + META_PROC_AUXV, + _META_MAX +-}; ++} meta_argv_t; + + static const char * const meta_field_names[_META_MAX] = { + [META_ARGV_PID] = "COREDUMP_PID=", +@@ -1193,12 +1191,24 @@ static int process_socket(int fd) { + if (r < 0) + goto finish; + +- /* Make sure we received at least all fields we need. */ +- for (int i = 0; i < _META_MANDATORY_MAX; i++) ++ /* Make sure we received all the expected fields. We support being called by an *older* ++ * systemd-coredump from the outside, so we require only the basic set of fields that ++ * was being sent when the support for sending to containers over a socket was added ++ * in a108c43e36d3ceb6e34efe37c014fc2cda856000. */ ++ meta_argv_t i; ++ VA_ARGS_FOREACH(i, ++ META_ARGV_PID, ++ META_ARGV_UID, ++ META_ARGV_GID, ++ META_ARGV_SIGNAL, ++ META_ARGV_TIMESTAMP, ++ META_ARGV_RLIMIT, ++ META_ARGV_HOSTNAME, ++ META_COMM) + if (!context.meta[i]) { + r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), +- "A mandatory argument (%i) has not been sent, aborting.", +- i); ++ "Mandatory argument %s not received on socket, aborting.", ++ meta_field_names[i]); + goto finish; + } + +-- +2.47.1 + diff --git a/SOURCES/CVE-2025-4598-0003-coredump-use-d-in-kernel-core-pattern.patch b/SOURCES/CVE-2025-4598-0003-coredump-use-d-in-kernel-core-pattern.patch new file mode 100644 index 0000000..2efd05b --- /dev/null +++ b/SOURCES/CVE-2025-4598-0003-coredump-use-d-in-kernel-core-pattern.patch @@ -0,0 +1,113 @@ +From 71f9f67970bde09f73812d59cbeafb7093e31f24 Mon Sep 17 00:00:00 2001 +From: Alex Burmashev +Date: Tue, 27 May 2025 10:47:46 +0000 +Subject: [PATCH 2/2] coredump: use %d in kernel core pattern + MIME-Version: 1.0 + Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The kernel provides %d which is documented as +"dump mode—same as value returned by prctl(2) PR_GET_DUMPABLE". + +We already query /proc/pid/auxv for this information, but unfortunately this +check is subject to a race, because the crashed process may be replaced by an +attacker before we read this data, for example replacing a SUID process that +was killed by a signal with another process that is not SUID, tricking us into +making the coredump of the original process readable by the attacker. + +With this patch, we effectively add one more check to the list of conditions +that need be satisfied if we are to make the coredump accessible to the user. + +Reportedy-by: Qualys Security Advisory + +CVE-2025-4598 + +Modified-by: Alex Burmashev +Signed-off-by: Alex Burmashev +--- + src/coredump/coredump.c | 20 +++++++++++++++++--- + sysctl.d/50-coredump.conf.in | 2 +- + 2 files changed, 30 insertions(+), 4 deletions(-) + +diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c +index 539bc83..f0b13a5 100644 +--- a/src/coredump/coredump.c ++++ b/src/coredump/coredump.c +@@ -95,6 +95,7 @@ typedef enum { + META_ARGV_TIMESTAMP, /* %t: time of dump, expressed as seconds since the Epoch (we expand this to µs granularity) */ + META_ARGV_RLIMIT, /* %c: core file size soft resource limit */ + META_ARGV_HOSTNAME, /* %h: hostname */ ++ META_ARGV_DUMPABLE, /* %d: as set by the kernel */ + _META_ARGV_MAX, + + /* The following indexes are cached for a couple of special fields we use (and +@@ -120,6 +121,7 @@ static const char * const meta_field_names[_META_MAX] = { + [META_ARGV_TIMESTAMP] = "COREDUMP_TIMESTAMP=", + [META_ARGV_RLIMIT] = "COREDUMP_RLIMIT=", + [META_ARGV_HOSTNAME] = "COREDUMP_HOSTNAME=", ++ [META_ARGV_DUMPABLE] = "COREDUMP_DUMPABLE=", + [META_COMM] = "COREDUMP_COMM=", + [META_EXE] = "COREDUMP_EXE=", + [META_UNIT] = "COREDUMP_UNIT=", +@@ -132,6 +134,7 @@ typedef struct Context { + pid_t pid; + uid_t uid; + gid_t gid; ++ unsigned dumpable; + bool is_pid1; + bool is_journald; + } Context; +@@ -447,14 +450,16 @@ static int grant_user_access(int core_fd, const Context *context) { + if (r < 0) + return r; + +- /* We allow access if we got all the data and at_secure is not set and +- * the uid/gid matches euid/egid. */ ++ /* We allow access if dumpable on the command line was exactly 1, we got all the data, ++ * at_secure is not set, and the uid/gid match euid/egid. */ + bool ret = ++ context->dumpable == 1 && + at_secure == 0 && + uid != UID_INVALID && euid != UID_INVALID && uid == euid && + gid != GID_INVALID && egid != GID_INVALID && gid == egid; +- log_debug("Will %s access (uid="UID_FMT " euid="UID_FMT " gid="GID_FMT " egid="GID_FMT " at_secure=%s)", ++ log_debug("Will %s access (dumpable=%u uid="UID_FMT " euid="UID_FMT " gid="GID_FMT " egid="GID_FMT " at_secure=%s)", + ret ? "permit" : "restrict", ++ context->dumpable, + uid, euid, gid, egid, yes_no(at_secure)); + return ret; + } +@@ -1082,6 +1087,15 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) { + return log_error_errno(r, "Failed to parse GID \"%s\": %m", context->meta[META_ARGV_GID]); + + ++ /* The value is set to contents of /proc/sys/fs/suid_dumpable, which we set to 2, ++ * if the process is marked as not dumpable, see PR_SET_DUMPABLE(2const). */ ++ if (context->meta[META_ARGV_DUMPABLE]) { ++ r = safe_atou_bounded(context->meta[META_ARGV_DUMPABLE], 0, 2, &context->dumpable); ++ if (r < 0) ++ return log_error_errno(r, "Failed to parse dumpable field \"%s\": %m", context->meta[META_ARGV_DUMPABLE]); ++ assert(context->dumpable <= 2); ++ } ++ + unit = context->meta[META_UNIT]; + context->is_pid1 = streq(context->meta[META_ARGV_PID], "1") || streq_ptr(unit, SPECIAL_INIT_SCOPE); + context->is_journald = streq_ptr(unit, SPECIAL_JOURNALD_SERVICE); +diff --git a/sysctl.d/50-coredump.conf.in b/sysctl.d/50-coredump.conf.in +index 5fb551a..9c10a89 100644 +--- a/sysctl.d/50-coredump.conf.in ++++ b/sysctl.d/50-coredump.conf.in +@@ -13,7 +13,7 @@ + # the core dump. + # + # See systemd-coredump(8) and core(5). +-kernel.core_pattern=|{{ROOTLIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h ++kernel.core_pattern=|{{ROOTLIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d + + # Allow 16 coredumps to be dispatched in parallel by the kernel. + # We collect metadata from /proc/%P/, and thus need to make sure the crashed +-- +2.47.1 + diff --git a/SPECS/systemd.spec b/SPECS/systemd.spec index b846e35..7769e9a 100644 --- a/SPECS/systemd.spec +++ b/SPECS/systemd.spec @@ -21,7 +21,7 @@ Name: systemd Url: https://systemd.io Version: 252 -Release: 51%{?dist}.1.alma.1 +Release: 51%{?dist}.1.alma.2 # For a breakdown of the licensing, see README License: LGPLv2+ and MIT and GPLv2+ Summary: System and Service Manager @@ -1197,6 +1197,9 @@ Patch1111: 1111-unit-always-return-1-in-log_kill.patch # AlmaLinux Patch Patch9000: 9000-core-reorder-systemd-arguments-on-reexec.patch +Patch9001: CVE-2025-4598-0001-basic-parse-util-add-helper-to-parse-bounded-unsigne.patch +Patch9002: CVE-2025-4598-0002-coredump-get-rid-of-_META_MANDATORY_MAX.patch +Patch9003: CVE-2025-4598-0003-coredump-use-d-in-kernel-core-pattern.patch # Downstream-only patches (9000–9999) @@ -2074,8 +2077,9 @@ systemd-hwdb update &>/dev/null || : %{_prefix}/lib/dracut/modules.d/70rhel-net-naming-sysattrs/* %changelog -* Tue Jun 24 2025 Andrew Lukoshko - 252-51.1.alma.1 +* Fri Aug 15 2025 Andrew Lukoshko - 252-51.1.alma.2 - core: reorder systemd arguments on reexe +- coredump: use %d in kernel core pattern - CVE-2025-4598 - Debrand for AlmaLinux * Tue Apr 08 2025 systemd maintenance team - 252-51.1