Linux kernel v4.17 added the ability to request sending control port frames via NL80211 instead of a normal network socket. Doing this provides the device driver with ordering information between the control port frames and the installation of keys. This empowers it to avoid race conditions between, for example, PTK replacement and the sending of frame 4 of the 4-way rekeying handshake in an RSNA. The key difference between a TX_CONTROL_PORT and normal socket send is that the device driver will certainly get any EAPoL frames comprising a 4-way handshake before it gets the key installation call for the derived key. By flushing its TX buffers it can then ensure that no pending EAPoL frames are inadvertently encrypted with a key that the peer will not yet have installed. This patch adds a TX_CONTROL_PORT flag to the hostap driver API to report that it supports, for a given device, a new operation called tx_control_port. This operation is exactly like an ethernet send except for the extra ordering information it provides for device drivers. The nl80211 driver is updated to support this operation when the device reports the CONTROL_PORT_OVER_NL80211 extended feature. Finally the RSN supplicant system is updated to use this new operation for sending EAPoL-Key frames when the driver reports that it is available; otherwise falling back to a normal ethernet TX. There may be other cases than these EAPoL-Key frames that would benefit from using the new operation but I do not know of them. I have tested this on DMG (11ad/ay) devices with an out-of-tree Linux driver that does not use mac80211. Without this patch I consistently see PTK rekeying fail if message 4/4 shares a stream with other in-flight traffic. With this patch, and the driver updated to flush the relevant TX queue before overwriting a PTK (knowing, now, that if there was a message 4/4 related to the key installation, it has already entered the driver queue), rekeying is reliable. There is still data loss surrounding key installation - this problem is alluded to in 802.11-2016 12.6.21, where extended Key ID support is described as the eventual solution. This patch aims to at least prevent rekeying from totally breaking the association, in a way that works on kernels as far back as 4.17 (as per Alexander Wetzel extended Key ID support should be possible on 5.2). See http://lists.infradead.org/pipermail/hostap/2019-May/040089.html for a little more context. Cc: Chaitanya Tata <chaitanya.tata@xxxxxxxxxxxxxxxxx> Cc: Antony King <antony.king@xxxxxxxxxxxxxxxxx> Cc: Johannes Berg <johannes@xxxxxxxxxxxxxxxx> Cc: Alexander Wetzel <alexander@xxxxxxxxxxxxxx> Cc: Krishna Chaitanya <chaitanya.mgit@xxxxxxxxx> Signed-off-by: Brendan Jackman <brendan.jackman@xxxxxxxxxxxxxxxxx> --- Original discussion of patch v1: http://lists.infradead.org/pipermail/hostap/2019-August/040400.html Only difference v1->v2 is typo correction and some additions to this commit message. I believe the conclusion of the previous discussion was that a) this can indeed help avoid rekeying issues and b) we can also use PACKET_QDISC_BYPASS to achieve almost the same thing with device drivers that do not supported TX_CONTROL_PORT (I haven't done that in this patch). --- src/drivers/driver.h | 26 +++++++++++++ src/drivers/driver_nl80211.c | 39 ++++++++++++++++++++ src/drivers/driver_nl80211_capa.c | 4 ++ src/rsn_supp/wpa.c | 2 +- src/rsn_supp/wpa.h | 2 + src/rsn_supp/wpa_i.h | 7 ++++ wpa_supplicant/driver_i.h | 8 ++++ wpa_supplicant/ibss_rsn.c | 18 +++++++++ wpa_supplicant/wpas_glue.c | 61 ++++++++++++++++++++++++------- 9 files changed, 153 insertions(+), 14 deletions(-) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 2a8459ae3..da776f3f7 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -1659,6 +1659,8 @@ struct wpa_driver_capa { #define WPA_DRIVER_FLAGS_FTM_RESPONDER 0x0100000000000000ULL /** Driver support 4-way handshake offload for WPA-Personal */ #define WPA_DRIVER_FLAGS_4WAY_HANDSHAKE_PSK 0x0200000000000000ULL +/** Driver has working tx_control_port */ +#define WPA_DRIVER_FLAGS_TX_CONTROL_PORT 0x0400000000000000ULL u64 flags; #define FULL_AP_CLIENT_STATE_SUPP(drv_flags) \ @@ -2316,6 +2318,30 @@ struct wpa_driver_ops { const u8 *seq, size_t seq_len, const u8 *key, size_t key_len); + /** + * tx_control_port - Send a frame over the 802.1X controlled port + * @priv: private driver interface data + * @dest: Destination MAC address + * @proto: Ethertype in host byte order + * @buf: Frame payload starting from IEEE 802.1X header + * @len: Frame payload length + * + * Returns 0 on success, else an error + * + * This is like a normal ethernet send except that the OS device driver + * is aware (by other means than the ethertype) that this frame is + * ~special~, and more importantly it gains an ordering between the + * transmission of the frame and other driver operations such as key + * installations. This can be used to work around known limitations in + * 802.11 protocols such as race conditions between 802.1X rekeying + * handshake message 4/4 and a PTK being overwritten. + * + * This function is only implemented for a given interface if the driver + * instance reports WPA_DRIVER_FLAGS_TX_CONTROL_PORT. Otherwise API + * users should fall back to sending the frame via a normal socket. + */ + int (*tx_control_port)(void *priv, const u8 *dest, + u16 proto, const u8 *buf, size_t len); /** * init - Initialize driver interface * @ctx: context to be used when calling wpa_supplicant functions, diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 7b31b52c7..f402b1fa0 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -3007,6 +3007,44 @@ static int nl80211_set_pmk(struct wpa_driver_nl80211_data *drv, } +static int driver_nl80211_tx_control_port(void *priv, const u8 *dest, + u16 proto, const u8 *buf, size_t len) +{ + struct i802_bss *bss = priv; + struct nl_msg *msg = NULL; + int ifindex = if_nametoindex(bss->ifname); + int ret = 0; + + wpa_printf(MSG_DEBUG, + "nl80211: tx_control_port "MACSTR" proto=0x%04x len=%zu", + MAC2STR(dest), proto, len); + + msg = nl80211_ifindex_msg(bss->drv, ifindex, 0, + NL80211_CMD_CONTROL_PORT_FRAME); + if (!msg) + return -ENOBUFS; + + if (nla_put_u16(msg, NL80211_ATTR_CONTROL_PORT_ETHERTYPE, proto)) + goto fail; + if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, dest)) + goto fail; + if (nla_put(msg, NL80211_ATTR_FRAME, len, buf)) + goto fail; + + ret = send_and_recv_msgs(bss->drv, msg, NULL, NULL); + if (ret) + wpa_printf(MSG_DEBUG, + "nl80211: tx_control_port failed: ret=%d (%s)", + ret, strerror(ret)); + + return ret; + +fail: + nl80211_nlmsg_clear(msg); + nlmsg_free(msg); + return -ENOBUFS; +} + static int wpa_driver_nl80211_set_key(const char *ifname, struct i802_bss *bss, enum wpa_alg alg, const u8 *addr, int key_idx, int set_tx, @@ -11040,6 +11078,7 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { .get_bssid = wpa_driver_nl80211_get_bssid, .get_ssid = wpa_driver_nl80211_get_ssid, .set_key = driver_nl80211_set_key, + .tx_control_port = driver_nl80211_tx_control_port, .scan2 = driver_nl80211_scan2, .sched_scan = wpa_driver_nl80211_sched_scan, .stop_sched_scan = wpa_driver_nl80211_stop_sched_scan, diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c index 8318b10ab..5321f0d2f 100644 --- a/src/drivers/driver_nl80211_capa.c +++ b/src/drivers/driver_nl80211_capa.c @@ -433,6 +433,10 @@ static void wiphy_info_ext_feature_flags(struct wiphy_info_data *info, if (ext_feature_isset(ext_features, len, NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER)) capa->flags |= WPA_DRIVER_FLAGS_FTM_RESPONDER; + + if (ext_feature_isset(ext_features, len, + NL80211_EXT_FEATURE_CONTROL_PORT_OVER_NL80211)) + capa->flags |= WPA_DRIVER_FLAGS_TX_CONTROL_PORT; } diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c index c929e8194..ef3c64cef 100644 --- a/src/rsn_supp/wpa.c +++ b/src/rsn_supp/wpa.c @@ -158,7 +158,7 @@ int wpa_eapol_key_send(struct wpa_sm *sm, struct wpa_ptk *ptk, } wpa_hexdump(MSG_MSGDUMP, "WPA: TX EAPOL-Key", msg, msg_len); - ret = wpa_sm_ether_send(sm, dest, proto, msg, msg_len); + ret = wpa_sm_tx_control_port(sm, dest, proto, msg, msg_len); eapol_sm_notify_tx_eapol_key(sm->eapol); out: os_free(msg); diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h index ae9cd6484..145d57988 100644 --- a/src/rsn_supp/wpa.h +++ b/src/rsn_supp/wpa.h @@ -33,6 +33,8 @@ struct wpa_sm_ctx { const u8 *key, size_t key_len); void * (*get_network_ctx)(void *ctx); int (*get_bssid)(void *ctx, u8 *bssid); + int (*tx_control_port)(void *ctx, const u8 *dest, u16 proto, + const u8 *buf, size_t len); int (*ether_send)(void *ctx, const u8 *dest, u16 proto, const u8 *buf, size_t len); int (*get_beacon_ie)(void *ctx); diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h index d86734b0d..908af3d25 100644 --- a/src/rsn_supp/wpa_i.h +++ b/src/rsn_supp/wpa_i.h @@ -216,6 +216,13 @@ static inline int wpa_sm_get_bssid(struct wpa_sm *sm, u8 *bssid) return sm->ctx->get_bssid(sm->ctx->ctx, bssid); } +static inline int wpa_sm_tx_control_port(struct wpa_sm *sm, const u8 *dest, + u16 proto, const u8 *buf, size_t len) +{ + WPA_ASSERT(sm->ctx->tx_control_port); + return sm->ctx->tx_control_port(sm->ctx->ctx, dest, proto, buf, len); +} + static inline int wpa_sm_ether_send(struct wpa_sm *sm, const u8 *dest, u16 proto, const u8 *buf, size_t len) { diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index cf9972a6b..45b18fd05 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -148,6 +148,14 @@ static inline int wpa_drv_get_ssid(struct wpa_supplicant *wpa_s, u8 *ssid) return -1; } +static inline int wpa_drv_tx_control_port(struct wpa_supplicant *wpa_s, + const u8 *dest, + u16 proto, const u8 *buf, size_t len) +{ + return wpa_s->driver->tx_control_port(wpa_s->drv_priv, + dest, proto, buf, len); +} + static inline int wpa_drv_set_key(struct wpa_supplicant *wpa_s, enum wpa_alg alg, const u8 *addr, int key_idx, int set_tx, diff --git a/wpa_supplicant/ibss_rsn.c b/wpa_supplicant/ibss_rsn.c index 6934c4725..91354fcbc 100644 --- a/wpa_supplicant/ibss_rsn.c +++ b/wpa_supplicant/ibss_rsn.c @@ -76,6 +76,23 @@ static int supp_ether_send(void *ctx, const u8 *dest, u16 proto, const u8 *buf, } +static int supp_tx_control_port(void *ctx, const u8 *dest, u16 proto, + const u8 *buf, size_t len) +{ + struct ibss_rsn_peer *peer = ctx; + struct wpa_supplicant *wpa_s = peer->ibss_rsn->wpa_s; + + wpa_printf(MSG_DEBUG, "SUPP: %s(dest=" MACSTR " proto=0x%04x " + "len=%lu)", + __func__, MAC2STR(dest), proto, (unsigned long) len); + + if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_TX_CONTROL_PORT) + return wpa_drv_tx_control_port(wpa_s, dest, proto, buf, len); + else + return supp_ether_send(wpa_s, dest, proto, buf, len); +} + + static u8 * supp_alloc_eapol(void *ctx, u8 type, const void *data, u16 data_len, size_t *msg_len, void **data_pos) { @@ -211,6 +228,7 @@ static int ibss_rsn_supp_init(struct ibss_rsn_peer *peer, const u8 *own_addr, ctx->set_state = supp_set_state; ctx->get_state = supp_get_state; ctx->ether_send = supp_ether_send; + ctx->tx_control_port = supp_tx_control_port; ctx->get_beacon_ie = supp_get_beacon_ie; ctx->alloc_eapol = supp_alloc_eapol; ctx->set_key = supp_set_key; diff --git a/wpa_supplicant/wpas_glue.c b/wpa_supplicant/wpas_glue.c index 62af7f6b1..b584ca034 100644 --- a/wpa_supplicant/wpas_glue.c +++ b/wpa_supplicant/wpas_glue.c @@ -85,17 +85,9 @@ static u8 * wpa_alloc_eapol(const struct wpa_supplicant *wpa_s, u8 type, } -/** - * wpa_ether_send - Send Ethernet frame - * @wpa_s: Pointer to wpa_supplicant data - * @dest: Destination MAC address - * @proto: Ethertype in host byte order - * @buf: Frame payload starting from IEEE 802.1X header - * @len: Frame payload length - * Returns: >=0 on success, <0 on failure - */ -static int wpa_ether_send(struct wpa_supplicant *wpa_s, const u8 *dest, - u16 proto, const u8 *buf, size_t len) +static inline int ext_eapol_frame_io_notify_tx(struct wpa_supplicant *wpa_s, + const u8 *dest, u16 proto, + const u8 *buf, size_t len) { #ifdef CONFIG_TESTING_OPTIONS if (wpa_s->ext_eapol_frame_io && proto == ETH_P_EAPOL) { @@ -108,18 +100,60 @@ static int wpa_ether_send(struct wpa_supplicant *wpa_s, const u8 *dest, wpa_msg(wpa_s, MSG_INFO, "EAPOL-TX " MACSTR " %s", MAC2STR(dest), hex); os_free(hex); - return 0; + return -1; } #endif /* CONFIG_TESTING_OPTIONS */ + return 0; +} + +/** + * wpa_ether_send - Send Ethernet frame + * @wpa_s: Pointer to wpa_supplicant data + * @dest: Destination MAC address + * @proto: Ethertype in host byte order + * @buf: Frame payload starting from IEEE 802.1X header + * @len: Frame payload length + * Returns: >=0 on success, <0 on failure + */ +static int wpa_ether_send(struct wpa_supplicant *wpa_s, const u8 *dest, + u16 proto, const u8 *buf, size_t len) +{ + if (ext_eapol_frame_io_notify_tx(wpa_s, dest, proto, buf, len)) + return 0; + if (wpa_s->l2) { return l2_packet_send(wpa_s->l2, dest, proto, buf, len); } return -1; } -#endif /* IEEE8021X_EAPOL || !CONFIG_NO_WPA */ +/** + * wpa_supplicant_tx_control_port - Send Ethernet frame over 802.1X control port + * @wpa_s: Pointer to wpa_supplicant data + * @dest: Destination MAC address + * @proto: Ethertype in host byte order + * @buf: Frame payload starting from IEEE 802.1X header + * @len: Frame payload length + * Just like wpa_ether_send, but when this function is used the driver may be + * able to handle control port frames specially. + * Returns: >=0 on success, <0 on failure + */ +static int wpa_supplicant_tx_control_port(void *ctx, const u8 *dest, + u16 proto, const u8 *buf, size_t len) +{ + struct wpa_supplicant *wpa_s = ctx; + + if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_TX_CONTROL_PORT) { + if (ext_eapol_frame_io_notify_tx(wpa_s, dest, proto, buf, len)) + return 0; + return wpa_drv_tx_control_port(wpa_s, dest, proto, buf, len); + } else { + return wpa_ether_send(wpa_s, dest, proto, buf, len); + } +} +#endif /* IEEE8021X_EAPOL || !CONFIG_NO_WPA */ #ifdef IEEE8021X_EAPOL @@ -1211,6 +1245,7 @@ int wpa_supplicant_init_wpa(struct wpa_supplicant *wpa_s) ctx->get_network_ctx = wpa_supplicant_get_network_ctx; ctx->get_bssid = wpa_supplicant_get_bssid; ctx->ether_send = _wpa_ether_send; + ctx->tx_control_port = wpa_supplicant_tx_control_port; ctx->get_beacon_ie = wpa_supplicant_get_beacon_ie; ctx->alloc_eapol = _wpa_alloc_eapol; ctx->cancel_auth_timeout = _wpa_supplicant_cancel_auth_timeout; -- 2.22.1 _______________________________________________ Hostap mailing list Hostap@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/hostap