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 = {