]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
AP MLD: Validate Link Reconfiguration Request frame after parsing
authorPooventhiran G <quic_pooventh@quicinc.com>
Mon, 16 Jun 2025 11:18:57 +0000 (16:48 +0530)
committerJouni Malinen <j@w1.fi>
Tue, 17 Jun 2025 20:46:49 +0000 (23:46 +0300)
When a Link Reconfiguration Request frame is successfully parsed and set
up with context, validate the request to verify links requested for
operation(s) are valid.

While at it, if accepting the request after current validation tends to
leave the ML Setup with no remaining links, recover from the situation
by rejecting a "delete" link request. Currently, the "delete" link
request with the lowest link ID will be rejected.

Reviewed-by: Rohan Dutta <quic_drohan@quicinc.com>
Signed-off-by: Pooventhiran G <quic_pooventh@quicinc.com>
src/ap/ieee802_11.c
src/ap/ieee802_11.h
src/ap/ieee802_11_eht.c
src/ap/wpa_auth.c
src/ap/wpa_auth.h

index 0e9c2364463c46a8e327e8729e6a1578f2103f18..d29fb29f5d23650276f43fab7b610d3f424bda0f 100644 (file)
@@ -4219,8 +4219,8 @@ static bool check_sa_query(struct hostapd_data *hapd, struct sta_info *sta,
 
 static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
                             const u8 *ies, size_t ies_len,
-                            struct ieee802_11_elems *elems, int reassoc,
-                            bool link)
+                            struct ieee802_11_elems *elems,
+                            enum link_parse_type type, bool link)
 {
        int resp;
        const u8 *wpa_ie;
@@ -4229,9 +4229,12 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
        struct hostapd_data *assoc_hapd;
        struct sta_info *assoc_sta = NULL;
 
-       resp = check_ssid(hapd, sta, elems->ssid, elems->ssid_len);
-       if (resp != WLAN_STATUS_SUCCESS)
-               return resp;
+       if (type != LINK_PARSE_RECONF) {
+               resp = check_ssid(hapd, sta, elems->ssid, elems->ssid_len);
+               if (resp != WLAN_STATUS_SUCCESS)
+                       return resp;
+       }
+
        resp = check_wmm(hapd, sta, elems->wmm, elems->wmm_len);
        if (resp != WLAN_STATUS_SUCCESS)
                return resp;
@@ -4346,6 +4349,15 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
        }
 #endif /* CONFIG_P2P */
 
