Commit 42fe1e51 authored by Ahmad Masri's avatar Ahmad Masri Committed by Kalle Valo
Browse files

wil6210: fix PTK re-key race



Fix a race between cfg80211 add_key call and transmitting of 4/4 EAP
packet. In case the transmit is delayed until after the add key takes
place, message 4/4 will be encrypted with the new key, and the
receiver side (AP) will drop it due to MIC error.

Wil6210 will monitor and look for the transmitted packet 4/4 eap key.
In case add_key takes place before the transmission completed, then
wil6210 will let the FW store the key and wil6210 will notify the FW
to use the PTK key only after 4/4 eap packet transmission was
completed.

Signed-off-by: default avatarAhmad Masri <amasri@codeaurora.org>
Signed-off-by: default avatarMaya Erez <merez@codeaurora.org>
Signed-off-by: default avatarKalle Valo <kvalo@codeaurora.org>
parent 977c45ab
Loading
Loading
Loading
Loading
+14 −1
Original line number Diff line number Diff line
@@ -331,6 +331,8 @@ static const char * const key_usage_str[] = {
	[WMI_KEY_USE_PAIRWISE]	= "PTK",
	[WMI_KEY_USE_RX_GROUP]	= "RX_GTK",
	[WMI_KEY_USE_TX_GROUP]	= "TX_GTK",
	[WMI_KEY_USE_STORE_PTK]	= "STORE_PTK",
	[WMI_KEY_USE_APPLY_PTK]	= "APPLY_PTK",
};

