[PATCH] fuse: fix use-after-free in fuse_chan_resend()
From: Shihuang Liu
Date: Thu Aug 06 2026 - 23:16:04 EST
fuse_chan_resend() sets FR_PENDING before acquiring fiq->lock
and before the request is actually inserted into fiq->pending.
A concurrent cancellation path may therefore observe FR_PENDING,
incorrectly assume that the request belongs to the lock-protected
pending list, remove it, and drop the queue-held reference.
The waiting thread may then release the final reference
and free the request while fuse_chan_resend() still holds
and accesses it, resulting in a use-after-free.
The following is a simple race scenario:
CPU1 CPUx
fuse_chan_resend()
move req from processing
to stack-local to_queue
set FR_PENDING
request receives SIGKILL
fuse_remove_pending_req()
sees FR_PENDING set
list_del(&req->list)
drop queue reference
request thread drops its reference
refcount reaches zero
req is freed
access struct fuse_req
lead to use-after-free
A FUSE request being resent concurrently with fatal-signal
cancellation can trigger a slab use-after-free.
[ 27.327266] ==================================================================
[ 27.329157] BUG: KASAN: slab-use-after-free in fuse_chan_resend+0x29c/0x7c0
[ 27.330554] Write of size 8 at addr ffff8880079975a0 by task exploit/1711
Move the FR_PENDING publication and the other resend state updates
under fiq->lock, in the same critical section that requeues the
requests on fiq->pending. This prevents cancellation from observing
FR_PENDING while the request is still on the private to_queue list.
Fixes: 760eac73f9f6 ("fuse: Introduce a new notification type for resend pending requests")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Shihuang Liu <shlomojune6@xxxxxxxxx>
---
fs/fuse/dev.c | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 5763a7cd3b37..e54567bd247e 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -1760,7 +1760,7 @@ static ssize_t fuse_dev_splice_read(struct file *in, loff_t *ppos,
void fuse_chan_resend(struct fuse_chan *fch)
{
struct fuse_dev *fud;
- struct fuse_req *req, *next;
+ struct fuse_req *req;
struct fuse_iqueue *fiq = &fch->iq;
LIST_HEAD(to_queue);
unsigned int i;
@@ -1781,18 +1781,9 @@ void fuse_chan_resend(struct fuse_chan *fch)
}
spin_unlock(&fch->lock);
- list_for_each_entry_safe(req, next, &to_queue, list) {
- set_bit(FR_PENDING, &req->flags);
- clear_bit(FR_SENT, &req->flags);
- /* mark the request as resend request */
- req->in.h.unique |= FUSE_UNIQUE_RESEND;
- }
-
spin_lock(&fiq->lock);
if (!fiq->connected) {
spin_unlock(&fiq->lock);
- list_for_each_entry(req, &to_queue, list)
- clear_bit(FR_PENDING, &req->flags);
fuse_dev_end_requests(&to_queue);
return;
}
@@ -1801,6 +1792,11 @@ void fuse_chan_resend(struct fuse_chan *fch)
* intr_entry on fiq->interrupts after the request is re-queued.
*/
list_for_each_entry(req, &to_queue, list) {
+ set_bit(FR_PENDING, &req->flags);
+ clear_bit(FR_SENT, &req->flags);
+ /* mark the request as resend request */
+ req->in.h.unique |= FUSE_UNIQUE_RESEND;
+
if (test_bit(FR_INTERRUPTED, &req->flags))
list_del_init(&req->intr_entry);
}
--
2.43.0