[PATCH v2] usb: gadget: f_tcm: reject a short write data-out transfer
From: Yehyeong Lee
Date: Mon Oct 12 2026 - 20:59:26 EST
usbg_data_write_cmpl() commits the WRITE buffer to the backend as soon as
the OUT request completes successfully, looking only at req->status and
never at req->actual. A host that declares an 8192-byte WRITE but ends the
data-out after 4096 bytes still reaches target_execute_cmd() with the full
length.
The buffer is not zeroed (transport_generic_new_cmd() leaves zero_flag
clear for a data CDB), so the bytes that never arrived keep the previous
page contents, which the backend writes to the device for the host to read
back. The target core refuses a WRITE whose declared length is wrong, but
it does not see a data phase that simply falls short.
Fail the command when the data-out is short. The OUT data phase is over at
this point, so clear cmd->data_len too: otherwise the BOT status phase
treats it as outstanding WRITE data and queues another OUT request to drain
a host that has already stopped, wedging the bulk pipe. Rate-limit the
failure message, which a host can now trigger at will.
There is no oops; poisoning the buffer and ending an 8192-byte write at
4096 bytes leaves 4096 stale bytes committed before the change, and after
it the command is aborted with nothing committed.
Fixes: c52661d60f63 ("usb-gadget: Initial merge of target module for UASP + BOT")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yehyeong Lee <yhlee@xxxxxxxxxxxxxxxxxx>
---
v2: on a short data-out also clear cmd->data_len so the BOT status phase
does not queue an OUT drain for a host that already stopped (would
wedge the bulk pipe, reported by Sashiko); rate-limit the failure log.
drivers/usb/gadget/function/f_tcm.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c
index 9e6d4f39900a2..cbe64f934724e 100644
--- a/drivers/usb/gadget/function/f_tcm.c
+++ b/drivers/usb/gadget/function/f_tcm.c
@@ -1090,8 +1090,14 @@ static void usbg_data_write_cmpl(struct usb_ep *ep, struct usb_request *req)
return;
}
- if (req->status) {
- pr_err("%s() state %d transfer failed\n", __func__, cmd->state);
+ /* a short data-out left the buffer unwritten; don't commit stale data */
+ if (req->status || req->actual != req->length) {
+ struct usb_gadget *gadget = fuas_to_gadget(cmd->fu);
+
+ /* the OUT data phase is over; nothing left for the status phase to drain */
+ cmd->data_len = 0;
+ dev_err_ratelimited(&gadget->dev, "%s() state %d transfer failed\n",
+ __func__, cmd->state);
goto cleanup;
}
--
2.43.0