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

Shihuang Liu posted 1 patch 1 month, 3 weeks ago
fs/fuse/dev.c | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
[PATCH] fuse: fix use-after-free in fuse_chan_resend()
Posted by Shihuang Liu 1 month, 3 weeks ago
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@vger.kernel.org
Signed-off-by: Shihuang Liu <shlomojune6@gmail.com>
---
 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