Commit 2c43897356 for qemu.org

commit 2c43897356ac10dcce76a0287de8f9561c84fe82
Author: Denis V. Lunev <den@openvz.org>
Date:   Thu Sep 3 21:26:46 2026 +0200

    hw/display/qxl: hold ssd.lock while replacing ssd.cursor

    qxl_spice_reset_cursor() unrefs qxl->ssd.cursor and installs the hidden
    cursor without holding qxl->ssd.lock. Every other writer of that field
    takes it: qxl_render_cursor(), display_mouse_define() and
    qemu_spice_cursor_refresh_bh().

    The unlocked path runs on a vCPU thread, reached from ioport_write() on
    QXL_IO_DESTROY_PRIMARY and QXL_IO_DESTROY_PRIMARY_ASYNC, and holds only
    the BQL, which the SPICE display worker never takes. Unlike
    qxl_hard_reset(), it leaves that worker running.
    spice_qxl_reset_cursor() does round trip through the dispatcher, but the
    worker is free again as soon as it returns, so it can enter
    qxl_render_cursor() and unref the same QEMUCursor a few instructions
    later. Both threads then drop one reference for what is a single
    reference, freeing a cursor that another user still holds. The store to
    ssd.cursor races the same way, and a guest that keeps this up also ends
    up waiting forever in qxl_fence_wait().

    A guest reaches this by switching QXL mode while it also updates the
    pointer shape.

    Fixes: 958c2bceba06 ("qxl: fix cursor reset")
    Cc: qemu-stable@nongnu.org
    Cc: Marc-André Lureau <marcandre.lureau@redhat.com>
    Signed-off-by: Denis V. Lunev <den@openvz.org>
    Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
    Message-ID: <20260903192647.2677279-2-den@openvz.org>

diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index 384b8767b8..c4f547e88b 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -294,10 +294,12 @@ void qxl_spice_reset_cursor(PCIQXLDevice *qxl)
     qemu_mutex_lock(&qxl->track_lock);
     qxl->guest_cursor = 0;
     qemu_mutex_unlock(&qxl->track_lock);
+    qemu_mutex_lock(&qxl->ssd.lock);
     if (qxl->ssd.cursor) {
         cursor_unref(qxl->ssd.cursor);
     }
     qxl->ssd.cursor = cursor_builtin_hidden();
+    qemu_mutex_unlock(&qxl->ssd.lock);
 }

 static uint32_t qxl_crc32(const uint8_t *p, unsigned len)