[PATCH] hw/display/virtio-gpu: validate iov_cnt in migration load

marcandre.lureau@redhat.com posted 1 patch 1 week, 5 days ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20260713125521.109320-1-marcandre.lureau@redhat.com
Maintainers: "Michael S. Tsirkin" <mst@redhat.com>, "Alex Bennée" <alex.bennee@linaro.org>, Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>, Dmitry Osipenko <dmitry.osipenko@collabora.com>
hw/display/virtio-gpu.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
[PATCH] hw/display/virtio-gpu: validate iov_cnt in migration load
Posted by marcandre.lureau@redhat.com 1 week, 5 days ago
From: Marc-André Lureau <marcandre.lureau@redhat.com>

Cap iov_cnt at 16384 in virtio_gpu_load() and virtio_gpu_blob_load()
to match the existing limit enforced in virtio_gpu_create_mapping_iov().

An unbounded iov_cnt from the migration stream drives two g_new()
allocations whose combined size can exceed available memory, causing
GLib to abort the process.

Fixes: 0c244e50ee12 ("virtio-gpu: add live migration support")
Fixes: f66767f75c9c ("virtio-gpu: add virtio-gpu/blob vmstate subsection")
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3753
Reported-by: Feifan Qian <bea1e@proton.me>
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 hw/display/virtio-gpu.c | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c
index 88526051a99..6289bff9f3b 100644
--- a/hw/display/virtio-gpu.c
+++ b/hw/display/virtio-gpu.c
@@ -34,6 +34,7 @@
 #include "qemu/error-report.h"
 
 #define VIRTIO_GPU_VM_VERSION 1
+#define VIRTIO_GPU_MAX_IOVEC 16384
 
 static struct virtio_gpu_simple_resource *
 virtio_gpu_find_check_resource(VirtIOGPU *g, uint32_t resource_id,
@@ -820,10 +821,10 @@ int virtio_gpu_create_mapping_iov(VirtIOGPU *g,
     size_t esize, s;
     int e, v;
 
-    if (nr_entries > 16384) {
+    if (nr_entries > VIRTIO_GPU_MAX_IOVEC) {
         qemu_log_mask(LOG_GUEST_ERROR,
-                      "%s: nr_entries is too big (%d > 16384)\n",
-                      __func__, nr_entries);
+                      "%s: nr_entries is too big (%d > %d)\n",
+                      __func__, nr_entries, VIRTIO_GPU_MAX_IOVEC);
         return -1;
     }
 
@@ -1323,6 +1324,11 @@ static int virtio_gpu_load(QEMUFile *f, void *opaque, size_t size,
         res->format = qemu_get_be32(f);
         res->iov_cnt = qemu_get_be32(f);
 
+        if (res->iov_cnt > VIRTIO_GPU_MAX_IOVEC) {
+            g_free(res);
+            return -EINVAL;
+        }
+
         /* allocate */
         pformat = virtio_gpu_get_pixman_format(res->format);
         if (!pformat) {
@@ -1424,6 +1430,12 @@ static int virtio_gpu_blob_load(QEMUFile *f, void *opaque, size_t size,
         res->resource_id = resource_id;
         res->blob_size = qemu_get_be32(f);
         res->iov_cnt = qemu_get_be32(f);
+
+        if (res->iov_cnt > VIRTIO_GPU_MAX_IOVEC) {
+            g_free(res);
+            return -EINVAL;
+        }
+
         res->addrs = g_new(uint64_t, res->iov_cnt);
         res->iov = g_new(struct iovec, res->iov_cnt);
 
-- 
2.55.0


Re: [PATCH] hw/display/virtio-gpu: validate iov_cnt in migration load
Posted by Akihiko Odaki 1 week, 2 days ago
On 2026/07/13 21:55, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Cap iov_cnt at 16384 in virtio_gpu_load() and virtio_gpu_blob_load()
> to match the existing limit enforced in virtio_gpu_create_mapping_iov().

These limits apply to different quantities. 
virtio_gpu_create_mapping_iov() limits the guest-supplied nr_entries,
whereas iov_cnt is the number of mappings produced by dma_memory_map().
Since dma_memory_map() may split one guest entry into multiple mappings,
iov_cnt may exceed nr_entries and therefore 16384.

Both save paths serialize this expanded count, so migration can fail 
even for mappings accepted by virtio_gpu_create_mapping_iov(), including 
between QEMU instances containing this patch.

> 
> An unbounded iov_cnt from the migration stream drives two g_new()
> allocations whose combined size can exceed available memory, causing
> GLib to abort the process.

Since there is no corresponding 16384 limit on the expanded iovec
count, I think these allocations should use g_try_new() and propagate
allocation failure as a migration error instead.

Regards,
Akihiko Odaki

> 
> Fixes: 0c244e50ee12 ("virtio-gpu: add live migration support")
> Fixes: f66767f75c9c ("virtio-gpu: add virtio-gpu/blob vmstate subsection")
> Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3753
> Reported-by: Feifan Qian <bea1e@proton.me>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>   hw/display/virtio-gpu.c | 18 +++++++++++++++---
>   1 file changed, 15 insertions(+), 3 deletions(-)
> 
> diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c
> index 88526051a99..6289bff9f3b 100644
> --- a/hw/display/virtio-gpu.c
> +++ b/hw/display/virtio-gpu.c
> @@ -34,6 +34,7 @@
>   #include "qemu/error-report.h"
>   
>   #define VIRTIO_GPU_VM_VERSION 1
> +#define VIRTIO_GPU_MAX_IOVEC 16384
>   
>   static struct virtio_gpu_simple_resource *
>   virtio_gpu_find_check_resource(VirtIOGPU *g, uint32_t resource_id,
> @@ -820,10 +821,10 @@ int virtio_gpu_create_mapping_iov(VirtIOGPU *g,
>       size_t esize, s;
>       int e, v;
>   
> -    if (nr_entries > 16384) {
> +    if (nr_entries > VIRTIO_GPU_MAX_IOVEC) {
>           qemu_log_mask(LOG_GUEST_ERROR,
> -                      "%s: nr_entries is too big (%d > 16384)\n",
> -                      __func__, nr_entries);
> +                      "%s: nr_entries is too big (%d > %d)\n",
> +                      __func__, nr_entries, VIRTIO_GPU_MAX_IOVEC);
>           return -1;
>       }
>   
> @@ -1323,6 +1324,11 @@ static int virtio_gpu_load(QEMUFile *f, void *opaque, size_t size,
>           res->format = qemu_get_be32(f);
>           res->iov_cnt = qemu_get_be32(f);
>   
> +        if (res->iov_cnt > VIRTIO_GPU_MAX_IOVEC) {
> +            g_free(res);
> +            return -EINVAL;
> +        }
> +
>           /* allocate */
>           pformat = virtio_gpu_get_pixman_format(res->format);
>           if (!pformat) {
> @@ -1424,6 +1430,12 @@ static int virtio_gpu_blob_load(QEMUFile *f, void *opaque, size_t size,
>           res->resource_id = resource_id;
>           res->blob_size = qemu_get_be32(f);
>           res->iov_cnt = qemu_get_be32(f);
> +
> +        if (res->iov_cnt > VIRTIO_GPU_MAX_IOVEC) {
> +            g_free(res);
> +            return -EINVAL;
> +        }
> +
>           res->addrs = g_new(uint64_t, res->iov_cnt);
>           res->iov = g_new(struct iovec, res->iov_cnt);
>