File 6007-coredump-use-d-in-kernel-core-pattern.patch of Package systemd.39177
From 934c5e68a199de57bbab8d594fb723ceb03a9b8b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Tue, 29 Apr 2025 14:47:59 +0200
Subject: [PATCH 6007/6007] 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 <qsa@qualys.com>
[fbui: adjust context]
[fbui: fixes CVE-2025-4598]
---
src/journal/coredump.c | 51 ++++++++++++++++++++++++------------
sysctl.d/50-coredump.conf.in | 2 +-
2 files changed, 35 insertions(+), 18 deletions(-)
diff --git a/src/journal/coredump.c b/src/journal/coredump.c
index 66b94ff701..03a4acb18b 100644
--- a/src/journal/coredump.c
+++ b/src/journal/coredump.c
@@ -80,6 +80,7 @@ enum {
INFO_PID,
INFO_UID,
INFO_GID,
+ INFO_DUMPABLE,
INFO_SIGNAL,
INFO_TIMESTAMP,
INFO_RLIMIT,
@@ -391,7 +392,7 @@ static int parse_auxv32(
return log_warning_errno(ENODATA, "AT_NULL terminator not found, cannot parse auxv structure.");
}
-static int grant_user_access(int core_fd, void *auxv_data, size_t auxv_size) {
+static int grant_user_access(int core_fd, unsigned dumpable, void *auxv_data, size_t auxv_size) {
int at_secure = -1;
uid_t uid = UID_INVALID, euid = UID_INVALID;
uid_t gid = GID_INVALID, egid = GID_INVALID;
@@ -430,14 +431,16 @@ static int grant_user_access(int core_fd, void *auxv_data, size_t auxv_size) {
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 =
+ 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",
+ dumpable,
uid, euid, gid, egid, yes_no(at_secure));
return ret;
}
@@ -445,6 +448,7 @@ static int grant_user_access(int core_fd, void *auxv_data, size_t auxv_size) {
static int save_external_coredump(
const char *info[_INFO_LEN],
uid_t uid,
+ unsigned dumpable,
void *auxv_data,
size_t auxv_size,
char **ret_filename,
@@ -512,7 +516,7 @@ static int save_external_coredump(
goto fail;
}
- bool allow_user = grant_user_access(fd, auxv_data, auxv_size) > 0;
+ bool allow_user = grant_user_access(fd, dumpable, auxv_data, auxv_size) > 0;
#if defined(HAVE_XZ) || defined(HAVE_LZ4)
/* If we will remove the coredump anyway, do not compress. */
@@ -711,7 +715,7 @@ int main(int argc, char* argv[]) {
/* The small core field we allocate on the stack, to keep things simple */
char
- *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_signal = NULL,
+ *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_dumpable = NULL, *core_signal = NULL,
*core_session = NULL, *core_exe = NULL, *core_comm = NULL, *core_cmdline = NULL,
*core_cgroup = NULL, *core_cwd = NULL, *core_root = NULL, *core_unit = NULL,
*core_slice = NULL;
@@ -720,20 +724,22 @@ int main(int argc, char* argv[]) {
_cleanup_free_ char
*core_timestamp = NULL, *core_message = NULL, *coredump_data = NULL, *core_owner_uid = NULL,
*core_open_fds = NULL, *core_proc_status = NULL, *core_proc_maps = NULL, *core_proc_limits = NULL,
- *core_proc_cgroup = NULL, *core_environ = NULL;
+ *core_proc_cgroup = NULL, *core_environ = NULL, *core_auxv = NULL;
+
+ _cleanup_free_ char *auxv_data = NULL;
_cleanup_free_ char *exe = NULL, *comm = NULL, *filename = NULL;
const char *info[_INFO_LEN];
_cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1;
- struct iovec iovec[26];
+ struct iovec iovec[27];
uint64_t coredump_size;
int r, j = 0;
uid_t uid, owner_uid;
gid_t gid;
pid_t pid;
- void *auxv_data;
+ unsigned dumpable;
size_t auxv_size;
char *t;
const char *p;
@@ -778,6 +784,13 @@ int main(int argc, char* argv[]) {
goto finish;
}
+ r = safe_atou(argv[INFO_DUMPABLE + 1], &dumpable);
+ if (r < 0) {
+ log_error("Failed to parse dumpable field \"%s\".", argv[INFO_DUMPABLE + 1]);
+ goto finish;
+ }
+ assert(dumpable <= 2);
+
if (get_process_comm(pid, &comm) < 0) {
log_warning("Failed to get COMM, falling back to the command line.");
comm = strv_join(argv + INFO_COMM + 1, " ");
@@ -789,6 +802,7 @@ int main(int argc, char* argv[]) {
info[INFO_PID] = argv[INFO_PID + 1];
info[INFO_UID] = argv[INFO_UID + 1];
info[INFO_GID] = argv[INFO_GID + 1];
+ info[INFO_DUMPABLE] = argv[INFO_DUMPABLE + 1];
info[INFO_SIGNAL] = argv[INFO_SIGNAL + 1];
info[INFO_TIMESTAMP] = argv[INFO_TIMESTAMP + 1];
info[INFO_RLIMIT] = argv[INFO_RLIMIT + 1];
@@ -807,7 +821,8 @@ int main(int argc, char* argv[]) {
if (arg_storage != COREDUMP_STORAGE_NONE)
arg_storage = COREDUMP_STORAGE_EXTERNAL;
- r = save_external_coredump(info, uid, NULL, 0, &filename, &coredump_node_fd, &coredump_fd, &coredump_size);
+ r = save_external_coredump(info, uid, dumpable, NULL, 0, &filename,
+ &coredump_node_fd, &coredump_fd, &coredump_size);
if (r < 0)
goto finish;
@@ -844,6 +859,9 @@ int main(int argc, char* argv[]) {
core_gid = strjoina("COREDUMP_GID=", info[INFO_GID]);
IOVEC_SET_STRING(iovec[j++], core_gid);
+ core_dumpable = strjoina("COREDUMP_DUMPABLE=", info[INFO_DUMPABLE]);
+ IOVEC_SET_STRING(iovec[j++], core_dumpable);
+
core_signal = strjoina("COREDUMP_SIGNAL=", info[INFO_SIGNAL]);
IOVEC_SET_STRING(iovec[j++], core_signal);
@@ -938,16 +956,15 @@ int main(int argc, char* argv[]) {
/* 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_virtual_file(p, &t, &auxv_size) >= 0) {
- auxv_data = malloc(strlen("COREDUMP_PROC_AUXV=") + auxv_size);
- if (auxv_data) {
- mempcpy(stpcpy(auxv_data, "COREDUMP_PROC_AUXV="), t, auxv_size);
+ if (read_full_virtual_file(p, &auxv_data, &auxv_size) >= 0) {
+ core_auxv = malloc(strlen("COREDUMP_PROC_AUXV=") + auxv_size);
+ if (core_auxv) {
+ mempcpy(stpcpy(core_auxv, "COREDUMP_PROC_AUXV="), auxv_data, auxv_size);
- iovec[j].iov_base = auxv_data;
+ iovec[j].iov_base = core_auxv;
iovec[j].iov_len = strlen("COREDUMP_PROC_AUXV=") + auxv_size;
j++;
}
- free(t);
}
if (get_process_cwd(pid, &t) >= 0) {
@@ -983,7 +1000,7 @@ int main(int argc, char* argv[]) {
coredump_vacuum(-1, arg_keep_free, arg_max_use);
/* Always stream the coredump to disk, if that's possible */
- r = save_external_coredump(info, uid, auxv_data, auxv_size, &filename,
+ r = save_external_coredump(info, uid, dumpable, auxv_data, auxv_size, &filename,
&coredump_node_fd, &coredump_fd, &coredump_size);
if (r < 0)
/* skip whole core dumping part */
diff --git a/sysctl.d/50-coredump.conf.in b/sysctl.d/50-coredump.conf.in
index 5a25de4512..8dc735e6da 100644
--- a/sysctl.d/50-coredump.conf.in
+++ b/sysctl.d/50-coredump.conf.in
@@ -9,4 +9,4 @@
# 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 %e
+kernel.core_pattern=|@rootlibexecdir@/systemd-coredump %P %u %g %d %s %t %c %e
--
2.43.0