[PATCH 2/2] usb: gadget: f_tcm: validate UAS command IU length before parsing

From: Radhey Shyam Pandey

Date: Sat Aug 08 2026 - 09:20:51 EST


usbg_submit_command() parsed Command and Task Management IUs from a
buffer sized to ep_cmd->maxpacket without checking req->actual. A host
could supply a short IU or inflated cmd_iu->len and trigger an
out-of-bounds read when copying the CDB. Validate the received length
before parsing, matching the BOT path, and re-queue the command
endpoint request when validation fails.

Assisted-by: claude-mythos-preview-high
Signed-off-by: Radhey Shyam Pandey <radhey.shyam.pandey@xxxxxxx>
---
drivers/usb/gadget/function/f_tcm.c | 33 ++++++++++++++++++++++-------
1 file changed, 25 insertions(+), 8 deletions(-)

diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c
index 565ad1934755..4b30b23ddac0 100644
--- a/drivers/usb/gadget/function/f_tcm.c
+++ b/drivers/usb/gadget/function/f_tcm.c
@@ -862,6 +862,7 @@ static int usbg_submit_command(struct f_uas *, struct usb_request *);
static void uasp_cmd_complete(struct usb_ep *ep, struct usb_request *req)
{
struct f_uas *fu = req->context;
+ int ret;

if (req->status == -ESHUTDOWN)
return;
@@ -871,7 +872,9 @@ static void uasp_cmd_complete(struct usb_ep *ep, struct usb_request *req)
return;
}

- usbg_submit_command(fu, req);
+ ret = usbg_submit_command(fu, req);
+ if (ret)
+ usb_ep_queue(fu->ep_cmd, req, GFP_ATOMIC);
}

static int uasp_alloc_stream_res(struct f_uas *fu, struct uas_stream *stream)
@@ -1382,6 +1385,27 @@ static int usbg_submit_command(struct f_uas *fu, struct usb_request *req)
return -EINVAL;
}

+ cmd_iu = (struct command_iu *)iu;
+
+ if (req->actual < offsetof(struct command_iu, cdb)) {
+ pr_err("Wrong length for UAS command IU\n");
+ return -EINVAL;
+ }
+
+ if (iu->iu_id == IU_ID_COMMAND) {
+ cmd_len = (cmd_iu->len & ~0x3) + 16;
+ if (cmd_len > USBG_MAX_CMD ||
+ req->actual < offsetof(struct command_iu, cdb) + cmd_len) {
+ pr_err("Wrong length for UAS command IU\n");
+ return -EINVAL;
+ }
+ } else if (iu->iu_id == IU_ID_TASK_MGMT) {
+ if (req->actual < sizeof(struct task_mgmt_iu)) {
+ pr_err("Wrong length for UAS task management IU\n");
+ return -EINVAL;
+ }
+ }
+
scsi_tag = be16_to_cpup(&iu->tag);
cmd = usbg_get_cmd(fu, tv_nexus, scsi_tag);
if (IS_ERR(cmd)) {
@@ -1398,8 +1422,6 @@ static int usbg_submit_command(struct f_uas *fu, struct usb_request *req)
cmd->flags = 0;
cmd->data_len = 0;

- cmd_iu = (struct command_iu *)iu;
-
/* Command and Task Management IUs share the same LUN offset */
cmd->unpacked_lun = scsilun_to_int(&cmd_iu->lun);

@@ -1434,11 +1456,6 @@ static int usbg_submit_command(struct f_uas *fu, struct usb_request *req)
}

cmd_len = (cmd_iu->len & ~0x3) + 16;
- if (cmd_len > USBG_MAX_CMD) {
- target_free_tag(tv_nexus->tvn_se_sess, &cmd->se_cmd);
- hash_del(&stream->node);
- return -EINVAL;
- }
memcpy(cmd->cmd_buf, cmd_iu->cdb, cmd_len);

switch (cmd_iu->prio_attr & 0x7) {
--
2.44.4