diff options
author | Stefan Sperling <stsp@cvs.openbsd.org> | 2020-05-15 14:21:10 +0000 |
---|---|---|
committer | Stefan Sperling <stsp@cvs.openbsd.org> | 2020-05-15 14:21:10 +0000 |
commit | 85171c247171aa6275562651af9975d6956bd4d3 (patch) | |
tree | f539c7ca7959d92926da76e473b92c2a6515cbbb /sys/net80211/ieee80211_input.c | |
parent | 4784011b1e948f83361f2ad8d5a5eac50ecb32a5 (diff) |
Fix CCMP replay check with 11n Rx aggregation and CCMP hardware offloading.
So far, drivers using hardware CCMP decryption were expected to keep the
most recently seen CCMP packet number (PN) up-to-date, and to discard frames
with lower PNs as replays.
A-MPDU subframes may legitimately arrive out of order, and the drivers skipped
CCMP replay checking for such frames. Re-ordering happens in ieee80211_inputm(),
after the driver is done with a frame. Drivers cannot tell replayed frames
apart from legitimate out-of-order retransmissions.
To fix this, update the PN value in ieee80211_inputm() after subframes have
been reordered into their proper sequence. Drivers still perform replay checks
but they no longer have to worry about updating the last seen PN value.
The 802.11 spec confirms that replay checking is supposed to happen after
A-MPDU re-ordering.
Tested by jmc@, benno@, solene@, and myself with the following drivers:
athn(4), iwn(4), iwm(4), wpi(4), urtwn(4)
ok solene@
Diffstat (limited to 'sys/net80211/ieee80211_input.c')
-rw-r--r-- | sys/net80211/ieee80211_input.c | 90 |
1 files changed, 88 insertions, 2 deletions
diff --git a/sys/net80211/ieee80211_input.c b/sys/net80211/ieee80211_input.c index d802a15ad6c..de44d5a0a95 100644 --- a/sys/net80211/ieee80211_input.c +++ b/sys/net80211/ieee80211_input.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ieee80211_input.c,v 1.215 2020/03/11 12:39:27 tobhe Exp $ */ +/* $OpenBSD: ieee80211_input.c,v 1.216 2020/05/15 14:21:09 stsp Exp $ */ /*- * Copyright (c) 2001 Atsushi Onoe @@ -58,6 +58,8 @@ #include <net80211/ieee80211_var.h> #include <net80211/ieee80211_priv.h> +struct mbuf *ieee80211_input_hwdecrypt(struct ieee80211com *, + struct ieee80211_node *, struct mbuf *); struct mbuf *ieee80211_defrag(struct ieee80211com *, struct mbuf *, int); void ieee80211_defrag_timeout(void *); void ieee80211_input_ba(struct ieee80211com *, struct mbuf *, @@ -147,6 +149,86 @@ ieee80211_get_hdrlen(const struct ieee80211_frame *wh) return size; } +/* Post-processing for drivers which perform decryption in hardware. */ +struct mbuf * +ieee80211_input_hwdecrypt(struct ieee80211com *ic, struct ieee80211_node *ni, + struct mbuf *m) +{ + struct ieee80211_key *k; + struct ieee80211_frame *wh; + uint64_t pn, *prsc; + int hdrlen; + + k = ieee80211_get_rxkey(ic, m, ni); + if (k == NULL) + return NULL; + + wh = mtod(m, struct ieee80211_frame *); + hdrlen = ieee80211_get_hdrlen(wh); + + /* + * Update the last-seen packet number (PN) for drivers using hardware + * crypto offloading. This cannot be done by drivers because A-MPDU + * reordering needs to occur before a valid lower bound can be + * determined for the PN. Drivers will read the PN we write here and + * are expected to discard replayed frames based on it. + * Drivers are expected to leave the IV of decrypted frames intact + * so we can update the last-seen PN and strip the IV here. + */ + switch (k->k_cipher) { + case IEEE80211_CIPHER_CCMP: + if (!(wh->i_fc[1] & IEEE80211_FC1_PROTECTED)) { + /* drop unencrypted */ + ic->ic_stats.is_rx_unencrypted++; + return NULL; + } + if (ieee80211_ccmp_get_pn(&pn, &prsc, m, k) != 0) + return NULL; + if (pn <= *prsc) { + ic->ic_stats.is_ccmp_replays++; + return NULL; + } + + /* Update last-seen packet number. */ + *prsc = pn; + + /* Clear Protected bit and strip IV. */ + wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; + memmove(mtod(m, caddr_t) + IEEE80211_CCMP_HDRLEN, wh, hdrlen); + m_adj(m, IEEE80211_CCMP_HDRLEN); + /* Drivers are expected to strip the MIC. */ + break; + case IEEE80211_CIPHER_TKIP: + if (!(wh->i_fc[1] & IEEE80211_FC1_PROTECTED)) { + /* drop unencrypted */ + ic->ic_stats.is_rx_unencrypted++; + return NULL; + } + if (ieee80211_tkip_get_tsc(&pn, &prsc, m, k) != 0) + return NULL; + + if (pn <= *prsc) { + ic->ic_stats.is_tkip_replays++; + return NULL; + } + + /* Update last-seen packet number. */ + *prsc = pn; + + /* Clear Protected bit and strip IV. */ + wh = mtod(m, struct ieee80211_frame *); + wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; + memmove(mtod(m, caddr_t) + IEEE80211_TKIP_HDRLEN, wh, hdrlen); + m_adj(m, IEEE80211_TKIP_HDRLEN); + /* Drivers are expected to strip the MIC. */ + break; + default: + break; + } + + return m; +} + /* * Process a received frame. The node associated with the sender * should be supplied. If nothing was found in the node table then @@ -454,8 +536,12 @@ ieee80211_inputm(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni, ic->ic_stats.is_rx_wepfail++; goto err; } - wh = mtod(m, struct ieee80211_frame *); + } else { + m = ieee80211_input_hwdecrypt(ic, ni, m); + if (m == NULL) + goto err; } + wh = mtod(m, struct ieee80211_frame *); } else if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) || (rxi->rxi_flags & IEEE80211_RXI_HWDEC)) { /* frame encrypted but protection off for Rx */ |