int wil_iftype_nl2wmi(enum nl80211_iftype type)
@@ -542,7 +544,7 @@ static int wil_cfg80211_get_station(struct wiphy *wiphy,
/*
 * Find @idx-th active STA for specific MID for station dump.
 */
static int wil_find_cid_by_idx(struct wil6210_priv *wil, u8 mid, int idx)
int wil_find_cid_by_idx(struct wil6210_priv *wil, u8 mid, int idx)
{
	int i;

@@ -1554,6 +1556,7 @@ void wil_set_crypto_rx(u8 key_index, enum wmi_key_usage key_usage,
		return;

	switch (key_usage) {
	case WMI_KEY_USE_STORE_PTK:
	case WMI_KEY_USE_PAIRWISE:
		for (tid = 0; tid < WIL_STA_TID_NUM; tid++) {
			cc = &cs->tid_crypto_rx[tid].key_id[key_index];
@@ -1651,6 +1654,16 @@ static int wil_cfg80211_add_key(struct wiphy *wiphy,
		return -EINVAL;
	}

	spin_lock_bh(&wil->eap_lock);
	if (pairwise && wdev->iftype == NL80211_IFTYPE_STATION &&
	    (vif->ptk_rekey_state == WIL_REKEY_M3_RECEIVED ||
	     vif->ptk_rekey_state == WIL_REKEY_WAIT_M4_SENT)) {
		key_usage = WMI_KEY_USE_STORE_PTK;
		vif->ptk_rekey_state = WIL_REKEY_WAIT_M4_SENT;
		wil_dbg_misc(wil, "Store EAPOL key\n");
	}
	spin_unlock_bh(&wil->eap_lock);

	rc = wmi_add_cipher_key(vif, key_index, mac_addr, params->key_len,
				params->key, key_usage);
	if (!rc && !IS_ERR(cs)) {
+4 −0
Original line number Diff line number Diff line
@@ -373,6 +373,7 @@ static void _wil6210_disconnect_complete(struct wil6210_vif *vif,
		}
		clear_bit(wil_vif_fwconnecting, vif->status);
		clear_bit(wil_vif_ft_roam, vif->status);
		vif->ptk_rekey_state = WIL_REKEY_IDLE;

		break;
	case NL80211_IFTYPE_AP:
@@ -724,6 +725,8 @@ int wil_priv_init(struct wil6210_priv *wil)
	INIT_LIST_HEAD(&wil->pending_wmi_ev);
	spin_lock_init(&wil->wmi_ev_lock);
	spin_lock_init(&wil->net_queue_lock);
	spin_lock_init(&wil->eap_lock);

	init_waitqueue_head(&wil->wq);
	init_rwsem(&wil->mem_lock);

@@ -1654,6 +1657,7 @@ int wil_reset(struct wil6210_priv *wil, bool load_fw)
			cancel_work_sync(&vif->disconnect_worker);
			wil6210_disconnect(vif, NULL,
					   WLAN_REASON_DEAUTH_LEAVING);
			vif->ptk_rekey_state = WIL_REKEY_IDLE;
		}
	}
	wil_bcast_fini_all(wil);
+3 −0
Original line number Diff line number Diff line
@@ -218,6 +218,7 @@ static void wil_vif_deinit(struct wil6210_vif *vif)
	cancel_work_sync(&vif->p2p.delayed_listen_work);
	wil_probe_client_flush(vif);
	cancel_work_sync(&vif->probe_client_worker);
	cancel_work_sync(&vif->enable_tx_key_worker);
}

void wil_vif_free(struct wil6210_vif *vif)
@@ -284,6 +285,7 @@ static void wil_vif_init(struct wil6210_vif *vif)
	INIT_WORK(&vif->probe_client_worker, wil_probe_client_worker);
	INIT_WORK(&vif->disconnect_worker, wil_disconnect_worker);
	INIT_WORK(&vif->p2p.delayed_listen_work, wil_p2p_delayed_listen_work);
	INIT_WORK(&vif->enable_tx_key_worker, wil_enable_tx_key_worker);

	INIT_LIST_HEAD(&vif->probe_client_pending);

@@ -540,6 +542,7 @@ void wil_vif_remove(struct wil6210_priv *wil, u8 mid)
	cancel_work_sync(&vif->disconnect_worker);
	wil_probe_client_flush(vif);
	cancel_work_sync(&vif->probe_client_worker);
	cancel_work_sync(&vif->enable_tx_key_worker);
	/* for VIFs, ndev will be freed by destructor after RTNL is unlocked.
	 * the main interface will be freed in wil_if_free, we need to keep it
	 * a bit longer so logging macros will work.
+184 −0
Original line number Diff line number Diff line
@@ -724,6 +724,182 @@ static void wil_get_netif_rx_params(struct sk_buff *skb, int *cid,
	*security = wil_rxdesc_security(d);
}

/*
 * Check if skb is ptk eapol key message
 *
 * returns a pointer to the start of the eapol key structure, NULL
 * if frame is not PTK eapol key
 */
static struct wil_eapol_key *wil_is_ptk_eapol_key(struct wil6210_priv *wil,
						  struct sk_buff *skb)
{
	u8 *buf;
	const struct wil_1x_hdr *hdr;
	struct wil_eapol_key *key;
	u16 key_info;
	int len = skb->len;

	if (!skb_mac_header_was_set(skb)) {
		wil_err(wil, "mac header was not set\n");
		return NULL;
	}

	len -= skb_mac_offset(skb);

	if (len < sizeof(struct ethhdr) + sizeof(struct wil_1x_hdr) +
	    sizeof(struct wil_eapol_key))
		return NULL;

	buf = skb_mac_header(skb) + sizeof(struct ethhdr);

	hdr = (const struct wil_1x_hdr *)buf;
	if (hdr->type != WIL_1X_TYPE_EAPOL_KEY)
		return NULL;

	key = (struct wil_eapol_key *)(buf + sizeof(struct wil_1x_hdr));
	if (key->type != WIL_EAPOL_KEY_TYPE_WPA &&
	    key->type != WIL_EAPOL_KEY_TYPE_RSN)
		return NULL;

	key_info = be16_to_cpu(key->key_info);
	if (!(key_info & WIL_KEY_INFO_KEY_TYPE)) /* check if pairwise */
		return NULL;

	return key;
}

static bool wil_skb_is_eap_3(struct wil6210_priv *wil, struct sk_buff *skb)
{
	struct wil_eapol_key *key;
	u16 key_info;

	key = wil_is_ptk_eapol_key(wil, skb);
	if (!key)
		return false;

	key_info = be16_to_cpu(key->key_info);
	if (key_info & (WIL_KEY_INFO_MIC |
			WIL_KEY_INFO_ENCR_KEY_DATA)) {
		/* 3/4 of 4-Way Handshake */
		wil_dbg_misc(wil, "EAPOL key message 3\n");
		return true;
	}
	/* 1/4 of 4-Way Handshake */
	wil_dbg_misc(wil, "EAPOL key message 1\n");

	return false;
}

static bool wil_skb_is_eap_4(struct wil6210_priv *wil, struct sk_buff *skb)
{
	struct wil_eapol_key *key;
	u32 *nonce, i;

	key = wil_is_ptk_eapol_key(wil, skb);
	if (!key)
		return false;

	nonce = (u32 *)key->key_nonce;
	for (i = 0; i < WIL_EAP_NONCE_LEN / sizeof(u32); i++, nonce++) {
		if (*nonce != 0) {
			/* message 2/4 */
			wil_dbg_misc(wil, "EAPOL key message 2\n");
			return false;
		}
	}
	wil_dbg_misc(wil, "EAPOL key message 4\n");

	return true;
}

void wil_enable_tx_key_worker(struct work_struct *work)
{
	struct wil6210_vif *vif = container_of(work,
			struct wil6210_vif, enable_tx_key_worker);
	struct wil6210_priv *wil = vif_to_wil(vif);
	int rc, cid;

	rtnl_lock();
	if (vif->ptk_rekey_state != WIL_REKEY_WAIT_M4_SENT) {
		wil_dbg_misc(wil, "Invalid rekey state = %d\n",
			     vif->ptk_rekey_state);
		rtnl_unlock();
		return;
	}

	cid =  wil_find_cid_by_idx(wil, vif->mid, 0);
	if (!wil_cid_valid(wil, cid)) {
		wil_err(wil, "Invalid cid = %d\n", cid);
		rtnl_unlock();
		return;
	}

	wil_dbg_misc(wil, "Apply PTK key after eapol was sent out\n");
	rc = wmi_add_cipher_key(vif, 0, wil->sta[cid].addr, 0, NULL,
				WMI_KEY_USE_APPLY_PTK);

	vif->ptk_rekey_state = WIL_REKEY_IDLE;
	rtnl_unlock();

	if (rc)
		wil_err(wil, "Apply PTK key failed %d\n", rc);
}

void wil_tx_complete_handle_eapol(struct wil6210_vif *vif, struct sk_buff *skb)
{
	struct wil6210_priv *wil = vif_to_wil(vif);
	struct wireless_dev *wdev = vif_to_wdev(vif);
	bool q = false;

	if (wdev->iftype != NL80211_IFTYPE_STATION ||
	    !test_bit(WMI_FW_CAPABILITY_SPLIT_REKEY, wil->fw_capabilities))
		return;

	/* check if skb is an EAP message 4/4 */
	if (!wil_skb_is_eap_4(wil, skb))
		return;

	spin_lock_bh(&wil->eap_lock);
	switch (vif->ptk_rekey_state) {
	case WIL_REKEY_IDLE:
		/* ignore idle state, can happen due to M4 retransmission */
		break;
	case WIL_REKEY_M3_RECEIVED:
		vif->ptk_rekey_state = WIL_REKEY_IDLE;
		break;
	case WIL_REKEY_WAIT_M4_SENT:
		q = true;
		break;
	default:
		wil_err(wil, "Unknown rekey state = %d",
			vif->ptk_rekey_state);
	}
	spin_unlock_bh(&wil->eap_lock);

	if (q) {
		q = queue_work(wil->wmi_wq, &vif->enable_tx_key_worker);
		wil_dbg_misc(wil, "queue_work of enable_tx_key_worker -> %d\n",
			     q);
	}
}

static void wil_rx_handle_eapol(struct wil6210_vif *vif, struct sk_buff *skb)
{
	struct wil6210_priv *wil = vif_to_wil(vif);
	struct wireless_dev *wdev = vif_to_wdev(vif);

	if (wdev->iftype != NL80211_IFTYPE_STATION ||
	    !test_bit(WMI_FW_CAPABILITY_SPLIT_REKEY, wil->fw_capabilities))
		return;

	/* check if skb is a EAP message 3/4 */
	if (!wil_skb_is_eap_3(wil, skb))
		return;

	if (vif->ptk_rekey_state == WIL_REKEY_IDLE)
		vif->ptk_rekey_state = WIL_REKEY_M3_RECEIVED;
}

/*
 * Pass Rx packet to the netif. Update statistics.
 * Called in softirq context (NAPI poll).
@@ -796,6 +972,10 @@ void wil_netif_rx(struct sk_buff *skb, struct net_device *ndev, int cid,
	if (skb) { /* deliver to local stack */
		skb->protocol = eth_type_trans(skb, ndev);
		skb->dev = ndev;

		if (skb->protocol == cpu_to_be16(ETH_P_PAE))
			wil_rx_handle_eapol(vif, skb);

		if (gro)
			rc = napi_gro_receive(&wil->napi_rx, skb);
		else
@@ -2332,6 +2512,10 @@ int wil_tx_complete(struct wil6210_vif *vif, int ringid)
					if (stats)
						stats->tx_errors++;
				}

				if (skb->protocol == cpu_to_be16(ETH_P_PAE))
					wil_tx_complete_handle_eapol(vif, skb);

				wil_consume_skb(skb, d->dma.error == 0);
			}
			memset(ctx, 0, sizeof(*ctx));
+40 −0
Original line number Diff line number Diff line
@@ -423,6 +423,46 @@ struct vring_rx_mac {
#define RX_DMA_STATUS_PHY_INFO	BIT(6)
#define RX_DMA_STATUS_FFM	BIT(7) /* EtherType Flex Filter Match */

/* IEEE 802.11, 8.5.2 EAPOL-Key frames */
#define WIL_KEY_INFO_KEY_TYPE BIT(3) /* val of 1 = Pairwise, 0 = Group key */

#define WIL_KEY_INFO_MIC BIT(8)
#define WIL_KEY_INFO_ENCR_KEY_DATA BIT(12) /* for rsn only */

#define WIL_EAP_NONCE_LEN 32
#define WIL_EAP_KEY_RSC_LEN 8
#define WIL_EAP_REPLAY_COUNTER_LEN 8
#define WIL_EAP_KEY_IV_LEN 16
#define WIL_EAP_KEY_ID_LEN 8

enum {
	WIL_1X_TYPE_EAP_PACKET = 0,
	WIL_1X_TYPE_EAPOL_START = 1,
	WIL_1X_TYPE_EAPOL_LOGOFF = 2,
	WIL_1X_TYPE_EAPOL_KEY = 3,
};

#define WIL_EAPOL_KEY_TYPE_RSN 2
#define WIL_EAPOL_KEY_TYPE_WPA 254

struct wil_1x_hdr {
	u8 version;
	u8 type;
	__be16 length;
	/* followed by data */
} __packed;

struct wil_eapol_key {
	u8 type;
	__be16 key_info;
	__be16 key_length;
	u8 replay_counter[WIL_EAP_REPLAY_COUNTER_LEN];
	u8 key_nonce[WIL_EAP_NONCE_LEN];
	u8 key_iv[WIL_EAP_KEY_IV_LEN];
	u8 key_rsc[WIL_EAP_KEY_RSC_LEN];
	u8 key_id[WIL_EAP_KEY_ID_LEN];
} __packed;

struct vring_rx_dma {
	u32 d0;
	struct wil_ring_dma_addr addr;
Loading