Repository navigation
mt7612u: a failed bulk submit reported the previous transfer's length - #512
Conversation
sync_xfer_new() recycles libusb transfers from a per-device pool, and a recycled transfer keeps its last actual_length: libusb writes the field only when a transfer completes, never when libusb_submit_transfer() fails. submit_and_wait() returns a submit failure directly and sync_bulk() then reads actual_length anyway - so once the device has left the bus, an IN read reported the previous transfer's length and memcpy'd that many bytes from its len-byte bounce buffer into the caller's buffer. Reproduced on an MT7612U (8-1): one 1400 B frame through mt7612u_tx() (sync bulk OUT, 1428 B on the wire), the adapter deauthorized in sysfs, then mt_bulk() on EP 0x85 with a 64 B length into a sentinel-filled buffer. Upstream master: rc LIBUSB_ERROR_IO, n = 1428, 1364 bytes written past the 64 B buffer. With this change: rc LIBUSB_ERROR_IO, n = 0, none. The worst real case is a TX of up to MT_TX_BUF_MAX (4096 B), an unplug, then an MCU reply read into its 1024 B stack buffer. sync_control() reads actual_length only on rc == 0 and was not affected; the async rings use their own transfers and read it only in completion callbacks. A physical unplug fails the submit with NO_DEVICE rather than IO; both take the same path. Introduced with the pool in b9b80de (OpenIPC#444). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011dReg1L6xtBhf95NpAeuge
PR Summary by Qodomt7612u: Clear stale transfer length before synchronous reuse
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewTip of the day💡 Did you know, you can route each severity your way: inline, summary, both, or drop |
josephnef
left a comment
There was a problem hiding this comment.
Review: PR 512
Verdict: approve. The diagnosis holds against the code: sync_bulk() reads actual_length on every return except the abandon path, submit_and_wait() returns a failed submit straight through, and a pooled transfer carries the previous completion's length. Zeroing it at hand-out in sync_xfer_new() is the right place, since both sync helpers go through it and nothing else pools transfers.
Checked here:
- Built the branch with
-DDEVOURER_MT7612U=ON: no warnings insrc/mt7612u/, ctest 86/86. sync_control()is unaffected as stated (it reads the field only whenrc == 0).
Not checked: the hardware reproducer. The bench's MT7612U (slot #17) is unplugged today, so the deauthorize-then-read case is the author's measurement only.
Gate: qodo-gate is red because Qodo posted its "no issues found" as an issue comment, not a review object, so the check sees no review. That is the gate's known shape for a clean verdict; the skip-qodo-gate label is the way through.
Problem
sync_xfer_new()recycles libusb transfers from a per-device pool (since #444,b9b80de). A recycled transfer keeps its lastactual_length, because libusb writes that field only when a transfer completes (usbi_handle_transfer_completion). It never writes it whenlibusb_submit_transfer()fails.submit_and_wait()returns a submit failure directly, andsync_bulk()then readsactual_lengthanyway. So once the device has left the bus, an IN read reports the previous transfer's length andmemcpys that many bytes from itslen-byte bounce buffer into the caller's buffer. That is a heap over-read plus an overflow of the caller's buffer.Fix
One line:
sync_xfer_new()zeroesactual_lengthon the transfer it hands out. Both sync helpers go through it.Evidence
Reproduced on hardware (MT7612U, USB 3). The reproducer:
mt7612u_tx()(a sync bulk OUT, 1428 B on the wire);mt_bulk()on EP0x85with a 64 B length into a sentinel-filled buffer.nreported34a9e6fLIBUSB_ERROR_IOLIBUSB_ERROR_IOWorst real case: a TX of up to
MT_TX_BUF_MAX(4096 B), then an unplug, then an MCU reply read into its 1024 B stack buffer (mcu.cpp).Scope (from an adversarial review before opening, checked against libusb 1.0.29's
io.c):libusb_alloc_transfercallocs, so a fresh transfer starts at 0.libusb_submit_transferresets only the internaltransferredcount. After a successful submit, every completion (cancel, timeout and disconnect included) writesactual_length, never abovelength. So only a failed submit leaves it stale, and a clamp insync_bulk()would add nothing.sync_control()readsactual_lengthonly whenrc == 0and was not affected.UsbTransportresets its slot's length on hand-out.Limits:
LIBUSB_ERROR_IO. A physical unplug givesLIBUSB_ERROR_NO_DEVICE, which takes the same path but was not run.tests/: it needs the internal header and root.ctestpasses 86/86 with-DDEVOURER_MT7612U=ON, and the fixed build brought the adapter up, started it and transmitted normally.🤖 Generated with Claude Code
https://claude.ai/code/session_011dReg1L6xtBhf95NpAeuge