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>
---
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
On 16/07/2026 16.11, marcandre.lureau@redhat.com wrote: > 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> > --- > hw/usb/redirect.c | 24 ++++++++++++++++++++++++ > 1 file changed, 24 insertions(+) Reviewed-by: Thomas Huth <thuth@redhat.com>
On 20/07/2026 09.38, Thomas Huth wrote: > On 16/07/2026 16.11, marcandre.lureau@redhat.com wrote: >> 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> >> --- >> hw/usb/redirect.c | 24 ++++++++++++++++++++++++ >> 1 file changed, 24 insertions(+) > Reviewed-by: Thomas Huth <thuth@redhat.com> I'm currently assembling a pull request with some other USB fixes, so I'm going to pick this up for that PR unless someone else wants to take this patch. Thomas
© 2016 - 2026 Red Hat, Inc.