@samitouri / QOSamiQemu / commits / a002485bfe

usbredir: fix infinite loop and SIGFPE with zero max_packet_size

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>

Marc-André Lureau committed Jul 16, 2026 at 18:11 UTC a002485bfef184f04a58adfd2a848c19f583f7d0
1 file changed +24
hw/usb/redirect.c
+24
@@ -690,6 +690,7 @@ static void usbredir_buffered_bulk_in_complete_ftdi(USBRedirDevice *dev,
690 struct buf_packet *bulkp;
691 int count;
692
693 + assert(maxp != 0);
694 while ((bulkp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq)) &&
695 p->actual_length < p->iov.size && p->status == USB_RET_SUCCESS) {
696 if (bulkp->len < 2) {
@@ -739,6 +740,7 @@ static void usbredir_handle_buffered_bulk_in_data(USBRedirDevice *dev,
740 .stream_id = 0,
741 .no_transfers = 5,
742 };
743 + assert(dev->endpoint[EP2I(ep)].max_packet_size != 0);
744 /* Round bytes_per_transfer up to a multiple of max_packet_size */
745 bpt = 512 + dev->endpoint[EP2I(ep)].max_packet_size - 1;
746 bpt /= dev->endpoint[EP2I(ep)].max_packet_size;
@@ -793,6 +795,7 @@ static void usbredir_handle_bulk_data(USBRedirDevice *dev, USBPacket *p,
795 }
796
797 if (dev->endpoint[EP2I(ep)].bulk_receiving_enabled) {
798 + assert(maxp != 0);
799 if (size != 0 && (size % maxp) == 0) {
800 usbredir_handle_buffered_bulk_in_data(dev, p, ep);
801 return;
@@ -1796,6 +1799,17 @@ static void usbredir_ep_info(void *priv,
1799 if (usbredirparser_peer_has_cap(dev->parser,
1800 usb_redir_cap_ep_info_max_packet_size)) {
1801 dev->endpoint[i].max_packet_size = ep_info->max_packet_size[i];
1802 + if (ep_info->max_packet_size[i] == 0 &&
1803 + dev->endpoint[i].bulk_receiving_enabled) {
1804 + USBPacket *p = dev->endpoint[i].pending_async_packet;
1805 + usbredir_stop_bulk_receiving(dev, I2EP(i));
1806 + dev->endpoint[i].bulk_receiving_enabled = 0;
1807 + if (p != NULL) {
1808 + dev->endpoint[i].pending_async_packet = NULL;
1809 + p->status = USB_RET_IOERROR;
1810 + usb_packet_complete(&dev->dev, p);
1811 + }
1812 + }
1813 }
1814 #if USBREDIR_VERSION >= 0x000700
1815 if (usbredirparser_peer_has_cap(dev->parser,
@@ -2156,6 +2170,7 @@ static void usbredir_buffered_bulk_packet(void *priv, uint64_t id,
2170 }
2171
2172 /* Data must be in maxp chunks for buffered_bulk_add_*_data_to_packet */
2173 + assert(dev->endpoint[EP2I(ep)].max_packet_size != 0);
2174 len = dev->endpoint[EP2I(ep)].max_packet_size;
2175 status = usb_redir_success;
2176 free_on_destroy = NULL;
@@ -2239,6 +2254,15 @@ static int usbredir_post_load(void *priv, int version_id)
2254 usbredir_setup_usb_eps(dev);
2255 usbredir_check_bulk_receiving(dev);
2256
2257 + for (int i = 0; i < MAX_ENDPOINTS; i++) {
2258 + if (dev->endpoint[i].bulk_receiving_started &&
2259 + dev->endpoint[i].max_packet_size == 0) {
2260 + error_report("usbredir: endpoint %d has bulk receiving started "
2261 + "with zero max_packet_size", i);
2262 + return -EINVAL;
2263 + }
2264 + }
2265 +
2266 return 0;
2267 }
2268