git: 7ba07a1452a8 - releng/13.0 - net80211: mitigation against A-MSDU design flaw

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Tue, 15 Mar 2022 18:14:04 UTC
The branch releng/13.0 has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=7ba07a1452a82c3cbe696c3dfd162cc76c168dda

commit 7ba07a1452a82c3cbe696c3dfd162cc76c168dda
Author:     Mathy Vanhoef <Mathy.Vanhoef@kuleuven.be>
AuthorDate: 2021-06-06 22:10:52 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2022-03-15 17:45:11 +0000

    net80211: mitigation against A-MSDU design flaw
    
    Mitigate A-MSDU injection attacks by detecting if the destination address
    of a subframe equals an RFC1042 (i.e., LLC/SNAP) header, and if so
    dropping the complete A-MSDU frame.  This mitigates known attacks,
    although new (unknown) aggregation-based attacks may remain possible.
    
    This defense works because in A-MSDU aggregation injection attacks, a
    normal encrypted Wi-Fi frame is turned into an A-MSDU frame. This means
    the first 6 bytes of the first A-MSDU subframe correspond to an RFC1042
    header. In other words, the destination MAC address of the first A-MSDU
    subframe contains the start of an RFC1042 header during an aggregation
    attack. We can detect this and thereby prevent this specific attack.
    
    This relates to section 7.2 in the 2021 Usenix "FragAttacks" (Fragment
    and Forge: Breaking Wi-Fi Through Frame Aggregation and Fragmentation)
    paper.
    
    Submitted by:   Mathy Vanhoef (Mathy.Vanhoef kuleuven.be)
    Security:       CVE-2020-24588
    PR:             256119
    
    (cherry picked from commit f024bdf1155f36d2d8c4caa533b66e4040c4c469)
    (cherry picked from commit 41ca1d50a8657959df2009daa300dda56a090d5e)
    
    Approved by:    so
    Security:       FreeBSD-SA-22:02.wifi
---
 sys/net80211/ieee80211_adhoc.c  |  2 +-
 sys/net80211/ieee80211_hostap.c |  2 +-
 sys/net80211/ieee80211_input.c  | 20 ++++++++++++++++++--
 sys/net80211/ieee80211_input.h  |  3 ++-
 sys/net80211/ieee80211_sta.c    |  2 +-
 sys/net80211/ieee80211_wds.c    |  2 +-
 6 files changed, 24 insertions(+), 7 deletions(-)

