From 0bb6b44d208ead4f887673f45e90b3568ef69d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjinyu=E2=80=9D?= Date: Wed, 2 Jul 2025 10:54:09 +0800 Subject: [PATCH] Fix CVE-2022-4415 and CVE-2025-4598 Add patch to fix CVE-2022-4415 and CVE-2025-4598 Signed-off-by: jinyu --- ...naligned_read_ne-32-64-to-parse-auxv.patch | 205 ++++++++++ ...allow-user-to-access-coredumps-with-.patch | 376 ++++++++++++++++++ ...oredump-use-d-in-kernel-core-pattern.patch | 117 ++++++ systemd.spec | 12 +- 4 files changed, 709 insertions(+), 1 deletion(-) create mode 100644 backport-coredump-use-unaligned_read_ne-32-64-to-parse-auxv.patch create mode 100644 backport-fix-CVE-2022-4415-coredump-do-not-allow-user-to-access-coredumps-with-.patch create mode 100644 backport-fix-CVE-2025-4598-coredump-use-d-in-kernel-core-pattern.patch diff --git a/backport-coredump-use-unaligned_read_ne-32-64-to-parse-auxv.patch b/backport-coredump-use-unaligned_read_ne-32-64-to-parse-auxv.patch new file mode 100644 index 0000000..3e8669a --- /dev/null +++ b/backport-coredump-use-unaligned_read_ne-32-64-to-parse-auxv.patch @@ -0,0 +1,205 @@ +From d4ffe5c6c7a808a437fcdcd96c21f80f468a6201 Mon Sep 17 00:00:00 2001 +From: "Tian, Chunsheng" +Date: Thu, 26 Jun 2025 09:09:36 +0800 +Subject: [PATCH 2/4] coredump: use unaligned_read_ne{32,64}() to parse auxv + +Fixes a bug introduced by 3e4d0f6cf99f8677edd6a237382a65bfe758de03. + +The auxv metadata is unaligned, as the length of the prefix +"COREDUMP_PROC_AUXV=" is 19. Hence, parse_auxv{32,64}() may triger +an undefined behavior (or at least cause slow down), which can be +detected when running on an undefined behavior sanitizer. + +This also introduces a macro to define `parse_auxv{32,64}()`. + +Fixes #26912. + +(picked 9b032f932c4172fac379234d9d42cf2b266ccaea) + +Signed-off-by: Tian, Chunsheng +--- + src/coredump/coredump.c | 149 ++++++++++++++++------------------------ + 1 file changed, 60 insertions(+), 89 deletions(-) + +diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c +index 884f269..27fc7e9 100644 +--- a/src/coredump/coredump.c ++++ b/src/coredump/coredump.c +@@ -48,6 +48,7 @@ + #include "string-util.h" + #include "strv.h" + #include "tmpfile-util.h" ++#include "unaligned.h" + #include "user-util.h" + + /* The maximum size up to which we process coredumps */ +@@ -349,95 +350,65 @@ static int make_filename(const Context *context, char **ret) { + return 0; + } + +-static int parse_auxv64( +- const uint64_t *auxv, +- size_t size_bytes, +- int *at_secure, +- uid_t *uid, +- uid_t *euid, +- gid_t *gid, +- gid_t *egid) { +- +- assert(auxv || size_bytes == 0); +- +- if (size_bytes % (2 * sizeof(uint64_t)) != 0) +- return log_warning_errno(SYNTHETIC_ERRNO(EIO), "Incomplete auxv structure (%zu bytes).", size_bytes); +- +- size_t words = size_bytes / sizeof(uint64_t); +- +- /* Note that we set output variables even on error. */ +- +- for (size_t i = 0; i + 1 < words; i += 2) +- switch (auxv[i]) { +- case AT_SECURE: +- *at_secure = auxv[i + 1] != 0; +- break; +- case AT_UID: +- *uid = auxv[i + 1]; +- break; +- case AT_EUID: +- *euid = auxv[i + 1]; +- break; +- case AT_GID: +- *gid = auxv[i + 1]; +- break; +- case AT_EGID: +- *egid = auxv[i + 1]; +- break; +- case AT_NULL: +- if (auxv[i + 1] != 0) +- goto error; +- return 0; +- } +- error: +- return log_warning_errno(SYNTHETIC_ERRNO(ENODATA), +- "AT_NULL terminator not found, cannot parse auxv structure."); +-} +- +-static int parse_auxv32( +- const uint32_t *auxv, +- size_t size_bytes, +- int *at_secure, +- uid_t *uid, +- uid_t *euid, +- gid_t *gid, +- gid_t *egid) { +- +- assert(auxv || size_bytes == 0); +- +- size_t words = size_bytes / sizeof(uint32_t); +- +- if (size_bytes % (2 * sizeof(uint32_t)) != 0) +- return log_warning_errno(SYNTHETIC_ERRNO(EIO), "Incomplete auxv structure (%zu bytes).", size_bytes); ++#define _DEFINE_PARSE_AUXV(size, type, unaligned_read) \ ++ static int parse_auxv##size( \ ++ const void *auxv, \ ++ size_t size_bytes, \ ++ int *at_secure, \ ++ uid_t *uid, \ ++ uid_t *euid, \ ++ gid_t *gid, \ ++ gid_t *egid) { \ ++ \ ++ assert(auxv || size_bytes == 0); \ ++ \ ++ if (size_bytes % (2 * sizeof(type)) != 0) \ ++ return log_warning_errno(SYNTHETIC_ERRNO(EIO), \ ++ "Incomplete auxv structure (%zu bytes).", \ ++ size_bytes); \ ++ \ ++ size_t words = size_bytes / sizeof(type); \ ++ \ ++ /* Note that we set output variables even on error. */ \ ++ \ ++ for (size_t i = 0; i + 1 < words; i += 2) { \ ++ type key, val; \ ++ \ ++ key = unaligned_read((uint8_t*) auxv + i * sizeof(type)); \ ++ val = unaligned_read((uint8_t*) auxv + (i + 1) * sizeof(type)); \ ++ \ ++ switch (key) { \ ++ case AT_SECURE: \ ++ *at_secure = val != 0; \ ++ break; \ ++ case AT_UID: \ ++ *uid = val; \ ++ break; \ ++ case AT_EUID: \ ++ *euid = val; \ ++ break; \ ++ case AT_GID: \ ++ *gid = val; \ ++ break; \ ++ case AT_EGID: \ ++ *egid = val; \ ++ break; \ ++ case AT_NULL: \ ++ if (val != 0) \ ++ goto error; \ ++ return 0; \ ++ } \ ++ } \ ++ error: \ ++ return log_warning_errno(SYNTHETIC_ERRNO(ENODATA), \ ++ "AT_NULL terminator not found, cannot parse auxv structure."); \ ++ } + +- /* Note that we set output variables even on error. */ ++#define DEFINE_PARSE_AUXV(size)\ ++ _DEFINE_PARSE_AUXV(size, uint##size##_t, unaligned_read_ne##size) + +- for (size_t i = 0; i + 1 < words; i += 2) +- switch (auxv[i]) { +- case AT_SECURE: +- *at_secure = auxv[i + 1] != 0; +- break; +- case AT_UID: +- *uid = auxv[i + 1]; +- break; +- case AT_EUID: +- *euid = auxv[i + 1]; +- break; +- case AT_GID: +- *gid = auxv[i + 1]; +- break; +- case AT_EGID: +- *egid = auxv[i + 1]; +- break; +- case AT_NULL: +- if (auxv[i + 1] != 0) +- goto error; +- return 0; +- } +- error: +- return log_warning_errno(SYNTHETIC_ERRNO(ENODATA), +- "AT_NULL terminator not found, cannot parse auxv structure."); +-} ++DEFINE_PARSE_AUXV(32); ++DEFINE_PARSE_AUXV(64); + + static int grant_user_access(int core_fd, const Context *context) { + int at_secure = -1; +@@ -474,11 +445,11 @@ static int grant_user_access(int core_fd, const Context *context) { + "Core file has non-native endianness, not adjusting permissions."); + + if (elf[EI_CLASS] == ELFCLASS64) +- r = parse_auxv64((const uint64_t*) context->meta[META_PROC_AUXV], ++ r = parse_auxv64(context->meta[META_PROC_AUXV], + context->meta_size[META_PROC_AUXV], + &at_secure, &uid, &euid, &gid, &egid); + else +- r = parse_auxv32((const uint32_t*) context->meta[META_PROC_AUXV], ++ r = parse_auxv32(context->meta[META_PROC_AUXV], + context->meta_size[META_PROC_AUXV], + &at_secure, &uid, &euid, &gid, &egid); + if (r < 0) +-- +2.20.1 + diff --git a/backport-fix-CVE-2022-4415-coredump-do-not-allow-user-to-access-coredumps-with-.patch b/backport-fix-CVE-2022-4415-coredump-do-not-allow-user-to-access-coredumps-with-.patch new file mode 100644 index 0000000..753a2c9 --- /dev/null +++ b/backport-fix-CVE-2022-4415-coredump-do-not-allow-user-to-access-coredumps-with-.patch @@ -0,0 +1,376 @@ +From 32e7d750a9611a2d40a5f50b5fb948722d2e6513 Mon Sep 17 00:00:00 2001 +From: "Tian, Chunsheng" +Date: Wed, 25 Jun 2025 17:31:30 +0800 +Subject: [PATCH 1/4] coredump: do not allow user to access coredumps with + changed uid/gid/capabilities + +From 3e4d0f6cf99f8677edd6a237382a65bfe758de03 + +When the user starts a program which elevates its permissions via setuid, +setgid, or capabilities set on the file, it may access additional information +which would then be visible in the coredump. We shouldn't make the the coredump +visible to the user in such cases. + +Reported-by: Matthias Gerstner + +This reads the /proc//auxv file and attaches it to the process metadata as +PROC_AUXV. Before the coredump is submitted, it is parsed and if either +at_secure was set (which the kernel will do for processes that are setuid, +setgid, or setcap), or if the effective uid/gid don't match uid/gid, the file +is not made accessible to the user. If we can't access this data, we assume the +file should not be made accessible either. In principle we could also access +the auxv data from a note in the core file, but that is much more complex and +it seems better to use the stand-alone file that is provided by the kernel. + +Attaching auxv is both convient for this patch (because this way it's passed +between the stages along with other fields), but I think it makes sense to save +it in general. + + We use the information early in the core file to figure out if the program was +32-bit or 64-bit and its endianness. This way we don't need heuristics to guess +whether the format of the auxv structure. This test might reject some cases on +fringe architecutes. But the impact would be limited: we just won't grant the +user permissions to view the coredump file. If people report that we're missing +some cases, we can always enhance this to support more architectures. + +I tested auxv parsing on amd64, 32-bit program on amd64, arm64, arm32, and +ppc64el, but not the whole coredump handling. + +Signed-off-by: Tian, Chunsheng +--- + src/basic/io-util.h | 7 ++ + src/coredump/coredump.c | 190 ++++++++++++++++++++++++++++++++++++++-- + 2 files changed, 188 insertions(+), 9 deletions(-) + +diff --git a/src/basic/io-util.h b/src/basic/io-util.h +index 719e19e..d111172 100644 +--- a/src/basic/io-util.h ++++ b/src/basic/io-util.h +@@ -86,6 +86,13 @@ struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw); + struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw); + void iovw_free_contents(struct iovec_wrapper *iovw, bool free_vectors); + int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len); ++static inline int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { ++ /* Move data into iovw or free on error */ ++ int r = iovw_put(iovw, data, len); ++ if (r < 0) ++ free(data); ++ return r; ++} + int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const char *value); + int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value); + void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new); +diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c +index 2888071..884f269 100644 +--- a/src/coredump/coredump.c ++++ b/src/coredump/coredump.c +@@ -3,6 +3,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -96,6 +97,7 @@ enum { + + META_EXE = _META_MANDATORY_MAX, + META_UNIT, ++ META_PROC_AUXV, + _META_MAX + }; + +@@ -110,10 +112,12 @@ static const char * const meta_field_names[_META_MAX] = { + [META_COMM] = "COREDUMP_COMM=", + [META_EXE] = "COREDUMP_EXE=", + [META_UNIT] = "COREDUMP_UNIT=", ++ [META_PROC_AUXV] = "COREDUMP_PROC_AUXV=", + }; + + typedef struct Context { + const char *meta[_META_MAX]; ++ size_t meta_size[_META_MAX]; + pid_t pid; + bool is_pid1; + bool is_journald; +@@ -172,7 +176,9 @@ static uint64_t storage_size_max(void) { + return 0; + } + +-static int fix_acl(int fd, uid_t uid) { ++static int fix_acl(int fd, uid_t uid, bool allow_user) { ++ assert(fd >= 0); ++ assert(uid_is_valid(uid)); + + #if HAVE_ACL + _cleanup_(acl_freep) acl_t acl = NULL; +@@ -180,7 +186,9 @@ static int fix_acl(int fd, uid_t uid) { + acl_permset_t permset; + int r; + +- assert(fd >= 0); ++ /* We don't allow users to read coredumps if the uid or capabilities were changed. */ ++ if (!allow_user) ++ return 0; + + if (uid_is_system(uid) || uid_is_dynamic(uid) || uid == UID_NOBODY) + return 0; +@@ -259,7 +267,8 @@ static int fix_permissions( + const char *filename, + const char *target, + const Context *context, +- uid_t uid) { ++ uid_t uid, ++ bool allow_user) { + + int r; + +@@ -269,7 +278,7 @@ static int fix_permissions( + + /* Ignore errors on these */ + (void) fchmod(fd, 0640); +- (void) fix_acl(fd, uid); ++ (void) fix_acl(fd, uid, allow_user); + (void) fix_xattr(fd, context); + + if (fsync(fd) < 0) +@@ -340,6 +349,154 @@ static int make_filename(const Context *context, char **ret) { + return 0; + } + ++static int parse_auxv64( ++ const uint64_t *auxv, ++ size_t size_bytes, ++ int *at_secure, ++ uid_t *uid, ++ uid_t *euid, ++ gid_t *gid, ++ gid_t *egid) { ++ ++ assert(auxv || size_bytes == 0); ++ ++ if (size_bytes % (2 * sizeof(uint64_t)) != 0) ++ return log_warning_errno(SYNTHETIC_ERRNO(EIO), "Incomplete auxv structure (%zu bytes).", size_bytes); ++ ++ size_t words = size_bytes / sizeof(uint64_t); ++ ++ /* Note that we set output variables even on error. */ ++ ++ for (size_t i = 0; i + 1 < words; i += 2) ++ switch (auxv[i]) { ++ case AT_SECURE: ++ *at_secure = auxv[i + 1] != 0; ++ break; ++ case AT_UID: ++ *uid = auxv[i + 1]; ++ break; ++ case AT_EUID: ++ *euid = auxv[i + 1]; ++ break; ++ case AT_GID: ++ *gid = auxv[i + 1]; ++ break; ++ case AT_EGID: ++ *egid = auxv[i + 1]; ++ break; ++ case AT_NULL: ++ if (auxv[i + 1] != 0) ++ goto error; ++ return 0; ++ } ++ error: ++ return log_warning_errno(SYNTHETIC_ERRNO(ENODATA), ++ "AT_NULL terminator not found, cannot parse auxv structure."); ++} ++ ++static int parse_auxv32( ++ const uint32_t *auxv, ++ size_t size_bytes, ++ int *at_secure, ++ uid_t *uid, ++ uid_t *euid, ++ gid_t *gid, ++ gid_t *egid) { ++ ++ assert(auxv || size_bytes == 0); ++ ++ size_t words = size_bytes / sizeof(uint32_t); ++ ++ if (size_bytes % (2 * sizeof(uint32_t)) != 0) ++ return log_warning_errno(SYNTHETIC_ERRNO(EIO), "Incomplete auxv structure (%zu bytes).", size_bytes); ++ ++ /* Note that we set output variables even on error. */ ++ ++ for (size_t i = 0; i + 1 < words; i += 2) ++ switch (auxv[i]) { ++ case AT_SECURE: ++ *at_secure = auxv[i + 1] != 0; ++ break; ++ case AT_UID: ++ *uid = auxv[i + 1]; ++ break; ++ case AT_EUID: ++ *euid = auxv[i + 1]; ++ break; ++ case AT_GID: ++ *gid = auxv[i + 1]; ++ break; ++ case AT_EGID: ++ *egid = auxv[i + 1]; ++ break; ++ case AT_NULL: ++ if (auxv[i + 1] != 0) ++ goto error; ++ return 0; ++ } ++ error: ++ return log_warning_errno(SYNTHETIC_ERRNO(ENODATA), ++ "AT_NULL terminator not found, cannot parse auxv structure."); ++} ++ ++static int grant_user_access(int core_fd, const Context *context) { ++ int at_secure = -1; ++ uid_t uid = UID_INVALID, euid = UID_INVALID; ++ uid_t gid = GID_INVALID, egid = GID_INVALID; ++ int r; ++ ++ assert(core_fd >= 0); ++ assert(context); ++ ++ if (!context->meta[META_PROC_AUXV]) ++ return log_warning_errno(SYNTHETIC_ERRNO(ENODATA), "No auxv data, not adjusting permissions."); ++ ++ uint8_t elf[EI_NIDENT]; ++ errno = 0; ++ if (pread(core_fd, &elf, sizeof(elf), 0) != sizeof(elf)) ++ return log_warning_errno(errno_or_else(EIO), ++ "Failed to pread from coredump fd: %s", strerror(errno)); ++ ++ if (elf[EI_MAG0] != ELFMAG0 || ++ elf[EI_MAG1] != ELFMAG1 || ++ elf[EI_MAG2] != ELFMAG2 || ++ elf[EI_MAG3] != ELFMAG3 || ++ elf[EI_VERSION] != EV_CURRENT) ++ return log_info_errno(SYNTHETIC_ERRNO(EUCLEAN), ++ "Core file does not have ELF header, not adjusting permissions."); ++ if (!IN_SET(elf[EI_CLASS], ELFCLASS32, ELFCLASS64) || ++ !IN_SET(elf[EI_DATA], ELFDATA2LSB, ELFDATA2MSB)) ++ return log_info_errno(SYNTHETIC_ERRNO(EUCLEAN), ++ "Core file has strange ELF class, not adjusting permissions."); ++ ++ if ((elf[EI_DATA] == ELFDATA2LSB) != (__BYTE_ORDER == __LITTLE_ENDIAN)) ++ return log_info_errno(SYNTHETIC_ERRNO(EUCLEAN), ++ "Core file has non-native endianness, not adjusting permissions."); ++ ++ if (elf[EI_CLASS] == ELFCLASS64) ++ r = parse_auxv64((const uint64_t*) context->meta[META_PROC_AUXV], ++ context->meta_size[META_PROC_AUXV], ++ &at_secure, &uid, &euid, &gid, &egid); ++ else ++ r = parse_auxv32((const uint32_t*) context->meta[META_PROC_AUXV], ++ context->meta_size[META_PROC_AUXV], ++ &at_secure, &uid, &euid, &gid, &egid); ++ 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. */ ++ bool ret = ++ 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)", ++ ret ? "permit" : "restrict", ++ uid, euid, gid, egid, yes_no(at_secure)); ++ return ret; ++} ++ ++ + static int save_external_coredump( + const Context *context, + int input_fd, +@@ -421,6 +578,7 @@ static int save_external_coredump( + goto fail; + } + ++ bool allow_user = grant_user_access(fd, context) > 0; + #if HAVE_XZ || HAVE_LZ4 + /* If we will remove the coredump anyway, do not compress. */ + if (arg_compress && !maybe_remove_external_coredump(NULL, st.st_size)) { +@@ -446,7 +604,7 @@ static int save_external_coredump( + goto fail_compressed; + } + +- r = fix_permissions(fd_compressed, tmp_compressed, fn_compressed, context, uid); ++ r = fix_permissions(fd_compressed, tmp_compressed, fn_compressed, context, uid, allow_user); + if (r < 0) + goto fail_compressed; + +@@ -469,7 +627,7 @@ static int save_external_coredump( + uncompressed: + #endif + +- r = fix_permissions(fd, tmp, fn, context, uid); ++ r = fix_permissions(fd, tmp, fn, context, uid, allow_user); + if (r < 0) + goto fail; + +@@ -720,7 +878,7 @@ static int change_uid_gid(const Context *context) { + } + + static int submit_coredump( +- Context *context, ++ const Context *context, + struct iovec_wrapper *iovw, + int input_fd) { + +@@ -842,16 +1000,16 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) { + struct iovec *iovec = iovw->iovec + n; + + for (i = 0; i < ELEMENTSOF(meta_field_names); i++) { +- char *p; + + /* Note that these strings are NUL terminated, because we made sure that a + * trailing NUL byte is in the buffer, though not included in the iov_len + * count (see process_socket() and gather_pid_metadata_*()) */ + assert(((char*) iovec->iov_base)[iovec->iov_len] == 0); + +- p = startswith(iovec->iov_base, meta_field_names[i]); ++ const char *p = startswith(iovec->iov_base, meta_field_names[i]); + if (p) { + context->meta[i] = p; ++ context->meta_size[i] = iovec->iov_len - strlen(meta_field_names[i]); + count++; + break; + } +@@ -1099,6 +1257,7 @@ static int gather_pid_metadata(struct iovec_wrapper *iovw, Context *context) { + uid_t owner_uid; + pid_t pid; + char *t; ++ size_t size; + const char *p; + int r; + +@@ -1171,6 +1330,19 @@ static int gather_pid_metadata(struct iovec_wrapper *iovw, Context *context) { + if (read_full_file(p, &t, NULL) >=0) + (void) iovw_put_string_field_free(iovw, "COREDUMP_PROC_MOUNTINFO=", t); + ++ /* We attach /proc/auxv here. ELF coredumps also contain a note for this (NT_AUXV), see elf(5). */ ++ p = procfs_file_alloca(pid, "auxv"); ++ if (read_full_file(p, &t, &size) >= 0) { ++ char *buf = malloc(strlen("COREDUMP_PROC_AUXV=") + size + 1); ++ if (buf) { ++ /* Add a dummy terminator to make save_context() happy. */ ++ *((uint8_t*) mempcpy(stpcpy(buf, "COREDUMP_PROC_AUXV="), t, size)) = '\0'; ++ (void) iovw_consume(iovw, buf, size + strlen("COREDUMP_PROC_AUXV=")); ++ } ++ ++ free(t); ++ } ++ + if (get_process_cwd(pid, &t) >= 0) + (void) iovw_put_string_field_free(iovw, "COREDUMP_CWD=", t); + +-- +2.20.1 + diff --git a/backport-fix-CVE-2025-4598-coredump-use-d-in-kernel-core-pattern.patch b/backport-fix-CVE-2025-4598-coredump-use-d-in-kernel-core-pattern.patch new file mode 100644 index 0000000..60a8014 --- /dev/null +++ b/backport-fix-CVE-2025-4598-coredump-use-d-in-kernel-core-pattern.patch @@ -0,0 +1,117 @@ +From 15f19a1678e1ce4d6abc56a45e8585325c2189c0 Mon Sep 17 00:00:00 2001 +From: "Tian, Chunsheng" +Date: Thu, 26 Jun 2025 10:05:01 +0800 +Subject: [PATCH 3/4] Subject: [PATCH] coredump: use %d in kernel core pattern +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 + +In principle, %d might return a value other than 0, 1, or 2 in the future. +Thus, we accept those, but emit a notice. + +(picked 0c49e0049b7665bb7769a13ef346fef92e1ad4d6) + +Signed-off-by: Tian, Chunsheng +--- + src/coredump/coredump.c | 24 +++++++++++++++++++++--- + sysctl.d/50-coredump.conf.in | 2 +- + 2 files changed, 22 insertions(+), 4 deletions(-) + +diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c +index 27fc7e9..740c9ae 100644 +--- a/src/coredump/coredump.c ++++ b/src/coredump/coredump.c +@@ -83,6 +83,7 @@ enum { + META_ARGV_TIMESTAMP, /* %t: time of dump, expressed as seconds since the Epoch */ + 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 +@@ -110,6 +111,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=", +@@ -120,6 +122,7 @@ typedef struct Context { + const char *meta[_META_MAX]; + size_t meta_size[_META_MAX]; + pid_t pid; ++ unsigned dumpable; + bool is_pid1; + bool is_journald; + } Context; +@@ -455,14 +458,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 %d/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; + } +@@ -987,6 +992,19 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) { + } + } + ++ /* 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(context->meta[META_ARGV_DUMPABLE], &context->dumpable); ++ if (r < 0) ++ return log_error_errno(r, "Failed to parse dumpable field \"%s\": %m", context->meta[META_ARGV_DUMPABLE]); ++ if (context->dumpable > 2) ++ log_notice("Got unexpected %%d/dumpable value %u.", context->dumpable); ++ }else{ ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Failed to got the dumpable value."); ++ } ++ + if (!context->meta[META_ARGV_PID]) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to find the PID of crashing process"); +diff --git a/sysctl.d/50-coredump.conf.in b/sysctl.d/50-coredump.conf.in +index 327863d..98452f8 100644 +--- a/sysctl.d/50-coredump.conf.in ++++ b/sysctl.d/50-coredump.conf.in +@@ -9,7 +9,7 @@ + # and systemd-coredump(8) and core(5) for the explanation of the + # setting below. + +-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 that 16 coredumps are dispatched in parallel by the kernel. We want to + # be able to collect process metadata from /proc/%P/ while processing +-- +2.20.1 + diff --git a/systemd.spec b/systemd.spec index c5202b3..833ac81 100644 --- a/systemd.spec +++ b/systemd.spec @@ -16,7 +16,7 @@ Name: systemd Url: https://systemd.io/ Version: 243 -Release: 83 +Release: 84 License: MIT and LGPLv2+ and GPLv2+ Summary: System and Service Manager @@ -329,6 +329,12 @@ Patch9012: set-kernel-core_pipe_limit-to-16.patch Patch9013: disable-systemd-timesyncd-networkd-resolved-by-defau.patch Patch9014: process-util-log-more-information-when-runnin.patch +#Fix CVE-2022-4415 +#Fix CVE-2025-4598 +Patch9015: backport-fix-CVE-2022-4415-coredump-do-not-allow-user-to-access-coredumps-with-.patch +Patch9016: backport-coredump-use-unaligned_read_ne-32-64-to-parse-auxv.patch +Patch9017: backport-fix-CVE-2025-4598-coredump-use-d-in-kernel-core-pattern.patch + BuildRequires: gcc, gcc-c++ BuildRequires: libcap-devel, libmount-devel, pam-devel, libselinux-devel BuildRequires: audit-libs-devel, cryptsetup-devel, dbus-devel, libacl-devel @@ -1719,6 +1725,10 @@ fi %exclude /usr/share/man/man3/* %changelog +* Wed Jul 2 2025 Tian Chunsheng - 243-84 +- Fix CVE-2022-4415 +- Fix CVE-2025-4598 + * Thu Jan 2 2025 Han Jinpeng - 243-83 - Enhance the logging function of the systemctl command Add process-util-log-more-information-when-runnin.patch -- Gitee