From fb7901c4f8d08ff59c7acda46cc9d8f6cea24507 Mon Sep 17 00:00:00 2001 From: Suchir Kavi Date: Wed, 15 Jul 2026 19:34:42 -0700 Subject: [PATCH 1/3] station: fix use-after-free in preauthenticate_cb station_preauthenticate_cb() falls through its error branch: after unreffing new_hs on a failed station_transition_reassociate(), it proceeds to swap station->hs to a reference on the freed handshake. Return instead. Assisted-by: Claude:claude-fable-5 --- src/station.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/station.c b/src/station.c index 8fcf8c70..0b2d7dc6 100644 --- a/src/station.c +++ b/src/station.c @@ -2598,6 +2598,7 @@ static void station_preauthenticate_cb(struct netdev *netdev, if (station_transition_reassociate(station, bss, new_hs) < 0) { handshake_state_unref(new_hs); station_roam_failed(station); + return; } handshake_state_unref(station->hs); From cbe943f8c999b13c04e4aa2ed605341af25cd8e1 Mon Sep 17 00:00:00 2001 From: Suchir Kavi Date: Wed, 15 Jul 2026 19:34:43 -0700 Subject: [PATCH 2/3] netdev: reject reassociation while connect pending netdev_reassociate() replaces netdev->handshake and unrefs the old handshake without freeing netdev->ap or dequeuing a pending connection work item. If a reassociation begins while an earlier attempt's radio work is still queued (reachable since station_cannot_roam() does not cover the connecting states), the stale auth proto keeps a raw pointer to the freed handshake. On the PMKSA-cache path netdev_connect_common() does not replace netdev->ap, and it re-inserts the already-queued embedded work item; when the work finally runs, netdev_begin_connection() starts the stale auth proto and sae_start() dereferences the freed handshake: #0 sae_choose_next_group <- reads handshake->ecc_sae_pts == NULL #1 sae_start #2 netdev_begin_connection #3 netdev_connection_work_ready #4 wiphy_radio_work_next #5 get_scan_done Mirror netdev_connect(), which already refuses to start while netdev->connect_cmd_id or netdev->work.id is set. Both are zero at any legitimate roam start since netdev_connect_ok() completes the work item, and the station roam paths handle a negative return by failing the roam cleanly. Assisted-by: Claude:claude-fable-5 --- src/netdev.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/netdev.c b/src/netdev.c index e639a1f8..dadd90da 100644 --- a/src/netdev.c +++ b/src/netdev.c @@ -4394,6 +4394,9 @@ int netdev_reassociate(struct netdev *netdev, const struct scan_bss *target_bss, struct handshake_state *old_hs; struct eapol_sm *old_sm; + if (netdev->connect_cmd_id || netdev->work.id) + return -EBUSY; + old_sm = netdev->sm; old_hs = netdev->handshake; From 0f60405e609c893bce142592f1c25729a10f5818 Mon Sep 17 00:00:00 2001 From: Suchir Kavi Date: Wed, 15 Jul 2026 19:34:44 -0700 Subject: [PATCH 3/3] station: fix NULL deref in ap_directed_roam station_ap_directed_roam() initialized ignore_candidates from station->connected_bss->vendor_quirks before the state != CONNECTED guard. station_disconnect() clears connected_bss before entering DISCONNECTING and the WNM frame watch outlives the connection, so a BSS Transition Management frame arriving in that window crashes on the dereference the guard was meant to prevent. Assign it after the state check, which guarantees connected_bss is set (the frame-sanitize memcmp below already relies on the same invariant). Assisted-by: Claude:claude-fable-5 --- src/station.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/station.c b/src/station.c index 0b2d7dc6..e3aeb3fd 100644 --- a/src/station.c +++ b/src/station.c @@ -3273,8 +3273,7 @@ static void station_ap_directed_roam(struct station *station, uint16_t dtimer; uint8_t valid_interval; bool can_roam = !station_cannot_roam(station); - bool ignore_candidates = - station->connected_bss->vendor_quirks.ignore_bss_tm_candidates; + bool ignore_candidates; l_debug("ifindex: %u", netdev_get_ifindex(station->netdev)); @@ -3283,6 +3282,9 @@ static void station_ap_directed_roam(struct station *station, return; } + ignore_candidates = + station->connected_bss->vendor_quirks.ignore_bss_tm_candidates; + /* * Sanitize the frame to check that it is from our current AP. *