[Bug 297053] xhci(4): TRB-array memory corruption and kernel panic for bulk frames larger than 64 KiB
[email protected] Sat, 25 Jul 2026 20:31:17 +0000
| Newsgroups | gmane.os.freebsd.bugs |
|---|---|
| Message-ID | <[email protected]/bugzilla/> |
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=297053
Bug ID: 297053
Summary: xhci(4): TRB-array memory corruption and kernel panic
for bulk frames larger than 64 KiB
Product: Base System
Version: 16.0-CURRENT
Hardware: amd64
OS: Any
Status: New
Severity: Affects Some People
Priority: ---
Component: kern
Assignee: [email protected]
Reporter: [email protected]
A regression introduced by commit
e0b235ecd4fa9a67578be68057dbcad797f96397 ("xhci: Refactor
xhci_generic_setup code") can make xhci_setup_normal_trbs() write beyond
struct xhci_td::td_trb[] when a bulk or interrupt transfer contains a
frame larger than XHCI_TD_PAYLOAD_MAX (65536 bytes).
The problem is still present in main as of 2026-07-25.
This is a bug in the vanilla xHCI implementation rather than a
device-driver-specific buffer-size violation.
The stock USB framework permits transfers larger than 64 KiB:
* struct usb_config describes bufsize as the total pipe-buffer size.
* The stock ugen implementation permits receive and transmit buffer
sizes up to 256 KiB.
* ugen_ctrl_read_callback() can submit the configured maximum length as
one USB frame.
* xhci_xfer_setup() sets hc_max_frame_size to 65536 and allocates
additional struct xhci_td objects according to max_data_length divided
by max_hc_frame_size.
This shows that the host-controller driver is expected to divide a large
frame among the additional TDs it allocated.
After the refactoring, xhci_setup_bulk() assigns only one xhci_td to each
USB frame. It passes the complete frame length to
xhci_setup_normal_trbs(). That function repeatedly increments i and
writes td->td_trb[i] until the entire frame has been described, without
checking the array bound or advancing to td->obj_next.
On amd64 with 4 KiB pages, a 128 KiB frame requires approximately 32
data TRBs. struct xhci_td only has enough TRB storage for a payload of at
most 64 KiB, plus boundary and link bookkeeping. The writes consequently
continue into fields following td_trb[], including page_cache.
Observed panic:
panic: page fault
usb_pc_cpu_flush(pc=0x104e8a000)
xhci_setup_normal_trbs(cache=..., offset=0, len=131072,
mps=1024, td=..., td_next=NULL, ...)
xhci_setup_bulk()
xhci_setup_generic_chain()
xhci_device_generic_enter()
usbd_pipe_enter()
usb_command_wrapper()
usbd_transfer_submit()
KGDB showed i == 32 in xhci_setup_normal_trbs(). The invalid page_cache
value passed to usb_pc_cpu_flush() is consistent with the preceding TRB
writes corrupting the remainder of struct xhci_td.
Code-level reproduction condition:
1. Use a USB device with a bulk endpoint on an xHCI controller.
2. Configure a one-frame bulk transfer with a 128 KiB buffer.
3. Set frame 0's length to 131072 bytes.
4. Submit the transfer.
A minimal ugen/USB_FS client can construct this using only the interfaces
provided by the vanilla FreeBSD USB stack. Drivers that happen to use
buffers no larger than 64 KiB merely leave the defect dormant.
Expected result:
xhci(4) should divide a large Normal-TRB frame among the additional
struct xhci_td objects allocated by xhci_xfer_setup(), or reject the
transfer cleanly during setup.
Actual result:
xhci_setup_normal_trbs() writes beyond td_trb[], corrupts kernel memory,
and can panic in usb_pc_cpu_flush().
The implementation before e0b235ecd4fa divided large frames according
to max_hc_frame_size and consumed multiple TDs. The proper repair would
restore that behavior for Normal TRBs. A bounds assertion before every
td_trb[] write would also provide useful defensive protection.
Regression commit:
https://github.com/freebsd/freebsd-src/commit/e0b235ecd4fa9a67578be68057dbcad797f96397
Associated review:
https://reviews.freebsd.org/D57130
--
You are receiving this mail because:
You are the assignee for the bug.