git: cf15ab553dd0 - releng/13.0 - net80211: reject mixed plaintext/encrypted fragments

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

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

commit cf15ab553dd0dabd8718995aedbe90e3ff0bc7e1
Author:     Mathy Vanhoef <Mathy.Vanhoef@kuleuven.be>
AuthorDate: 2021-06-06 22:10:41 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2022-03-15 17:43:54 +0000

    net80211: reject mixed plaintext/encrypted fragments
    
    ieee80211_defrag() accepts fragmented 802.11 frames in a protected Wi-Fi
    network even when some of the fragments are not encrypted.
    Track whether the fragments are encrypted or not and only accept
    successive ones if they match the state of the first fragment.
    
    This relates to section 6.3 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-26147
    PR:             256118
    
    (cherry picked from commit 11572d7d7fb9802ceb46ea9dc6cbe3bb95373e55)
    (cherry picked from commit e13d483c5677d12b52f1c81537d54faa85ed43b9)
    
    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  | 21 ++++++++++++++++++---
 sys/net80211/ieee80211_input.h  |  2 +-
 sys/net80211/ieee80211_mesh.c   |  2 +-
 sys/net80211/ieee80211_sta.c    |  2 +-
 sys/net80211/ieee80211_wds.c    |  2 +-
 7 files changed, 24 insertions(+), 9 deletions(-)

