[PATCH v2] nfc: nfcmrvl: validate the firmware download length chain

Pengpeng Hou <[email protected]>
Newsgroups dev.linux.lists.oe-linux-nfc,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
The firmware download state machine reads its fixed firmware header before
proving that the image contains it.  It also accepts a 16-bit
helper-requested chunk length, narrows it to the 8-bit NCI payload field,
and copies that many bytes from a firmware offset without binding the
complete chain to fw->size.

Validate the fixed firmware header and both image offsets, require the
exact NCI helper-command frame and payload length, and reject chunks that
exceed either the NCI payload limit or the remaining firmware bytes.  Keep
offsets and chunk lengths in size_t so the checks dominate allocation,
copy and advancement without an intermediate narrowing.

Fixes: 3194c6870158 ("NFC: nfcmrvl: add firmware download support")

Assisted-by: Codex:gpt-5
Signed-off-by: Pengpeng Hou <[email protected]>
---
Changes since v1:
https://lore.kernel.org/all/[email protected]/
- validate the firmware header and both initial image offsets
- require the exact NCI helper-command frame and payload length
- reject chunks that exceed U8_MAX or the remaining firmware image
- keep chunk lengths and offsets wide through allocation and copying
- clear download state through one release path

This revision was not built or tested with nfcmrvl hardware, malformed
helper frames, or malformed firmware images.

 drivers/nfc/nfcmrvl/fw_dnld.c | 52 +++++++++++++++++++++++++++++------
 drivers/nfc/nfcmrvl/fw_dnld.h |  4 +--
 2 files changed, 45 insertions(+), 11 deletions(-)

diff --git a/drivers/nfc/nfcmrvl/fw_dnld.c b/drivers/nfc/nfcmrvl/fw_dnld.c
index 2b8f401d8fd7..d97e7491e22c 100644
--- a/drivers/nfc/nfcmrvl/fw_dnld.c
+++ b/drivers/nfc/nfcmrvl/fw_dnld.c
@@ -238,6 +238,8 @@ static int process_state_open_lc(struct nfcmrvl_private *priv,
 	    memcmp(skb->data, nci_pattern_core_conn_create_rsp,
 		   sizeof(nci_pattern_core_conn_create_rsp)))
 		return -EINVAL;
+	if (priv->fw_dnld.binary_config->offset > priv->fw_dnld.fw->size)
+		return -EINVAL;
 
 	priv->fw_dnld.state = STATE_FW_DNLD;
 	priv->fw_dnld.substate = SUBSTATE_WAIT_COMMAND;
