hw/display/virtio-gpu.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-)
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
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);
>
© 2016 - 2026 Red Hat, Inc.