virtio_pmem_flush submits fsync requests to the thread pool and stores a
VirtIOPMEM pointer in each request. If device is deleted e.g. by
hot-unplug, once these complete, done_cb can run after
virtio_pmem_unrealize frees the device, causing a use-after-free.
Track in-flight requests and wait in virtio_pmem_unrealize until
their completions finish before tearing the device down.
Fixes: CVE-2026-63323
Fixes: 5f503cd9f3 ("virtio-pmem: add virtio device")
Cc: David Hildenbrand <david@kernel.org>
Cc: Pankaj Gupta <pagupta@redhat.com>
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3938
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
---
include/hw/virtio/virtio-pmem.h | 1 +
hw/virtio/virtio-pmem.c | 17 +++++++++++++++--
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/include/hw/virtio/virtio-pmem.h b/include/hw/virtio/virtio-pmem.h
index 9cce600d0b..bb959d10cf 100644
--- a/include/hw/virtio/virtio-pmem.h
+++ b/include/hw/virtio/virtio-pmem.h
@@ -32,6 +32,7 @@ struct VirtIOPMEM {
VirtQueue *rq_vq;
uint64_t start;
HostMemoryBackend *memdev;
+ unsigned int inflight;
};
struct VirtIOPMEMClass {
diff --git a/hw/virtio/virtio-pmem.c b/hw/virtio/virtio-pmem.c
index c3b3299c9c..6f7271c140 100644
--- a/hw/virtio/virtio-pmem.c
+++ b/hw/virtio/virtio-pmem.c
@@ -23,6 +23,7 @@
#include "standard-headers/linux/virtio_pmem.h"
#include "system/hostmem.h"
#include "block/thread-pool.h"
+#include "qemu/aio-wait.h"
#include "trace.h"
typedef struct VirtIODeviceRequest {
@@ -54,14 +55,20 @@ static int worker_cb(void *opaque)
static void done_cb(void *opaque, int ret)
{
VirtIODeviceRequest *req_data = opaque;
+ VirtIOPMEM *pmem = req_data->pmem;
int len = iov_from_buf(req_data->elem.in_sg, req_data->elem.in_num, 0,
&req_data->resp, sizeof(struct virtio_pmem_resp));
/* Callbacks are serialized, so no need to use atomic ops. */
- virtqueue_push(req_data->pmem->rq_vq, &req_data->elem, len);
- virtio_notify((VirtIODevice *)req_data->pmem, req_data->pmem->rq_vq);
+ virtqueue_push(pmem->rq_vq, &req_data->elem, len);
+ virtio_notify((VirtIODevice *)pmem, pmem->rq_vq);
trace_virtio_pmem_response();
g_free(req_data);
+
+ pmem->inflight--;
+ if (!pmem->inflight) {
+ aio_wait_kick();
+ }
}
static void virtio_pmem_flush(VirtIODevice *vdev, VirtQueue *vq)
@@ -85,6 +92,7 @@ static void virtio_pmem_flush(VirtIODevice *vdev, VirtQueue *vq)
req_data->fd = memory_region_get_fd(&backend->mr);
req_data->pmem = pmem;
req_data->vdev = vdev;
+ pmem->inflight++;
thread_pool_submit_aio(worker_cb, req_data, done_cb, req_data);
}
@@ -122,6 +130,7 @@ static void virtio_pmem_realize(DeviceState *dev, Error **errp)
host_memory_backend_set_mapped(pmem->memdev, true);
virtio_init(vdev, VIRTIO_ID_PMEM, sizeof(struct virtio_pmem_config));
pmem->rq_vq = virtio_add_queue(vdev, 128, virtio_pmem_flush);
+ pmem->inflight = 1;
}
static void virtio_pmem_unrealize(DeviceState *dev)
@@ -129,6 +138,10 @@ static void virtio_pmem_unrealize(DeviceState *dev)
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
VirtIOPMEM *pmem = VIRTIO_PMEM(dev);
+ /* Release the device's own reference and wait for in-flight flushes */
+ pmem->inflight--;
+ AIO_WAIT_WHILE(NULL, pmem->inflight > 0);
+
host_memory_backend_set_mapped(pmem->memdev, false);
virtio_delete_queue(pmem->rq_vq);
virtio_cleanup(vdev);
--
MST