@samitouri / QOSamiQemu / commits / 1c1232c2ee

hw/display/vhost-user-gpu: validate message payload sizes

A malicious or buggy vhost-user-gpu backend can send messages with undersized payloads, leading to out-of-bounds reads when the handler accesses struct fields beyond the allocated buffer. However, vhost-user-gpu is considered trusted by QEMU by design (it has access to shared memory etc). Add a centralized minimum payload size check in vhost_user_gpu_chr_read() that rejects messages before dispatch, and a per-pixel bounds check in the VHOST_USER_GPU_UPDATE handler to ensure the variable-length data covers the declared width x height. Fixes: 267f66465 ("hw/display: add vhost-user-vga & gpu-pci") Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3866 Reported-by: Feifan Qian <bea1e@proton.me> Reviewed-by: Thomas Huth <thuth@redhat.com> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

Marc-André Lureau committed Jul 7, 2026 at 11:38 UTC 1c1232c2ee0798e5f8446200005f045a153a5648
1 file changed +38
hw/display/vhost-user-gpu.c
+38
@@ -119,6 +119,31 @@ static VhostUserGpuMsg m __attribute__ ((unused));
119
120 static void vhost_user_gpu_update_blocked(VhostUserGPU *g, bool blocked);
121
122 +static size_t
123 +vhost_user_gpu_min_payload_size(VhostUserGpuRequest request)
124 +{
125 + switch (request) {
126 + case VHOST_USER_GPU_CURSOR_POS:
127 + case VHOST_USER_GPU_CURSOR_POS_HIDE:
128 + return sizeof(VhostUserGpuCursorPos);
129 + case VHOST_USER_GPU_CURSOR_UPDATE:
130 + return sizeof(VhostUserGpuCursorUpdate);
131 + case VHOST_USER_GPU_GET_EDID:
132 + return sizeof(VhostUserGpuEdidRequest);
133 + case VHOST_USER_GPU_SCANOUT:
134 + return sizeof(VhostUserGpuScanout);
135 + case VHOST_USER_GPU_DMABUF_SCANOUT:
136 + return sizeof(VhostUserGpuDMABUFScanout);
137 + case VHOST_USER_GPU_DMABUF_SCANOUT2:
138 + return sizeof(VhostUserGpuDMABUFScanout2);
139 + case VHOST_USER_GPU_DMABUF_UPDATE:
140 + case VHOST_USER_GPU_UPDATE:
141 + return sizeof(VhostUserGpuUpdate);
142 + default:
143 + return 0;
144 + }
145 +}
146 +
147 static void
148 vhost_user_gpu_handle_cursor(VhostUserGPU *g, VhostUserGpuMsg *msg)
149 {
@@ -322,6 +347,14 @@ vhost_user_gpu_handle_display(VhostUserGPU *g, VhostUserGpuMsg *msg)
347 if (m->scanout_id >= g->parent_obj.conf.max_outputs) {
348 break;
349 }
350 +
351 + if ((uint64_t)m->width * m->height >
352 + (msg->size - sizeof(VhostUserGpuUpdate)) / sizeof(uint32_t)) {
353 + error_report("vhost-user-gpu: update payload too small"
354 + " for %ux%u", m->width, m->height);
355 + break;
356 + }
357 +
358 s = &g->parent_obj.scanout[m->scanout_id];
359 con = s->con;
360 pixman_image_t *image =
@@ -396,6 +429,11 @@ vhost_user_gpu_chr_read(void *opaque)
429 msg->flags = flags;
430 msg->size = size;
431
432 + if (size < vhost_user_gpu_min_payload_size(request)) {
433 + error_report("vhost-user-gpu: message %d payload too small", request);
434 + goto end;
435 + }
436 +
437 if (request == VHOST_USER_GPU_CURSOR_UPDATE ||
438 request == VHOST_USER_GPU_CURSOR_POS ||
439 request == VHOST_USER_GPU_CURSOR_POS_HIDE) {