mac80211 EPCS: OOB Array Access in the Linux Kernel WiFi Stack
An out-of-bounds array access in the mainline mac80211 WiFi stack, triggerable by a rogue WiFi 7 AP. The EPCS handler in ieee80211_ml_epcs() extracts a 4-bit link_id from an attacker-controlled frame field, producing values 0–15. The sdata->link[] array has 15 entries (indices 0–14), so link_id 15 reads past the end of the array into adjacent memory and crashes the kernel.
1. Background
WiFi 7 (802.11be) introduced EPCS — Emergency Preparedness Communication Service — a mechanism that lets an AP grant priority access to specific links in a multi-link operation (MLO) setup. The kernel's mac80211 subsystem added support for EPCS configuration in commit de86c5f60839 ("wifi: mac80211: Add support for EPCS configuration"), merged in v6.15.
I found this bug while reading through the MLO-related code in net/mac80211/mlme.c, looking at how link_id values extracted from over-the-air frames are used as array indices. The same class of bug had already been fixed once before in ieee80211_ml_reconfiguration() by commit 162d331d833d, so I checked whether the pattern repeated elsewhere. It did.
2. The vulnerable code
Inside ieee80211_ml_epcs(), the function parses PER_STA_PROFILE subelements from a PRIO_ACCESS ML element in an EPCS Enable Response action frame. Each subelement carries a control field from which link_id is extracted:
/* net/mac80211/mlme.c */
control = get_unaligned_le16(pos);
link_id = control & IEEE80211_MLE_STA_EPCS_CONTROL_LINK_ID;
link = sdata_dereference(sdata->link[link_id], sdata);
if (!link)
continue;
IEEE80211_MLE_STA_EPCS_CONTROL_LINK_ID is 0x000f, so the mask produces values 0–15. sdata->link[] is declared with IEEE80211_MLD_MAX_NUM_LINKS entries, which is 15 — valid indices are 0–14. When link_id is 15, sdata->link[15] is a one-past-the-end access.
3. What sdata->link[15] actually hits
The link[] array is an array of struct ieee80211_link_data * pointers. Index 15 reads the first word of sdata->activate_links_work, which is a wiphy_work struct. After INIT_LIST_HEAD() during initialization, the embedded list_head in that struct points to itself — it is non-NULL.
This means the NULL check on the result does not catch the invalid access. The garbage pointer is treated as a valid struct ieee80211_link_data * and passed to ieee80211_sta_wmm_params(), which dereferences link->sdata. That read hits an arbitrary memory location and crashes the kernel.
4. Reachability
The function processes EPCS Enable Response action frames received from the AP. There are two paths into this code:
- Solicited response — the client sends an EPCS Enable Request, the AP responds. The response is parsed by this function.
- Unsolicited notification — when
dialog_tokenis 0, the AP can send an EPCS Enable Response at any time without a prior request from the client. This path is reachable whenever EPCS is already enabled on the connection.
The unsolicited path is the more interesting one. A connected WiFi 7 AP — or an attacker who has taken over the AP's MAC address on the channel — can send the frame at any time. The action frame is protected by CCMP (it is a robust management frame), so the attacker needs to be the legitimate AP or to have compromised the AP's PTK. A rogue AP that the client associates with from the start has full control.
The trigger payload is minimal: an EPCS Enable Response action frame containing a single PER_STA_PROFILE subelement with the link_id field set to 15.
5. Affected devices
The bug affects any Linux system running kernel v6.15 or later with a WiFi 7 adapter whose driver enables EPCS support. The EPCS feature was introduced in v6.15 and is present in every kernel release since, including the current v7.x series.
Affected drivers include:
- iwlwifi — Intel BE200, BE202 (the most common WiFi 7 adapters in current laptops)
- mt76/mt7925 — MediaTek MT7925
- ath12k — Qualcomm WCN7850
LTS kernel trees v6.12 and older are not affected — the EPCS code does not exist there. Android devices are also unaffected as of this writing, as Android kernels have not picked up the EPCS feature.
6. The fix
The fix is identical to the one applied in ieee80211_ml_reconfiguration() — a bounds check on link_id before it is used as an array index:
control = get_unaligned_le16(pos);
link_id = control & IEEE80211_MLE_STA_EPCS_CONTROL_LINK_ID;
+if (link_id >= IEEE80211_MLD_MAX_NUM_LINKS)
+ continue;
+
link = sdata_dereference(sdata->link[link_id], sdata);
if (!link)
continue;
Three lines. The subelement with the out-of-range link_id is skipped, and parsing continues with the next one.
7. Patch status
The patch was sent to the linux-wireless mailing list on 2026-05-15 and accepted by Johannes Berg (Intel) as upstream commit f718506edd2d. Greg Kroah-Hartman backported it to the 6.18-stable and 7.0-stable trees.
Other recent kernel work: a heap buffer overflow in the iSCSI target CHAP authentication code (separate post), and three patch series for the rtl8723bs staging WiFi driver (separate post).