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 17/17] fs: close files from the highest descriptor down
Date: Mon, 21 Sep 2026 15:45:06 +0200 [thread overview]
Message-ID: <20260921-work-coredump-fixes-v3-17-8e4adb1619e6@kernel.org> (raw)
In-Reply-To: <20260921-work-coredump-fixes-v3-0-8e4adb1619e6@kernel.org>
close_files(), __range_close() and close_cloexec_files() walk the
descriptor table from the lowest descriptor up. They used to call
filp_close(), which left the final __fput() to task work. task work is a
LIFO list, so the releases ran after the walk had finished and in the
opposite direction, highest descriptor first.
This dumb ordering is relevant for a bunch of broken but long-standing
cases. It matters whenever the ->flush() or ->release() of one file
waits for something that only the release of another file of the same
table provides. Then one of the two orders deadlocks and the other one
doesn't:
exit, fd 3 is one end of a pipe peer
------------------------------- ----
splice(socket -> pipe)
pipe_lock()
waits for data or EOF
close_files()
fd 3: pipe_release()
mutex_lock(&pipe->mutex) held by the peer
fd 5: the socket, not reached would be the peer's EOF
Programs create the thing that guards or wakes another thing first.
Hence, it gets the lower descriptor which is the layout that breaks:
(1) a tap device released before the AF_LLC socket that holds a
reference to it
(2) an unlinked fsdax file evicted before the pipe that holds its
vmspliced pages
(3) an overlayfs directory whose release queues up behind an unlink that
waits for a splice into the same directory
The exiting task is unkillable in all of them. All of that crap can
obviously also become a bug if you reorder the file descriptors.
Continue walking all three tables from the highest descriptor down. That
restores the order the deferred puts had. ->flush() moves with the
release. So it now runs highest descriptor first as well.
None of this fixes the underlying defects. For every one of these pairs
the mirrored layout deadlocked before and deadlocks
again now:
(1') splice() holding pipe->mutex across unbounded socket and tty I/O
(2') AF_LLC keeping a netdev reference without a NETDEV_UNREGISTER handler
(3') uninterruptible wait in dax_break_layout_final()
(4') ovl_splice_write() sleeping under the inode lock
It all predates the synchronous close and each should really get fixed.
Reported-by: Chris Mason <mason@kernel.org>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
fs/file.c | 55 +++++++++++++++++++++++++++++--------------------------
1 file changed, 29 insertions(+), 26 deletions(-)
diff --git a/fs/file.c b/fs/file.c
index 76e328edf630..7f8d0afd8807 100644
--- a/fs/file.c
+++ b/fs/file.c
@@ -497,24 +497,21 @@ static struct fdtable *close_files(struct files_struct *files)
* files structure.
*/
struct fdtable *fdt = rcu_dereference_raw(files->fdt);
- unsigned int i, j = 0;
+ unsigned int j = fdt->max_fds / BITS_PER_LONG;
+
+ /* Highest fd first, the order the deferred puts ran in. */
+ while (j--) {
+ unsigned long set = fdt->open_fds[j];
- for (;;) {
- unsigned long set;
- i = j * BITS_PER_LONG;
- if (i >= fdt->max_fds)
- break;
- set = fdt->open_fds[j++];
while (set) {
- if (set & 1) {
- struct file *file = fdt->fd[i];
- if (file) {
- filp_close_sync(file, files);
- cond_resched();
- }
+ unsigned int bit = __fls(set);
+ struct file *file = fdt->fd[j * BITS_PER_LONG + bit];
+
+ set ^= 1UL << bit;
+ if (file) {
+ filp_close_sync(file, files);
+ cond_resched();
}
- i++;
- set >>= 1;
}
}
@@ -801,10 +798,14 @@ static inline void __range_close(struct files_struct *files, unsigned int fd,
n = last_fd(fdt);
max_fd = min(max_fd, n);
- for (fd = find_next_bit(fdt->open_fds, max_fd + 1, fd);
- fd <= max_fd;
- fd = find_next_bit(fdt->open_fds, max_fd + 1, fd + 1)) {
- file = file_close_fd_locked(files, fd);
+ /* Highest fd first, see close_files(). */
+ for (n = max_fd + 1; n > fd; ) {
+ unsigned int cur = find_last_bit(fdt->open_fds, n);
+
+ if (cur >= n || cur < fd)
+ break;
+ n = cur;
+ file = file_close_fd_locked(files, cur);
if (file) {
spin_unlock(&files->file_lock);
filp_close_sync(file, files);
@@ -908,20 +909,22 @@ void close_cloexec_files(struct files_struct *files)
/* exec unshares first */
spin_lock(&files->file_lock);
- for (i = 0; ; i++) {
+ fdt = files_fdtable(files);
+ /* Highest fd first, see close_files(). */
+ for (i = fdt->max_fds / BITS_PER_LONG; i--; ) {
unsigned long set;
- unsigned fd = i * BITS_PER_LONG;
+
fdt = files_fdtable(files);
- if (fd >= fdt->max_fds)
- break;
set = fdt->close_on_exec[i];
if (!set)
continue;
fdt->close_on_exec[i] = 0;
- for ( ; set ; fd++, set >>= 1) {
+ while (set) {
+ unsigned int bit = __fls(set);
+ unsigned fd = i * BITS_PER_LONG + bit;
struct file *file;
- if (!(set & 1))
- continue;
+
+ set ^= 1UL << bit;
file = fdt->fd[fd];
if (!file)
continue;
--
2.53.0
prev parent reply other threads:[~2026-09-21 13:46 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 ` [PATCH v3 03/17] coredump: parse a snapshot of core_pattern Christian Brauner
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 ` Christian Brauner [this message]
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-17-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