public inbox for io-uring@vger.kernel.org
 help / color / mirror / Atom feed
From: Christian Brauner <brauner@kernel.org>
To: Oleg Nesterov <oleg@redhat.com>, Chris Mason <mason@kernel.org>,
	 linux-fsdevel@vger.kernel.org
Cc: Jens Axboe <axboe@kernel.dk>,
	Alexander Viro <viro@zeniv.linux.org.uk>,
	 Jan Kara <jack@suse.cz>, NeilBrown <neil@brown.name>,
	 Ingo Molnar <mingo@redhat.com>,
	Peter Zijlstra <peterz@infradead.org>,
	 linux-mm@kvack.org, io-uring@vger.kernel.org,
	 "Christian Brauner (Amutable)" <brauner@kernel.org>
Subject: [PATCH v3 03/17] coredump: parse a snapshot of core_pattern
Date: Mon, 21 Sep 2026 15:44:52 +0200	[thread overview]
Message-ID: <20260921-work-coredump-fixes-v3-3-8e4adb1619e6@kernel.org> (raw)
In-Reply-To: <20260921-work-coredump-fixes-v3-0-8e4adb1619e6@kernel.org>

This is a long-standing problem that I discussed a while back with Jann.
I didn't care enough about it to really fix it and it's from the
before-fore-times.

coredump_parse() reads core_pattern directly while it can concurrently
be modified. So it reads the first byte, figures out what mode is
wanted, then allocates the number buffer and then consumes the rest of
the core_pattern string.

Say the sysctl handler updates the core_pattern array byte by byte
(idiotic but supported). So that can lead to all kinds of insane mixups.
Say you could transform the old "|/usr/bin/helper" and the new
"/tmp/core.%p" into a usermodehelper started as "tmp/core.<pid>".

So copy what proc_do_uts_string() does and let the handler run
proc_dostring() on a copy, validate the copy and make it visible beneath
a spinlock. Then coredump_parse() can take a snapshot under the same
spinlock and parse a stable copy.

From now on, rejected patterns are never visible and we can drop the
whole rollback logic. It has the same minor defect that utsname has,
namely that two writers can race on a non-zero offset. Irrelevant imho.

Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/coredump.c | 59 +++++++++++++++++++++++++++++++++++++----------------------
 1 file changed, 37 insertions(+), 22 deletions(-)

diff --git a/fs/coredump.c b/fs/coredump.c
index 40eca2b85b81..d5d76704df81 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -85,6 +85,8 @@ static int core_uses_pid;
 static unsigned int core_pipe_limit;
 static unsigned int core_sort_vma;
 static char core_pattern[CORENAME_MAX_SIZE] = "core";
+/* Taken around every copy in and out of core_pattern. */
+static DEFINE_SPINLOCK(core_pattern_lock);
 static int core_name_size = CORENAME_MAX_SIZE;
 unsigned int core_file_note_size_limit = CORE_FILE_NOTE_SIZE_DEFAULT;
 static atomic_t core_pipe_count = ATOMIC_INIT(0);
@@ -240,11 +242,16 @@ static bool coredump_parse(struct core_name *cn, struct coredump_params *cprm,
 			   size_t **argv, int *argc)
 {
 	const struct cred *cred = current_cred();
-	const char *pat_ptr = core_pattern;
+	char pattern[CORENAME_MAX_SIZE];
+	const char *pat_ptr = pattern;
 	bool was_space = false;
 	int pid_in_pattern = 0;
 	int err = 0;
 
+	/* The sysctl handler may be publishing a new pattern. */
+	scoped_guard(spinlock, &core_pattern_lock)
+		strscpy(pattern, core_pattern);
+
 	cprm->mask = COREDUMP_KERNEL;
 	if (core_pipe_limit)
 		cprm->mask |= COREDUMP_WAIT;
@@ -1640,11 +1647,11 @@ void validate_coredump_safety(void)
 	}
 }
 
