[PATCH 0/3] Fix two station roaming crashes
Suchir Kavi <[email protected]> Wed, 15 Jul 2026 19:34:41 -0700
| Newsgroups | dev.linux.lists.iwd |
|---|---|
| Message-ID | <[email protected]> |
We run iwd on a large number of embedded aarch64 Linux devices (WPA3-SAE, frequently multi-AP mesh networks) and root-caused two recurring segfaults from coredumps and journals. Both are present in current master by inspection, and together they account for the majority of the iwd crashes we see on current builds. Heads up that this cover letter and patchset are primarily AI-generated (Fable) but reviewed by me. Crash 1: use-after-free of handshake_state when a reassociation overlaps a pending connection attempt. netdev_connect() refuses to start while an attempt is pending (netdev->connected || netdev->connect_cmd_id || netdev->work.id -> -EISCONN). netdev_reassociate() has no equivalent guard, and the station layer can invoke it mid-connect: station_cannot_roam() does not cover the connecting states, connected_bss is already set while connecting, and station_roam_scan_notify() does not re-check state. Captured sequence (journal shows "state, old: connecting (auto), new: roaming" in the same second as the crash): 1. A fresh SAE-H2E connect is queued behind a scan - the auth proto is created holding its raw handshake pointer, netdev->work is queued and never starts (sm->group_retry == -1 in the core). 2. A roam scan completes mid-connect -> netdev_reassociate() to a BSS with a cached PMKSA. netdev_connect_common() takes the PMKSA/CMD_CONNECT path, so netdev->ap is NOT replaced; netdev->handshake is swapped; old_hs is unreffed here and the caller swaps station->hs - both references gone, the handshake is zeroed and freed under the stale auth proto. netdev_connect_common() also re-inserts the already-queued embedded work item, linking the same wiphy_radio_work_item into the queue twice. 3. The scan finishes -> the connect work runs -> netdev_begin_connection() sends the CMD_CONNECT, then starts the stale softmac auth proto -> sae_start() -> sae_choose_next_group() reads sm->handshake->ecc_sae_pts from the freed (zeroed) handshake -> NULL dereference. Core anatomy: sm->handshake points at zeroed memory plus allocator metadata; netdev->ap == sm while netdev->handshake is a different, live handshake; station state == ROAMING. Patch 2 adds the same guard netdev_connect() has. Both fields are zero at any legitimate roam start since netdev_connect_ok() completes the work item, and the station roam paths fail a rejected roam cleanly - except station_preauthenticate_cb(), which is missing a return in its error branch and falls through to handshake_state_ref() on the new_hs it just unreffed. Patch 1 fixes that first so the new error return cannot widen it. Crash 2: station_ap_directed_roam() dereferences connected_bss before its own state guard. station_disconnect() clears connected_bss (via station_reset_connection_state()) before entering DISCONNECTING, and the WNM frame watch outlives the connection - so a BSS Transition Management request arriving in that window (mesh APs send them around disconnects; our journal shows connected -> disconnecting 2 s before the crash) crashes on the ignore_candidates initializer that runs before the guard meant to drop exactly that frame. The invariant "state == CONNECTED => connected_bss != NULL" is otherwise sound - the unguarded sanitize memcmp just below already relies on it. Patch 3 assigns ignore_candidates after the state check. Suchir Kavi (3): station: fix use-after-free in preauthenticate_cb netdev: reject reassociation while connect pending station: fix NULL deref in ap_directed_roam src/netdev.c | 3 +++ src/station.c | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) -- 2.54.0