]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
Remove WPA per-VLAN groups when no more stations remain
authorMichael Braun <michael-dev@fami-braun.de>
Sun, 26 Apr 2015 12:22:55 +0000 (14:22 +0200)
committerJouni Malinen <j@w1.fi>
Sun, 26 Apr 2015 13:56:22 +0000 (16:56 +0300)
Previously, struct wpa_group was created when the first station enters
the group and the struct wpa_group was not freed when all station left
the group. This causes a problem because wpa_group will enter
FATAL_FAILURE when a wpa_group is running while the AP_VLAN interface
has already been removed.

Fix this by adding a reference counter to struct wpa_group and free a
group if it is unused.

Signed-off-by: Michael Braun <michael-dev@fami-braun.de>
src/ap/wpa_auth.c
src/ap/wpa_auth_i.h

index 35c554a108ee41cec8a38c6bdfd4394119b99a63..f23a57a09ed9ee3b7e1641f83f0c45103f9a4647 100644 (file)
@@ -45,6 +45,12 @@ static int wpa_group_config_group_keys(struct wpa_authenticator *wpa_auth,
                                       struct wpa_group *group);
 static int wpa_derive_ptk(struct wpa_state_machine *sm, const u8 *snonce,
                          const u8 *pmk, struct wpa_ptk *ptk);
+static void wpa_group_free(struct wpa_authenticator *wpa_auth,
+                          struct wpa_group *group);
+static void wpa_group_get(struct wpa_authenticator *wpa_auth,
+                         struct wpa_group *group);
+static void wpa_group_put(struct wpa_authenticator *wpa_auth,
+                         struct wpa_group *group);
 
 static const u32 dot11RSNAConfigGroupUpdateCount = 4;
 static const u32 dot11RSNAConfigPairwiseUpdateCount = 4;
@@ -262,15 +268,22 @@ static void wpa_rekey_gmk(void *eloop_ctx, void *timeout_ctx)
 static void wpa_rekey_gtk(void *eloop_ctx, void *timeout_ctx)
 {
        struct wpa_authenticator *wpa_auth = eloop_ctx;
-       struct wpa_group *group;
+       struct wpa_group *group, *next;
 
        wpa_auth_logger(wpa_auth, NULL, LOGGER_DEBUG, "rekeying GTK");
-       for (group = wpa_auth->group; group; group = group->next) {
+       group = wpa_auth->group;
+       while (group) {
+               wpa_group_get(wpa_auth, group);
+
                group->GTKReKey = TRUE;
                do {
                        group->changed = FALSE;
                        wpa_group_sm_step(wpa_auth, group);
                } while (group->changed);
+
+               next = group->next;
+               wpa_group_put(wpa_auth, group);
+               group = next;
        }
 
        if (wpa_auth->conf.wpa_group_rekey) {
@@ -573,6 +586,7 @@ wpa_auth_sta_init(struct wpa_authenticator *wpa_auth, const u8 *addr,
 
        sm->wpa_auth = wpa_auth;
        sm->group = wpa_auth->group;
+       wpa_group_get(sm->wpa_auth, sm->group);
 
        return sm;
 }
@@ -651,6 +665,7 @@ static void wpa_free_sta_sm(struct wpa_state_machine *sm)
 #endif /* CONFIG_IEEE80211R */
        os_free(sm->last_rx_eapol_key);
        os_free(sm->wpa_ie);
+       wpa_group_put(sm->wpa_auth, sm->group);
        os_free(sm);
 }
 
@@ -3281,6 +3296,63 @@ void wpa_auth_pmksa_remove(struct wpa_authenticator *wpa_auth,
 }
 
 
+/*
+ * Remove and free the group from wpa_authenticator. This is triggered by a
+ * callback to make sure nobody is currently iterating the group list while it
+ * gets modified.
+ */
+static void wpa_group_free(struct wpa_authenticator *wpa_auth,
+                          struct wpa_group *group)
+{
+       struct wpa_group *prev = wpa_auth->group;
+
+       wpa_printf(MSG_DEBUG, "WPA: Remove group state machine for VLAN-ID %d",
+                  group->vlan_id);
+
+       while (prev) {
+               if (prev->next == group) {
+                       /* This never frees the special first group as needed */
+                       prev->next = group->next;
+                       os_free(group);
+                       break;
+               }
+               prev = prev->next;
+       }
+
+}
+
+
+/* Increase the reference counter for group */
+static void wpa_group_get(struct wpa_authenticator *wpa_auth,
+                         struct wpa_group *group)
+{
+       /* Skip the special first group */
+       if (wpa_auth->group == group)
+               return;
+
+       group->references++;
+}
+
+
+/* Decrease the reference counter and maybe free the group */
+static void wpa_group_put(struct wpa_authenticator *wpa_auth,
+                         struct wpa_group *group)
+{
+       /* Skip the special first group */
+       if (wpa_auth->group == group)
+               return;
+
+       group->references--;
+       if (group->references)
+               return;
+       wpa_group_free(wpa_auth, group);
+}
+
+
+/*
+ * Add a group that has its references counter set to zero. Caller needs to
+ * call wpa_group_get() on the return value to mark the entry in use.
+ */
 static struct wpa_group *
 wpa_auth_add_group(struct wpa_authenticator *wpa_auth, int vlan_id)
 {
@@ -3331,7 +3403,10 @@ int wpa_auth_sta_set_vlan(struct wpa_state_machine *sm, int vlan_id)
        wpa_printf(MSG_DEBUG, "WPA: Moving STA " MACSTR " to use group state "
                   "machine for VLAN ID %d", MAC2STR(sm->addr), vlan_id);
 
+       wpa_group_get(sm->wpa_auth, group);
+       wpa_group_put(sm->wpa_auth, sm->group);
        sm->group = group;
+
        return 0;
 }
 
index 7b2cd3ea8ed445cb74df0662efff61a3282172d0..57b098f2ed72a5e5399964503f9def59ea8da811 100644 (file)
@@ -169,6 +169,8 @@ struct wpa_group {
        u8 IGTK[2][WPA_IGTK_MAX_LEN];
        int GN_igtk, GM_igtk;
 #endif /* CONFIG_IEEE80211W */
+       /* Number of references except those in struct wpa_group->next */
+       unsigned int references;
 };