[PATCH] fuse: fix use-after-free in fuse_chan_resend()

Shihuang Liu <[email protected]>
Newsgroups org.kernel.vger.stable,dev.linux.lists.fuse-devel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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: [email protected]
Signed-off-by: Shihuang Liu <[email protected]>
---
 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
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.