75 lines
2.8 KiB
Diff
75 lines
2.8 KiB
Diff
From 7a6182b5469b0c09373c8c02517c75305a899291 Mon Sep 17 00:00:00 2001
|
|
From: Nikolay Kuratov <kniv@yandex-team.ru>
|
|
Date: Tue, 5 Aug 2025 16:09:17 +0300
|
|
Subject: vhost/net: Protect ubufs with rcu read lock in vhost_net_ubuf_put()
|
|
|
|
When operating on struct vhost_net_ubuf_ref, the following execution
|
|
sequence is theoretically possible:
|
|
CPU0 is finalizing DMA operation CPU1 is doing VHOST_NET_SET_BACKEND
|
|
// ubufs->refcount == 2
|
|
vhost_net_ubuf_put() vhost_net_ubuf_put_wait_and_free(oldubufs)
|
|
vhost_net_ubuf_put_and_wait()
|
|
vhost_net_ubuf_put()
|
|
int r = atomic_sub_return(1, &ubufs->refcount);
|
|
// r = 1
|
|
int r = atomic_sub_return(1, &ubufs->refcount);
|
|
// r = 0
|
|
wait_event(ubufs->wait, !atomic_read(&ubufs->refcount));
|
|
// no wait occurs here because condition is already true
|
|
kfree(ubufs);
|
|
if (unlikely(!r))
|
|
wake_up(&ubufs->wait); // use-after-free
|
|
|
|
This leads to use-after-free on ubufs access. This happens because CPU1
|
|
skips waiting for wake_up() when refcount is already zero.
|
|
|
|
To prevent that use a read-side RCU critical section in vhost_net_ubuf_put(),
|
|
as suggested by Hillf Danton. For this lock to take effect, free ubufs with
|
|
kfree_rcu().
|
|
|
|
Cc: stable@vger.kernel.org
|
|
Fixes: 0ad8b480d6ee9 ("vhost: fix ref cnt checking deadlock")
|
|
Reported-by: Andrey Ryabinin <arbn@yandex-team.com>
|
|
Suggested-by: Hillf Danton <hdanton@sina.com>
|
|
Signed-off-by: Nikolay Kuratov <kniv@yandex-team.ru>
|
|
Message-Id: <20250805130917.727332-1-kniv@yandex-team.ru>
|
|
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
|
|
---
|
|
drivers/vhost/net.c | 9 +++++++--
|
|
1 file changed, 7 insertions(+), 2 deletions(-)
|
|
|
|
--- a/drivers/vhost/net.c
|
|
+++ b/drivers/vhost/net.c
|
|
@@ -96,6 +96,7 @@ struct vhost_net_ubuf_ref {
|
|
atomic_t refcount;
|
|
wait_queue_head_t wait;
|
|
struct vhost_virtqueue *vq;
|
|
+ struct rcu_head rcu;
|
|
};
|
|
|
|
#define VHOST_NET_BATCH 64
|
|
@@ -247,9 +248,13 @@ vhost_net_ubuf_alloc(struct vhost_virtqu
|
|
|
|
static int vhost_net_ubuf_put(struct vhost_net_ubuf_ref *ubufs)
|
|
{
|
|
- int r = atomic_sub_return(1, &ubufs->refcount);
|
|
+ int r;
|
|
+
|
|
+ rcu_read_lock();
|
|
+ r = atomic_sub_return(1, &ubufs->refcount);
|
|
if (unlikely(!r))
|
|
wake_up(&ubufs->wait);
|
|
+ rcu_read_unlock();
|
|
return r;
|
|
}
|
|
|
|
@@ -262,7 +267,7 @@ static void vhost_net_ubuf_put_and_wait(
|
|
static void vhost_net_ubuf_put_wait_and_free(struct vhost_net_ubuf_ref *ubufs)
|
|
{
|
|
vhost_net_ubuf_put_and_wait(ubufs);
|
|
- kfree(ubufs);
|
|
+ kfree_rcu(ubufs, rcu);
|
|
}
|
|
|
|
static void vhost_net_clear_ubuf_info(struct vhost_net *n)
|