[PATCH] usb: storage: shuttle_usbat: clamp device-reported read length
From: Haofeng Li
Date: Sat Aug 22 2026 - 15:47:57 EST
For device-to-host commands other than READ_10 and READ_CD,
usbat_hp8200e_transport() asks the device how many bytes of response it
is about to send by reading the ATA LBA_ME (cylL) and LBA_HI (cylH)
registers, and then uses that device-supplied value, unvalidated, as the
bulk-in length for usbat_read_block().
The value is never checked against scsi_bufflen(srb). A malicious device
answering 0xFF turns a 36-byte INQUIRY into a 255-byte read that runs 219
bytes past the command buffer, and for a command buffer above 0x100 bytes
the 16-bit form can claim up to 0xFFFF bytes. The length flows through
usbat_bulk_read() -> usb_stor_bulk_transfer_sg() -> usb_sg_init(), which
stores it in urb->transfer_buffer_length without reference to the
scatterlist it covers. xHCI paces the transfer on that field while
walking the SG list, and once the (shorter) list is exhausted it keeps
enqueueing TRBs past its end, so the device's data is DMAed outside the
SCSI buffer.
Attack chain (the attacker only supplies a malicious USB device; the SCSI
commands involved are issued by the kernel itself during usb-storage
probing, so no local privileges and no race are required):
malicious device identifies as 03f0:0207 / 03f0:0307 (HP USBAT)
-> unusual_usbat.h: USB_SC_8070 / USB_PR_USBAT, init_usbat_cd()
-> queuecommand(INQUIRY), scsi_bufflen = 36
-> usbat_hp8200e_transport(): DMA_FROM_DEVICE, not READ_10
-> device answers the LBA_ME / LBA_HI register reads with 0xFF
-> len = 0xFF (up to 0xFFFF for scsi_bufflen > 0xFF) replaces 36
-> usbat_read_block() -> usb_stor_bulk_transfer_sg()
submits a 255-byte bulk-in over a 36-byte scatterlist
-> xhci_queue_bulk_tx() keeps queueing TRBs while
enqd_len < full_len, past the end of the SG list
-> out-of-bounds DMA write into kernel memory
Reproduced end-to-end with a FunctionFS device emulator standing in for
the HP 8200e: a kprobe on usb_stor_bulk_transfer_sg records length = 0xff
aimed at the 36-byte INQUIRY buffer while the device log shows the forged
register answers, and the host controller WARNs in dummy_perform_transfer
with the scatterlist exhausted and 219 bytes unplaced (dummy_hcd stops at
the SG end; the out-of-bounds DMA itself needs xHCI hardware and follows
from the TRB loop's enqd_len < full_len condition).
Cap the transfer length at scsi_bufflen(srb). A well-behaved device
reports the actual response length, which never exceeds the command
buffer, so only malicious devices are affected.
Signed-off-by: Haofeng Li <lihaofeng@xxxxxxxxxx>
Assisted-by: opencode:deepseek-v4-flash-free
---
drivers/usb/storage/shuttle_usbat.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/usb/storage/shuttle_usbat.c b/drivers/usb/storage/shuttle_usbat.c
index 7e5424268c73..4bcb7e684d7f 100644
--- a/drivers/usb/storage/shuttle_usbat.c
+++ b/drivers/usb/storage/shuttle_usbat.c
@@ -1666,6 +1666,9 @@ static int usbat_hp8200e_transport(struct scsi_cmnd *srb, struct us_data *us)
else
len = *status;
+ /* Device-controlled; clamp to the command buffer to avoid OOB DMA */
+ if (len > scsi_bufflen(srb))
+ len = scsi_bufflen(srb);
result = usbat_read_block(us, scsi_sglist(srb), len,
scsi_sg_count(srb));
--
2.25.1