* [PATCH v2] hw/usb/hcd-xhci-sysbus: Fix OOB heap access in xhci_sysbus_intr_raise()
@ 2026-07-17 20:32 Thomas Huth
2026-07-18 8:26 ` Peter Maydell
0 siblings, 1 reply; 2+ messages in thread
From: Thomas Huth @ 2026-07-17 20:32 UTC (permalink / raw)
To: qemu-devel, Peter Maydell
Cc: kraxel, Philippe Mathieu-Daudé, qemu-stable,
Daniel P . Berrange
From: Thomas Huth <thuth@redhat.com>
Some machines like the microvm machine instantiate a "sysbus-xhci"
device with just 1 interrupt (by setting the "intrs" property to 1).
xhci_sysbus_realize() then only allocates the s->irq array with one
entry.
When the guest writes to the ERDP register of a corresponding XHCI
"interrupter", the generic XHCI code calls the xhci_sysbus_intr_raise()
function with n > 1, and this function then calls qemu_set_irq() with
s->irq[n] pointing to a bad heap address. The qemu_set_irq() then tries
to call an IRQ handler via a function pointer in that heap space. This
either causes QEMU to die with a segmentation fault (if it's a bad
address), or even worse runs some unexpected code if the destination
of the pointer is executable code.
Looking at the xHCI spec, it is up to the implementation of the host
controller how many interrupters are available. So if we only support
one or some few interrupters, the registers of the other interrupters
should not do anything, i.e. reads should result in zeros and writes
should be completely ignored. (big thanks to Peter Maydell for helping
with the analyzation of the correct way to fix this here)
This way, the xhci_sysbus_intr_raise() function cannot be called with
an invalid interrupt number anymore. But for good measure, also add an
assert() statement to the xhci_sysbus_intr_raise() function to prevent
that similar problems with calling arbitrary function pointers on the
heap could occur again.
Fixes: CVE-2026-16043
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4001
Reported-by: Tristan Madani <tristan@talencesecurity.com>
Signed-off-by: Thomas Huth <thuth@redhat.com>
---
hw/usb/hcd-xhci-sysbus.c | 1 +
hw/usb/hcd-xhci.c | 12 ++++++++++++
2 files changed, 13 insertions(+)
diff --git a/hw/usb/hcd-xhci-sysbus.c b/hw/usb/hcd-xhci-sysbus.c
index 19664c5985e..bbdd5fd64ab 100644
--- a/hw/usb/hcd-xhci-sysbus.c
+++ b/hw/usb/hcd-xhci-sysbus.c
@@ -20,6 +20,7 @@ static bool xhci_sysbus_intr_raise(XHCIState *xhci, int n, bool level)
{
XHCISysbusState *s = container_of(xhci, XHCISysbusState, xhci);
+ assert(n < xhci->numintrs);
qemu_set_irq(s->irq[n], level);
return false;
diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c
index ae8add4227e..dedce34f432 100644
--- a/hw/usb/hcd-xhci.c
+++ b/hw/usb/hcd-xhci.c
@@ -3044,6 +3044,12 @@ static uint64_t xhci_runtime_read(void *ptr, hwaddr reg,
}
} else {
int v = (reg - 0x20) / 0x20;
+
+ if (v >= xhci->numintrs) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "xhci: read from non-existing interrupter %i\n", v);
+ return 0;
+ }
XHCIInterrupter *intr = &xhci->intr[v];
switch (reg & 0x1f) {
case 0x00: /* IMAN */
@@ -3087,7 +3093,13 @@ static void xhci_runtime_write(void *ptr, hwaddr reg,
trace_usb_xhci_unimplemented("runtime write", reg);
return;
}
+
v = (reg - 0x20) / 0x20;
+ if (v >= xhci->numintrs) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "xhci: write to non-existing interrupter %i\n", v);
+ return;
+ }
intr = &xhci->intr[v];
switch (reg & 0x1f) {
--
2.55.0
^ permalink raw reply related [flat|nested] 2+ messages in thread* Re: [PATCH v2] hw/usb/hcd-xhci-sysbus: Fix OOB heap access in xhci_sysbus_intr_raise()
2026-07-17 20:32 [PATCH v2] hw/usb/hcd-xhci-sysbus: Fix OOB heap access in xhci_sysbus_intr_raise() Thomas Huth
@ 2026-07-18 8:26 ` Peter Maydell
0 siblings, 0 replies; 2+ messages in thread
From: Peter Maydell @ 2026-07-18 8:26 UTC (permalink / raw)
To: Thomas Huth
Cc: qemu-devel, kraxel, Philippe Mathieu-Daudé, qemu-stable,
Daniel P . Berrange
On Fri, 17 Jul 2026 at 21:32, Thomas Huth <thuth@redhat.com> wrote:
>
> From: Thomas Huth <thuth@redhat.com>
>
> Some machines like the microvm machine instantiate a "sysbus-xhci"
> device with just 1 interrupt (by setting the "intrs" property to 1).
> xhci_sysbus_realize() then only allocates the s->irq array with one
> entry.
>
> When the guest writes to the ERDP register of a corresponding XHCI
> "interrupter", the generic XHCI code calls the xhci_sysbus_intr_raise()
> function with n > 1, and this function then calls qemu_set_irq() with
> s->irq[n] pointing to a bad heap address. The qemu_set_irq() then tries
> to call an IRQ handler via a function pointer in that heap space. This
> either causes QEMU to die with a segmentation fault (if it's a bad
> address), or even worse runs some unexpected code if the destination
> of the pointer is executable code.
>
> Looking at the xHCI spec, it is up to the implementation of the host
> controller how many interrupters are available. So if we only support
> one or some few interrupters, the registers of the other interrupters
> should not do anything, i.e. reads should result in zeros and writes
> should be completely ignored. (big thanks to Peter Maydell for helping
> with the analyzation of the correct way to fix this here)
>
> This way, the xhci_sysbus_intr_raise() function cannot be called with
> an invalid interrupt number anymore. But for good measure, also add an
> assert() statement to the xhci_sysbus_intr_raise() function to prevent
> that similar problems with calling arbitrary function pointers on the
> heap could occur again.
>
> Fixes: CVE-2026-16043
> Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4001
> Reported-by: Tristan Madani <tristan@talencesecurity.com>
> Signed-off-by: Thomas Huth <thuth@redhat.com>
> ---
> hw/usb/hcd-xhci-sysbus.c | 1 +
> hw/usb/hcd-xhci.c | 12 ++++++++++++
> 2 files changed, 13 insertions(+)
>
> diff --git a/hw/usb/hcd-xhci-sysbus.c b/hw/usb/hcd-xhci-sysbus.c
> index 19664c5985e..bbdd5fd64ab 100644
> --- a/hw/usb/hcd-xhci-sysbus.c
> +++ b/hw/usb/hcd-xhci-sysbus.c
> @@ -20,6 +20,7 @@ static bool xhci_sysbus_intr_raise(XHCIState *xhci, int n, bool level)
> {
> XHCISysbusState *s = container_of(xhci, XHCISysbusState, xhci);
>
> + assert(n < xhci->numintrs);
> qemu_set_irq(s->irq[n], level);
>
> return false;
> diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c
> index ae8add4227e..dedce34f432 100644
> --- a/hw/usb/hcd-xhci.c
> +++ b/hw/usb/hcd-xhci.c
> @@ -3044,6 +3044,12 @@ static uint64_t xhci_runtime_read(void *ptr, hwaddr reg,
> }
> } else {
> int v = (reg - 0x20) / 0x20;
> +
> + if (v >= xhci->numintrs) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "xhci: read from non-existing interrupter %i\n", v);
"nonexistent"
> + return 0;
> + }
This early return skips the trace_usb_xhci_runtime_read()
tracepoint. A 'goto out' is probably the least invasive way
to arrange to still go through that.
> XHCIInterrupter *intr = &xhci->intr[v];
> switch (reg & 0x1f) {
> case 0x00: /* IMAN */
> @@ -3087,7 +3093,13 @@ static void xhci_runtime_write(void *ptr, hwaddr reg,
> trace_usb_xhci_unimplemented("runtime write", reg);
> return;
> }
> +
> v = (reg - 0x20) / 0x20;
> + if (v >= xhci->numintrs) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "xhci: write to non-existing interrupter %i\n", v);
> + return;
> + }
Early return in the write is fine because we do the trace at the
start of the function.
(I've occasionally thought that it would be nice if there was
some way to have MemoryRegionOps read/write fns have "automatic"
tracepoints, so you could e.g. set ".tracepoint_name = usb_xhci_runtime"
in the MemoryRegionOps struct and have it automatically interpose
tracing. Putting them in manually in the read/write functions is
always a little awkward and boilerplate-y.)
thanks
-- PMM
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-18 8:27 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-17 20:32 [PATCH v2] hw/usb/hcd-xhci-sysbus: Fix OOB heap access in xhci_sysbus_intr_raise() Thomas Huth
2026-07-18 8:26 ` Peter Maydell
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.