Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access
@ 2026-06-12 18:50 Zhao Li
  2026-06-12 18:50 ` [PATCH 2/3] wifi: cfg80211: validate assoc response length before status and IE access Zhao Li
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Zhao Li @ 2026-06-12 18:50 UTC (permalink / raw)
  To: johannes; +Cc: linux-wireless, linux-kernel

cfg80211_rx_mlme_mgmt() and cfg80211_tx_mlme_mgmt() read the frame
control field without first checking len >= 2, then dispatch into
subtype handlers that assume their fixed fields are present.

Add a frame-control length gate, then validate each subtype's minimum
frame size in an if/else-if chain that mirrors the dispatch logic.
Trace only after the frame is known to be well-formed.

Side effects of this change:
 - The WARN_ON(len < 2) is replaced by a silent early return, since
   these cfg80211 callbacks can legitimately receive short frames from
   drivers.
 - cfg80211_tx_mlme_mgmt() previously routed every non-deauth subtype
   through disassociation handling; it now silently ignores unrecognised
   subtypes.

Assisted-by: Codex:gpt-5.5
Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Zhao Li <enderaoelyther@gmail.com>
---
 net/wireless/mlme.c | 45 +++++++++++++++++++++++++++++++++++++--------
 1 file changed, 37 insertions(+), 8 deletions(-)

diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index bd72317c4964e..a0f7b08bfcc9c 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -150,19 +150,35 @@ void cfg80211_rx_mlme_mgmt(struct net_device *dev, const u8 *buf, size_t len)
 {
 	struct wireless_dev *wdev = dev->ieee80211_ptr;
 	struct ieee80211_mgmt *mgmt = (void *)buf;
+	__le16 fc;
 
 	lockdep_assert_wiphy(wdev->wiphy);
 
-	trace_cfg80211_rx_mlme_mgmt(dev, buf, len);
+	if (len < sizeof(fc))
+		return;
+
+	fc = mgmt->frame_control;
 
-	if (WARN_ON(len < 2))
+	if (ieee80211_is_auth(fc)) {
+		if (len < offsetofend(struct ieee80211_mgmt, u.auth.status_code))
+			return;
+	} else if (ieee80211_is_deauth(fc)) {
+		if (len < offsetofend(struct ieee80211_mgmt, u.deauth.reason_code))
+			return;
+	} else if (ieee80211_is_disassoc(fc)) {
+		if (len < offsetofend(struct ieee80211_mgmt, u.disassoc.reason_code))
+			return;
+	} else {
 		return;
+	}
+
+	trace_cfg80211_rx_mlme_mgmt(dev, buf, len);
 
-	if (ieee80211_is_auth(mgmt->frame_control))
+	if (ieee80211_is_auth(fc))
 		cfg80211_process_auth(wdev, buf, len);
-	else if (ieee80211_is_deauth(mgmt->frame_control))
+	else if (ieee80211_is_deauth(fc))
 		cfg80211_process_deauth(wdev, buf, len, false);
-	else if (ieee80211_is_disassoc(mgmt->frame_control))
+	else
 		cfg80211_process_disassoc(wdev, buf, len, false);
 }
 EXPORT_SYMBOL(cfg80211_rx_mlme_mgmt);
@@ -215,15 +231,28 @@ void cfg80211_tx_mlme_mgmt(struct net_device *dev, const u8 *buf, size_t len,
 {
 	struct wireless_dev *wdev = dev->ieee80211_ptr;
 	struct ieee80211_mgmt *mgmt = (void *)buf;
+	__le16 fc;
 
 	lockdep_assert_wiphy(wdev->wiphy);
 
-	trace_cfg80211_tx_mlme_mgmt(dev, buf, len, reconnect);
+	if (len < sizeof(fc))
+		return;
 
-	if (WARN_ON(len < 2))
+	fc = mgmt->frame_control;
+
+	if (ieee80211_is_deauth(fc)) {
+		if (len < offsetofend(struct ieee80211_mgmt, u.deauth.reason_code))
+			return;
+	} else if (ieee80211_is_disassoc(fc)) {
+		if (len < offsetofend(struct ieee80211_mgmt, u.disassoc.reason_code))
+			return;
+	} else {
 		return;
+	}
+
+	trace_cfg80211_tx_mlme_mgmt(dev, buf, len, reconnect);
 
-	if (ieee80211_is_deauth(mgmt->frame_control))
+	if (ieee80211_is_deauth(fc))
 		cfg80211_process_deauth(wdev, buf, len, reconnect);
 	else
 		cfg80211_process_disassoc(wdev, buf, len, reconnect);
-- 
2.50.1 (Apple Git-155)

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 2/3] wifi: cfg80211: validate assoc response length before status and IE access
  2026-06-12 18:50 [PATCH 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access Zhao Li
@ 2026-06-12 18:50 ` Zhao Li
  2026-07-06 10:39   ` Johannes Berg
  2026-06-12 18:50 ` [PATCH 3/3] wifi: mac80211: validate deauth frame length before reason access Zhao Li
  2026-07-06 10:37 ` [PATCH 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access Johannes Berg
  2 siblings, 1 reply; 11+ messages in thread
From: Zhao Li @ 2026-06-12 18:50 UTC (permalink / raw)
  To: johannes; +Cc: linux-wireless, linux-kernel

cfg80211_rx_assoc_resp() initialises the status and response-IE fields
of cfg80211_connect_resp_params from the management frame before
proving that the frame is long enough for those offsets. S1G and
regular association responses also have different IE offsets, but the
S1G path only patched resp_ie after the unsafe initialiser had already
run.

Defer resp_ie, resp_ie_len, and status to after the link-iteration
loop. Use a bool to remember whether the frame is S1G, then validate
the appropriate minimum length and set all three fields in a single
if/else block. Funnel short-frame and SME-reject cleanup through a
shared free_bss label for the abandon paths.

Assisted-by: Codex:gpt-5.5
Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Zhao Li <enderaoelyther@gmail.com>
---
 net/wireless/mlme.c | 56 ++++++++++++++++++++++++++++-----------------
 1 file changed, 35 insertions(+), 21 deletions(-)

diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index a0f7b08bfcc9c..097b66f758ba2 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -32,14 +32,10 @@ void cfg80211_rx_assoc_resp(struct net_device *dev,
 		.timeout_reason = NL80211_TIMEOUT_UNSPECIFIED,
 		.req_ie = data->req_ies,
 		.req_ie_len = data->req_ies_len,
-		.resp_ie = mgmt->u.assoc_resp.variable,
-		.resp_ie_len = data->len -
-			       offsetof(struct ieee80211_mgmt,
-					u.assoc_resp.variable),
-		.status = le16_to_cpu(mgmt->u.assoc_resp.status_code),
 		.ap_mld_addr = data->ap_mld_addr,
 	};
 	unsigned int link_id;
+	bool is_s1g = false;
 
 	for (link_id = 0; link_id < ARRAY_SIZE(data->links); link_id++) {
 		cr.links[link_id].status = data->links[link_id].status;
@@ -60,16 +56,32 @@ void cfg80211_rx_assoc_resp(struct net_device *dev,
 
 		if (cr.links[link_id].bss->channel->band == NL80211_BAND_S1GHZ) {
 			WARN_ON(link_id);
-			cr.resp_ie = (u8 *)&mgmt->u.s1g_assoc_resp.variable;
-			cr.resp_ie_len = data->len -
-					 offsetof(struct ieee80211_mgmt,
-						  u.s1g_assoc_resp.variable);
+			is_s1g = true;
 		}
 
 		if (cr.ap_mld_addr)
 			cr.valid_links |= BIT(link_id);
 	}
 
+	if (is_s1g) {
+		if (data->len < offsetof(struct ieee80211_mgmt,
+					 u.s1g_assoc_resp.variable))
+			goto free_bss;
+		cr.resp_ie = (u8 *)&mgmt->u.s1g_assoc_resp.variable;
+		cr.resp_ie_len = data->len -
+				 offsetof(struct ieee80211_mgmt,
+					  u.s1g_assoc_resp.variable);
+	} else {
+		if (data->len < offsetof(struct ieee80211_mgmt,
+					 u.assoc_resp.variable))
+			goto free_bss;
+		cr.resp_ie = mgmt->u.assoc_resp.variable;
+		cr.resp_ie_len = data->len -
+				 offsetof(struct ieee80211_mgmt,
+					  u.assoc_resp.variable);
+	}
+	cr.status = le16_to_cpu(mgmt->u.assoc_resp.status_code);
+
 	trace_cfg80211_send_rx_assoc(dev, data);
 
 	/*
@@ -78,22 +90,24 @@ void cfg80211_rx_assoc_resp(struct net_device *dev,
 	 * and got a reject -- we only try again with an assoc
 	 * frame instead of reassoc.
 	 */
-	if (cfg80211_sme_rx_assoc_resp(wdev, cr.status)) {
-		for (link_id = 0; link_id < ARRAY_SIZE(data->links); link_id++) {
-			struct cfg80211_bss *bss = data->links[link_id].bss;
-
-			if (!bss)
-				continue;
-
-			cfg80211_unhold_bss(bss_from_pub(bss));
-			cfg80211_put_bss(wiphy, bss);
-		}
-		return;
-	}
+	if (cfg80211_sme_rx_assoc_resp(wdev, cr.status))
+		goto free_bss;
 
 	nl80211_send_rx_assoc(rdev, dev, data);
 	/* update current_bss etc., consumes the bss reference */
 	__cfg80211_connect_result(dev, &cr, cr.status == WLAN_STATUS_SUCCESS);
+	return;
+
+free_bss:
+	for (link_id = 0; link_id < ARRAY_SIZE(data->links); link_id++) {
+		struct cfg80211_bss *bss = data->links[link_id].bss;
+
+		if (!bss)
+			continue;
+
+		cfg80211_unhold_bss(bss_from_pub(bss));
+		cfg80211_put_bss(wiphy, bss);
+	}
 }
 EXPORT_SYMBOL(cfg80211_rx_assoc_resp);
 
-- 
2.50.1 (Apple Git-155)

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 3/3] wifi: mac80211: validate deauth frame length before reason access
  2026-06-12 18:50 [PATCH 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access Zhao Li
  2026-06-12 18:50 ` [PATCH 2/3] wifi: cfg80211: validate assoc response length before status and IE access Zhao Li
