* [PULL 0/7] USB-related bug fixes
@ 2026-07-20 10:38 Thomas Huth
2026-07-20 10:38 ` [PULL 1/7] hw/usb/xhci: clamp interval exponent to avoid UB shift in xhci_init_epctx() Thomas Huth
` (8 more replies)
0 siblings, 9 replies; 10+ messages in thread
From: Thomas Huth @ 2026-07-20 10:38 UTC (permalink / raw)
To: qemu-devel, Stefan Hajnoczi
Hi Stefan!
The following changes since commit 5ef0ecc5942ee07bb581cc96a97bbfc0fdf4005c:
Merge tag 'hw-misc-20260714' of https://github.com/philmd/qemu into staging (2026-07-17 10:01:52 +0100)
are available in the Git repository at:
https://gitlab.com/thuth/qemu.git tags/pull-request-2026-07-20
for you to fetch changes up to cc479aa89743655a725ce29328bcd0585a65983a:
hw/usb/hcd-xhci-sysbus: Fix OOB heap access in xhci_sysbus_intr_raise() (2026-07-20 11:27:52 +0200)
----------------------------------------------------------------
* Fixes for various USB-related bugs
----------------------------------------------------------------
Feifan Qian (1):
hw/usb/xhci: clamp interval exponent to avoid UB shift in xhci_init_epctx()
Marc-André Lureau (2):
usbredir: fix use-after-free on buffered bulk packet overflow
usbredir: fix infinite loop and SIGFPE with zero max_packet_size
Thomas Huth (2):
hw/usb/hcd-xhci: Fix guest-triggerable assert() in xhci_find_stream()
hw/usb/hcd-xhci-sysbus: Fix OOB heap access in xhci_sysbus_intr_raise()
Xiangfeng Cai (2):
hw/usb/hcd-xhci-pci: break host link cycle so device_finalize() runs on unplug
tests/qtest: add xhci-pci unplug finalize regression test
hw/usb/hcd-xhci-pci.c | 12 ++++++++
hw/usb/hcd-xhci-sysbus.c | 1 +
hw/usb/hcd-xhci.c | 22 ++++++++++++--
hw/usb/redirect.c | 42 +++++++++++++++++++++++++-
tests/qtest/usb-hcd-xhci-test.c | 67 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 141 insertions(+), 3 deletions(-)
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PULL 1/7] hw/usb/xhci: clamp interval exponent to avoid UB shift in xhci_init_epctx()
2026-07-20 10:38 [PULL 0/7] USB-related bug fixes Thomas Huth
@ 2026-07-20 10:38 ` Thomas Huth
2026-07-20 10:38 ` [PULL 2/7] hw/usb/hcd-xhci-pci: break host link cycle so device_finalize() runs on unplug Thomas Huth
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Thomas Huth @ 2026-07-20 10:38 UTC (permalink / raw)
To: qemu-devel, Stefan Hajnoczi; +Cc: Feifan Qian
From: Feifan Qian <bea1e@proton.me>
The xHCI endpoint context dword 0 bits 23:16 ("Interval") are written
by the guest and passed directly as the shift amount in:
epctx->interval = 1 << ((ctx[0] >> 16) & 0xff);
The shift amount can be 0-255. Shifting a 32-bit `int` left by >= 32
is undefined behaviour under C11 §6.5.7p4. With UBSan
(halt_on_error=1) this causes QEMU to abort; with aggressive compiler
optimisations that assume UB is unreachable the result is
unpredictable.
Clamp the exponent to [0, 18] with MIN() before the shift, and use
`1u` (unsigned) to avoid shifting a signed integer. The xHCI
specification defines a maximum meaningful Interval value of 18 for
most endpoint types; thus clamping to 18 is a safe fix that
preserves the full unsigned 32-bit range for any compliant value.
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3703
Reported-by: Feifan Qian <bea1e@proton.me>
Signed-off-by: Feifan Qian <bea1e@proton.me>
[thuth: Clamp to 18 instead of 31]
Signed-off-by: Thomas Huth <thuth@redhat.com>
---
hw/usb/hcd-xhci.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c
index c7e050e38fd..47b7484d533 100644
--- a/hw/usb/hcd-xhci.c
+++ b/hw/usb/hcd-xhci.c
@@ -1121,7 +1121,7 @@ static void xhci_init_epctx(XHCIEPContext *epctx,
epctx->ring.ccs = ctx[2] & 1;
}
- epctx->interval = 1 << ((ctx[0] >> 16) & 0xff);
+ epctx->interval = 1u << MIN((ctx[0] >> 16) & 0xffu, 18u);
}
static TRBCCode xhci_enable_ep(XHCIState *xhci, unsigned int slotid,
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PULL 2/7] hw/usb/hcd-xhci-pci: break host link cycle so device_finalize() runs on unplug
2026-07-20 10:38 [PULL 0/7] USB-related bug fixes Thomas Huth
2026-07-20 10:38 ` [PULL 1/7] hw/usb/xhci: clamp interval exponent to avoid UB shift in xhci_init_epctx() Thomas Huth
@ 2026-07-20 10:38 ` Thomas Huth
2026-07-20 10:38 ` [PULL 3/7] tests/qtest: add xhci-pci unplug finalize regression test Thomas Huth
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Thomas Huth @ 2026-07-20 10:38 UTC (permalink / raw)
To: qemu-devel, Stefan Hajnoczi; +Cc: Xiangfeng Cai, Marc-André Lureau
From: Xiangfeng Cai <caixiangfeng@bytedance.com>
The xHCI PCI wrapper embeds an xhci-core child via object_initialize_child()
and, in usb_xhci_pci_realize(), points the child's "host" link back at the PCI
device:
object_property_set_link(OBJECT(&s->xhci), "host", OBJECT(s), NULL);
"host" is a DEFINE_PROP_LINK property, which qdev registers as an
OBJ_PROP_LINK_STRONG link. A strong link takes a reference on its target, so
this creates a refcount cycle: the PCI device owns the child, and the child's
strong link pins the PCI device.
On unplug (guest ACPI eject or QMP device_del), pci_qdev_unrealize() calls
pc->exit() but never unrealizes the no-bus child. object_unparent() then drops
only the parent/bus references, leaving the link reference in place. The PCI
device stays at refcount 1 forever, so object_finalize()/device_finalize() is
never reached. Symptom observed under gdb after eject:
p *((Object *)dev) => ref = 1, parent = 0x0, realized = false
p ((XHCIPciState *)dev)->xhci.hostOpaque => points back at dev
Fix usb_xhci_pci_exit() to tear down the embedded child explicitly: unrealize
it first (so the set-link-before-realize check passes), then clear the "host"
link. This releases the strong reference, lets the PCI device refcount reach 0,
and allows device_finalize() to run.
Fixes: 8ddab8dd3d81 ("usb/hcd-xhci: Split pci wrapper for xhci base model")
Signed-off-by: Xiangfeng Cai <caixiangfeng@bytedance.com>
Acked-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20260618110119.3084296-2-caixiangfeng@bytedance.com>
Signed-off-by: Thomas Huth <thuth@redhat.com>
---
hw/usb/hcd-xhci-pci.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/hw/usb/hcd-xhci-pci.c b/hw/usb/hcd-xhci-pci.c
index c5446a4a5e1..b124251ae33 100644
--- a/hw/usb/hcd-xhci-pci.c
+++ b/hw/usb/hcd-xhci-pci.c
@@ -196,6 +196,18 @@ static void usb_xhci_pci_exit(PCIDevice *dev)
&& dev->msix_entry_used) {
msix_uninit(dev, &s->xhci.mem, &s->xhci.mem);
}
+ /*
+ * The embedded xhci-core child holds a strong "host" link back to this
+ * PCI device (set in usb_xhci_pci_realize()), forming a refcount cycle:
+ * the PCI device owns the child, and the child's strong link pins the PCI
+ * device. On unplug, object_unparent() only drops the parent/bus refs, so
+ * the link ref keeps this device at refcount 1 forever and
+ * device_finalize() never runs. Unrealize the child first (so the
+ * realized-check in set_link passes), then clear the link to break the
+ * cycle.
+ */
+ qdev_unrealize(DEVICE(&s->xhci));
+ object_property_set_link(OBJECT(&s->xhci), "host", NULL, &error_abort);
}
static const VMStateDescription vmstate_xhci_pci = {
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PULL 3/7] tests/qtest: add xhci-pci unplug finalize regression test
2026-07-20 10:38 [PULL 0/7] USB-related bug fixes Thomas Huth
2026-07-20 10:38 ` [PULL 1/7] hw/usb/xhci: clamp interval exponent to avoid UB shift in xhci_init_epctx() Thomas Huth
2026-07-20 10:38 ` [PULL 2/7] hw/usb/hcd-xhci-pci: break host link cycle so device_finalize() runs on unplug Thomas Huth
@ 2026-07-20 10:38 ` Thomas Huth
2026-07-20 10:38 ` [PULL 4/7] usbredir: fix use-after-free on buffered bulk packet overflow Thomas Huth
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Thomas Huth @ 2026-07-20 10:38 UTC (permalink / raw)
To: qemu-devel, Stefan Hajnoczi; +Cc: Xiangfeng Cai
From: Xiangfeng Cai <caixiangfeng@bytedance.com>
Add a qtest that hot-adds an nec-usb-xhci controller, requests unplug,
resets the system to process the request, and waits for DEVICE_DELETED.
This covers the xHCI PCI host-link refcount cycle by verifying that
device_finalize() runs after unplug.
Signed-off-by: Xiangfeng Cai <caixiangfeng@bytedance.com>
Message-ID: <20260618110119.3084296-3-caixiangfeng@bytedance.com>
Signed-off-by: Thomas Huth <thuth@redhat.com>
---
tests/qtest/usb-hcd-xhci-test.c | 67 +++++++++++++++++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/tests/qtest/usb-hcd-xhci-test.c b/tests/qtest/usb-hcd-xhci-test.c
index 0cccfd85a64..b58fa1e2dae 100644
--- a/tests/qtest/usb-hcd-xhci-test.c
+++ b/tests/qtest/usb-hcd-xhci-test.c
@@ -10,6 +10,72 @@
#include "qemu/osdep.h"
#include "libqtest-single.h"
#include "libqos/usb.h"
+#include "qobject/qdict.h"
+
+static void wait_device_deleted_event(QTestState *qtest, const char *id)
+{
+ QDict *resp, *data;
+ const char *device;
+
+ /*
+ * Other devices might get removed along with the removed device. Skip
+ * these. The device of interest will be the last one.
+ */
+ for (;;) {
+ resp = qtest_qmp_eventwait_ref(qtest, "DEVICE_DELETED");
+ data = qdict_get_qdict(resp, "data");
+ device = data ? qdict_get_try_str(data, "device") : NULL;
+ if (device && !strcmp(device, id)) {
+ qobject_unref(resp);
+ break;
+ }
+ qobject_unref(resp);
+ }
+}
+
+/*
+ * Regression test for the xHCI-PCI "host" strong-link reference cycle.
+ *
+ * The xHCI PCI wrapper embeds an xhci-core child whose strong "host" link
+ * points back at the PCI device, forming a refcount cycle. If
+ * usb_xhci_pci_exit() does not break that cycle, the device's refcount never
+ * reaches 0 on unplug, device_finalize() never runs, and therefore the
+ * DEVICE_DELETED event (emitted from device_finalize()) is never sent.
+ *
+ * This test hot-plugs an xHCI controller into an ACPI-hotpluggable bus,
+ * requests its removal and waits for DEVICE_DELETED. Without the fix the event
+ * is never delivered (device_finalize() is blocked), so the test would
+ * hang/time out.
+ */
+static void test_xhci_unplug_finalize(void)
+{
+ QTestState *qtest;
+ const char *arch = qtest_get_arch();
+
+ if (strcmp(arch, "i386") != 0 && strcmp(arch, "x86_64") != 0) {
+ g_test_skip("Test only runs on x86 (ACPI PCI hotplug)");
+ return;
+ }
+ if (!qtest_has_device("nec-usb-xhci")) {
+ g_test_skip("Device nec-usb-xhci not available");
+ return;
+ }
+
+ qtest = qtest_initf("-machine pc");
+
+ qtest_qmp_device_add(qtest, "nec-usb-xhci", "xhci-finalize", "{}");
+
+ /*
+ * Request device removal. As the guest is not running, the unplug request
+ * won't be processed until the next system reset, which performs the
+ * removal and triggers device_finalize() (and thus DEVICE_DELETED).
+ */
+ qtest_qmp_device_del_send(qtest, "xhci-finalize");
+ qtest_system_reset_nowait(qtest);
+ wait_device_deleted_event(qtest, "xhci-finalize");
+
+ qtest_quit(qtest);
+}
static void test_xhci_hotplug(void)
{
@@ -50,6 +116,7 @@ int main(int argc, char **argv)
g_test_init(&argc, &argv, NULL);
qtest_add_func("/xhci/pci/hotplug", test_xhci_hotplug);
+ qtest_add_func("/xhci/pci/unplug/finalize", test_xhci_unplug_finalize);
if (qtest_has_device("usb-uas")) {
qtest_add_func("/xhci/pci/hotplug/usb-uas", test_usb_uas_hotplug);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PULL 4/7] usbredir: fix use-after-free on buffered bulk packet overflow
2026-07-20 10:38 [PULL 0/7] USB-related bug fixes Thomas Huth
` (2 preceding siblings ...)
2026-07-20 10:38 ` [PULL 3/7] tests/qtest: add xhci-pci unplug finalize regression test Thomas Huth
@ 2026-07-20 10:38 ` Thomas Huth
2026-07-20 10:38 ` [PULL 5/7] usbredir: fix infinite loop and SIGFPE with zero max_packet_size Thomas Huth
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Thomas Huth @ 2026-07-20 10:38 UTC (permalink / raw)
To: qemu-devel, Stefan Hajnoczi; +Cc: Feifan Qian, Marc-André Lureau
From: Marc-André Lureau <marcandre.lureau@redhat.com>
When usbredir_buffered_bulk_packet() splits a multi-fragment buffered
bulk packet into max-packet-size chunks, only the final fragment owns
the shared parser allocation (via free_on_destroy). If bufp_alloc()
drops the final fragment due to queue overflow, it frees the backing
buffer while earlier fragments already queued still hold interior
pointers into it. Subsequent guest bulk-IN transfers then read from
freed heap memory.
Fix this by tracking how many fragments were queued during the current
packet. When bufp_alloc() fails, remove all already-queued fragments
from the tail of the endpoint queue before breaking out of the loop.
If the dropped fragment was non-final, free the data buffer explicitly
since no fragment took ownership.
Fixes: CVE-2026-15705
Fixes: b2d1fe67d09d ("usbredir: Add support for buffered bulk input (v2)")
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3808
Reported-by: Feifan Qian <bea1e@proton.me>
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20260714185717.1156157-1-marcandre.lureau@redhat.com>
Signed-off-by: Thomas Huth <thuth@redhat.com>
---
hw/usb/redirect.c | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/hw/usb/redirect.c b/hw/usb/redirect.c
index bde821e214b..284bcbdb34d 100644
--- a/hw/usb/redirect.c
+++ b/hw/usb/redirect.c
@@ -2138,7 +2138,7 @@ static void usbredir_buffered_bulk_packet(void *priv, uint64_t id,
USBRedirDevice *dev = priv;
uint8_t status, ep = buffered_bulk_packet->endpoint;
void *free_on_destroy;
- int i, len;
+ int i, len, queued = 0;
DPRINTF("buffered-bulk-in status %d ep %02X len %d id %"PRIu64"\n",
buffered_bulk_packet->status, ep, data_len, id);
@@ -2169,8 +2169,24 @@ static void usbredir_buffered_bulk_packet(void *priv, uint64_t id,
/* bufp_alloc also adds the packet to the ep queue */
r = bufp_alloc(dev, data + i, len, status, ep, free_on_destroy);
if (r) {
+ /*
+ * Earlier fragments from this packet are in the queue
+ * with interior pointers into data. If the dropped
+ * fragment was the final one, bufp_alloc already freed
+ * data so those pointers are dangling. Remove them.
+ */
+ while (queued > 0) {
+ struct buf_packet *bufp;
+ bufp = QTAILQ_LAST(&dev->endpoint[EP2I(ep)].bufpq);
+ bufp_free(dev, bufp, ep);
+ queued--;
+ }
+ if (!free_on_destroy) {
+ free(data);
+ }
break;
}
+ queued++;
}
if (dev->endpoint[EP2I(ep)].pending_async_packet) {
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PULL 5/7] usbredir: fix infinite loop and SIGFPE with zero max_packet_size
2026-07-20 10:38 [PULL 0/7] USB-related bug fixes Thomas Huth
` (3 preceding siblings ...)
2026-07-20 10:38 ` [PULL 4/7] usbredir: fix use-after-free on buffered bulk packet overflow Thomas Huth
@ 2026-07-20 10:38 ` Thomas Huth
2026-07-20 10:38 ` [PULL 6/7] hw/usb/hcd-xhci: Fix guest-triggerable assert() in xhci_find_stream() Thomas Huth
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Thomas Huth @ 2026-07-20 10:38 UTC (permalink / raw)
To: qemu-devel, Stefan Hajnoczi; +Cc: Marc-André Lureau
From: Marc-André Lureau <marcandre.lureau@redhat.com>
A malicious usbredir peer can send an ep_info message resetting
max_packet_size to 0 after bulk receiving has started. This causes:
- infinite loop in usbredir_buffered_bulk_packet() where the splitting
loop increments by max_packet_size (0)
- SIGFPE in usbredir_buffered_bulk_in_complete_ftdi() from modulo by 0
- SIGFPE in usbredir_handle_buffered_bulk_in_data() from division by 0
when computing bytes_per_transfer
Fix by stopping and disabling bulk receiving in usbredir_ep_info() when
max_packet_size is set to 0.
Add post-load check, and assert() for the invariant.
Fixes: CVE-2026-63319
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3995
Reported-by: Tristan @TristanInSec
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Message-ID: <20260716141107.3597076-1-marcandre.lureau@redhat.com>
Signed-off-by: Thomas Huth <thuth@redhat.com>
---
hw/usb/redirect.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/hw/usb/redirect.c b/hw/usb/redirect.c
index 284bcbdb34d..dfd9e8bb50c 100644
--- a/hw/usb/redirect.c
+++ b/hw/usb/redirect.c
@@ -690,6 +690,7 @@ static void usbredir_buffered_bulk_in_complete_ftdi(USBRedirDevice *dev,
struct buf_packet *bulkp;
int count;
+ assert(maxp != 0);
while ((bulkp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq)) &&
p->actual_length < p->iov.size && p->status == USB_RET_SUCCESS) {
if (bulkp->len < 2) {
@@ -739,6 +740,7 @@ static void usbredir_handle_buffered_bulk_in_data(USBRedirDevice *dev,
.stream_id = 0,
.no_transfers = 5,
};
+ assert(dev->endpoint[EP2I(ep)].max_packet_size != 0);
/* Round bytes_per_transfer up to a multiple of max_packet_size */
bpt = 512 + dev->endpoint[EP2I(ep)].max_packet_size - 1;
bpt /= dev->endpoint[EP2I(ep)].max_packet_size;
@@ -793,6 +795,7 @@ static void usbredir_handle_bulk_data(USBRedirDevice *dev, USBPacket *p,
}
if (dev->endpoint[EP2I(ep)].bulk_receiving_enabled) {
+ assert(maxp != 0);
if (size != 0 && (size % maxp) == 0) {
usbredir_handle_buffered_bulk_in_data(dev, p, ep);
return;
@@ -1796,6 +1799,17 @@ static void usbredir_ep_info(void *priv,
if (usbredirparser_peer_has_cap(dev->parser,
usb_redir_cap_ep_info_max_packet_size)) {
dev->endpoint[i].max_packet_size = ep_info->max_packet_size[i];
+ if (ep_info->max_packet_size[i] == 0 &&
+ dev->endpoint[i].bulk_receiving_enabled) {
+ USBPacket *p = dev->endpoint[i].pending_async_packet;
+ usbredir_stop_bulk_receiving(dev, I2EP(i));
+ dev->endpoint[i].bulk_receiving_enabled = 0;
+ if (p != NULL) {
+ dev->endpoint[i].pending_async_packet = NULL;
+ p->status = USB_RET_IOERROR;
+ usb_packet_complete(&dev->dev, p);
+ }
+ }
}
#if USBREDIR_VERSION >= 0x000700
if (usbredirparser_peer_has_cap(dev->parser,
@@ -2156,6 +2170,7 @@ static void usbredir_buffered_bulk_packet(void *priv, uint64_t id,
}
/* Data must be in maxp chunks for buffered_bulk_add_*_data_to_packet */
+ assert(dev->endpoint[EP2I(ep)].max_packet_size != 0);
len = dev->endpoint[EP2I(ep)].max_packet_size;
status = usb_redir_success;
free_on_destroy = NULL;
@@ -2239,6 +2254,15 @@ static int usbredir_post_load(void *priv, int version_id)
usbredir_setup_usb_eps(dev);
usbredir_check_bulk_receiving(dev);
+ for (int i = 0; i < MAX_ENDPOINTS; i++) {
+ if (dev->endpoint[i].bulk_receiving_started &&
+ dev->endpoint[i].max_packet_size == 0) {
+ error_report("usbredir: endpoint %d has bulk receiving started "
+ "with zero max_packet_size", i);
+ return -EINVAL;
+ }
+ }
+
return 0;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PULL 6/7] hw/usb/hcd-xhci: Fix guest-triggerable assert() in xhci_find_stream()
2026-07-20 10:38 [PULL 0/7] USB-related bug fixes Thomas Huth
` (4 preceding siblings ...)
2026-07-20 10:38 ` [PULL 5/7] usbredir: fix infinite loop and SIGFPE with zero max_packet_size Thomas Huth
@ 2026-07-20 10:38 ` Thomas Huth
2026-07-20 10:38 ` [PULL 7/7] hw/usb/hcd-xhci-sysbus: Fix OOB heap access in xhci_sysbus_intr_raise() Thomas Huth
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Thomas Huth @ 2026-07-20 10:38 UTC (permalink / raw)
To: qemu-devel, Stefan Hajnoczi; +Cc: Peter Maydell
From: Thomas Huth <thuth@redhat.com>
The assert() statement in xhci_find_stream() can be triggered by
the guest (see bug tickets #273, #3895 and #3988 on gitlab.com).
Turn it into a qemu_log_mask() instead to fix this problem.
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/273
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Thomas Huth <thuth@redhat.com>
Message-ID: <20260715203357.424556-1-thuth@redhat.com>
---
hw/usb/hcd-xhci.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c
index 47b7484d533..b94249993a1 100644
--- a/hw/usb/hcd-xhci.c
+++ b/hw/usb/hcd-xhci.c
@@ -1009,7 +1009,12 @@ static XHCIStreamContext *xhci_find_stream(XHCIEPContext *epctx,
dma_addr_t base;
uint32_t ctx[2], sct;
- assert(streamid != 0);
+ if (!streamid) {
+ qemu_log_mask(LOG_GUEST_ERROR, "xhci: stream ID is zero\n");
+ *cc_error = CC_INVALID_STREAM_ID_ERROR;
+ return NULL;
+ }
+
if (epctx->lsa) {
if (streamid >= epctx->nr_pstreams) {
*cc_error = CC_INVALID_STREAM_ID_ERROR;
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PULL 7/7] hw/usb/hcd-xhci-sysbus: Fix OOB heap access in xhci_sysbus_intr_raise()
2026-07-20 10:38 [PULL 0/7] USB-related bug fixes Thomas Huth
` (5 preceding siblings ...)
2026-07-20 10:38 ` [PULL 6/7] hw/usb/hcd-xhci: Fix guest-triggerable assert() in xhci_find_stream() Thomas Huth
@ 2026-07-20 10:38 ` Thomas Huth
2026-07-20 17:11 ` [PULL 0/7] USB-related bug fixes Stefan Hajnoczi
2026-07-20 19:08 ` Michael Tokarev
8 siblings, 0 replies; 10+ messages in thread
From: Thomas Huth @ 2026-07-20 10:38 UTC (permalink / raw)
To: qemu-devel, Stefan Hajnoczi; +Cc: Tristan Madani, Peter Maydell
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>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Thomas Huth <thuth@redhat.com>
Message-ID: <20260719061528.15587-1-thuth@redhat.com>
---
hw/usb/hcd-xhci-sysbus.c | 1 +
hw/usb/hcd-xhci.c | 13 +++++++++++++
2 files changed, 14 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 b94249993a1..569386b8cf1 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 nonexistent interrupter %i\n", v);
+ goto out_trace;
+ }
XHCIInterrupter *intr = &xhci->intr[v];
switch (reg & 0x1f) {
case 0x00: /* IMAN */
@@ -3070,6 +3076,7 @@ static uint64_t xhci_runtime_read(void *ptr, hwaddr reg,
}
}
+out_trace:
trace_usb_xhci_runtime_read(reg, ret);
return ret;
}
@@ -3087,7 +3094,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 nonexistent interrupter %i\n", v);
+ return;
+ }
intr = &xhci->intr[v];
switch (reg & 0x1f) {
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PULL 0/7] USB-related bug fixes
2026-07-20 10:38 [PULL 0/7] USB-related bug fixes Thomas Huth
` (6 preceding siblings ...)
2026-07-20 10:38 ` [PULL 7/7] hw/usb/hcd-xhci-sysbus: Fix OOB heap access in xhci_sysbus_intr_raise() Thomas Huth
@ 2026-07-20 17:11 ` Stefan Hajnoczi
2026-07-20 19:08 ` Michael Tokarev
8 siblings, 0 replies; 10+ messages in thread
From: Stefan Hajnoczi @ 2026-07-20 17:11 UTC (permalink / raw)
To: Thomas Huth; +Cc: qemu-devel, Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 116 bytes --]
Applied, thanks.
Please update the changelog at https://wiki.qemu.org/ChangeLog/11.1 for any user-visible changes.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PULL 0/7] USB-related bug fixes
2026-07-20 10:38 [PULL 0/7] USB-related bug fixes Thomas Huth
` (7 preceding siblings ...)
2026-07-20 17:11 ` [PULL 0/7] USB-related bug fixes Stefan Hajnoczi
@ 2026-07-20 19:08 ` Michael Tokarev
8 siblings, 0 replies; 10+ messages in thread
From: Michael Tokarev @ 2026-07-20 19:08 UTC (permalink / raw)
To: Thomas Huth, qemu-devel, Stefan Hajnoczi; +Cc: QEMU Stable
On 7/20/26 13:38, Thomas Huth wrote:
..
> * Fixes for various USB-related bugs
>
> ----------------------------------------------------------------
> Feifan Qian (1):
> hw/usb/xhci: clamp interval exponent to avoid UB shift in xhci_init_epctx()
>
> Marc-André Lureau (2):
> usbredir: fix use-after-free on buffered bulk packet overflow
> usbredir: fix infinite loop and SIGFPE with zero max_packet_size
>
> Thomas Huth (2):
> hw/usb/hcd-xhci: Fix guest-triggerable assert() in xhci_find_stream()
> hw/usb/hcd-xhci-sysbus: Fix OOB heap access in xhci_sysbus_intr_raise()
>
> Xiangfeng Cai (2):
> hw/usb/hcd-xhci-pci: break host link cycle so device_finalize() runs on unplug
> tests/qtest: add xhci-pci unplug finalize regression test
Hi!
It smells like everything in there needs to be picked up for the current
stable qemu series, not only the patches which were Cc'ed qemu-stable@
explicitly.
I'm picking everything up. Please let me know if I shoudln't.
Thanks,
/mjt
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-07-20 19:09 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-20 10:38 [PULL 0/7] USB-related bug fixes Thomas Huth
2026-07-20 10:38 ` [PULL 1/7] hw/usb/xhci: clamp interval exponent to avoid UB shift in xhci_init_epctx() Thomas Huth
2026-07-20 10:38 ` [PULL 2/7] hw/usb/hcd-xhci-pci: break host link cycle so device_finalize() runs on unplug Thomas Huth
2026-07-20 10:38 ` [PULL 3/7] tests/qtest: add xhci-pci unplug finalize regression test Thomas Huth
2026-07-20 10:38 ` [PULL 4/7] usbredir: fix use-after-free on buffered bulk packet overflow Thomas Huth
2026-07-20 10:38 ` [PULL 5/7] usbredir: fix infinite loop and SIGFPE with zero max_packet_size Thomas Huth
2026-07-20 10:38 ` [PULL 6/7] hw/usb/hcd-xhci: Fix guest-triggerable assert() in xhci_find_stream() Thomas Huth
2026-07-20 10:38 ` [PULL 7/7] hw/usb/hcd-xhci-sysbus: Fix OOB heap access in xhci_sysbus_intr_raise() Thomas Huth
2026-07-20 17:11 ` [PULL 0/7] USB-related bug fixes Stefan Hajnoczi
2026-07-20 19:08 ` Michael Tokarev
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.