* [PATCH v2 1/3] fuse: fix race between registration and connection abortion
[not found] <20260516021138.2759874-1-joannelkoong@gmail.com>
@ 2026-05-16 2:11 ` Joanne Koong
2026-05-16 2:11 ` [PATCH v2 3/3] fuse: fix moving cancelled entry to ent_in_userspace list Joanne Koong
1 sibling, 0 replies; 2+ messages in thread
From: Joanne Koong @ 2026-05-16 2:11 UTC (permalink / raw)
To: miklos; +Cc: fuse-devel, bernd, ali, horst, stable
This fixes this race:
- thread a: io_uring_enter -> register sqe ->
fuse_uring_create_ring_ent -> allocate ent but doesn't grab queue_ref
yet
- thread b: fuse_conn_destroy() -> fuse_chan_abort() ->
fuse_uring_abort() is a no-op due to queue ref being 0
- thread a: grabs the queue_ref, queue_ref is now 1, rest of
fuse_uring_do_register() logic executes
- thread b: fuse_chan_abort() returns, fuse_chan_wait_aborted() now runs
and calls
"wait_event(ring->stop_waitq, atomic_read(&ring->queue_refs) == 0);"
The abort/unmount thread will hang indefinitely in unkillable state as
nothing will decrement queue_refs or wake stop_waitq, and the ring,
queue, and ent are leaked.
Fix this by checking fch->connected under fch->lock after the created
ent has grabbed a ref count on the queue. This ensures that in the
scenario above, it is guaranteed that we either release the queue ref
and wake up stop_waitq (in case fuse_chan_wait_aborted() is already
waiting) in fuse_uring_do_register() when we detect !fch->connected, or
if the connection is aborted after the check, it is guaranteed that the
async teardown worker will be running in the background cleaning up ents
and decrementing the ent's ref on the queue, which will unblock the
eventual queue and ring teardown.
Fixes: 24fe962c86f5 ("fuse: {io-uring} Handle SQEs - register commands")
Cc: <stable@vger.kernel.org>
Reviewed-by: Bernd Schubert <bernd@bsbernd.com>
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
---
fs/fuse/dev_uring.c | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index e467b23e6895..99ebb7c9cc61 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -973,15 +973,26 @@ static bool is_ring_ready(struct fuse_ring *ring, int current_qid)
/*
* fuse_uring_req_fetch command handling
*/
-static void fuse_uring_do_register(struct fuse_ring_ent *ent,
- struct io_uring_cmd *cmd,
- unsigned int issue_flags)
+static int fuse_uring_do_register(struct fuse_ring_ent *ent,
+ struct io_uring_cmd *cmd,
+ unsigned int issue_flags)
{
struct fuse_ring_queue *queue = ent->queue;
struct fuse_ring *ring = queue->ring;
struct fuse_chan *fch = ring->chan;
struct fuse_iqueue *fiq = &fch->iq;
+ spin_lock(&fch->lock);
+ /* abort teardown path is running or has run */
+ if (!fch->connected) {
+ spin_unlock(&fch->lock);
+ if (atomic_dec_and_test(&ring->queue_refs))
+ wake_up_all(&ring->stop_waitq);
+ kfree(ent);
+ return -ECONNABORTED;
+ }
+ spin_unlock(&fch->lock);
+
fuse_uring_prepare_cancel(cmd, issue_flags, ent);
spin_lock(&queue->lock);
@@ -998,6 +1009,7 @@ static void fuse_uring_do_register(struct fuse_ring_ent *ent,
wake_up_all(&fch->blocked_waitq);
}
}
+ return 0;
}
/*
@@ -1114,9 +1126,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd,
if (IS_ERR(ent))
return PTR_ERR(ent);
- fuse_uring_do_register(ent, cmd, issue_flags);
-
- return 0;
+ return fuse_uring_do_register(ent, cmd, issue_flags);
}
/*
--
2.52.0
^ permalink raw reply related [flat|nested] 2+ messages in thread* [PATCH v2 3/3] fuse: fix moving cancelled entry to ent_in_userspace list
[not found] <20260516021138.2759874-1-joannelkoong@gmail.com>
2026-05-16 2:11 ` [PATCH v2 1/3] fuse: fix race between registration and connection abortion Joanne Koong
@ 2026-05-16 2:11 ` Joanne Koong
1 sibling, 0 replies; 2+ messages in thread
From: Joanne Koong @ 2026-05-16 2:11 UTC (permalink / raw)
To: miklos; +Cc: fuse-devel, bernd, ali, horst, Heechan Kang, stable
fuse_uring_cancel() moves entries that are available (these have no reqs
attached) to the ent_in_userspace list. ent_list_request_expired()
checks the first entry on ent_in_userspace and dereferences
ent->fuse_req unconditionally, which will crash on a cancelled entry
that was moved to this list.
Fix this by freeing the entry and dropping queue_refs directly in
fuse_uring_cancel(). This is safe because cancel is the cancel handler
itself - after io_uring_cmd_done(), no more cancels will be dispatched
for this command, and teardown serializes with cancel via queue->lock.
Since cancel now decrements queue_refs, fuse_uring_abort() must no
longer gate fuse_uring_abort_end_requests() on queue_refs > 0, as
cancelled entries may have already dropped queue_refs while requests are
still queued. Remove the gate so abort always flushes requests and stops
queues.
Reported-by: Heechan Kang <gganji11@naver.com>
Tested-by: Heechan Kang <gganji11@naver.com>
Fixes: 4fea593e625c ("fuse: optimize over-io-uring request expiration check")
Cc: stable@vger.kernel.org
Co-developed-by: Jian Huang Li <ali@ddn.com>
Co-developed-by: Horst Birthelmer <horst@birthelmer.de>
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
---
fs/fuse/dev_uring.c | 6 ++++--
fs/fuse/dev_uring_i.h | 6 +++---
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index d9108b5b5db8..f4ba64a1796a 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -511,8 +511,7 @@ static void fuse_uring_cancel(struct io_uring_cmd *cmd,
queue = ent->queue;
spin_lock(&queue->lock);
if (ent->state == FRRS_AVAILABLE) {
- ent->state = FRRS_USERSPACE;
- list_move_tail(&ent->list, &queue->ent_in_userspace);
+ list_del_init(&ent->list);
need_cmd_done = true;
ent->cmd = NULL;
}
@@ -521,6 +520,9 @@ static void fuse_uring_cancel(struct io_uring_cmd *cmd,
if (need_cmd_done) {
/* no queue lock to avoid lock order issues */
io_uring_cmd_done(cmd, -ENOTCONN, issue_flags);
+ kfree(ent);
+ if (atomic_dec_and_test(&queue->ring->queue_refs))
+ wake_up_all(&queue->ring->stop_waitq);
}
}
diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h
index 368f4d0790eb..22ec67e39ee0 100644
--- a/fs/fuse/dev_uring_i.h
+++ b/fs/fuse/dev_uring_i.h
@@ -150,10 +150,10 @@ static inline void fuse_uring_abort(struct fuse_chan *fch)
if (ring == NULL)
return;
- if (atomic_read(&ring->queue_refs) > 0) {
- fuse_uring_abort_end_requests(ring);
+ fuse_uring_abort_end_requests(ring);
+
+ if (atomic_read(&ring->queue_refs) > 0)
fuse_uring_stop_queues(ring);
- }
}
static inline void fuse_uring_wait_stopped_queues(struct fuse_chan *fch)
--
2.52.0
^ permalink raw reply related [flat|nested] 2+ messages in thread