-static inline bool check_coredump_socket(void)
+static inline bool check_coredump_socket(const char *pattern)
 {
 	const char *p;
 
-	if (core_pattern[0] != '@')
+	if (pattern[0] != '@')
 		return true;
 
 	/*
@@ -1656,16 +1663,16 @@ static inline bool check_coredump_socket(void)
 		return false;
 
 	/* Must be an absolute path... */
-	if (core_pattern[1] != '/') {
+	if (pattern[1] != '/') {
 		/* ... or the socket request protocol... */
-		if (core_pattern[1] != '@')
+		if (pattern[1] != '@')
 			return false;
 		/* ... and if so must be an absolute path. */
-		if (core_pattern[2] != '/')
+		if (pattern[2] != '/')
 			return false;
-		p = &core_pattern[2];
+		p = &pattern[2];
 	} else {
-		p = &core_pattern[1];
+		p = &pattern[1];
 	}
 
 	/* The path obviously cannot exceed UNIX_PATH_MAX. */
@@ -1673,7 +1680,7 @@ static inline bool check_coredump_socket(void)
 		return false;
 
 	/* Must not contain ".." in the path. */
-	if (name_contains_dotdot(core_pattern))
+	if (name_contains_dotdot(pattern))
 		return false;
 
 	return true;
@@ -1682,27 +1689,35 @@ static inline bool check_coredump_socket(void)
 static int proc_dostring_coredump(const struct ctl_table *table, int write,
 		  void *buffer, size_t *lenp, loff_t *ppos)
 {
+	char pattern[CORENAME_MAX_SIZE];
+	const struct ctl_table tmp = {
+		.procname	= table->procname,
+		.data		= pattern,
+		.maxlen		= sizeof(pattern),
+	};
+	bool changed = false;
 	int error;
-	ssize_t retval;
-	char old_core_pattern[CORENAME_MAX_SIZE];
-
-	if (!write)
-		return proc_dostring(table, write, buffer, lenp, ppos);
 
-	retval = strscpy(old_core_pattern, core_pattern, CORENAME_MAX_SIZE);
+	/* Work on a copy, proc_dostring() appends at *ppos. */
+	scoped_guard(spinlock, &core_pattern_lock)
+		strscpy(pattern, core_pattern);
 
-	error = proc_dostring(table, write, buffer, lenp, ppos);
-	if (error)
+	error = proc_dostring(&tmp, write, buffer, lenp, ppos);
+	if (error || !write)
 		return error;
 
-	if (!check_coredump_socket()) {
-		strscpy(core_pattern, old_core_pattern, retval + 1);
+	if (!check_coredump_socket(pattern))
 		return -EINVAL;
-	}
 
-	if (strncmp(old_core_pattern, core_pattern, CORENAME_MAX_SIZE))
+	/* Publish the validated pattern whole. */
+	scoped_guard(spinlock, &core_pattern_lock) {
+		changed = strncmp(pattern, core_pattern, CORENAME_MAX_SIZE);
+		if (changed)
+			strscpy(core_pattern, pattern);
+	}
+	if (changed)
 		validate_coredump_safety();
-	return error;
+	return 0;
 }
 
 static const unsigned int core_file_note_size_min = CORE_FILE_NOTE_SIZE_DEFAULT;

-- 
2.53.0


  parent reply	other threads:[~2026-09-21 13:45 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-21 13:44 [PATCH v3 00/17] coredump & signals: an impossible affair Christian Brauner
2026-09-21 13:44 ` [PATCH v3 01/17] coredump: hold RCU while releasing parked threads Christian Brauner
2026-09-21 13:44 ` [PATCH v3 02/17] signal: only SIGKILL and the freezers interrupt a coredumping task Christian Brauner
2026-09-22 12:55   ` Oleg Nesterov
2026-09-22 14:33     ` Christian Brauner
2026-09-21 13:44 ` Christian Brauner [this message]
2026-09-21 13:44 ` [PATCH v3 04/17] io-wq: order the exit bit against worker creation task work Christian Brauner
2026-09-21 13:44 ` [PATCH v3 05/17] signal: don't retarget shared signals in a dying thread group Christian Brauner
2026-09-21 13:44 ` [PATCH v3 06/17] selftests/coredump: test shared signal retargeting during a dump Christian Brauner
2026-09-21 13:44 ` [PATCH v3 07/17] fork: release the files of a failed fork after sched_cancel_fork() Christian Brauner
2026-09-21 13:44 ` [PATCH v3 08/17] exit: hang up the tty before closing the files Christian Brauner
2026-09-24 12:10   ` Oleg Nesterov
2026-09-21 13:44 ` [PATCH v3 09/17] ptrace: refuse to change the signal mask of a user worker Christian Brauner
2026-09-21 14:14   ` Oleg Nesterov
2026-09-21 13:44 ` [PATCH v3 10/17] selftests/coredump: test a user worker as the coredumping thread Christian Brauner
2026-09-21 13:45 ` [PATCH v3 11/17] selftests/coredump: expect PTRACE_SETSIGMASK to be refused on a user worker Christian Brauner
2026-09-21 13:45 ` [PATCH v3 12/17] exec: cancel io_uring requests before de_thread() Christian Brauner
2026-09-21 14:14   ` Oleg Nesterov
2026-09-24 14:19   ` Jens Axboe
2026-09-21 13:45 ` [PATCH v3 13/17] fork: move the coredump and exec checks into create_io_thread() Christian Brauner
2026-09-21 14:15   ` Oleg Nesterov
2026-09-21 13:45 ` [PATCH v3 14/17] fork: don't create io threads once PF_POSTCOREDUMP is set Christian Brauner
2026-09-21 14:26   ` Oleg Nesterov
2026-09-21 13:45 ` [PATCH v3 15/17] fork: use SIG_KERNEL_ONLY_MASK for the user worker signal mask Christian Brauner
2026-09-21 14:29   ` Oleg Nesterov
2026-09-21 13:45 ` [PATCH v3 16/17] signal: enforce the user worker signal mask in __set_task_blocked() Christian Brauner
2026-09-21 16:16   ` Oleg Nesterov
2026-09-21 20:05     ` Christian Brauner
2026-09-21 13:45 ` [PATCH v3 17/17] fs: close files from the highest descriptor down Christian Brauner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260921-work-coredump-fixes-v3-3-8e4adb1619e6@kernel.org \
    --to=brauner@kernel.org \
    --cc=axboe@kernel.dk \
    --cc=io-uring@vger.kernel.org \
    --cc=jack@suse.cz \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=mason@kernel.org \
    --cc=mingo@redhat.com \
    --cc=neil@brown.name \
    --cc=oleg@redhat.com \
    --cc=peterz@infradead.org \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox