@samitouri / QOSamiQemu / commits / 5cc182ba39

virtio-pmem: wait for flush requests on unrealize

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 Reported-by: Jia Jia <physicalmtea@gmail.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-ID: <417b6685f37ce818c660ca3c84945992f5c30dcf.1784894206.git.mst@redhat.com>

Michael S. Tsirkin committed Jul 8, 2026 at 11:39 UTC 5cc182ba39a3ca8ec9ba0576de9696be76dc087d
2 files changed +16 -2
hw/virtio/virtio-pmem.c
+15 -2
@@ -23,6 +23,7 @@
23 #include "standard-headers/linux/virtio_pmem.h"
24 #include "system/hostmem.h"
25 #include "block/thread-pool.h"
26 +#include "qemu/aio-wait.h"
27 #include "trace.h"
28
29 typedef struct VirtIODeviceRequest {
@@ -54,14 +55,20 @@ static int worker_cb(void *opaque)
55 static void done_cb(void *opaque, int ret)
56 {
57 VirtIODeviceRequest *req_data = opaque;
58 + VirtIOPMEM *pmem = req_data->pmem;
59 int len = iov_from_buf(req_data->elem.in_sg, req_data->elem.in_num, 0,
60 &req_data->resp, sizeof(struct virtio_pmem_resp));
61
62 /* Callbacks are serialized, so no need to use atomic ops. */
61 - virtqueue_push(req_data->pmem->rq_vq, &req_data->elem, len);
62 - virtio_notify((VirtIODevice *)req_data->pmem, req_data->pmem->rq_vq);
63 + virtqueue_push(pmem->rq_vq, &req_data->elem, len);
64 + virtio_notify((VirtIODevice *)pmem, pmem->rq_vq);
65 trace_virtio_pmem_response();
66 g_free(req_data);
67 +
68 + pmem->inflight--;
69 + if (!pmem->inflight) {
70 + aio_wait_kick();
71 + }
72 }
73
74 static void virtio_pmem_flush(VirtIODevice *vdev, VirtQueue *vq)
@@ -85,6 +92,7 @@ static void virtio_pmem_flush(VirtIODevice *vdev, VirtQueue *vq)
92 req_data->fd = memory_region_get_fd(&backend->mr);
93 req_data->pmem = pmem;
94 req_data->vdev = vdev;
95 + pmem->inflight++;
96 thread_pool_submit_aio(worker_cb, req_data, done_cb, req_data);
97 }
98
@@ -122,6 +130,7 @@ static void virtio_pmem_realize(DeviceState *dev, Error **errp)
130 host_memory_backend_set_mapped(pmem->memdev, true);
131 virtio_init(vdev, VIRTIO_ID_PMEM, sizeof(struct virtio_pmem_config));
132 pmem->rq_vq = virtio_add_queue(vdev, 128, virtio_pmem_flush);
133 + pmem->inflight = 1;
134 }
135
136 static void virtio_pmem_unrealize(DeviceState *dev)
@@ -129,6 +138,10 @@ static void virtio_pmem_unrealize(DeviceState *dev)
138 VirtIODevice *vdev = VIRTIO_DEVICE(dev);
139 VirtIOPMEM *pmem = VIRTIO_PMEM(dev);
140
141 + /* Release the device's own reference and wait for in-flight flushes */
142 + pmem->inflight--;
143 + AIO_WAIT_WHILE(NULL, pmem->inflight > 0);
144 +
145 host_memory_backend_set_mapped(pmem->memdev, false);
146 virtio_delete_queue(pmem->rq_vq);
147 virtio_cleanup(vdev);
include/hw/virtio/virtio-pmem.h
+1
@@ -32,6 +32,7 @@ struct VirtIOPMEM {
32 VirtQueue *rq_vq;
33 uint64_t start;
34 HostMemoryBackend *memdev;
35 + unsigned int inflight;
36 };
37
38 struct VirtIOPMEMClass {