From 44969e9a46ff678df0215ba0caab5590675ff8ea Mon Sep 17 00:00:00 2001 From: Haofeng Li Date: Wed, 26 Aug 2026 21:34:01 +0800 Subject: usb: dwc2: truncate PIO RX FIFO reads to the request's remaining space dwc2_hsotg_rx_data() reads the OUT packet length from the RX FIFO status (GRXSTS.BYTECNT, host-controlled) and, when it exceeds the remaining space of the active gadget request (max_req = req.length - req.actual), merely fires WARN_ON_ONCE(1) and then stores the whole packet into hs_req->req.buf + req->req.actual via dwc2_readl_rep(): req.actual += size; dwc2_readl_rep(hsotg, EPFIFO(ep_idx), req.buf + actual, DIV_ROUND_UP(size, 4)); A packet larger than the request buffer therefore over-writes up to maxpacket bytes past it. The rounded-up word read additionally emits up to 3 bytes past the logical end even when the size does not exceed the remaining space (short-request boundary). Attack chain (USB peripheral/gadget mode, PIO only; the attacker is the USB host): malicious host -> OUT packet on epN -> RX FIFO interrupt -> dwc2_hsotg_handle_rx() (GRXSTS_PKTSTS_OUTRX / SETUPRX) -> dwc2_hsotg_rx_data(hsotg, epnum, BYTECNT=64) with the queued request having req.length=8, req.actual=7 (1 byte left) -> "to_read(64) > max_req(1)" -> WARN_ON_ONCE(1) only -> dwc2_readl_rep() writes 16 words at req.buf+7 -> 63 bytes past the 8-byte request buffer Reproduced (kernel 7.2.0+, KASAN/SLUB debug): calling the real dwc2_hsotg_rx_data() with the above state (request buffer 8 bytes, actual 7, size 64) triggers the WARN and leaves req.actual = 71 (the 64 bytes are accrued regardless), and the SLUB redzone immediately after the 8-byte object plus neighbouring slab objects are overwritten with FIFO content (byte 7 changes from a marker to FIFO data) - the out-of-bounds write is visible byte-for-byte. The FIFO read is done by raw 32-bit I/O words, which generic KASAN does not instrument, so the redzone/neighbour clobber is the forensic evidence. Signed-off-by: Haofeng Li Assisted-by: opencode:deepseek-v4-flash-free Link: https://patch.msgid.link/20260826133401.3796639-1-lihaofeng@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/gadget.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 43947673696d..b3976876c06b 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2298,6 +2298,7 @@ static void dwc2_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) int to_read; int max_req; int read_ptr; + u32 drain; if (!hs_req) { u32 epctl = dwc2_readl(hsotg, DOEPCTL(ep_idx)); @@ -2317,30 +2318,46 @@ static void dwc2_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) to_read = size; read_ptr = hs_req->req.actual; max_req = hs_req->req.length - read_ptr; + drain = 0; dev_dbg(hsotg->dev, "%s: read %d/%d, done %d/%d\n", __func__, to_read, max_req, read_ptr, hs_req->req.length); if (to_read > max_req) { /* - * more data appeared than we where willing - * to deal with in this request. + * More data appeared than we were willing to deal with in + * this request. Keep only what fits in the request buffer + * and discard the rest from the FIFO, instead of overwriting + * bytes past the request buffer (a 64-byte packet into a + * request with one byte left used to over-write 63 bytes past + * its end). */ - - /* currently we don't deal this */ - WARN_ON_ONCE(1); + dev_dbg(hsotg->dev, "%s: packet %d > request space %d, dropping %d\n", + __func__, to_read, max_req, to_read - max_req); + drain = DIV_ROUND_UP(to_read - max_req, 4); + to_read = max_req; } hs_ep->total_data += to_read; hs_req->req.actual += to_read; - to_read = DIV_ROUND_UP(to_read, 4); /* - * note, we might over-write the buffer end by 3 bytes depending on - * alignment of the data. + * Copy word-at-a-time so the request buffer is exactly filled and + * never over-run by the 4-byte rounding of the previous FIFO bulk + * read (which could also emit up to 3 bytes past the buffer end). */ - dwc2_readl_rep(hsotg, EPFIFO(ep_idx), - hs_req->req.buf + read_ptr, to_read); + while (to_read > 0) { + u32 word = dwc2_readl(hsotg, EPFIFO(ep_idx)); + unsigned int chunk = min_t(unsigned int, to_read, 4); + + memcpy(hs_req->req.buf + read_ptr, &word, chunk); + read_ptr += chunk; + to_read -= chunk; + } + + /* drain the discarded bytes so the next FIFO event is a fresh packet */ + while (drain-- > 0) + (void)dwc2_readl(hsotg, EPFIFO(ep_idx)); } /** -- cgit v1.2.3