mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v3 wireless] wifi: wfx: validate firmware message body lengths before processing
@ 2026-11-07 12:07 Tristan Madani
  0 siblings, 0 replies; only message in thread
From: Tristan Madani @ 2026-11-07 12:07 UTC (permalink / raw)
  To: Jérôme Pouiller, linux-wireless
  Cc: Greg Kroah-Hartman, stable, Aamir Ahmed, linux-kernel, Tristan Madani

From: Tristan Madani <tristan@talencesecurity.com>

The HIF message handlers cast the body pointer to typed structures
without verifying that the firmware actually sent enough data.  A
shorter-than-expected message causes reads beyond the message boundary.

Add a min_body_len field to the hif_handlers dispatch table and reject
messages that are too short before calling into any handler.  For the
multi-transmit confirmation, which has a variable-length tx_conf_payload
array, additionally validate the array extent with struct_size(), both
in rx_helper() before buffer accounting and in the handler itself.
Guard the body->data accesses in the error and exception indication
handlers against truncated messages.  Add the same minimum length check
for the inline-handled HIF_IND_ID_RX and generic confirmation paths.

Fixes: 9bca45f3d692 ("staging: wfx: allow to send 802.11 frames")
Cc: stable@vger.kernel.org
Signed-off-by: Tristan Madani <tristan@talencesecurity.com>
---
v3:
  - Add min_body_len field to hif_handlers dispatch table and validate
    before calling any handler, addressing all hif_handlers (Jerome)
  - Guard flexible-array data[] accesses in error and exception handlers
    against truncated messages
  - Add body length check for inline-handled RX indication and generic
    confirmation paths
  - Keep struct_size() validation for multi-transmit flexible array
v2:
  - Added validation in bh.c rx_helper() before buffer accounting
  - Used struct_size() for overflow-safe flexible array validation
 drivers/net/wireless/silabs/wfx/bh.c     | 20 +++++--
 drivers/net/wireless/silabs/wfx/hif_rx.c | 70 ++++++++++++++++++------
 2 files changed, 69 insertions(+), 21 deletions(-)

diff --git a/drivers/net/wireless/silabs/wfx/bh.c b/drivers/net/wireless/silabs/wfx/bh.c
index 0071659bc9c98..b877dd01fb00a 100644
--- a/drivers/net/wireless/silabs/wfx/bh.c
+++ b/drivers/net/wireless/silabs/wfx/bh.c
@@ -6,6 +6,7 @@
  * Copyright (c) 2010, ST-Ericsson
  */
 #include <linux/gpio/consumer.h>
+#include <linux/overflow.h>
 #include <net/mac80211.h>
 
 #include "bh.h"
@@ -99,11 +100,22 @@ static int rx_helper(struct wfx_dev *wdev, size_t read_len, int *is_cnf)
 
 	if (!(hif->id & HIF_ID_IS_INDICATION)) {
 		(*is_cnf)++;
-		if (hif->id == HIF_CNF_ID_MULTI_TRANSMIT)
-			release_count =
-				((struct wfx_hif_cnf_multi_transmit *)hif->body)->num_tx_confs;
-		else
+		if (hif->id == HIF_CNF_ID_MULTI_TRANSMIT) {
+			const struct wfx_hif_cnf_multi_transmit *body =
+				(const struct wfx_hif_cnf_multi_transmit *)hif->body;
+			size_t body_len = le16_to_cpu(hif->len) - sizeof(*hif);
+
+			if (body_len < sizeof(*body) ||
+			    body_len < struct_size(body, tx_conf_payload,
+						   body->num_tx_confs)) {
+				dev_err(wdev->dev,
+					"invalid multi_tx_confirm length\n");
+				goto err;
+			}
+			release_count = body->num_tx_confs;
+		} else {
 			release_count = 1;
+		}
 		WARN(wdev->hif.tx_buffers_used < release_count, "corrupted buffer counter");
 		wdev->hif.tx_buffers_used -= release_count;
 	}
diff --git a/drivers/net/wireless/silabs/wfx/hif_rx.c b/drivers/net/wireless/silabs/wfx/hif_rx.c
index 64ca8acb8e4f2..ea3eba49552d7 100644
--- a/drivers/net/wireless/silabs/wfx/hif_rx.c
+++ b/drivers/net/wireless/silabs/wfx/hif_rx.c
@@ -7,6 +7,7 @@
  */
 #include <linux/skbuff.h>
 #include <linux/etherdevice.h>
