Commit cfa70f3a82 for qemu.org

commit cfa70f3a82f581ad758586ecf408fe7f3602850f
Author: Thomas Huth <thuth@redhat.com>
Date:   Fri Oct 16 17:21:04 2026 +0200

    hw/usb/hcd-xhci: More reentrancy guard fixes for the XHCI code

    The XHCI controller's USB port and bus callbacks -- xhci_complete(),
    xhci_wakeup_endpoint(), xhci_attach(), xhci_detach(), and
    xhci_wakeup() -- can be invoked from outside any MMIO handler context.
    The device's mem_reentrancy_guard is therefore not engaged during
    this processing.

    A malicious guest can exploit this gap via MSI-X self-targeting: it
    programs the MSI-X vector's message address to point at the XHCI
    doorbell register array (both reside in the same BAR0 MemoryRegion).
    When any of these callbacks posts an event via xhci_event() ->
    xhci_intr_raise(), the resulting msix_notify() -> pci_msi_trigger()
    issues an address_space_stl_le() that writes doorbell 0, triggering
    xhci_process_commands() reentrantly. A CR_DISABLE_SLOT command
    planted on the command ring then frees XHCITransfer/XHCIEPContext
    objects that the outer call stack still references, causing a
    heap use-after-free.

    Fix this by setting engaged_in_io on the device's mem_reentrancy_guard
    around the processing in all five callbacks. The guard is set
    conditionally (only when not already engaged) because these callbacks
    may also be reached from within a guarded MMIO context.

    xhci_child_detach() is intentionally left unguarded: it only calls
    xhci_detach_slot() which passes report=0 to xhci_ep_nuke_xfers(),
    suppressing event generation, so it never reaches xhci_event().

    Note: This is similar to the fixes done for CVE-2026-17588, see
    f91b0544fe ("Set reentrancy guard in timer functions").

    CVE: CVE-2026-111819
    Message-ID: <20261020114031.687929-1-thuth@redhat.com>
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4625
    Reported-by: Ken Hsu, Royce Lu, and Bo Qu of Palo Alto Networks
    Buglink: https://gitlab.com/qemu-project/qemu/-/work_items/4642
    Reported-by: Nikhil Agrawal <nikhil.agrawal@mettlelabs.ai>
    Signed-off-by: Thomas Huth <thuth@redhat.com>

diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c
index 376bc00264..5d7a77b8bc 100644
--- a/hw/usb/hcd-xhci.c
+++ b/hw/usb/hcd-xhci.c
@@ -3251,49 +3251,73 @@ static const MemoryRegionOps xhci_doorbell_ops = {
     .endianness = DEVICE_LITTLE_ENDIAN,
 };

+static bool xhci_engage_reentrancy_guard(XHCIState *xhci)
+{
+    bool prev_engaged = xhci->parent.mem_reentrancy_guard.engaged_in_io;
+
+    xhci->parent.mem_reentrancy_guard.engaged_in_io = true;
+    return prev_engaged;
+}
+
+static void xhci_restore_reentrancy_guard(XHCIState *xhci, bool engaged)
+{
+    xhci->parent.mem_reentrancy_guard.engaged_in_io = engaged;
+}
+
 static void xhci_attach(USBPort *usbport)
 {
     XHCIState *xhci = usbport->opaque;
     XHCIPort *port = xhci_lookup_port(xhci, usbport);
+    bool prev_engaged = xhci_engage_reentrancy_guard(xhci);

     xhci_port_update(port, 0);
+    xhci_restore_reentrancy_guard(xhci, prev_engaged);
 }

 static void xhci_detach(USBPort *usbport)
 {
     XHCIState *xhci = usbport->opaque;
     XHCIPort *port = xhci_lookup_port(xhci, usbport);
+    bool prev_engaged = xhci_engage_reentrancy_guard(xhci);

     xhci_detach_slot(xhci, usbport);
     xhci_port_update(port, 1);
+    xhci_restore_reentrancy_guard(xhci, prev_engaged);
 }

 static void xhci_wakeup(USBPort *usbport)
 {
     XHCIState *xhci = usbport->opaque;
     XHCIPort *port = xhci_lookup_port(xhci, usbport);
+    bool prev_engaged;

     assert(port);
     if (get_field(port->portsc, PORTSC_PLS) != PLS_U3) {
         return;
     }
+    prev_engaged = xhci_engage_reentrancy_guard(xhci);
     set_field(&port->portsc, PLS_RESUME, PORTSC_PLS);
     xhci_port_notify(port, PORTSC_PLC);
+    xhci_restore_reentrancy_guard(xhci, prev_engaged);
 }

 static void xhci_complete(USBPort *port, USBPacket *packet)
 {
     XHCITransfer *xfer = container_of(packet, XHCITransfer, packet);
+    XHCIState *xhci = xfer->epctx->xhci;
+    bool prev_engaged;

     if (packet->status == USB_RET_REMOVE_FROM_QUEUE) {
         xhci_ep_nuke_one_xfer(xfer, 0);
         return;
     }
+    prev_engaged = xhci_engage_reentrancy_guard(xhci);
     xhci_try_complete_packet(xfer);
     xhci_kick_epctx(xfer->epctx, xfer->streamid);
     if (xfer->complete) {
         xhci_ep_free_xfer(xfer);
     }
+    xhci_restore_reentrancy_guard(xhci, prev_engaged);
 }

 static void xhci_child_detach(USBPort *uport, USBDevice *child)
@@ -3345,6 +3369,7 @@ static void xhci_wakeup_endpoint(USBBus *bus, USBEndpoint *ep,
 {
     XHCIState *xhci = container_of(bus, XHCIState, bus);
     int slotid;
+    bool prev_engaged;

     DPRINTF("%s\n", __func__);
     slotid = ep->dev->addr;
@@ -3353,7 +3378,9 @@ static void xhci_wakeup_endpoint(USBBus *bus, USBEndpoint *ep,
         DPRINTF("%s: oops, no slot for dev %d\n", __func__, ep->dev->addr);
         return;
     }
+    prev_engaged = xhci_engage_reentrancy_guard(xhci);
     xhci_kick_ep(xhci, slotid, xhci_find_epid(ep), stream);
+    xhci_restore_reentrancy_guard(xhci, prev_engaged);
 }

 static USBBusOps xhci_bus_ops = {