git: 267e8f64e4d3 - main - net80211: validate control frame TA/RA before further processing

From: Adrian Chadd <adrian_at_FreeBSD.org>
Date: Fri, 25 Apr 2025 05:38:42 UTC
The branch main has been updated by adrian:

URL: https://cgit.FreeBSD.org/src/commit/?id=267e8f64e4d366ce6901cea5e360af528acb0887

commit 267e8f64e4d366ce6901cea5e360af528acb0887
Author:     Adrian Chadd <adrian@FreeBSD.org>
AuthorDate: 2025-03-30 00:03:17 +0000
Commit:     Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2025-04-25 05:35:49 +0000

    net80211: validate control frame TA/RA before further processing
    
    An earlier commit relaxed the TA/RA rules around control frames
    to fix other issues, however it now results in control frames
    not specifically from a known node / to us to be handled in the control
    path.
    
    Specifically, rtwn(4) RTL8812/RTL8821 NICs are currently passing BARs
    from the AP TA to any destination to us; which is tripping up BAW
    tracking and causing traffic hangs.
    
    So do the check before vap->iv_recv_ctl() is called in each input path.
    
    Note that mesh doesn't seem to pass the control frames up; however
    I haven't tested/validated mesh in a long while and I know it's
    currently broken.
    
    Differential Revision:  https://reviews.freebsd.org/D49575
---
 sys/net80211/ieee80211.c        | 54 +++++++++++++++++++++++++++++++++++++++++
 sys/net80211/ieee80211_adhoc.c  |  3 ++-
 sys/net80211/ieee80211_hostap.c |  3 ++-
 sys/net80211/ieee80211_sta.c    |  4 ++-
 sys/net80211/ieee80211_var.h    |  3 +++
 5 files changed, 64 insertions(+), 3 deletions(-)

diff --git a/sys/net80211/ieee80211.c b/sys/net80211/ieee80211.c
index 9d036f298ed3..dbc7023c7fee 100644
--- a/sys/net80211/ieee80211.c
+++ b/sys/net80211/ieee80211.c
@@ -2732,3 +2732,57 @@ ieee80211_is_key_unicast(const struct ieee80211vap *vap,
 	 */
 	return (!ieee80211_is_key_global(vap, key));
 }