+#include <linux/overflow.h>
 
 #include "hif_rx.h"
 #include "wfx.h"
@@ -62,9 +63,12 @@ static int wfx_hif_multi_tx_confirm(struct wfx_dev *wdev,
 				    const struct wfx_hif_msg *hif, const void *buf)
 {
 	const struct wfx_hif_cnf_multi_transmit *body = buf;
+	size_t body_len = le16_to_cpu(hif->len) - sizeof(*hif);
 	int i;
 
 	WARN(body->num_tx_confs <= 0, "corrupted message");
+	if (body_len < struct_size(body, tx_conf_payload, body->num_tx_confs))
+		return -EINVAL;
 	for (i = 0; i < body->num_tx_confs; i++)
 		wfx_tx_confirm_cb(wdev, &body->tx_conf_payload[i]);
 	return 0;
@@ -297,17 +301,17 @@ static int wfx_hif_error_indication(struct wfx_dev *wdev,
 				    const struct wfx_hif_msg *hif, const void *buf)
 {
 	const struct wfx_hif_ind_error *body = buf;
+	size_t body_len = le16_to_cpu(hif->len) - sizeof(*hif);
 	int type = le32_to_cpu(body->type);
-	int param = (s8)body->data[0];
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(hif_errors); i++)
 		if (type == hif_errors[i].val)
 			break;
 	if (i < ARRAY_SIZE(hif_errors))
-		if (hif_errors[i].has_param)
+		if (hif_errors[i].has_param && body_len > sizeof(*body))
 			dev_err(wdev->dev, "asynchronous error: %s: %d\n",
-				hif_errors[i].str, param);
+				hif_errors[i].str, (s8)body->data[0]);
 		else
 			dev_err(wdev->dev, "asynchronous error: %s\n", hif_errors[i].str);
 	else