diff --git a/sys/net80211/ieee80211_adhoc.c b/sys/net80211/ieee80211_adhoc.c
index ea1519b3381d..a23f138802dc 100644
--- a/sys/net80211/ieee80211_adhoc.c
+++ b/sys/net80211/ieee80211_adhoc.c
@@ -531,7 +531,7 @@ adhoc_input(struct ieee80211_node *ni, struct mbuf *m,
 		 * Next up, any fragmentation.
 		 */
 		if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
-			m = ieee80211_defrag(ni, m, hdrspace);
+			m = ieee80211_defrag(ni, m, hdrspace, has_decrypted);
 			if (m == NULL) {
 				/* Fragment dropped or frame not complete yet */
 				goto out;
diff --git a/sys/net80211/ieee80211_hostap.c b/sys/net80211/ieee80211_hostap.c
index 8402ade857ff..d2daaea5bda6 100644
--- a/sys/net80211/ieee80211_hostap.c
+++ b/sys/net80211/ieee80211_hostap.c
@@ -719,7 +719,7 @@ hostap_input(struct ieee80211_node *ni, struct mbuf *m,
 		 * Next up, any fragmentation.
 		 */
 		if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
-			m = ieee80211_defrag(ni, m, hdrspace);
+			m = ieee80211_defrag(ni, m, hdrspace, has_decrypted);
 			if (m == NULL) {
 				/* Fragment dropped or frame not complete yet */
 				goto out;
diff --git a/sys/net80211/ieee80211_input.c b/sys/net80211/ieee80211_input.c
index aa557fc1ec24..eaeceb9d228e 100644
--- a/sys/net80211/ieee80211_input.c
+++ b/sys/net80211/ieee80211_input.c
@@ -170,7 +170,8 @@ ieee80211_input_mimo_all(struct ieee80211com *ic, struct mbuf *m)
  * XXX should handle 3 concurrent reassemblies per-spec.
  */
 struct mbuf *
-ieee80211_defrag(struct ieee80211_node *ni, struct mbuf *m, int hdrspace)
+ieee80211_defrag(struct ieee80211_node *ni, struct mbuf *m, int hdrspace,
+	int has_decrypted)
 {
 	struct ieee80211vap *vap = ni->ni_vap;
 	struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *);
@@ -189,6 +190,11 @@ ieee80211_defrag(struct ieee80211_node *ni, struct mbuf *m, int hdrspace)
 	if (!more_frag && fragno == 0 && ni->ni_rxfrag[0] == NULL)
 		return m;
 
+	/* Temporarily set flag to remember if fragment was encrypted. */
+	/* XXX use a non-packet altering storage for this in the future. */
+	if (has_decrypted)
+		wh->i_fc[1] |= IEEE80211_FC1_PROTECTED;
+
 	/*
 	 * Remove frag to insure it doesn't get reaped by timer.
 	 */
@@ -219,10 +225,14 @@ ieee80211_defrag(struct ieee80211_node *ni, struct mbuf *m, int hdrspace)
 
 		lwh = mtod(mfrag, struct ieee80211_frame *);
 		last_rxseq = le16toh(*(uint16_t *)lwh->i_seq);
-		/* NB: check seq # and frag together */
+		/*
+		 * NB: check seq # and frag together. Also check that both
+		 * fragments are plaintext or that both are encrypted.
+		 */
 		if (rxseq == last_rxseq+1 &&
 		    IEEE80211_ADDR_EQ(wh->i_addr1, lwh->i_addr1) &&
-		    IEEE80211_ADDR_EQ(wh->i_addr2, lwh->i_addr2)) {
+		    IEEE80211_ADDR_EQ(wh->i_addr2, lwh->i_addr2) &&
+		    !((wh->i_fc[1] ^ lwh->i_fc[1]) & IEEE80211_FC1_PROTECTED)) {
 			/* XXX clear MORE_FRAG bit? */
 			/* track last seqnum and fragno */
 			*(uint16_t *) lwh->i_seq = *(uint16_t *) wh->i_seq;
@@ -253,6 +263,11 @@ ieee80211_defrag(struct ieee80211_node *ni, struct mbuf *m, int hdrspace)
 		ni->ni_rxfrag[0] = mfrag;
 		mfrag = NULL;
 	}
+	/* Remember to clear protected flag that was temporarily set. */
+	if (mfrag != NULL) {
+		wh = mtod(mfrag, struct ieee80211_frame *);
+		wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED;
+	}
 	return mfrag;
 }
 
diff --git a/sys/net80211/ieee80211_input.h b/sys/net80211/ieee80211_input.h
index 810dcbde7978..8ec82eef7369 100644
--- a/sys/net80211/ieee80211_input.h
+++ b/sys/net80211/ieee80211_input.h
@@ -309,7 +309,7 @@ fail:
 void	ieee80211_deliver_data(struct ieee80211vap *,
 		struct ieee80211_node *, struct mbuf *);
 struct mbuf *ieee80211_defrag(struct ieee80211_node *,
-		struct mbuf *, int);
+		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_decap1(struct mbuf *, int *);
diff --git a/sys/net80211/ieee80211_mesh.c b/sys/net80211/ieee80211_mesh.c
index fdb84a2acb3d..510c986b13e4 100644
--- a/sys/net80211/ieee80211_mesh.c
+++ b/sys/net80211/ieee80211_mesh.c
@@ -1642,7 +1642,7 @@ mesh_input(struct ieee80211_node *ni, struct mbuf *m,
 		 */
 		hdrspace = ieee80211_hdrspace(ic, wh);
 		if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
-			m = ieee80211_defrag(ni, m, hdrspace);
+			m = ieee80211_defrag(ni, m, hdrspace, 0);
 			if (m == NULL) {
 				/* Fragment dropped or frame not complete yet */
 				goto out;
diff --git a/sys/net80211/ieee80211_sta.c b/sys/net80211/ieee80211_sta.c
index 43dc8b6dfeca..6d24eadc11a6 100644
--- a/sys/net80211/ieee80211_sta.c
+++ b/sys/net80211/ieee80211_sta.c
@@ -795,7 +795,7 @@ sta_input(struct ieee80211_node *ni, struct mbuf *m,
 		 * Next up, any fragmentation.
 		 */
 		if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
-			m = ieee80211_defrag(ni, m, hdrspace);
+			m = ieee80211_defrag(ni, m, hdrspace, has_decrypted);
 			if (m == NULL) {
 				/* Fragment dropped or frame not complete yet */
 				goto out;
diff --git a/sys/net80211/ieee80211_wds.c b/sys/net80211/ieee80211_wds.c
index 8eaffcf87733..f59a92b992d7 100644
--- a/sys/net80211/ieee80211_wds.c
+++ b/sys/net80211/ieee80211_wds.c
@@ -594,7 +594,7 @@ wds_input(struct ieee80211_node *ni, struct mbuf *m,
 		 * Next up, any fragmentation.
 		 */
 		if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
-			m = ieee80211_defrag(ni, m, hdrspace);
+			m = ieee80211_defrag(ni, m, hdrspace, has_decrypted);
 			if (m == NULL) {
 				/* Fragment dropped or frame not complete yet */
 				goto out;