From 8759e9116aa6c18d1f68caf1b5bc1cd338f6c142 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Fri, 3 Jan 2020 16:17:41 +0100 Subject: [PATCH] nl80211: Control port over nl80211 helpers Linux kernel v4.17 added the ability to request sending controlled port frames (e.g., IEEE 802.1X controlled port EAPOL 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 the specific 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. Add a CONTROL_PORT flag to the hostap driver API to report driver capability for using a separate control port for EAPOL frames. 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 support for NL80211_EXT_FEATURE_CONTROL_PORT_OVER_NL80211. Also add a driver op tx_control_port() for request a frame to be sent over the controlled port. Signed-off-by: Brendan Jackman --- src/drivers/driver.h | 27 ++++++++++++++++++++++++++ src/drivers/driver_nl80211.c | 32 +++++++++++++++++++++++++++++++ src/drivers/driver_nl80211_capa.c | 4 ++++ wpa_supplicant/driver_i.h | 10 ++++++++++ 4 files changed, 73 insertions(+) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 08c82a0fb..09867f2cd 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -1704,6 +1704,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 supports a separate control port for EAPOL frames */ +#define WPA_DRIVER_FLAGS_CONTROL_PORT 0x0400000000000000ULL u64 flags; #define FULL_AP_CLIENT_STATE_SUPP(drv_flags) \ @@ -2869,6 +2871,31 @@ struct wpa_driver_ops { int (*read_sta_data)(void *priv, struct hostap_sta_driver_data *data, const u8 *addr); + /** + * 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 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 management operations such as key + * installations. This can be used to work around known limitations in + * IEEE 802.11 protocols such as race conditions between rekeying 4-way + * handshake message 4/4 and a PTK being overwritten. + * + * This function is only used for a given interface if the driver + * instance reports WPA_DRIVER_FLAGS_CONTROL_PORT capability. Otherwise, + * API users will 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); + /** * hapd_send_eapol - Send an EAPOL packet (AP only) * @priv: private driver interface data diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index e4bc44ffb..f940ac552 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -5092,6 +5092,37 @@ static void nl80211_teardown_ap(struct i802_bss *bss) } +static int 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; + int ret; + + wpa_printf(MSG_DEBUG, + "nl80211: Send over control port dest=" MACSTR + " proto=0x%04x len=%u", + MAC2STR(dest), proto, (unsigned int) len); + + msg = nl80211_bss_msg(bss, 0, NL80211_CMD_CONTROL_PORT_FRAME); + if (!msg || + nla_put_u16(msg, NL80211_ATTR_CONTROL_PORT_ETHERTYPE, proto) || + nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, dest) || + nla_put(msg, NL80211_ATTR_FRAME, len, buf)) { + nlmsg_free(msg); + return -ENOBUFS; + } + + 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; +} + + static int nl80211_send_eapol_data(struct i802_bss *bss, const u8 *addr, const u8 *data, size_t data_len) @@ -11232,6 +11263,7 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { .get_hw_feature_data = nl80211_get_hw_feature_data, .sta_add = wpa_driver_nl80211_sta_add, .sta_remove = driver_nl80211_sta_remove, + .tx_control_port = nl80211_tx_control_port, .hapd_send_eapol = wpa_driver_nl80211_hapd_send_eapol, .sta_set_flags = wpa_driver_nl80211_sta_set_flags, .sta_set_airtime_weight = driver_nl80211_sta_set_airtime_weight, diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c index 9a82cd1e5..e1cf9f7b1 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_CONTROL_PORT; } diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index f8d63a07b..d091c8761 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -342,6 +342,16 @@ static inline int wpa_drv_sta_remove(struct wpa_supplicant *wpa_s, 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) +{ + if (!wpa_s->driver->tx_control_port) + return -1; + return wpa_s->driver->tx_control_port(wpa_s->drv_priv, dest, proto, + buf, len); +} + static inline int wpa_drv_hapd_send_eapol(struct wpa_supplicant *wpa_s, const u8 *addr, const u8 *data, size_t data_len, int encrypt,