+       /* Link Reconfiguration Request frame for add link operation will not
+        * have RSN and other security IEs. So, skip the checks.
+        */
+       if (type == LINK_PARSE_RECONF) {
+               wpa_printf(MSG_DEBUG,
+                          "MLD: Skip security IE checks for Link Reconfiguration request");
+               goto skip_wpa_ies;
+       }
+
        if ((hapd->conf->wpa & WPA_PROTO_RSN) && elems->rsn_ie) {
                wpa_ie = elems->rsn_ie;
                wpa_ie_len = elems->rsn_ie_len;
@@ -4458,7 +4470,7 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 
 #ifdef CONFIG_IEEE80211R_AP
                if (sta->auth_alg == WLAN_AUTH_FT) {
-                       if (!reassoc) {
+                       if (type != LINK_PARSE_REASSOC) {
                                wpa_printf(MSG_DEBUG, "FT: " MACSTR " tried "
                                           "to use association (not "
                                           "re-association) with FT auth_alg",
@@ -4577,6 +4589,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
        } else
                wpa_auth_sta_no_wpa(sta->wpa_sm);
 
+skip_wpa_ies:
+
 #ifdef CONFIG_P2P
        if (ies && ies_len)
                p2p_group_notif_assoc(hapd->p2p_group, sta->addr, ies, ies_len);
@@ -4631,7 +4645,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 #endif /* CONFIG_MBO */
 
 #if defined(CONFIG_FILS) && defined(CONFIG_OCV)
-       if (wpa_auth_uses_ocv(sta->wpa_sm) &&
+       if (type != LINK_PARSE_RECONF &&
+           wpa_auth_uses_ocv(sta->wpa_sm) &&
            (sta->auth_alg == WLAN_AUTH_FILS_SK ||
             sta->auth_alg == WLAN_AUTH_FILS_SK_PFS ||
             sta->auth_alg == WLAN_AUTH_FILS_PK)) {
@@ -4710,7 +4725,8 @@ static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 
 
 static int check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
-                          const u8 *ies, size_t ies_len, int reassoc)
+                          const u8 *ies, size_t ies_len,
+                          enum link_parse_type type)
 {
        struct ieee802_11_elems elems;
 
@@ -4721,8 +4737,7 @@ static int check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
                return WLAN_STATUS_UNSPECIFIED_FAILURE;
        }
 
-       return __check_assoc_ies(hapd, sta, ies, ies_len, &elems, reassoc,
-                                false);
+       return __check_assoc_ies(hapd, sta, ies, ies_len, &elems, type, false);
 }
 
 
@@ -4789,11 +4804,11 @@ out:
 }
 
 
-static int ieee80211_ml_process_link(struct hostapd_data *hapd,
-                                    struct sta_info *origin_sta,
-                                    struct mld_link_info *link,
-                                    const u8 *ies, size_t ies_len,
-                                    bool reassoc, bool offload)
+int ieee80211_ml_process_link(struct hostapd_data *hapd,
+                             struct sta_info *origin_sta,
+                             struct mld_link_info *link,
+                             const u8 *ies, size_t ies_len,
+                             enum link_parse_type type, bool offload)
 {
        struct ieee802_11_elems elems;
        struct wpabuf *mlbuf = NULL;
@@ -4825,23 +4840,27 @@ static int ieee80211_ml_process_link(struct hostapd_data *hapd,
                goto out;
        }
 
-       mlbuf = ieee802_11_defrag(elems.basic_mle, elems.basic_mle_len, true);
-       if (!mlbuf)
-               goto out;
+       if (type != LINK_PARSE_RECONF) {
+               mlbuf = ieee802_11_defrag(elems.basic_mle, elems.basic_mle_len,
+                                         true);
+               if (!mlbuf)
+                       goto out;
 
-       if (ieee802_11_parse_link_assoc_req(&elems, mlbuf, hapd->mld_link_id,
-                                           true) == ParseFailed) {
-               wpa_printf(MSG_DEBUG,
-                          "MLD: link: Failed to parse association request Multi-Link element");
-               status = WLAN_STATUS_UNSPECIFIED_FAILURE;
-               goto out;
+               if (ieee802_11_parse_link_assoc_req(&elems, mlbuf,
+                                                   hapd->mld_link_id, true) ==
+                   ParseFailed) {
+                       wpa_printf(MSG_DEBUG,
+                                  "MLD: link: Failed to parse association request Multi-Link element");
+                       status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+                       goto out;
+               }
        }
 
        sta->flags |= origin_sta->flags | WLAN_STA_ASSOC_REQ_OK;
        sta->mld_assoc_link_id = origin_sta->mld_assoc_link_id;
        ap_sta_set_mld(sta, true);
 
-       status = __check_assoc_ies(hapd, sta, NULL, 0, &elems, reassoc, true);
+       status = __check_assoc_ies(hapd, sta, NULL, 0, &elems, type, true);
        if (status != WLAN_STATUS_SUCCESS) {
                wpa_printf(MSG_DEBUG, "MLD: link: Element check failed");
                goto out;
@@ -4853,6 +4872,11 @@ static int ieee80211_ml_process_link(struct hostapd_data *hapd,
 
                li->resp_sta_profile = NULL;
                li->resp_sta_profile_len = 0;
+
+               if (type == LINK_PARSE_RECONF && i == hapd->mld_link_id) {
+                       os_memcpy(li->local_addr, hapd->own_addr, ETH_ALEN);
+                       os_memcpy(li->peer_addr, link->peer_addr, ETH_ALEN);
+               }
        }
 
        if (!offload) {
@@ -4891,13 +4915,14 @@ static int ieee80211_ml_process_link(struct hostapd_data *hapd,
 
        /* TODO: What other processing is required? */
 
-       if (!offload && add_associated_sta(hapd, sta, reassoc))
+       if (!offload &&
+           add_associated_sta(hapd, sta, type == LINK_PARSE_REASSOC))
                status = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
 out:
        wpabuf_free(mlbuf);
        link->status = status;
 
-       if (!offload)
+       if (!offload && type != LINK_PARSE_RECONF)
                ieee80211_ml_build_assoc_resp(hapd, link);
 
        wpa_printf(MSG_DEBUG, "MLD: link: status=%u", status);
@@ -4973,9 +4998,10 @@ int hostapd_process_assoc_ml_info(struct hostapd_data *hapd,
                        if (!offload)
                                ieee80211_ml_build_assoc_resp(hapd, link);
                } else {
-                       if (ieee80211_ml_process_link(bss, sta, link,
-                                                     ies, ies_len, reassoc,
-                                                     offload))
+                       if (ieee80211_ml_process_link(
+                                   bss, sta, link, ies, ies_len,
+                                   reassoc ? LINK_PARSE_REASSOC :
+                                   LINK_PARSE_ASSOC, offload))
                                ret = -1;
                }
        }
@@ -5870,7 +5896,8 @@ static void handle_assoc(struct hostapd_data *hapd,
 
        /* followed by SSID and Supported rates; and HT capabilities if 802.11n
         * is used */
-       resp = check_assoc_ies(hapd, sta, pos, left, reassoc);
+       resp = check_assoc_ies(hapd, sta, pos, left,
+                              reassoc ? LINK_PARSE_REASSOC : LINK_PARSE_ASSOC);
        if (resp != WLAN_STATUS_SUCCESS)
                goto fail;
 #ifdef CONFIG_IEEE80211R_AP
index 9ab276ee7e9abfe030502bfff877f806ea945927..befea10149dc1afffe5d1ab20080d0c2ff0ade95 100644 (file)
@@ -26,6 +26,15 @@ struct sae_pk;
 struct sae_pt;
 struct sae_password_entry;
 struct mld_info;
+struct mld_link_info;
+
+enum link_parse_type {
+       LINK_PARSE_ASSOC,
+       LINK_PARSE_REASSOC,
+       LINK_PARSE_RECONF,
+};
+
+#define LINK_RECONF_GROUP_KDE_MAX_LEN 255
 
 struct link_reconf_req_info {
        struct dl_list list;
@@ -295,6 +304,11 @@ int hostapd_process_assoc_ml_info(struct hostapd_data *hapd,
                                  bool offload);
 
 void ml_deinit_link_reconf_req(struct link_reconf_req_list **req_list_ptr);
+int ieee80211_ml_process_link(struct hostapd_data *hapd,
+                             struct sta_info *origin_sta,
+                             struct mld_link_info *link,
+                             const u8 *ies, size_t ies_len,
+                             enum link_parse_type type, bool offload);
 
 void ieee802_11_rx_protected_eht_action(struct hostapd_data *hapd,
                                        const struct ieee80211_mgmt *mgmt,
index a0e0522938060321b062da9a19797d77e741226d..c109c3242038fae47e0d53d2c4e52c324950ab10 100644 (file)
@@ -1532,6 +1532,85 @@ void ml_deinit_link_reconf_req(struct link_reconf_req_list **req_list_ptr)
 }
 
 
+static bool recover_from_zero_links(u16 *links_del_ok, u8 *recovery_link)
+{
+       u8 pos = 0;
+       u16 del_links;
+
+       del_links = *links_del_ok;
+
+       while (del_links) {
+               if (del_links & 1)
+                       break;
+               del_links >>= 1;
+               pos++;
+       }
+
+       /* No link found */
+       if (!del_links) {
+               wpa_printf(MSG_DEBUG,
+                          "MLD: Total valid links is 0 and no del-link found to reject for recovery");
+               return false;
+       }
+
+       *recovery_link = pos;
+       *links_del_ok &= ~BIT(*recovery_link);
+       wpa_printf(MSG_INFO,
+                  "MLD: Del-link request for link (%u) rejected to recover from no remaining links",
+                  *recovery_link);
+       return true;
+}
+
+
+static u16
+hostapd_ml_process_reconf_link(struct hostapd_data *hapd,
+                              struct sta_info *assoc_sta, const u8 *ies,
+                              size_t ies_len, u8 link_id, const u8 *link_addr)
+{
+       struct hostapd_data *lhapd, *other_hapd;
+       struct mld_link_info link;
+       struct sta_info *lsta, *other_sta;
+
+       lhapd = hostapd_mld_get_link_bss(hapd, link_id);
+       if (!lhapd) /* This cannot be NULL */
+               return WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+       os_memset(&link, 0, sizeof(link));
+
+       link.valid = 1;
+       os_memcpy(link.local_addr, lhapd->own_addr, ETH_ALEN);
+       os_memcpy(link.peer_addr, link_addr, ETH_ALEN);
+
+       /* Parse STA profile, check the IEs, and send ADD_LINK_STA */
+       ieee80211_ml_process_link(lhapd, assoc_sta, &link, ies, ies_len,
+                                 LINK_PARSE_RECONF, false);
+       if (link.status != WLAN_STATUS_SUCCESS)
+               return link.status;
+
+       lsta = ap_get_sta(lhapd, assoc_sta->addr);
+       if (!lsta)
+               return WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
+
+       for_each_mld_link(other_hapd, lhapd) {
+               struct mld_link_info *_link;
+
+               other_sta = ap_get_sta(other_hapd, lsta->addr);
+               if (!other_sta)
+                       continue;
+
+               _link = &other_sta->mld_info.links[link_id];
+               _link->valid = true;
+               _link->status = WLAN_STATUS_SUCCESS;
+               os_memcpy(_link->local_addr, other_hapd->own_addr, ETH_ALEN);
+               os_memcpy(_link->peer_addr, link_addr, ETH_ALEN);
+       }
+       wpa_auth_set_ml_info(lsta->wpa_sm, lsta->mld_assoc_link_id,
+                            &lsta->mld_info);
+
+       return WLAN_STATUS_SUCCESS;
+}
+
+
 /* Returns:
  * 0 = successful parsing
  * 1 = per-STA profile (subelement) skipped or rejected
@@ -1880,6 +1959,132 @@ fail:
 }
 
 
+static bool
+hostapd_validate_link_reconf_req(struct hostapd_data *hapd,
+                                struct sta_info *sta,
+                                struct link_reconf_req_list *req_list)
+{
+       struct hostapd_data *assoc_hapd;
+       struct link_reconf_req_info *info;
+       struct sta_info *assoc_sta;
+       struct mld_info *mld_info;
+       u8 recovery_link;
+       u16 valid_links = 0, links_add_ok = 0, links_del_ok = 0, status;
+       size_t link_kde_len, total_kde_len = 0;
+       int i;
+
+       assoc_sta = hostapd_ml_get_assoc_sta(hapd, sta, &assoc_hapd);
+       if (!assoc_sta)
+               return false;
+
+       if (dl_list_empty(&req_list->add_req) &&
+           dl_list_empty(&req_list->del_req)) {
+               wpa_printf(MSG_DEBUG, "MLD: No add or delete request found");
+               return false;
+       }
+
+       mld_info = &assoc_sta->mld_info;
+       for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+               if (mld_info->links[i].valid &&
+                   mld_info->links[i].status == WLAN_STATUS_SUCCESS)
+                       valid_links |= BIT(i);
+       }
+
+       /* Check IEs for add-link STA profiles */
+       dl_list_for_each(info, &req_list->add_req, struct link_reconf_req_info,
+                        list) {
+               wpa_printf(MSG_DEBUG,
+                          "MLD: Add Link Reconf STA for link id=%u status=%u",
+                          info->link_id, info->status);
+               if (info->status != WLAN_STATUS_SUCCESS ||
+                   info->sta_prof_len < 2)
+                       continue;
+
+               /* Offset 2 bytes for Capabilities in STA Profile */
+               status = hostapd_ml_process_reconf_link(hapd, assoc_sta,
+                                                       info->sta_prof + 2,
+                                                       info->sta_prof_len - 2,
+                                                       info->link_id,
+                                                       info->peer_addr);
+               if (status != WLAN_STATUS_SUCCESS) {
+                       wpa_printf(MSG_DEBUG,
+                                  "MLD: Add link IE validation failed for link=%u",
+                                  info->link_id);
+                       info->status = status;
+                       continue;
+               }
+
+               link_kde_len = wpa_auth_ml_group_kdes_len(assoc_sta->wpa_sm,
+                                                         BIT(info->link_id));
+
+               /* Since Group KDE element Length subfield is one byte,
+                * accept as many add-link requests as can be fit.
+                */
+               if (total_kde_len + link_kde_len >
+                   LINK_RECONF_GROUP_KDE_MAX_LEN) {
+                       wpa_printf(MSG_INFO,
+                                  "MLD: Group KDEs cannot fit (%zu > %u) for link=%u",
+                                  total_kde_len + link_kde_len,
+                                  LINK_RECONF_GROUP_KDE_MAX_LEN,
+                                  info->link_id);
+                       status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+               } else {
+                       total_kde_len += link_kde_len;
+                       links_add_ok |= BIT(info->link_id);
+               }
+
+               info->status = status;
+       }
+
+       dl_list_for_each(info, &req_list->del_req, struct link_reconf_req_info,
+                       list) {
+               wpa_printf(MSG_DEBUG,
+                          "MLD: Del Link Reconf STA for link id=%u status=%u",
+                          info->link_id, info->status);
+               if (info->status == WLAN_STATUS_SUCCESS)
+                       links_del_ok |= BIT(info->link_id);
+       }
+
+       wpa_printf(MSG_INFO, "MLD: valid_links=0x%x add_ok=0x%x del_ok=0x%x",
+                  valid_links, links_add_ok, links_del_ok);
+
+       if ((links_add_ok && (valid_links & links_add_ok)) ||
+           (links_del_ok && !(valid_links & links_del_ok))) {
+               wpa_printf(MSG_INFO,
+                          "MLD: Links requested failed to satisfy valid links");
+               return false;
+       }
+
+       if (links_add_ok & links_del_ok) {
+               wpa_printf(MSG_INFO,
+                          "MLD: Links (0x%x) present in both valid add and delete requests",
+                          links_add_ok & links_del_ok);
+               return false;
+       }
+
+       valid_links |= links_add_ok;
+       valid_links &= ~links_del_ok;
+       if (!valid_links) {
+               if (!recover_from_zero_links(&links_del_ok, &recovery_link)) {
+                       wpa_printf(MSG_INFO,
+                                  "MLD: Total-links validation failed");
+                       return false;
+               }
+               /* Add the recovery link back to valid_links */
+               valid_links |= BIT(recovery_link);
+       }
+
+       req_list->new_valid_links = valid_links;
+       req_list->links_add_ok = links_add_ok;
+       req_list->links_del_ok = links_del_ok;
+
+       /* TODO: Add support to handle multiple requests from the non-AP MLD */
+       assoc_sta->reconf_req = req_list;
+
+       return true;
+}
+
+
 static int
 hostapd_handle_link_reconf_req(struct hostapd_data *hapd, const u8 *buf,
                               size_t len)
@@ -1996,8 +2201,11 @@ hostapd_handle_link_reconf_req(struct hostapd_data *hapd, const u8 *buf,
        }
 
 skip_oci_validation:
+       /* Do STA profile validation */
+       if (!hostapd_validate_link_reconf_req(hapd, assoc_sta, req_list))
+               goto out;
+
        req_list->dialog_token = dialog_token;
-       assoc_sta->reconf_req = req_list;
        ret = 0;
 
 out:
index 3f82bae7bb378d77d9496c2a9cc7d941a7d7a9b9..ee6f46d21cde29e40ba2a4ef9a72938351ffb532 100644 (file)
@@ -4354,8 +4354,7 @@ static void wpa_auth_get_ml_key_info(struct wpa_authenticator *wpa_auth,
 }
 
 
-static size_t wpa_auth_ml_group_kdes_len(struct wpa_state_machine *sm,
-                                        u16 req_links)
+size_t wpa_auth_ml_group_kdes_len(struct wpa_state_machine *sm, u16 req_links)
 {
        struct wpa_authenticator *wpa_auth;
        size_t kde_len = 0;
index a98a0448721a8e86ae452a1e612ff26342f566bc..d3ea6eeb4f7fd3a210a6f6d4b071cbff7718b481 100644 (file)
@@ -687,6 +687,8 @@ void wpa_auth_ml_get_key_info(struct wpa_authenticator *a,
 void wpa_release_link_auth_ref(struct wpa_state_machine *sm, u8 link_id,
                               bool rejected);
 
+size_t wpa_auth_ml_group_kdes_len(struct wpa_state_machine *sm, u16 req_links);
+
 #define for_each_sm_auth(sm, link_id) \
        for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++)       \
                if (sm->mld_links[link_id].valid &&                     \