The xHCI controller processes USB events from timer callbacks (xhci_mfwrap_timer and xhci_ep_kick_timer) outside of any MMIO handler context. The device's mem_reentrancy_guard is therefore not engaged during this processing.
A malicious guest can exploit this by pointing the event ring base address (er_start) at the xHCI doorbell MMIO region. When xhci_write_event() performs a DMA write to deliver a transfer completion event, the write lands on doorbell register 0. If the written value is 0, this triggers xhci_process_commands() reentrantly. A CR_DISABLE_SLOT command prepared on the command ring then frees endpoint and transfer objects via xhci_disable_ep() / g_free() while the outer call stack still holds references to them, causing a heap use-after-free. Fix this by setting engaged_in_io on the device's mem_reentrancy_guard around the processing in these non-MMIO entry points. This mirrors the protection that EHCI, DWC2, and UHCI already have via qemu_bh_new_guarded(). With the guard active, the memory subsystem's automatic reentrancy check (system/memory.c) blocks DMA writes that would dispatch into the device's own MMIO handlers. CVE: CVE-2026-17588 Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3926 Reported-by: XlabAI Team of Tencent Xuanwu Lab <[email protected]>, Guannan Wang <[email protected]>, Zhanpeng Liu <[email protected]>, Jiashuo Liang <[email protected]>, Guancheng Li <[email protected]> Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4195 Reported By: Ken Hsu and Royce Lu of Palo Alto Networks Signed-off-by: Thomas Huth <[email protected]> --- hw/usb/hcd-xhci.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c index d342aa2739e..12f8ffece0f 100644 --- a/hw/usb/hcd-xhci.c +++ b/hw/usb/hcd-xhci.c @@ -456,9 +456,15 @@ static void xhci_mfwrap_timer(void *opaque) { XHCIState *xhci = opaque; XHCIEvent wrap = { ER_MFINDEX_WRAP, CC_SUCCESS }; + MemReentrancyGuard *guard = &xhci->parent.mem_reentrancy_guard; + + assert(!guard->engaged_in_io); + guard->engaged_in_io = true; xhci_event(xhci, &wrap, 0); xhci_mfwrap_update(xhci); + + guard->engaged_in_io = false; } static void xhci_die(XHCIState *xhci) @@ -1086,7 +1092,14 @@ static void xhci_set_ep_state(XHCIState *xhci, XHCIEPContext *epctx, static void xhci_ep_kick_timer(void *opaque) { XHCIEPContext *epctx = opaque; + MemReentrancyGuard *guard = &epctx->xhci->parent.mem_reentrancy_guard; + + assert(!guard->engaged_in_io); + guard->engaged_in_io = true; + xhci_kick_epctx(epctx, 0); + + guard->engaged_in_io = false; } static XHCIEPContext *xhci_alloc_epctx(XHCIState *xhci, -- 2.55.0
