// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2022 - 2023 Intel Corporation */ #include #include #include "mvm.h" #include "fw/api/context.h" #include "fw/api/datapath.h" static u32 iwl_mvm_get_sec_sta_mask(struct iwl_mvm *mvm, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct ieee80211_key_conf *keyconf) { struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); struct iwl_mvm_vif_link_info *link_info = &mvmvif->deflink; lockdep_assert_held(&mvm->mutex); if (keyconf->link_id >= 0) { link_info = mvmvif->link[keyconf->link_id]; if (!link_info) return 0; } /* AP group keys are per link and should be on the mcast/bcast STA */ if (vif->type == NL80211_IFTYPE_AP && !(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { /* IGTK/BIGTK to bcast STA */ if (keyconf->keyidx >= 4) return BIT(link_info->bcast_sta.sta_id); /* GTK for data to mcast STA */ return BIT(link_info->mcast_sta.sta_id); } /* for client mode use the AP STA also for group keys */ if (!sta && vif->type == NL80211_IFTYPE_STATION) sta = mvmvif->ap_sta; /* During remove the STA was removed and the group keys come later * (which sounds like a bad sequence, but remember that to mac80211 the * group keys have no sta pointer), so we don't have a STA now. * Since this happens for group keys only, just use the link_info as * the group keys are per link; make sure that is the case by checking * we do have a link_id or are not doing MLO. * Of course the same can be done during add as well, but we must do * it during remove, since we don't have the mvmvif->ap_sta pointer. */ if (!sta && (keyconf->link_id >= 0 || !ieee80211_vif_is_mld(vif))) return BIT(link_info->ap_sta_id); /* STA should be non-NULL now, but iwl_mvm_sta_fw_id_mask() checks */ /* pass link_id to filter by it if not -1 (GTK on client) */ return iwl_mvm_sta_fw_id_mask(mvm, sta, keyconf->link_id); } u32 iwl_mvm_get_sec_flags(struct iwl_mvm *mvm, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct ieee80211_key_conf *keyconf) { struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); u32 flags = 0; lockdep_assert_held(&mvm->mutex); if (!(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE)) flags |= IWL_SEC_KEY_FLAG_MCAST_KEY; switch (keyconf->cipher) { case WLAN_CIPHER_SUITE_WEP104: flags |= IWL_SEC_KEY_FLAG_KEY_SIZE; fallthrough; case WLAN_CIPHER_SUITE_WEP40: flags |= IWL_SEC_KEY_FLAG_CIPHER_WEP; break; case WLAN_CIPHER_SUITE_TKIP: flags |= IWL_SEC_KEY_FLAG_CIPHER_TKIP; break; case WLAN_CIPHER_SUITE_AES_CMAC: case WLAN_CIPHER_SUITE_CCMP: flags |= IWL_SEC_KEY_FLAG_CIPHER_CCMP; break; case WLAN_CIPHER_SUITE_GCMP_256: case WLAN_CIPHER_SUITE_BIP_GMAC_256: flags |= IWL_SEC_KEY_FLAG_KEY_SIZE; fallthrough; case WLAN_CIPHER_SUITE_GCMP: case WLAN_CIPHER_SUITE_BIP_GMAC_128: flags |= IWL_SEC_KEY_FLAG_CIPHER_GCMP; break; } if (!sta && vif->type == NL80211_IFTYPE_STATION) sta = mvmvif->ap_sta; /* Set the MFP flag also for an AP interface where the key is an IGTK * key as in such a case the station would always be NULL */ if ((!IS_ERR_OR_NULL(sta) && sta->mfp) || (vif->type == NL80211_IFTYPE_AP && (keyconf->keyidx == 4 || keyconf->keyidx == 5))) flags |= IWL_SEC_KEY_FLAG_MFP; return flags; } struct iwl_mvm_sta_key_update_data { struct ieee80211_sta *sta; u32 old_sta_mask; u32 new_sta_mask; int err; }; static void iwl_mvm_mld_update_sta_key(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct ieee80211_key_conf *key, void *_data) { u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, SEC_KEY_CMD); struct iwl_mvm_sta_key_update_data *data = _data; struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); struct iwl_sec_key_cmd cmd = { .action = cpu_to_le32(FW_CTXT_ACTION_MODIFY), .u.modify.old_sta_mask = cpu_to_le32(data->old_sta_mask), .u.modify.new_sta_mask = cpu_to_le32(data->new_sta_mask), .u.modify.key_id = cpu_to_le32(key->keyidx), .u.modify.key_flags = cpu_to_le32(iwl_mvm_get_sec_flags(mvm, vif, sta, key)), }; int err; /* only need to do this for pairwise keys (link_id == -1) */ if (sta != data->sta || key->link_id >= 0) return; err = iwl_mvm_send_cmd_pdu(mvm, cmd_id, CMD_ASYNC, sizeof(cmd), &cmd); if (err) data->err = err; } int iwl_mvm_mld_update_sta_keys(struct iwl_mvm *mvm, struct ieee80211_vif *vif, struct ieee80211_sta *sta, u32 old_sta_mask, u32 new_sta_mask) { struct iwl_mvm_sta_key_update_data data = { .sta = sta, .old_sta_mask = old_sta_mask, .new_sta_mask = new_sta_mask, }; ieee80211_iter_keys_rcu(mvm->hw, vif, iwl_mvm_mld_update_sta_key, &data); return data.err; } static int __iwl_mvm_sec_key_del(struct iwl_mvm *mvm, u32 sta_mask, u32 key_flags, u32 keyidx, u32 flags) { u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, SEC_KEY_CMD); struct iwl_sec_key_cmd cmd = { .action = cpu_to_le32(FW_CTXT_ACTION_REMOVE), .u.remove.sta_mask = cpu_to_le32(sta_mask), .u.remove.key_id = cpu_to_le32(keyidx), .u.remove.key_flags = cpu_to_le32(key_flags), }; return iwl_mvm_send_cmd_pdu(mvm, cmd_id, flags, sizeof(cmd), &cmd); } int iwl_mvm_mld_send_key(struct iwl_mvm *mvm, u32 sta_mask, u32 key_flags, struct ieee80211_key_conf *keyconf) { u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, SEC_KEY_CMD); struct iwl_sec_key_cmd cmd = { .action = cpu_to_le32(FW_CTXT_ACTION_ADD), .u.add.sta_mask = cpu_to_le32(sta_mask), .u.add.key_id = cpu_to_le32(keyconf->keyidx), .u.add.key_flags = cpu_to_le32(key_flags), .u.add.tx_seq = cpu_to_le64(atomic64_read(&keyconf->tx_pn)), }; int max_key_len = sizeof(cmd.u.add.key); int ret; if (keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 || keyconf->cipher == WLAN_CIPHER_SUITE_WEP104) max_key_len -= IWL_SEC_WEP_KEY_OFFSET; if (WARN_ON(keyconf->keylen > max_key_len)) return -EINVAL; if (WARN_ON(!sta_mask)) return -EINVAL; if (keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 || keyconf->cipher == WLAN_CIPHER_SUITE_WEP104) memcpy(cmd.u.add.key + IWL_SEC_WEP_KEY_OFFSET, keyconf->key, keyconf->keylen); else memcpy(cmd.u.add.key, keyconf->key, keyconf->keylen); if (keyconf->cipher == WLAN_CIPHER_SUITE_TKIP) { memcpy(cmd.u.add.tkip_mic_rx_key, keyconf->key + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY, 8); memcpy(cmd.u.add.tkip_mic_tx_key, keyconf->key + NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY, 8); } ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, sizeof(cmd), &cmd); if (ret) return ret; /* * For WEP, the same key is used for multicast and unicast so need to * upload it again. If this fails, remove the original as well. */ if (keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 || keyconf->cipher == WLAN_CIPHER_SUITE_WEP104) { cmd.u.add.key_flags ^= cpu_to_le32(IWL_SEC_KEY_FLAG_MCAST_KEY); ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, sizeof(cmd), &cmd); if (ret) __iwl_mvm_sec_key_del(mvm, sta_mask, key_flags, keyconf->keyidx, 0); } return ret; } int iwl_mvm_sec_key_add(struct iwl_mvm *mvm, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct ieee80211_key_conf *keyconf) { u32 sta_mask = iwl_mvm_get_sec_sta_mask(mvm, vif, sta, keyconf); u32 key_flags = iwl_mvm_get_sec_flags(mvm, vif, sta, keyconf); struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); struct iwl_mvm_vif_link_info *mvm_link = NULL; int ret; if (keyconf->keyidx == 4 || keyconf->keyidx == 5) { unsigned int link_id = 0; /* set to -1 for non-MLO right now */ if (keyconf->link_id >= 0) link_id = keyconf->link_id; mvm_link = mvmvif->link[link_id]; if (WARN_ON(!mvm_link)) return -EINVAL; if (mvm_link->igtk) { IWL_DEBUG_MAC80211(mvm, "remove old IGTK %d\n", mvm_link->igtk->keyidx); ret = iwl_mvm_sec_key_del(mvm, vif, sta, mvm_link->igtk); if (ret) IWL_ERR(mvm, "failed to remove old IGTK (ret=%d)\n", ret); } WARN_ON(mvm_link->igtk); } ret = iwl_mvm_mld_send_key(mvm, sta_mask, key_flags, keyconf); if (ret) return ret; if (mvm_link) mvm_link->igtk = keyconf; /* We don't really need this, but need it to be not invalid, * and if we switch links multiple times it might go to be * invalid when removed. */ keyconf->hw_key_idx = 0; return 0; } static int _iwl_mvm_sec_key_del(struct iwl_mvm *mvm, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct ieee80211_key_conf *keyconf, u32 flags) { u32 sta_mask = iwl_mvm_get_sec_sta_mask(mvm, vif, sta, keyconf); u32 key_flags = iwl_mvm_get_sec_flags(mvm, vif, sta, keyconf); struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); int ret; if (WARN_ON(!sta_mask)) return -EINVAL; if (keyconf->keyidx == 4 || keyconf->keyidx == 5) { struct iwl_mvm_vif_link_info *mvm_link; unsigned int link_id = 0; /* set to -1 for non-MLO right now */ if (keyconf->link_id >= 0) link_id = keyconf->link_id; mvm_link = mvmvif->link[link_id]; if (WARN_ON(!mvm_link)) return -EINVAL; if (mvm_link->igtk == keyconf) { /* no longer in HW - mark for later */ mvm_link->igtk->hw_key_idx = STA_KEY_IDX_INVALID; mvm_link->igtk = NULL; } } ret = __iwl_mvm_sec_key_del(mvm, sta_mask, key_flags, keyconf->keyidx, flags); if (ret) return ret; /* For WEP, delete the key again as unicast */ if (keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 || keyconf->cipher == WLAN_CIPHER_SUITE_WEP104) { key_flags ^= IWL_SEC_KEY_FLAG_MCAST_KEY; ret = __iwl_mvm_sec_key_del(mvm, sta_mask, key_flags, keyconf->keyidx, flags); } return ret; } int iwl_mvm_sec_key_del_pasn(struct iwl_mvm *mvm, struct ieee80211_vif *vif, u32 sta_mask, struct ieee80211_key_conf *keyconf) { u32 key_flags = iwl_mvm_get_sec_flags(mvm, vif, NULL, keyconf) | IWL_SEC_KEY_FLAG_MFP; if (WARN_ON(!sta_mask)) return -EINVAL; return __iwl_mvm_sec_key_del(mvm, sta_mask, key_flags, keyconf->keyidx, 0); } int iwl_mvm_sec_key_del(struct iwl_mvm *mvm, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct ieee80211_key_conf *keyconf) { return _iwl_mvm_sec_key_del(mvm, vif, sta, keyconf, 0); } static void iwl_mvm_sec_key_remove_ap_iter(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct ieee80211_key_conf *key, void *data) { struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); unsigned int link_id = (uintptr_t)data; if (key->hw_key_idx == STA_KEY_IDX_INVALID) return; if (sta) return; if (key->link_id >= 0 && key->link_id != link_id) return; _iwl_mvm_sec_key_del(mvm, vif, NULL, key, CMD_ASYNC); key->hw_key_idx = STA_KEY_IDX_INVALID; } void iwl_mvm_sec_key_remove_ap(struct iwl_mvm *mvm, struct ieee80211_vif *vif, struct iwl_mvm_vif_link_info *link, unsigned int link_id) { u32 sec_key_id = WIDE_ID(DATA_PATH_GROUP, SEC_KEY_CMD); u8 sec_key_ver = iwl_fw_lookup_cmd_ver(mvm->fw, sec_key_id, 0); if (WARN_ON_ONCE(vif->type != NL80211_IFTYPE_STATION || link->ap_sta_id == IWL_MVM_INVALID_STA)) return; if (!sec_key_ver) return; ieee80211_iter_keys_rcu(mvm->hw, vif, iwl_mvm_sec_key_remove_ap_iter, (void *)(uintptr_t)link_id); }