@@ -323,10 +327,13 @@ static int wfx_hif_exception_indication(struct wfx_dev *wdev,
 					const struct wfx_hif_msg *hif, const void *buf)
 {
 	const struct wfx_hif_ind_exception *body = buf;
+	size_t body_len = le16_to_cpu(hif->len) - sizeof(*hif);
 	int type = le32_to_cpu(body->type);
 
-	if (type == 4)
+	if (type == 4 && body_len >= sizeof(*body) + sizeof(__le32))
 		dev_err(wdev->dev, "firmware assert %d\n", le32_to_cpup((__le32 *)body->data));
+	else if (type == 4)
+		dev_err(wdev->dev, "firmware assert\n");
 	else
 		dev_err(wdev->dev, "firmware exception\n");
 	print_hex_dump(KERN_INFO, "hif: ", DUMP_PREFIX_OFFSET,
@@ -339,21 +346,31 @@ static int wfx_hif_exception_indication(struct wfx_dev *wdev,
 static const struct {
 	int msg_id;
 	int (*handler)(struct wfx_dev *wdev, const struct wfx_hif_msg *hif, const void *buf);
+	size_t min_body_len;
 } hif_handlers[] = {
 	/* Confirmations */
-	{ HIF_CNF_ID_TX,                wfx_hif_tx_confirm },
-	{ HIF_CNF_ID_MULTI_TRANSMIT,    wfx_hif_multi_tx_confirm },
+	{ HIF_CNF_ID_TX,                wfx_hif_tx_confirm,
+	  sizeof(struct wfx_hif_cnf_tx) },
+	{ HIF_CNF_ID_MULTI_TRANSMIT,    wfx_hif_multi_tx_confirm,
+	  sizeof(struct wfx_hif_cnf_multi_transmit) },
 	/* Indications */
-	{ HIF_IND_ID_STARTUP,           wfx_hif_startup_indication },
-	{ HIF_IND_ID_WAKEUP,            wfx_hif_wakeup_indication },
-	{ HIF_IND_ID_JOIN_COMPLETE,     wfx_hif_join_complete_indication },
-	{ HIF_IND_ID_SET_PM_MODE_CMPL,  wfx_hif_pm_mode_complete_indication },
-	{ HIF_IND_ID_SCAN_CMPL,         wfx_hif_scan_complete_indication },
-	{ HIF_IND_ID_SUSPEND_RESUME_TX, wfx_hif_suspend_resume_indication },
-	{ HIF_IND_ID_EVENT,             wfx_hif_event_indication },
-	{ HIF_IND_ID_GENERIC,           wfx_hif_generic_indication },
-	{ HIF_IND_ID_ERROR,             wfx_hif_error_indication },
-	{ HIF_IND_ID_EXCEPTION,         wfx_hif_exception_indication },
+	{ HIF_IND_ID_STARTUP,           wfx_hif_startup_indication,
+	  sizeof(struct wfx_hif_ind_startup) },
+	{ HIF_IND_ID_WAKEUP,            wfx_hif_wakeup_indication, 0 },
+	{ HIF_IND_ID_JOIN_COMPLETE,     wfx_hif_join_complete_indication, 0 },
+	{ HIF_IND_ID_SET_PM_MODE_CMPL,  wfx_hif_pm_mode_complete_indication, 0 },
+	{ HIF_IND_ID_SCAN_CMPL,         wfx_hif_scan_complete_indication,
+	  sizeof(struct wfx_hif_ind_scan_cmpl) },
+	{ HIF_IND_ID_SUSPEND_RESUME_TX, wfx_hif_suspend_resume_indication,
+	  sizeof(struct wfx_hif_ind_suspend_resume_tx) },
+	{ HIF_IND_ID_EVENT,             wfx_hif_event_indication,
+	  sizeof(struct wfx_hif_ind_event) },
+	{ HIF_IND_ID_GENERIC,           wfx_hif_generic_indication,
+	  sizeof(struct wfx_hif_ind_generic) },
+	{ HIF_IND_ID_ERROR,             wfx_hif_error_indication,
+	  sizeof(struct wfx_hif_ind_error) },
+	{ HIF_IND_ID_EXCEPTION,         wfx_hif_exception_indication,
+	  sizeof(struct wfx_hif_ind_exception) },
 	/* FIXME: allocate skb_p from wfx_hif_receive_indication and make it generic */
 	//{ HIF_IND_ID_RX,              wfx_hif_receive_indication },
 };
@@ -363,8 +380,14 @@ void wfx_handle_rx(struct wfx_dev *wdev, struct sk_buff *skb)
 	int i;
 	const struct wfx_hif_msg *hif = (const struct wfx_hif_msg *)skb->data;
 	int hif_id = hif->id;
+	size_t body_len = le16_to_cpu(hif->len) - sizeof(*hif);
 
 	if (hif_id == HIF_IND_ID_RX) {
+		if (body_len < sizeof(struct wfx_hif_ind_rx)) {
+			dev_err(wdev->dev, "rx indication too short: %zu\n",
+				body_len);
+			goto free;
+		}
 		/* wfx_hif_receive_indication take care of skb lifetime */
 		wfx_hif_receive_indication(wdev, hif, hif->body, skb);
 		return;
@@ -372,13 +395,26 @@ void wfx_handle_rx(struct wfx_dev *wdev, struct sk_buff *skb)
 	/* Note: mutex_is_lock cause an implicit memory barrier that protect buf_send */
 	if (mutex_is_locked(&wdev->hif_cmd.lock) &&
 	    wdev->hif_cmd.buf_send && wdev->hif_cmd.buf_send->id == hif_id) {
+		if (body_len < sizeof(__le32)) {
+			dev_err(wdev->dev, "confirmation too short: %zu\n",
+				body_len);
+			goto free;
+		}
 		wfx_hif_generic_confirm(wdev, hif, hif->body);
 		goto free;
 	}
 	for (i = 0; i < ARRAY_SIZE(hif_handlers); i++) {
 		if (hif_handlers[i].msg_id == hif_id) {
-			if (hif_handlers[i].handler)
+			if (hif_handlers[i].handler) {
+				if (body_len < hif_handlers[i].min_body_len) {
+					dev_err(wdev->dev,
+						"message 0x%02x too short: %zu < %zu\n",
+						hif_id, body_len,
+						hif_handlers[i].min_body_len);
+					goto free;
+				}
 				hif_handlers[i].handler(wdev, hif, hif->body);
+			}
 			goto free;
 		}
 	}
-- 
2.47.3


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-11-07 12:07 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-07 12:07 [PATCH v3 wireless] wifi: wfx: validate firmware message body lengths before processing Tristan Madani

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®