[PATCH 2/2] fuse: don't queue an interrupt for a request that is back on fiq->pending

Jun Yang <[email protected]>
Newsgroups dev.linux.lists.fuse-devel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
fuse_dev_queue_interrupt() links a request onto fiq->interrupts based on
an FR_SENT observation its callers make without fiq->lock: in
request_wait_answer(), in fuse_dev_do_read() after setting FR_SENT, and in
fuse_dev_do_write() on an interrupt reply with -EAGAIN.

fuse_chan_resend() invalidates that observation: under fiq->lock it clears
FR_SENT, sets FR_PENDING and splices the request back onto fiq->pending. A
caller that sampled FR_SENT just before that happens links the request onto
fiq->interrupts just after, so the request ends up queued on fiq->pending
*and* on fiq->interrupts.

That combination is a problem, because a request on fiq->pending can be
released without ever going through fuse_request_end(). A waiter whose wait
is interrupted calls fuse_remove_pending_req(), which sees FR_PENDING,
unlinks the request from fiq->pending and drops the queue's reference;
fuse_chan_send() then drops the last one. Unlike fuse_request_end(), that
path has no FR_INTERRUPTED cleanup, so the request can be released while
still linked on fiq->interrupts, and the next fuse_dev_do_read() walks it
in fuse_read_interrupt().

Re-check FR_SENT in fuse_dev_queue_interrupt() under fiq->lock, which is
the lock fuse_chan_resend() holds when it clears it. This restores the
invariant "FR_PENDING set => intr_entry not linked", both sides of it now
being taken under fiq->lock. No interrupt is lost: the request is going
back to the daemon, and fuse_dev_do_read() re-queues the interrupt once it
has set FR_SENT again.

Confirmed on v7.2-rc6 (075b74841bd0).

Fixes: 760eac73f9f6 ("fuse: Introduce a new notification type for resend pending requests")
Cc: [email protected]
Reported-by: TencentOS Corvus AI <[email protected]>
Assisted-by: tencentos-corvus-ai:kimi-k3
Signed-off-by: Jun Yang <[email protected]>
---
A KASAN reproducer for this issue is available if requested.

 fs/fuse/dev.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index e62c7ed8bcf4..c4df1d4abd33 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -240,6 +240,11 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq,
 void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req)
 {
 	spin_lock(&fiq->lock);
+	/* fuse_chan_resend() may have put the request back on fiq->pending */
+	if (!test_bit(FR_SENT, &req->flags)) {
+		spin_unlock(&fiq->lock);
+		return;
+	}
 	if (list_empty(&req->intr_entry)) {
 		list_add_tail(&req->intr_entry, &fiq->interrupts);
 		/*
-- 
2.43.7
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.