On 2026/09/04 22:10, Marc-André Lureau wrote:
When the backend sends VHOST_USER_GPU_SCANOUT with zero dimensions to
disable a scanout, the frontend only calls qemu_console_set_surface(NULL)
without releasing the g->dmabuf[] entry or invoking
qemu_console_gl_scanout_disable(). Display listeners (GTK, SDL) can
therefore remain in GL scanout mode and keep redrawing the stale buffer.

Release the DMA-BUF and call qemu_console_gl_scanout_disable() in the
SCANOUT handler when a previously active DMA-BUF exists.

Additionally, vhost_user_gpu_reset() and
vhost_user_gpu_instance_finalize() never iterate the dmabuf[] array, so
active DMA-BUFs leak on device reset or object destruction. Add cleanup
loops to both paths.

We need to ensure that DMA-BUF reset cleanup happens on the main-thread.

A guest writes zero to the modern PCI status register → virtio_pci_common_write() → virtio_pci_reset() → virtio_bus_reset() → virtio_reset() → vhost_user_gpu_reset(). This executes on the vCPU thread. The newly added release calls invoke GTK/SDL/EGL display listeners there. For example, egl_release_dmabuf() attempts to bind qemu_egl_rn_ctx, which remains current on the main thread, then calls glDeleteTextures() through egl_dmabuf_release_texture(). The context bind fails, leaving the GL deletion without the required current context. EGL 1.5 §3.7.3 explicitly requires EGL_BAD_ACCESS when the context is current on another thread.

Existing virtio_gpu_reset() handles this constraint by scheduling a main-loop BH and waiting, but it also has its own race and deadlock issues. [1] is my latest attempt to fix it. You may review it, and adopt its approach if it looks reasonable to you.

[1] https://lore.kernel.org/qemu-devel/[email protected]/
("[PATCH v3 0/9] virtio-gpu: Do not wait for the main thread during reset")


Fixes: 267f664658fe ("hw/display: add vhost-user-vga & gpu-pci")
Signed-off-by: Marc-André Lureau <[email protected]>
---
  hw/display/vhost-user-gpu.c | 18 ++++++++++++++++++
  1 file changed, 18 insertions(+)

diff --git a/hw/display/vhost-user-gpu.c b/hw/display/vhost-user-gpu.c
index c09aca041135..3f1fd333eeab 100644
--- a/hw/display/vhost-user-gpu.c
+++ b/hw/display/vhost-user-gpu.c
@@ -263,6 +263,11 @@ vhost_user_gpu_handle_display(VhostUserGPU *g, 
VhostUserGpuMsg *msg)
          con = s->con;
if (m->width == 0) {
+            if (g->dmabuf[m->scanout_id]) {
+                qemu_console_gl_release_dmabuf(con, g->dmabuf[m->scanout_id]);
+                g_clear_pointer(&g->dmabuf[m->scanout_id], qemu_dmabuf_free);
+                qemu_console_gl_scanout_disable(con);
+            }
              qemu_console_set_surface(con, NULL);
          } else {
              s->ds = qemu_create_displaysurface(m->width, m->height);
@@ -631,6 +636,10 @@ vhost_user_gpu_instance_finalize(Object *obj)
  {
      VhostUserGPU *g = VHOST_USER_GPU(obj);
+ for (int i = 0; i < g->parent_obj.conf.max_outputs; i++) {

Please bound finalizer cleanup by the actual DMA-BUF array size.

conf.max_outputs is a writable uint32 property, and the rejection of values above 16 happens only in virtio_gpu_base_device_realize(). The finalizer also runs after realization failure, including missing chardev, before that validation. Thus setting max_outputs=4096 makes this new loop traverse beyond dmabuf[16], reading and potentially freeing arbitrary values. Iterating ARRAY_SIZE(g->dmabuf) instead is the simplest fix I came up.

Regards,
Akihiko Odaki

+        g_clear_pointer(&g->dmabuf[i], qemu_dmabuf_free);
+    }
+
      object_unref(OBJECT(g->vhost));
  }
@@ -639,6 +648,15 @@ vhost_user_gpu_reset(VirtIODevice *vdev)
  {
      VhostUserGPU *g = VHOST_USER_GPU(vdev);
+ for (int i = 0; i < g->parent_obj.conf.max_outputs; i++) {
+        if (g->dmabuf[i]) {
+            QemuConsole *con = g->parent_obj.scanout[i].con;
+            qemu_console_gl_release_dmabuf(con, g->dmabuf[i]);
+            g_clear_pointer(&g->dmabuf[i], qemu_dmabuf_free);
+            qemu_console_gl_scanout_disable(con);
+        }
+    }
+
      virtio_gpu_base_reset(VIRTIO_GPU_BASE(vdev));
vhost_user_backend_stop(g->vhost);



Reply via email to