@samitouri / QOSamiQemu / commits / 67550d65b4

virtio-iommu: fix OOM due to unbounded call_rcu

Currently, within virtio-iommu, handle_command processes the command vq without any limits on the number of entries processed. This can easily and repeatedly enable/disable multiple memory regions. Within the memory code, this causes an accumulation of an unbounded number of RCU-deferred FlatViews - each of these is supposed to be freed with call_rcu, but that never happens because the main thread never returns to the main loop. Given FlatView is big, it's easy to have this balloon out to multiple Gigabytes of memory. Limit the loop defer any remaining work to a timer. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3930 Cc: Eric Auger <eric.auger@redhat.com> Cc: Jean-Philippe Brucker <jean-philippe@linaro.org> Reviewed-by: Eric Auger <eric.auger@redhat.com> Tested-by: Eric Auger <eric.auger@redhat.com> Reported-by: Jia Jia <physicalmtea@gmail.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-ID: <eb46ab360dbe28c29cfa78812a7440dcb7444d59.1784807826.git.mst@redhat.com>

Michael S. Tsirkin committed Jul 23, 2026 at 07:58 UTC 67550d65b4bfdbcbfee2dae7ae1ebbbccd01dfe5
2 files changed +30
hw/virtio/virtio-iommu.c
+29
@@ -993,6 +993,18 @@ static int virtio_iommu_handle_probe(VirtIOIOMMU *s,
993 return ret ? ret : virtio_iommu_probe(s, &req, buf);
994 }
995
996 +static void virtio_iommu_handle_command(VirtIODevice *vdev, VirtQueue *vq);
997 +
998 +static void virtio_iommu_handle_command_timer(void *opaque)
999 +{
1000 + VirtIOIOMMU *s = opaque;
1001 + VirtIODevice *vdev = VIRTIO_DEVICE(s);
1002 +
1003 + if (virtio_device_started(vdev, vdev->status) && !vdev->broken) {
1004 + virtio_iommu_handle_command(vdev, s->req_vq);
1005 + }
1006 +}
1007 +
1008 static void virtio_iommu_handle_command(VirtIODevice *vdev, VirtQueue *vq)
1009 {
1010 VirtIOIOMMU *s = VIRTIO_IOMMU(vdev);
@@ -1003,10 +1015,17 @@ static void virtio_iommu_handle_command(VirtIODevice *vdev, VirtQueue *vq)
1015 struct iovec *iov;
1016 void *buf = NULL;
1017 size_t sz;
1018 + unsigned int batch = 0;
1019
1020 for (;;) {
1021 size_t output_size = sizeof(tail);
1022
1023 + if (++batch > virtio_queue_get_num(vdev, virtio_get_queue_index(vq))) {
1024 + timer_mod(s->cmd_timer,
1025 + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL_RT) + 1);
1026 + break;
1027 + }
1028 +
1029 elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
1030 if (!elem) {
1031 return;
@@ -1416,6 +1435,8 @@ static void virtio_iommu_device_realize(DeviceState *dev, Error **errp)
1435 s->req_vq = virtio_add_queue(vdev, VIOMMU_DEFAULT_QUEUE_SIZE,
1436 virtio_iommu_handle_command);
1437 s->event_vq = virtio_add_queue(vdev, VIOMMU_DEFAULT_QUEUE_SIZE, NULL);
1438 + s->cmd_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL_RT,
1439 + virtio_iommu_handle_command_timer, s);
1440
1441 /*
1442 * config.bypass is needed to get initial address space early, such as
@@ -1498,6 +1519,7 @@ static void virtio_iommu_device_unrealize(DeviceState *dev)
1519
1520 qemu_rec_mutex_destroy(&s->mutex);
1521
1522 + timer_free(s->cmd_timer);
1523 virtio_delete_queue(s->req_vq);
1524 virtio_delete_queue(s->event_vq);
1525 virtio_cleanup(vdev);
@@ -1509,6 +1531,8 @@ static void virtio_iommu_device_reset_exit(Object *obj, ResetType type)
1531
1532 trace_virtio_iommu_device_reset_exit();
1533
1534 + timer_del(s->cmd_timer);
1535 +
1536 if (s->domains) {
1537 g_tree_destroy(s->domains);
1538 }
@@ -1628,6 +1652,11 @@ static int iommu_post_load(void *opaque, int version_id)
1652 * still correct.
1653 */
1654 virtio_iommu_switch_address_space_all(s);
1655 +
1656 + if (virtio_device_started(VIRTIO_DEVICE(s), VIRTIO_DEVICE(s)->status)) {
1657 + timer_mod(s->cmd_timer,
1658 + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL_RT) + 1);
1659 + }
1660 return 0;
1661 }
1662
include/hw/virtio/virtio-iommu.h
+1
@@ -65,6 +65,7 @@ struct VirtIOIOMMU {
65 GTree *domains;
66 QemuRecMutex mutex;
67 GTree *endpoints;
68 + QEMUTimer *cmd_timer;
69 bool boot_bypass;
70 Notifier machine_done;
71 bool granule_frozen;