When the guest writes 1 to the VIRTIO_PCI_COMMON_Q_ENABLE field of a
queue that is already enabled, QEMU re-applies the ring geometry stored
in the PCI proxy shadow registers by calling virtio_queue_set_num() and
virtio_queue_set_rings().  Those functions update the vring descriptor,
available, and used addresses and the region cache, but they do not
touch the host-side progress fields - last_avail_idx, shadow_avail_idx,
used_idx, inuse, and the packed-ring wrap counters - which are only
zeroed by __virtio_queue_reset().

Because the progress indices are stale while the ring layout is new, the
host can treat the new ring as though there are up to 65535 pending
descriptors already waiting, causing virtqueue_pop() to iterate far
beyond what the guest actually posted.  On virtio-iommu this translates
into a stream of ATTACH/DETACH/MAP/UNMAP commands, driving the QEMU
process toward OOM from guest-controlled BAR writes.

The virtio specification requires the driver to complete a full queue
reset cycle before re-enabling a queue.  Fix this by checking the
proxy's per-queue enabled flag before processing the write.  If the
queue is already enabled, log the violation as a guest error and ignore
the write.

Fixes: d1060e3dc5 ("virtio-pci: support queue enable")
Cc: Kangjie Xu <[email protected]>
Reviewed-by: Michael S. Tsirkin <[email protected]>
Signed-off-by: Michael S. Tsirkin <[email protected]>
---
 hw/virtio/virtio-pci.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c
index 6f5db5fc42..c73f1f546c 100644
--- a/hw/virtio/virtio-pci.c
+++ b/hw/virtio/virtio-pci.c
@@ -1692,6 +1692,13 @@ static void virtio_pci_common_write(void *opaque, hwaddr 
addr,
         break;
     case VIRTIO_PCI_COMMON_Q_ENABLE:
         if (val == 1) {
+            if (proxy->vqs[vdev->queue_sel].enabled) {
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "%s: queue %d already enabled - "
+                              "reset queue before re-enabling\n",
+                              __func__, vdev->queue_sel);
+                break;
+            }
             virtio_queue_set_num(vdev, vdev->queue_sel,
                                  proxy->vqs[vdev->queue_sel].num);
             virtio_queue_set_rings(vdev, vdev->queue_sel,
-- 
MST


Reply via email to