Index: sys/net80211/ieee80211_crypto.h =================================================================== --- sys/net80211/ieee80211_crypto.h +++ sys/net80211/ieee80211_crypto.h @@ -101,6 +101,8 @@ #define wk_rxmic wk_key+IEEE80211_KEYBUF_SIZE+8 /* XXX can't () right */ /* key receive sequence counter */ uint64_t wk_keyrsc[IEEE80211_TID_SIZE]; + /* Suspect key receive seq counter */ + uint64_t wk_suspect_keyrsc[IEEE80211_TID_SIZE]; uint64_t wk_keytsc; /* key transmit sequence counter */ const struct ieee80211_cipher *wk_cipher; void *wk_private; /* private cipher state */ Index: sys/net80211/ieee80211_crypto_ccmp.c =================================================================== --- sys/net80211/ieee80211_crypto_ccmp.c +++ sys/net80211/ieee80211_crypto_ccmp.c @@ -240,6 +240,8 @@ struct ieee80211_frame *wh; uint8_t *ivp, tid; uint64_t pn; + int do_update = 1; + int do_replay = 0; rxs = ieee80211_get_rx_params_ptr(m); @@ -263,10 +265,147 @@ } tid = ieee80211_gettid(wh); pn = READ_6(ivp[0], ivp[1], ivp[4], ivp[5], ivp[6], ivp[7]); - if (pn <= k->wk_keyrsc[tid] && + + /* + * Handle a chipset specific bug in some (all?) Atheros chips + * doing hardware CCMP decryption - the PN gets corrupted in a frame. + * + * It looks like a /huge/ jump in the PN as some high bits in the + * IV get flipped to 1 somehow. The next decrypted frame is likely + * fine with the old PN but all subsequent frames are ignored. + */ + + /* + * First part - is this a potentially garbage high PN. + * This could be a garbage PN, it could be a valid PN + * with a bunch of missing frames in the PN space after + * a garbage PN. So, we handle all of those cases below. + */ + + if (k->wk_suspect_keyrsc[tid] != 0) { + /* Handle replay with the same suspect PN; tsk */ + if (pn == k->wk_suspect_keyrsc[tid]) { + IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, + wh->i_addr2, + "Suspect PN equal/replay; tid=%d, " + "pn=0x%jx, rsc=0x%jx, suspect_rsc=0x%jx", + tid, (uintmax_t) pn, + (uintmax_t) k->wk_keyrsc[tid], + (uintmax_t) k->wk_suspect_keyrsc[tid]); + do_replay = 1; + do_update = 0; + } + + /* + * Handle it being bigger than the suspected PN. + * It's quite possible we'll get a SECOND invalid + * frame after the first, and if that's the case + * this logic will need adjusting. + */ + else if (pn > k->wk_suspect_keyrsc[tid]) { + IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, + wh->i_addr2, + "Suspect PN not a suspect; tid=%d, " + "pn=0x%jx, rsc=0x%jx, suspect_rsc=0x%jx", + tid, (uintmax_t) pn, + (uintmax_t) k->wk_keyrsc[tid], + (uintmax_t) k->wk_suspect_keyrsc[tid]); + /* + * This isn't technically needed, but let's + * just be explicit here - if it passes + * the rest of the checks then we'll + * update the rsc and clear the suspect + * rsc counter. + */ + do_update = 1; + } + + /* + * It's smaller than the suspect rsc but bigger + * than the rsc. We're back to normal. + */ + else if (pn > k->wk_keyrsc[tid]) { + IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, + wh->i_addr2, + "Suspect PN; back to normal; tid=%d, " + "pn=0x%jx, rsc=0x%jx, suspect_rsc=0x%jx", + tid, (uintmax_t) pn, + (uintmax_t) k->wk_keyrsc[tid], + (uintmax_t) k->wk_suspect_keyrsc[tid]); + do_update = 1; + } else { + /* + * pn is less or equal to the suspect rsc, + * but greater than the last seen rsc. + * We don't know whether this is another + * corrupted frame with a lower bit corrupted, + * or a replay attack. + * + * Mark it as a replay attack and clear + * ths suspect PN. This may result in some + * incorrectly dropped frames but that's + * better than mucking up the valid-y looking + * RSC and potentially leaving a replay attack + * vector open. + */ + IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, + wh->i_addr2, + "Suspect PN is a replay attack; tid=%d, " + "pn=0x%jx, rsc=0x%jx, suspect_rsc=0x%jx", + tid, (uintmax_t) pn, + (uintmax_t) k->wk_keyrsc[tid], + (uintmax_t) k->wk_suspect_keyrsc[tid]); + k->wk_suspect_keyrsc[tid] = 0; + do_update = 0; + do_replay = 1; + } + } + + /* + * Next, check if we have a garbage looking PN and we need to + * set the suspect PN track. + */ + else if ((k->wk_keyrsc[tid] > 1) && (pn > (k->wk_keyrsc[tid] + 32))) { + /* + * This is a suspected invalid PN. This may be because + * of a hole in the sender side (eg multicast frame + * or non-aggregate frames with holes in their transmit + * sequence counters - typically the latter is net80211 + * style stacks with sequence numbers assigned BEFORE + * actual transmit!). + * + * Track it in the per-key suspected key array. + * Then let it through, but don't update the rsc yet. + * If the next frame seen resumes the normal sequence + * count then we'll use it; else we'll use this suspect one. + */ + IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, + wh->i_addr2, "Suspect PN; tid=%d, pn=0x%jx, rsc=0x%jx", + tid, (uintmax_t) pn, (uintmax_t) k->wk_keyrsc[tid]); + k->wk_suspect_keyrsc[tid] = pn; + /* XXX TODO: statistics, etc */ + do_update = 0; + } + + /* + * Check if the PN is invalid / replay attack. + * + * Note that if we have a suspect rsc here then the checks + * are being done above; this would have to be ALSO before + * the last non-suspect rsc to trigger it here! + */ + if (pn <= k->wk_keyrsc[tid]) { + do_replay = 1; + } + + if ((do_replay == 1) && (k->wk_flags & IEEE80211_KEY_NOREPLAY) == 0) { /* * Replay violation. + * + * Yes, this also will ignore suspect PNs if we see + * them being invalid as whomever asked for + * IEEE80211_KEY_NOREPLAY asked for everything.. */ ieee80211_notify_replay_failure(vap, wh, k, pn, tid); vap->iv_stats.is_rx_ccmpreplay++; @@ -303,8 +442,13 @@ /* * Ok to update rsc now. */ - if (! ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_IV_STRIP))) { + if ((do_update == 1) && + (! ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_IV_STRIP)))) { + /* + * Update rsc and override suspect rsc tracking. + */ k->wk_keyrsc[tid] = pn; + k->wk_suspect_keyrsc[tid] = 0; } return 1; Index: sys/net80211/ieee80211_ht.c =================================================================== --- sys/net80211/ieee80211_ht.c +++ sys/net80211/ieee80211_ht.c @@ -875,6 +875,7 @@ ieee80211_seq rxseq; uint8_t tid; int off; + int amsdu_more = ieee80211_check_rxseq_amsdu_more(rxs); KASSERT((m->m_flags & (M_AMPDU | M_AMPDU_MPDU)) == M_AMPDU, ("!a-mpdu or already re-ordered, flags 0x%x", m->m_flags)); @@ -949,10 +950,11 @@ return CONSUMED; } else { /* - * In order; advance window and notify + * In order; advance window if needed and notify * caller to dispatch directly. */ - rap->rxa_start = IEEE80211_SEQ_INC(rxseq); + if (amsdu_more) + rap->rxa_start = IEEE80211_SEQ_INC(rxseq); return PROCESS; } } @@ -996,7 +998,8 @@ rap->rxa_qframes; ampdu_rx_flush(ni, rap); } - rap->rxa_start = IEEE80211_SEQ_INC(rxseq); + if (amsdu_more) + rap->rxa_start = IEEE80211_SEQ_INC(rxseq); return PROCESS; } } else {