@samitouri / QOSamiQemu / commits / 9bf52d056a

usbredir: fix use-after-free on buffered bulk packet overflow

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>

Marc-André Lureau committed Jul 14, 2026 at 22:57 UTC 9bf52d056a03cd3768caccbc4c88fcc34be26c28
1 file changed +17 -1
hw/usb/redirect.c
+17 -1
@@ -2138,7 +2138,7 @@ static void usbredir_buffered_bulk_packet(void *priv, uint64_t id,
2138 USBRedirDevice *dev = priv;
2139 uint8_t status, ep = buffered_bulk_packet->endpoint;
2140 void *free_on_destroy;
2141 - int i, len;
2141 + int i, len, queued = 0;
2142
2143 DPRINTF("buffered-bulk-in status %d ep %02X len %d id %"PRIu64"\n",
2144 buffered_bulk_packet->status, ep, data_len, id);
@@ -2169,8 +2169,24 @@ static void usbredir_buffered_bulk_packet(void *priv, uint64_t id,
2169 /* bufp_alloc also adds the packet to the ep queue */
2170 r = bufp_alloc(dev, data + i, len, status, ep, free_on_destroy);
2171 if (r) {
2172 + /*
2173 + * Earlier fragments from this packet are in the queue
2174 + * with interior pointers into data. If the dropped
2175 + * fragment was the final one, bufp_alloc already freed
2176 + * data so those pointers are dangling. Remove them.
2177 + */
2178 + while (queued > 0) {
2179 + struct buf_packet *bufp;
2180 + bufp = QTAILQ_LAST(&dev->endpoint[EP2I(ep)].bufpq);
2181 + bufp_free(dev, bufp, ep);
2182 + queued--;
2183 + }
2184 + if (!free_on_destroy) {
2185 + free(data);
2186 + }
2187 break;
2188 }
2189 + queued++;
2190 }
2191
2192 if (dev->endpoint[EP2I(ep)].pending_async_packet) {