[PATCH] usbredir: fix infinite loop and SIGFPE with zero max_packet_size

marcandre.lureau@redhat.com posted 1 patch 1 week, 2 days ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20260716141107.3597076-1-marcandre.lureau@redhat.com
hw/usb/redirect.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
[PATCH] usbredir: fix infinite loop and SIGFPE with zero max_packet_size
Posted by marcandre.lureau@redhat.com 1 week, 2 days ago
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


Re: [PATCH] usbredir: fix infinite loop and SIGFPE with zero max_packet_size
Posted by Thomas Huth 5 days, 7 hours ago
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>


Re: [PATCH] usbredir: fix infinite loop and SIGFPE with zero max_packet_size
Posted by Thomas Huth 5 days, 7 hours ago
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