diff --git a/sys/net80211/ieee80211_adhoc.c b/sys/net80211/ieee80211_adhoc.c
index a23f138802dc..e2164bbb46a1 100644
--- a/sys/net80211/ieee80211_adhoc.c
+++ b/sys/net80211/ieee80211_adhoc.c
@@ -558,7 +558,7 @@ adhoc_input(struct ieee80211_node *ni, struct mbuf *m,
 		/*
 		 * Finally, strip the 802.11 header.
 		 */
-		m = ieee80211_decap(vap, m, hdrspace);
+		m = ieee80211_decap(vap, m, hdrspace, qos);
 		if (m == NULL) {
 			/* XXX mask bit to check for both */
 			/* don't count Null data frames as errors */
diff --git a/sys/net80211/ieee80211_hostap.c b/sys/net80211/ieee80211_hostap.c
index d2daaea5bda6..80e58fd752a7 100644
--- a/sys/net80211/ieee80211_hostap.c
+++ b/sys/net80211/ieee80211_hostap.c
@@ -744,7 +744,7 @@ hostap_input(struct ieee80211_node *ni, struct mbuf *m,
 		/*
 		 * Finally, strip the 802.11 header.
 		 */
-		m = ieee80211_decap(vap, m, hdrspace);
+		m = ieee80211_decap(vap, m, hdrspace, qos);
 		if (m == NULL) {
 			/* XXX mask bit to check for both */
 			/* don't count Null data frames as errors */
diff --git a/sys/net80211/ieee80211_input.c b/sys/net80211/ieee80211_input.c
index eaeceb9d228e..66a5ba1c4035 100644
--- a/sys/net80211/ieee80211_input.c
+++ b/sys/net80211/ieee80211_input.c
@@ -309,7 +309,8 @@ ieee80211_deliver_data(struct ieee80211vap *vap,
 }
 
 struct mbuf *
-ieee80211_decap(struct ieee80211vap *vap, struct mbuf *m, int hdrlen)
+ieee80211_decap(struct ieee80211vap *vap, struct mbuf *m, int hdrlen,
+	uint8_t qos)
 {
 	struct ieee80211_qosframe_addr4 wh;
 	struct ether_header *eh;
@@ -331,7 +332,9 @@ ieee80211_decap(struct ieee80211vap *vap, struct mbuf *m, int hdrlen)
 	    llc->llc_snap.org_code[1] == 0 && llc->llc_snap.org_code[2] == 0 &&
 	    /* NB: preserve AppleTalk frames that have a native SNAP hdr */
 	    !(llc->llc_snap.ether_type == htons(ETHERTYPE_AARP) ||
-	      llc->llc_snap.ether_type == htons(ETHERTYPE_IPX))) {
+	      llc->llc_snap.ether_type == htons(ETHERTYPE_IPX)) &&
+	    /* Do not want to touch A-MSDU frames. */
+	    !(qos & IEEE80211_QOS_AMSDU)) {
 		m_adj(m, hdrlen + sizeof(struct llc) - sizeof(*eh));
 		llc = NULL;
 	} else {
@@ -379,6 +382,10 @@ ieee80211_decap1(struct mbuf *m, int *framelen)
 #define	FF_LLC_SIZE	(sizeof(struct ether_header) + sizeof(struct llc))
 	struct ether_header *eh;
 	struct llc *llc;
+	const uint8_t llc_hdr_mac[ETHER_ADDR_LEN] = {
+		/* MAC address matching the 802.2 LLC header */
+		LLC_SNAP_LSAP, LLC_SNAP_LSAP, LLC_UI, 0, 0, 0
+	};
 
 	/*
 	 * The frame has an 802.3 header followed by an 802.2
@@ -391,6 +398,15 @@ ieee80211_decap1(struct mbuf *m, int *framelen)
 	if (m->m_len < FF_LLC_SIZE && (m = m_pullup(m, FF_LLC_SIZE)) == NULL)
 		return NULL;
 	eh = mtod(m, struct ether_header *);	/* 802.3 header is first */
+
+	/*
+	 * Detect possible attack where a single 802.11 frame is processed
+	 * as an A-MSDU frame due to an adversary setting the A-MSDU present
+	 * bit in the 802.11 QoS header. [FragAttacks]
+	 */
+	if (memcmp(eh->ether_dhost, llc_hdr_mac, ETHER_ADDR_LEN) == 0)
+		return NULL;
+
 	llc = (struct llc *)&eh[1];		/* 802.2 header follows */
 	*framelen = ntohs(eh->ether_type)	/* encap'd frame size */
 		  + sizeof(struct ether_header) - sizeof(struct llc);
diff --git a/sys/net80211/ieee80211_input.h b/sys/net80211/ieee80211_input.h
index 8ec82eef7369..ae2b4644cc9e 100644
--- a/sys/net80211/ieee80211_input.h
+++ b/sys/net80211/ieee80211_input.h
@@ -311,7 +311,8 @@ void	ieee80211_deliver_data(struct ieee80211vap *,
 struct mbuf *ieee80211_defrag(struct ieee80211_node *,
 		struct mbuf *, int, int);
 struct mbuf *ieee80211_realign(struct ieee80211vap *, struct mbuf *, size_t);
-struct mbuf *ieee80211_decap(struct ieee80211vap *, struct mbuf *, int);
+struct mbuf *ieee80211_decap(struct ieee80211vap *, struct mbuf *, int,
+		uint8_t);
 struct mbuf *ieee80211_decap1(struct mbuf *, int *);
 int	ieee80211_setup_rates(struct ieee80211_node *ni,
 		const uint8_t *rates, const uint8_t *xrates, int flags);
diff --git a/sys/net80211/ieee80211_sta.c b/sys/net80211/ieee80211_sta.c
index 6d24eadc11a6..60a5ea100556 100644
--- a/sys/net80211/ieee80211_sta.c
+++ b/sys/net80211/ieee80211_sta.c
@@ -827,7 +827,7 @@ sta_input(struct ieee80211_node *ni, struct mbuf *m,
 		/*
 		 * Finally, strip the 802.11 header.
 		 */
-		m = ieee80211_decap(vap, m, hdrspace);
+		m = ieee80211_decap(vap, m, hdrspace, qos);
 		if (m == NULL) {
 			/* XXX mask bit to check for both */
 			/* don't count Null data frames as errors */
diff --git a/sys/net80211/ieee80211_wds.c b/sys/net80211/ieee80211_wds.c
index f59a92b992d7..f88871ca4ae6 100644
--- a/sys/net80211/ieee80211_wds.c
+++ b/sys/net80211/ieee80211_wds.c
@@ -621,7 +621,7 @@ wds_input(struct ieee80211_node *ni, struct mbuf *m,
 		/*
 		 * Finally, strip the 802.11 header.
 		 */
-		m = ieee80211_decap(vap, m, hdrspace);
+		m = ieee80211_decap(vap, m, hdrspace, qos);
 		if (m == NULL) {
 			/* XXX mask bit to check for both */
 			/* don't count Null data frames as errors */