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_token is 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).

The bug was introduced in v6.15 (February 2025) and affects every kernel release since. The fix has been backported to the 6.18 and 7.0 stable trees. Kernels between v6.15 and v6.17 that are still in use should apply the patch manually or cherry-pick from the upstream commit.