@samitouri / QOSamiQemu / commits / 8720061c6d

hw/display/virtio-gpu: cap submit_3d command buffer allocation

Both virgl_cmd_submit_3d() and rutabaga_cmd_submit_3d() pass the guest-controlled cs.size directly to malloc() without bounds checking. A malicious guest can set cs.size to an arbitrarily large value, causing an OOM abort that crashes the QEMU process. Checking cs.size against the descriptor payload length (iov_size) is not sufficient: indirect descriptor tables can repeat entries aliasing the same guest-physical range, inflating iov_size() to nearly 4 GiB while referring to only a small amount of unique memory. Instead, cap cs.size at 4 MiB. With 4 KiB pages and QEMU's VIRTQUEUE_MAX_SIZE (1024) mapped-iov limit, the Linux virtio driver cannot carry more than ~4 MiB of inline command data, so legitimate submissions are unaffected. Fixes: 9d9e152136bd ("virtio-gpu: add 3d mode and virgl rendering support.") Fixes: 1dcc6adbc168 ("gfxstream + rutabaga: add initial support for gfxstream") Fixes: d52c454aadc ("contrib: add vhost-user-gpu") Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3776 Reported-by: admin@fluentlogic.org Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

Marc-André Lureau committed Jul 7, 2026 at 12:34 UTC 8720061c6d343c77b4f3a061cce631c05ecc0482
5 files changed +41
contrib/vhost-user-gpu/virgl.c
+7
@@ -202,6 +202,13 @@ virgl_cmd_submit_3d(VuGpu *g,
202
203 VUGPU_FILL_CMD(cs);
204
205 + if (cs.size > VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE) {
206 + g_critical("%s: command buffer too large (%u)",
207 + __func__, cs.size);
208 + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
209 + return;
210 + }
211 +
212 buf = g_malloc(cs.size);
213 s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num,
214 sizeof(cs), buf, cs.size);
contrib/vhost-user-gpu/vugpu.h
+9
@@ -22,6 +22,7 @@
22 #include "qemu/queue.h"
23 #include "qemu/iov.h"
24 #include "qemu/bswap.h"
25 +#include "qemu/units.h"
26 #include "vugbm.h"
27
28 typedef enum VhostUserGpuRequest {
@@ -163,6 +164,14 @@ struct virtio_gpu_ctrl_command {
164 QTAILQ_ENTRY(virtio_gpu_ctrl_command) next;
165 };
166
167 +/*
168 + * With 4 KiB pages and QEMU's VIRTQUEUE_MAX_SIZE (1024) mapped-iov
169 + * limit, the largest inline command is ~4 MiB. Cap submit_3d
170 + * allocations to this value to prevent a malicious guest from
171 + * triggering an OOM abort via an inflated cs.size field.
172 + */
173 +#define VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE (4 * MiB)
174 +
175 #define VUGPU_FILL_CMD(out) do { \
176 size_t vugpufillcmd_s_ = \
177 iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, 0, \
hw/display/virtio-gpu-rutabaga.c
+8
@@ -351,6 +351,14 @@ rutabaga_cmd_submit_3d(VirtIOGPU *g,
351 VIRTIO_GPU_FILL_CMD(cs);
352 trace_virtio_gpu_cmd_ctx_submit(cs.hdr.ctx_id, cs.size);
353
354 + if (cs.size > VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE) {
355 + qemu_log_mask(LOG_GUEST_ERROR,
356 + "%s: command buffer too large (%u)\n",
357 + __func__, cs.size);
358 + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
359 + return;
360 + }
361 +
362 buf = g_new0(uint8_t, cs.size);
363 s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num,
364 sizeof(cs), buf, cs.size);
hw/display/virtio-gpu-virgl.c
+8
@@ -607,6 +607,14 @@ static void virgl_cmd_submit_3d(VirtIOGPU *g,
607 VIRTIO_GPU_FILL_CMD(cs);
608 trace_virtio_gpu_cmd_ctx_submit(cs.hdr.ctx_id, cs.size);
609
610 + if (cs.size > VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE) {
611 + qemu_log_mask(LOG_GUEST_ERROR,
612 + "%s: command buffer too large (%u)\n",
613 + __func__, cs.size);
614 + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
615 + return;
616 + }
617 +
618 buf = g_malloc(cs.size);
619 s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num,
620 sizeof(cs), buf, cs.size);
include/hw/virtio/virtio-gpu.h
+9
@@ -15,6 +15,7 @@
15 #define HW_VIRTIO_GPU_H
16
17 #include "qemu/queue.h"
18 +#include "qemu/units.h"
19 #include "ui/qemu-pixman.h"
20 #include "ui/console.h"
21 #include "hw/virtio/virtio.h"
@@ -299,6 +300,14 @@ struct VirtIOGPURutabaga {
300 struct rutabaga *rutabaga;
301 };
302
303 +/*
304 + * With 4 KiB pages and QEMU's VIRTQUEUE_MAX_SIZE (1024) mapped-iov
305 + * limit, the largest inline command is ~4 MiB. Cap submit_3d
306 + * allocations to this value to prevent a malicious guest from
307 + * triggering an OOM abort via an inflated cs.size field.
308 + */
309 +#define VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE (4 * MiB)
310 +
311 #define VIRTIO_GPU_FILL_CMD(out) do { \
312 size_t virtiogpufillcmd_s_ = \
313 iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, 0, \