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

Radhey Shyam Pandey <[email protected]>
Newsgroups org.kernel.vger.linux-usb,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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 <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.