@ 2026-06-12 18:50 ` Zhao Li
  2026-07-06 10:37 ` [PATCH 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access Johannes Berg
  2 siblings, 0 replies; 11+ messages in thread
From: Zhao Li @ 2026-06-12 18:50 UTC (permalink / raw)
  To: johannes; +Cc: linux-wireless, linux-kernel

ieee80211_rx_mgmt_deauth() reads the deauth reason code before checking
that the fixed field is actually present in the received frame.

Validate the deauth frame length first and only then read the reason
code.

Assisted-by: Codex:gpt-5.5
Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Zhao Li <enderaoelyther@gmail.com>
---
 net/mac80211/mlme.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index b98ddfa3003e1..8be470d730f52 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -5179,13 +5179,15 @@ static void ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata,
 				     struct ieee80211_mgmt *mgmt, size_t len)
 {
 	struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
-	u16 reason_code = le16_to_cpu(mgmt->u.deauth.reason_code);
+	u16 reason_code;
 
 	lockdep_assert_wiphy(sdata->local->hw.wiphy);
 
-	if (len < 24 + 2)
+	if (len < offsetofend(struct ieee80211_mgmt, u.deauth.reason_code))
 		return;
 
+	reason_code = le16_to_cpu(mgmt->u.deauth.reason_code);
+
 	if (!ether_addr_equal(mgmt->bssid, mgmt->sa)) {
 		ieee80211_tdls_handle_disconnect(sdata, mgmt->sa, reason_code);
 		return;
-- 
2.50.1 (Apple Git-155)

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access
  2026-06-12 18:50 [PATCH 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access Zhao Li
  2026-06-12 18:50 ` [PATCH 2/3] wifi: cfg80211: validate assoc response length before status and IE access Zhao Li
  2026-06-12 18:50 ` [PATCH 3/3] wifi: mac80211: validate deauth frame length before reason access Zhao Li
@ 2026-07-06 10:37 ` Johannes Berg
  2026-07-07  2:52   ` Zhao Li
  2026-07-07  2:53   ` [PATCH v2 " Zhao Li
  2 siblings, 2 replies; 11+ messages in thread
From: Johannes Berg @ 2026-07-06 10:37 UTC (permalink / raw)
  To: Zhao Li; +Cc: linux-wireless, linux-kernel

On Sat, 2026-06-13 at 02:50 +0800, Zhao Li wrote:
> cfg80211_rx_mlme_mgmt() and cfg80211_tx_mlme_mgmt() read the frame
> control field without first checking len >= 2, then dispatch into
> subtype handlers that assume their fixed fields are present.
> 
> Add a frame-control length gate, then validate each subtype's minimum
> frame size in an if/else-if chain that mirrors the dispatch logic.
> Trace only after the frame is known to be well-formed.
> 
> Side effects of this change:
>  - The WARN_ON(len < 2) is replaced by a silent early return, since
>    these cfg80211 callbacks can legitimately receive short frames from
>    drivers.

Can they? How?

johannes

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 2/3] wifi: cfg80211: validate assoc response length before status and IE access
  2026-06-12 18:50 ` [PATCH 2/3] wifi: cfg80211: validate assoc response length before status and IE access Zhao Li
@ 2026-07-06 10:39   ` Johannes Berg
  2026-07-07  3:17     ` Zhao Li
  0 siblings, 1 reply; 11+ messages in thread
From: Johannes Berg @ 2026-07-06 10:39 UTC (permalink / raw)
  To: Zhao Li; +Cc: linux-wireless, linux-kernel

On Sat, 2026-06-13 at 02:50 +0800, Zhao Li wrote:
> cfg80211_rx_assoc_resp() initialises the status and response-IE fields
> of cfg80211_connect_resp_params from the management frame before
> proving that the frame is long enough for those offsets. S1G and
> regular association responses also have different IE offsets, but the
> S1G path only patched resp_ie after the unsafe initialiser had already
> run.

What makes you claim "unsafe"?

johannes

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access
  2026-07-06 10:37 ` [PATCH 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access Johannes Berg
@ 2026-07-07  2:52   ` Zhao Li
  2026-07-07  7:21     ` Johannes Berg
  2026-07-07  2:53   ` [PATCH v2 " Zhao Li
  1 sibling, 1 reply; 11+ messages in thread
From: Zhao Li @ 2026-07-07  2:52 UTC (permalink / raw)
  To: Johannes Berg; +Cc: linux-wireless, linux-kernel

On Mon, 2026-07-06 at 18:37 +0800, Johannes Berg wrote:
> > Side effects of this change:
> >  - The WARN_ON(len < 2) is replaced by a silent early return, since
> >    these cfg80211 callbacks can legitimately receive short frames from
> >    drivers.
>
> Can they? How?

You're right, that wording is wrong; no in-tree caller passes len < 2.
mac80211 gates every path well above that (ieee80211_rx_mgmt_auth() at
len < 24 + 6, ieee80211_rx_mgmt_deauth()/disassoc() at len < 24 + 2), and
the locally built deauth/disassoc frames are always full size.

The frames that do reach these callbacks undersized are short relative
to their subtype, not shorter than 2 bytes. mwifiex accepts a 4-address
ieee80211_hdr plus the 2-byte firmware length prefix. After it strips
the prefix and removes addr4, pkt_len can be exactly 24: a bare
3-address management header with no reason-code body.
WARN_ON(len < 2) does not fire on that, and cfg80211_process_deauth()
then reads u.deauth.reason_code as a two-byte access starting at offset
24, immediately past the 24-byte buffer.

So the len >= 2 check only guards the frame_control read; the per-subtype
offsetofend() checks are the actual fix. I'll drop the WARN rather than
keep it, because these are exported callbacks and a malformed frame from
a driver should be dropped silently instead of backtraced.

v2 rewrites the commit message to describe the mwifiex path instead of
the inaccurate "legitimately receive short frames" claim; no code change.

Thanks,
Zhao Li

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v2 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access
  2026-07-06 10:37 ` [PATCH 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access Johannes Berg
  2026-07-07  2:52   ` Zhao Li
@ 2026-07-07  2:53   ` Zhao Li
  2026-07-07  2:53     ` [PATCH v2 2/3] wifi: cfg80211: validate assoc response length before status and IE access Zhao Li
  2026-07-07  2:53     ` [PATCH v2 3/3] wifi: mac80211: validate deauth frame length before reason access Zhao Li
  1 sibling, 2 replies; 11+ messages in thread
From: Zhao Li @ 2026-07-07  2:53 UTC (permalink / raw)
  To: Johannes Berg; +Cc: linux-wireless, linux-kernel

cfg80211_rx_mlme_mgmt() and cfg80211_tx_mlme_mgmt() call tracepoints
before rejecting frames shorter than the frame-control field. After
that, they only require len >= 2 before dispatching into subtype
handlers that assume their fixed fields are present.

The frames that trip this are not shorter than 2 bytes; they are short
relative to their subtype. mwifiex is a concrete in-tree example on the
length side: mwifiex_process_mgmt_packet() only requires a 4-address
ieee80211_hdr plus the 2-byte firmware length prefix before handing the
frame to cfg80211_rx_mlme_mgmt(). After stripping the length prefix and
removing addr4, pkt_len can be exactly 24: a bare 3-address management
header with no reason-code body. The existing WARN_ON(len < 2) does not
fire on such a frame, and cfg80211_process_deauth() then reads
u.deauth.reason_code as a two-byte access starting at offset 24,
immediately past the 24-byte buffer.

Add a frame-control length gate, then validate each subtype's minimum
frame size in an if/else-if chain that mirrors the dispatch logic. Trace
only after the frame is known to be well-formed.

Side effects of this change:
 - The WARN_ON(len < 2) is dropped. It only guarded the frame_control
   read, never the subtype fixed fields, and it does not fire on the
   frames that actually trigger the out-of-bounds read (which are >= 2).
   The len >= 2 check is kept as the guard before dereferencing
   frame_control, but without the warning: these are exported callbacks
   and a malformed frame from a driver should be dropped silently rather
   than backtraced.
 - cfg80211_tx_mlme_mgmt() previously routed every non-deauth subtype
   through disassociation handling; it now silently ignores unrecognised
   subtypes.

Assisted-by: Codex:gpt-5.5
Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Zhao Li <enderaoelyther@gmail.com>
---
v2: Rewrite the commit message. Drop the inaccurate claim that these
    callbacks "legitimately receive short frames" (< 2) from drivers;
    no in-tree caller passes len < 2. Document the concrete mwifiex
    header-only deauth/disassoc path that reaches the callback below the
    subtype minimum, and clarify why the WARN is dropped while the
    len >= 2 guard is kept. Per Johannes' review. No code change.

 net/wireless/mlme.c | 45 +++++++++++++++++++++++++++++++++++++--------
 1 file changed, 37 insertions(+), 8 deletions(-)

diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index bd72317c4964e..a0f7b08bfcc9c 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -150,19 +150,35 @@ void cfg80211_rx_mlme_mgmt(struct net_device *dev, const u8 *buf, size_t len)
 {
 	struct wireless_dev *wdev = dev->ieee80211_ptr;
 	struct ieee80211_mgmt *mgmt = (void *)buf;
+	__le16 fc;
 
 	lockdep_assert_wiphy(wdev->wiphy);
 
-	trace_cfg80211_rx_mlme_mgmt(dev, buf, len);
+	if (len < sizeof(fc))
+		return;
+
+	fc = mgmt->frame_control;
 
-	if (WARN_ON(len < 2))
+	if (ieee80211_is_auth(fc)) {
+		if (len < offsetofend(struct ieee80211_mgmt, u.auth.status_code))
+			return;
+	} else if (ieee80211_is_deauth(fc)) {
+		if (len < offsetofend(struct ieee80211_mgmt, u.deauth.reason_code))
+			return;
+	} else if (ieee80211_is_disassoc(fc)) {
+		if (len < offsetofend(struct ieee80211_mgmt, u.disassoc.reason_code))
+			return;
+	} else {
 		return;
+	}
+
+	trace_cfg80211_rx_mlme_mgmt(dev, buf, len);
 
-	if (ieee80211_is_auth(mgmt->frame_control))
+	if (ieee80211_is_auth(fc))
 		cfg80211_process_auth(wdev, buf, len);
-	else if (ieee80211_is_deauth(mgmt->frame_control))
+	else if (ieee80211_is_deauth(fc))
 		cfg80211_process_deauth(wdev, buf, len, false);
-	else if (ieee80211_is_disassoc(mgmt->frame_control))
+	else
 		cfg80211_process_disassoc(wdev, buf, len, false);
 }
 EXPORT_SYMBOL(cfg80211_rx_mlme_mgmt);
@@ -215,15 +231,28 @@ void cfg80211_tx_mlme_mgmt(struct net_device *dev, const u8 *buf, size_t len,
 {
 	struct wireless_dev *wdev = dev->ieee80211_ptr;
 	struct ieee80211_mgmt *mgmt = (void *)buf;
+	__le16 fc;
 
 	lockdep_assert_wiphy(wdev->wiphy);
 
-	trace_cfg80211_tx_mlme_mgmt(dev, buf, len, reconnect);
+	if (len < sizeof(fc))
+		return;
 
-	if (WARN_ON(len < 2))
+	fc = mgmt->frame_control;
+
+	if (ieee80211_is_deauth(fc)) {
+		if (len < offsetofend(struct ieee80211_mgmt, u.deauth.reason_code))
+			return;
+	} else if (ieee80211_is_disassoc(fc)) {
+		if (len < offsetofend(struct ieee80211_mgmt, u.disassoc.reason_code))
+			return;
+	} else {
 		return;
+	}
+
+	trace_cfg80211_tx_mlme_mgmt(dev, buf, len, reconnect);
 
-	if (ieee80211_is_deauth(mgmt->frame_control))
+	if (ieee80211_is_deauth(fc))
 		cfg80211_process_deauth(wdev, buf, len, reconnect);
 	else
 		cfg80211_process_disassoc(wdev, buf, len, reconnect);
-- 
2.50.1 (Apple Git-155)

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 2/3] wifi: cfg80211: validate assoc response length before status and IE access
  2026-07-07  2:53   ` [PATCH v2 " Zhao Li
@ 2026-07-07  2:53     ` Zhao Li
  2026-07-07  2:53     ` [PATCH v2 3/3] wifi: mac80211: validate deauth frame length before reason access Zhao Li
  1 sibling, 0 replies; 11+ messages in thread
From: Zhao Li @ 2026-07-07  2:53 UTC (permalink / raw)
  To: Johannes Berg; +Cc: linux-wireless, linux-kernel

cfg80211_rx_assoc_resp() initialises the status and response-IE fields
of cfg80211_connect_resp_params from the management frame before
proving that the frame is long enough for those offsets. S1G and
regular association responses also have different IE offsets, but the
S1G path only patched resp_ie after the unsafe initialiser had already
run.

Defer resp_ie, resp_ie_len, and status to after the link-iteration
loop. Use a bool to remember whether the frame is S1G, then validate
the appropriate minimum length and set all three fields in a single
if/else block. Funnel short-frame and SME-reject cleanup through a
shared free_bss label for the abandon paths.

Assisted-by: Codex:gpt-5.5
Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Zhao Li <enderaoelyther@gmail.com>
---
v2: No change.

 net/wireless/mlme.c | 56 ++++++++++++++++++++++++++++-----------------
 1 file changed, 35 insertions(+), 21 deletions(-)

diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index a0f7b08bfcc9c..097b66f758ba2 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -32,14 +32,10 @@ void cfg80211_rx_assoc_resp(struct net_device *dev,
 		.timeout_reason = NL80211_TIMEOUT_UNSPECIFIED,
 		.req_ie = data->req_ies,
 		.req_ie_len = data->req_ies_len,
-		.resp_ie = mgmt->u.assoc_resp.variable,
-		.resp_ie_len = data->len -
-			       offsetof(struct ieee80211_mgmt,
-					u.assoc_resp.variable),
-		.status = le16_to_cpu(mgmt->u.assoc_resp.status_code),
 		.ap_mld_addr = data->ap_mld_addr,
 	};
 	unsigned int link_id;
+	bool is_s1g = false;
 
 	for (link_id = 0; link_id < ARRAY_SIZE(data->links); link_id++) {
 		cr.links[link_id].status = data->links[link_id].status;
@@ -60,16 +56,32 @@ void cfg80211_rx_assoc_resp(struct net_device *dev,
 
 		if (cr.links[link_id].bss->channel->band == NL80211_BAND_S1GHZ) {
 			WARN_ON(link_id);
-			cr.resp_ie = (u8 *)&mgmt->u.s1g_assoc_resp.variable;
-			cr.resp_ie_len = data->len -
-					 offsetof(struct ieee80211_mgmt,
-						  u.s1g_assoc_resp.variable);
+			is_s1g = true;
 		}
 
 		if (cr.ap_mld_addr)
 			cr.valid_links |= BIT(link_id);
 	}
 
+	if (is_s1g) {
+		if (data->len < offsetof(struct ieee80211_mgmt,
+					 u.s1g_assoc_resp.variable))
+			goto free_bss;
+		cr.resp_ie = (u8 *)&mgmt->u.s1g_assoc_resp.variable;
+		cr.resp_ie_len = data->len -
+				 offsetof(struct ieee80211_mgmt,
+					  u.s1g_assoc_resp.variable);
+	} else {
+		if (data->len < offsetof(struct ieee80211_mgmt,
+					 u.assoc_resp.variable))
+			goto free_bss;
+		cr.resp_ie = mgmt->u.assoc_resp.variable;
+		cr.resp_ie_len = data->len -
+				 offsetof(struct ieee80211_mgmt,
+					  u.assoc_resp.variable);
+	}
+	cr.status = le16_to_cpu(mgmt->u.assoc_resp.status_code);
+
 	trace_cfg80211_send_rx_assoc(dev, data);
 
 	/*
@@ -78,22 +90,24 @@ void cfg80211_rx_assoc_resp(struct net_device *dev,
 	 * and got a reject -- we only try again with an assoc
 	 * frame instead of reassoc.
 	 */
-	if (cfg80211_sme_rx_assoc_resp(wdev, cr.status)) {
-		for (link_id = 0; link_id < ARRAY_SIZE(data->links); link_id++) {
-			struct cfg80211_bss *bss = data->links[link_id].bss;
-
-			if (!bss)
-				continue;
-
-			cfg80211_unhold_bss(bss_from_pub(bss));
-			cfg80211_put_bss(wiphy, bss);
-		}
-		return;
-	}
+	if (cfg80211_sme_rx_assoc_resp(wdev, cr.status))
+		goto free_bss;
 
 	nl80211_send_rx_assoc(rdev, dev, data);
 	/* update current_bss etc., consumes the bss reference */
 	__cfg80211_connect_result(dev, &cr, cr.status == WLAN_STATUS_SUCCESS);
+	return;
+
+free_bss:
+	for (link_id = 0; link_id < ARRAY_SIZE(data->links); link_id++) {
+		struct cfg80211_bss *bss = data->links[link_id].bss;
+
+		if (!bss)
+			continue;
+
+		cfg80211_unhold_bss(bss_from_pub(bss));
+		cfg80211_put_bss(wiphy, bss);
+	}
 }
 EXPORT_SYMBOL(cfg80211_rx_assoc_resp);
 
-- 
2.50.1 (Apple Git-155)

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 3/3] wifi: mac80211: validate deauth frame length before reason access
  2026-07-07  2:53   ` [PATCH v2 " Zhao Li
  2026-07-07  2:53     ` [PATCH v2 2/3] wifi: cfg80211: validate assoc response length before status and IE access Zhao Li
@ 2026-07-07  2:53     ` Zhao Li
  1 sibling, 0 replies; 11+ messages in thread
From: Zhao Li @ 2026-07-07  2:53 UTC (permalink / raw)
  To: Johannes Berg; +Cc: linux-wireless, linux-kernel

ieee80211_rx_mgmt_deauth() reads the deauth reason code before checking
that the fixed field is actually present in the received frame.

Validate the deauth frame length first and only then read the reason
code.

Assisted-by: Codex:gpt-5.5
Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Zhao Li <enderaoelyther@gmail.com>
---
v2: No change.

 net/mac80211/mlme.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index b98ddfa3003e1..8be470d730f52 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -5179,13 +5179,15 @@ static void ieee80211_rx_mgmt_deauth(struct ieee80211_sub_if_data *sdata,
 				     struct ieee80211_mgmt *mgmt, size_t len)
 {
 	struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
-	u16 reason_code = le16_to_cpu(mgmt->u.deauth.reason_code);
+	u16 reason_code;
 
 	lockdep_assert_wiphy(sdata->local->hw.wiphy);
 
-	if (len < 24 + 2)
+	if (len < offsetofend(struct ieee80211_mgmt, u.deauth.reason_code))
 		return;
 
+	reason_code = le16_to_cpu(mgmt->u.deauth.reason_code);
+
 	if (!ether_addr_equal(mgmt->bssid, mgmt->sa)) {
 		ieee80211_tdls_handle_disconnect(sdata, mgmt->sa, reason_code);
 		return;
-- 
2.50.1 (Apple Git-155)

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH 2/3] wifi: cfg80211: validate assoc response length before status and IE access
  2026-07-06 10:39   ` Johannes Berg
@ 2026-07-07  3:17     ` Zhao Li
  0 siblings, 0 replies; 11+ messages in thread
From: Zhao Li @ 2026-07-07  3:17 UTC (permalink / raw)
  To: Johannes Berg; +Cc: linux-wireless, linux-kernel

On Mon, 2026-07-06 at 12:39 +0200, Johannes Berg wrote:
> What makes you claim "unsafe"?

"Unsafe" as in accessed before data->len is validated against the
fixed-field layout, not as in known-exploitable on valid traffic. In
the original struct initialiser, unconditionally:

 - .status = le16_to_cpu(mgmt->u.assoc_resp.status_code) reads two bytes
   at offset 26, needing data->len >= 28;
 - .resp_ie = mgmt->u.assoc_resp.variable points at offset 30 (non-S1G);
 - .resp_ie_len = data->len - 30 is a size_t, so it underflows to near
   SIZE_MAX when data->len < 30.

resp_ie/resp_ie_len are not a harmless local intermediate: they are
consumed by __cfg80211_connect_result() and nl80211_send_connect_result(),
including the kzalloc() sizing and the memcpy()/nla_put() of resp_ie_len
bytes. So the underflow can feed a later copy/nla_put length rather than
remaining a harmless local value.

The mac80211 caller is fine: ieee80211_rx_mgmt_assoc_resp() gates on
len < 24 + 6 (== offsetof(..., u.assoc_resp.variable)) first. The
mwifiex handoff to cfg80211_rx_assoc_resp() only checks that assoc_rsp_size
is non-zero.

For S1G the IE offset is 28, not 30; the old code filled the regular
assoc-response fields first and only patched resp_ie later once it saw
S1G. v2 makes both layouts explicit and validates data->len before
assigning the fields.

If "unsafe" is too loaded here, I can reword it to "unvalidated" or
"unchecked" in a v3.

Thanks,
Zhao Li

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access
  2026-07-07  2:52   ` Zhao Li
@ 2026-07-07  7:21     ` Johannes Berg
  0 siblings, 0 replies; 11+ messages in thread
From: Johannes Berg @ 2026-07-07  7:21 UTC (permalink / raw)
  To: Zhao Li; +Cc: linux-wireless, linux-kernel

On Tue, 2026-07-07 at 10:52 +0800, Zhao Li wrote:
> 
> mwifiex accepts a 4-address
> ieee80211_hdr plus the 2-byte firmware length prefix. After it strips
> the prefix and removes addr4, pkt_len can be exactly 24: a bare
> 3-address management header with no reason-code body.
> WARN_ON(len < 2) does not fire on that, and cfg80211_process_deauth()
> then reads u.deauth.reason_code as a two-byte access starting at offset
> 24, immediately past the 24-byte buffer.

Right, thanks for the explanations here (and in the other mail). I'm
fairly sure the firmware would (have to) discard such frames, but we
shouldn't rely on it. Looks like mwifiex is pretty much a trainwreck on
this anyway though, e.g. mwifiex_parse_mgmt_packet()? I vaguely remember
fixing something like this but I guess it was elsewhere ...

johannes

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-07-07  7:21 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-12 18:50 [PATCH 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access Zhao Li
2026-06-12 18:50 ` [PATCH 2/3] wifi: cfg80211: validate assoc response length before status and IE access Zhao Li
2026-07-06 10:39   ` Johannes Berg
2026-07-07  3:17     ` Zhao Li
2026-06-12 18:50 ` [PATCH 3/3] wifi: mac80211: validate deauth frame length before reason access Zhao Li
2026-07-06 10:37 ` [PATCH 1/3] wifi: cfg80211: validate rx/tx MLME callback frame lengths before access Johannes Berg
2026-07-07  2:52   ` Zhao Li
2026-07-07  7:21     ` Johannes Berg
2026-07-07  2:53   ` [PATCH v2 " Zhao Li
2026-07-07  2:53     ` [PATCH v2 2/3] wifi: cfg80211: validate assoc response length before status and IE access Zhao Li
2026-07-07  2:53     ` [PATCH v2 3/3] wifi: mac80211: validate deauth frame length before reason access Zhao Li

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