+
+/**
+ * Determine whether the given control frame is from a known node
+ * and destined to us.
+ *
+ * In some instances a control frame won't have a TA (eg ACKs), so
+ * we should only verify the RA for those.
+ *
+ * @param ni	ieee80211_node representing the sender, or BSS node
+ * @param m0	mbuf representing the 802.11 frame.
+ * @returns	false if the frame is not a CTL frame (with a warning logged);
+ *		true if the frame is from a known sender / valid recipient,
+ *		false otherwise.
+ */
+bool
+ieee80211_is_ctl_frame_for_vap(struct ieee80211_node *ni, const struct mbuf *m0)
+{
+	const struct ieee80211vap *vap = ni->ni_vap;
+	const struct ieee80211_frame *wh;
+	uint8_t subtype;
+
+	wh = mtod(m0, const struct ieee80211_frame *);
+	subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
+
+	/* Verify it's a ctl frame. */
+	KASSERT(IEEE80211_IS_CTL(wh), ("%s: not a CTL frame (fc[0]=0x%04x)",
+	    __func__, wh->i_fc[0]));
+	if (!IEEE80211_IS_CTL(wh)) {
+		if_printf(vap->iv_ifp,
+		    "%s: not a control frame (fc[0]=0x%04x)\n",
+		    __func__, wh->i_fc[0]);
+		return (false);
+	}
+
+	/* Verify the TA if present. */
+	switch (subtype) {
+	case IEEE80211_FC0_SUBTYPE_CTS:
+	case IEEE80211_FC0_SUBTYPE_ACK:
+		/* No TA. */
+		break;
+	default:
+		/*
+		 * Verify TA matches ni->ni_macaddr; for unknown
+		 * sources it will be the BSS node and ni->ni_macaddr
+		 * will the BSS MAC.
+		 */
+		if (!IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_macaddr))
+			return (false);
+		break;
+	}
+
+	/* Verify the RA */
+	return (IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr));
+}
diff --git a/sys/net80211/ieee80211_adhoc.c b/sys/net80211/ieee80211_adhoc.c
index 77e5a2d99904..210c9fc75bd9 100644
--- a/sys/net80211/ieee80211_adhoc.c
+++ b/sys/net80211/ieee80211_adhoc.c
@@ -661,7 +661,8 @@ adhoc_input(struct ieee80211_node *ni, struct mbuf *m,
 	case IEEE80211_FC0_TYPE_CTL:
 		vap->iv_stats.is_rx_ctl++;
 		IEEE80211_NODE_STAT(ni, rx_ctrl);
-		vap->iv_recv_ctl(ni, m, subtype);
+		if (ieee80211_is_ctl_frame_for_vap(ni, m))
+			vap->iv_recv_ctl(ni, m, subtype);
 		goto out;
 
 	default:
diff --git a/sys/net80211/ieee80211_hostap.c b/sys/net80211/ieee80211_hostap.c
index c9e2c4896f15..1573d83f4cb4 100644
--- a/sys/net80211/ieee80211_hostap.c
+++ b/sys/net80211/ieee80211_hostap.c
@@ -889,7 +889,8 @@ hostap_input(struct ieee80211_node *ni, struct mbuf *m,
 	case IEEE80211_FC0_TYPE_CTL:
 		vap->iv_stats.is_rx_ctl++;
 		IEEE80211_NODE_STAT(ni, rx_ctrl);
-		vap->iv_recv_ctl(ni, m, subtype);
+		if (ieee80211_is_ctl_frame_for_vap(ni, m))
+			vap->iv_recv_ctl(ni, m, subtype);
 		goto out;
 	default:
 		IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY,
diff --git a/sys/net80211/ieee80211_sta.c b/sys/net80211/ieee80211_sta.c
index 48d56d0ad217..887eb81dd3c4 100644
--- a/sys/net80211/ieee80211_sta.c
+++ b/sys/net80211/ieee80211_sta.c
@@ -972,7 +972,8 @@ sta_input(struct ieee80211_node *ni, struct mbuf *m,
 	case IEEE80211_FC0_TYPE_CTL:
 		vap->iv_stats.is_rx_ctl++;
 		IEEE80211_NODE_STAT(ni, rx_ctrl);
-		vap->iv_recv_ctl(ni, m, subtype);
+		if (ieee80211_is_ctl_frame_for_vap(ni, m))
+			vap->iv_recv_ctl(ni, m, subtype);
 		goto out;
 
 	default:
@@ -2054,6 +2055,7 @@ sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, int subtype,
 static void
 sta_recv_ctl(struct ieee80211_node *ni, struct mbuf *m, int subtype)
 {
+
 	switch (subtype) {
 	case IEEE80211_FC0_SUBTYPE_BAR:
 		ieee80211_recv_bar(ni, m);
diff --git a/sys/net80211/ieee80211_var.h b/sys/net80211/ieee80211_var.h
index e011d2dd32ed..91beaec6f997 100644
--- a/sys/net80211/ieee80211_var.h
+++ b/sys/net80211/ieee80211_var.h
@@ -845,6 +845,9 @@ bool	ieee80211_is_key_global(const struct ieee80211vap *vap,
 bool	ieee80211_is_key_unicast(const struct ieee80211vap *vap,
 	    const struct ieee80211_key *key);
 
+bool	ieee80211_is_ctl_frame_for_vap(struct ieee80211_node *,
+	    const struct mbuf *);
+
 void	ieee80211_radiotap_attach(struct ieee80211com *,
 	    struct ieee80211_radiotap_header *th, int tlen,
 		uint32_t tx_radiotap,