@@ -263,9 +265,19 @@ static int process_state_fw_dnld(struct nfcmrvl_private *priv,
 		 * B8..N: payload
 		 */
 
-		/* Remove NCI HDR */
-		skb_pull(skb, 3);
-		if (skb->data[0] != HELPER_CMD_PACKET_FORMAT || skb->len != 5) {
+		if (skb->len != NCI_DATA_HDR_SIZE + 5) {
+			nfc_err(priv->dev, "bad command");
+			return -EINVAL;
+		}
+
+		if (nci_plen(skb->data) != 5) {
+			nfc_err(priv->dev, "bad command length");
+			return -EINVAL;
+		}
+
+		/* Remove NCI header */
+		skb_pull(skb, NCI_DATA_HDR_SIZE);
+		if (skb->data[0] != HELPER_CMD_PACKET_FORMAT) {
 			nfc_err(priv->dev, "bad command");
 			return -EINVAL;
 		}
@@ -273,7 +285,6 @@ static int process_state_fw_dnld(struct nfcmrvl_private *priv,
 		len = get_unaligned_le16(skb->data);
 		skb_pull(skb, 2);
 		comp_len = get_unaligned_le16(skb->data);
-		memcpy(&comp_len, skb->data, 2);
 		skb_pull(skb, 2);
 		if (((~len) & 0xFFFF) != comp_len) {
 			nfc_err(priv->dev, "bad len complement: %x %x %x",
@@ -286,6 +297,12 @@ static int process_state_fw_dnld(struct nfcmrvl_private *priv,
 			priv->fw_dnld.substate = SUBSTATE_WAIT_NACK_CREDIT;
 			return 0;
 		}
+		if (len > NCI_MAX_PAYLOAD_SIZE ||
+		    priv->fw_dnld.offset > priv->fw_dnld.fw->size ||
+		    len > priv->fw_dnld.fw->size - priv->fw_dnld.offset) {
+			nfc_err(priv->dev, "invalid firmware chunk: %u", len);
+			return -EINVAL;
+		}
 		priv->fw_dnld.chunk_len = len;
 		out_skb = alloc_lc_skb(priv, 1);
 		if (!out_skb)
@@ -310,7 +327,8 @@ static int process_state_fw_dnld(struct nfcmrvl_private *priv,
 			nci_send_cmd(priv->ndev, NCI_OP_CORE_CONN_CLOSE_CMD,
 				     1, &conn_id);
 		} else {
-			out_skb = alloc_lc_skb(priv, priv->fw_dnld.chunk_len);
+			out_skb = alloc_lc_skb(priv,
+					       (u8)priv->fw_dnld.chunk_len);
 			if (!out_skb)
 				return -ENOMEM;
 			skb_put_data(out_skb,
@@ -507,16 +525,25 @@ int nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name)
 		return -ENOENT;
 	}
 
-	fw_dnld->header = (const struct nfcmrvl_fw *) priv->fw_dnld.fw->data;
+	if (fw_dnld->fw->size < sizeof(struct nfcmrvl_fw)) {
+		nfc_err(priv->dev, "firmware binary %s is too small",
+			firmware_name);
+		goto release_firmware;
+	}
+	fw_dnld->header = (const struct nfcmrvl_fw *)fw_dnld->fw->data;
 
 	if (fw_dnld->header->magic != NFCMRVL_FW_MAGIC ||
 	    fw_dnld->header->phy != priv->phy) {
 		nfc_err(priv->dev, "bad firmware binary %s magic=0x%x phy=%d",
 			firmware_name, fw_dnld->header->magic,
 			fw_dnld->header->phy);
-		release_firmware(fw_dnld->fw);
-		fw_dnld->header = NULL;
-		return -EINVAL;
+		goto release_firmware;
+	}
+	if (fw_dnld->header->helper.offset > fw_dnld->fw->size ||
+	    fw_dnld->header->firmware.offset > fw_dnld->fw->size) {
+		nfc_err(priv->dev, "firmware binary %s has invalid offsets",
+			firmware_name);
+		goto release_firmware;
 	}
 
 	if (fw_dnld->header->helper.offset != 0) {
@@ -546,4 +573,11 @@ int nfcmrvl_fw_dnld_start(struct nci_dev *ndev, const char *firmware_name)
 	/* Now wait for CORE_RESET_NTF or timeout */
 
 	return 0;
+
+release_firmware:
+	release_firmware(fw_dnld->fw);
+	fw_dnld->fw = NULL;
+	fw_dnld->header = NULL;
+	fw_dnld->binary_config = NULL;
+	return -EINVAL;
 }
diff --git a/drivers/nfc/nfcmrvl/fw_dnld.h b/drivers/nfc/nfcmrvl/fw_dnld.h
index 7c4d91b01910..68f8cf8eee71 100644
--- a/drivers/nfc/nfcmrvl/fw_dnld.h
+++ b/drivers/nfc/nfcmrvl/fw_dnld.h
@@ -67,8 +67,8 @@ struct nfcmrvl_fw_dnld {
 
 	int state;
 	int substate;
-	int offset;
-	int chunk_len;
+	size_t offset;
+	size_t chunk_len;
 
 	struct workqueue_struct	*rx_wq;
 	struct work_struct rx_work;

base-commit: dac3e89a2c90c2feeb471e1f22a2512ad424b792
-- 
2.50.1 (Apple Git-155)
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.