rtl8723bs — WiFi Heap Overflow in the Linux Kernel
A heap overflow in the Linux staging driver for the rtl8723bs WiFi chip, exploitable by any rogue AP within radio range. No authentication required. The bug has been sitting in the kernel since 2015.
1. Background
The rtl8723bs driver handles the Realtek RTL8723BS SDIO WiFi/Bluetooth combo chip, which ships in a wide range of embedded Linux devices — tablets, single-board computers, industrial controllers. It was added to the kernel's staging tree in 2015 (commit 554c0a3abf21) and has been there ever since.
I found the primary bug — a heap overflow in OnAuthClient() — while reading through the driver's 802.11 management frame handling code. From there, a reviewer pointed out that the same function was also missing basic frame length guards, which led to a second patch. The same IE parsing pattern repeated across the driver, so I kept going. Three patch series in total: the first two cover OnAuthClient() and the IE handler OOBs already visible in the association path; the third adds fixes for four more functions — update_beacon_info(), issue_assocreq(), join_cmd_hdl(), and a separate one-byte heap overflow in rtw_cfg80211_set_wpa_ie() reachable via nl80211.
2. OnAuthClient heap overflow
The function OnAuthClient() in core/rtw_mlme_ext.c processes 802.11 Authentication frames received by the station. When the AP sends an Auth sequence 2 frame using Shared Key authentication, the driver looks for a Challenge Text IE (element ID 0x10) and copies its payload into a fixed 128-byte buffer:
/* rtw_mlme_ext.c ~line 891 */
p = rtw_get_ie(pframe + WLAN_HDR_A3_LEN + _AUTH_IE_OFFSET_,
WLAN_EID_CHALLENGE, (int *)&len,
pkt_len - WLAN_HDR_A3_LEN - _AUTH_IE_OFFSET_);
if (!p)
goto authclnt_fail;
memcpy(pmlmeinfo->chg_txt, p + 2, len); /* len = raw IE length byte, 0-255 */
rtw_get_ie() validates that the IE fits within the received frame buffer — so the copy source is legitimate. What it does not check is whether len fits in the 128-byte destination chg_txt[]. The raw IE length field is a single byte and can be anywhere from 0 to 255. If an AP sends a Challenge Text IE with length 200, the driver writes 72 bytes past the end of chg_txt.
IEEE 802.11-2020 §12.3.3 is unambiguous: the Challenge Text element carries exactly 128 bytes of challenge data. The fix is a one-line addition — reject the frame if the IE length is anything other than sizeof(pmlmeinfo->chg_txt):
-if (!p)
+if (!p || len != sizeof(pmlmeinfo->chg_txt))
The overflow target is struct mlme_ext_info, with chg_txt at offset 40. Immediately following it are aid, bcn_interval, capability, and a handful of protocol state flags. With a crafted length of 200, 72 bytes past the end of chg_txt are overwritten.
3. Auth algorithm toggle — any device is affected
The overflow is in the Shared Key path, which at first looks like it only affects devices explicitly configured for WEP Shared Key authentication. It does not. There's a toggle in the same function:
if (status == WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG) {
pmlmeinfo->auth_algo ^= 1; /* toggle Open ↔ Shared */
pmlmeinfo->reauth_count = 0;
set_link_timer(pmlmeext, 1);
return _SUCCESS;
}
The attack sequence is two frames. First, send an Auth Response (seq=2, status=13 — WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG). The driver toggles from Open System to Shared Key and sends a new Auth Request. Then immediately send a second Auth Response (seq=2, status=0) with a crafted Challenge Text IE. The driver is now in the Shared Key path and executes the overflow unconditionally.
This works against any device running this driver that is attempting to connect to a network, regardless of how the device is configured. The attacker just needs to be within radio range.
4. Missing frame length checks
After I submitted the first patch, Dan Carpenter pointed out that OnAuthClient() was also missing basic frame length validation. Two problems:
First, get_da(pframe) reads bytes 4–9 of the frame (the destination MAC address), and GetPrivacy(pframe) reads the Frame Control field at bytes 0–1. Both are called before any check that pkt_len is at least WLAN_HDR_A3_LEN (24 bytes).
Second, when pkt_len < WLAN_HDR_A3_LEN + _AUTH_IE_OFFSET_ (which is 30 bytes), the length computation for the rtw_get_ie() call wraps:
/* pkt_len and WLAN_HDR_A3_LEN are both 'uint' (unsigned) */
/* If pkt_len < 30: this wraps to a value near 4 GB */
rtw_get_ie(pframe + ..., id, &len,
pkt_len - WLAN_HDR_A3_LEN - _AUTH_IE_OFFSET_);
rtw_get_ie() then scans well past the end of the frame buffer with that ~4 GB limit. The fix adds two guards: one before the first pframe access, and a second after computing offset (0 or 4) to cover the seq/status reads and prevent the wrap:
+if (pkt_len < WLAN_HDR_A3_LEN)
+ goto authclnt_fail;
/* check A1 matches or not */
if (memcmp(myid(&(padapter->eeprompriv)), get_da(pframe), ETH_ALEN))
return _SUCCESS;
...
offset = (GetPrivacy(pframe)) ? 4 : 0;
+if (pkt_len < WLAN_HDR_A3_LEN + offset + 6)
+ goto authclnt_fail;
5. HT_caps_handler OOB write
While in the same file, I found a similar issue in HT_caps_handler() in core/rtw_wlan_util.c. This function processes the HT Capabilities IE from an Association Response. It iterates pIE->length bytes and writes them into HT_caps.u.HT_cap[], which is a fixed 26-byte array:
for (i = 0; i < (pIE->length); i++) {
HT_caps.u.HT_cap[i] = pIE->data[i];
...
}
Since pIE->length is a raw byte from the received frame (0–255), a malicious AP can cause up to 229 bytes of out-of-bounds writes into adjacent fields of struct mlme_ext_info. The fix truncates the iteration count with min_t() instead of rejecting the IE outright — APs that send an oversized HT Caps element are handled gracefully rather than causing a connection failure:
-for (i = 0; i < (pIE->length); i++) {
+for (i = 0; i < min_t(unsigned int, pIE->length,
+ sizeof(pmlmeinfo->HT_caps.u.HT_cap)); i++) {
6. OnAssocRsp IE loop OOB read
The IE parsing loop in OnAssocRsp() iterates through all information elements in the Association Response frame. The loop condition is i < pkt_len, and at the top of each iteration it casts pframe + i to struct ndis_80211_var_ie * — which has a two-byte header (element ID + length). If the last IE in the frame starts at pkt_len - 1, reading pIE->length touches pframe[pkt_len], one byte past the allocation. Even when the header bytes are in bounds, the IE's declared pIE->length can extend past pkt_len, passing a truncated IE to the handler functions.
The fix adds two guards at the top of the loop body:
for (i = (6 + WLAN_HDR_A3_LEN); i < pkt_len;) {
+ if (i + sizeof(*pIE) > pkt_len)
+ break;
pIE = (struct ndis_80211_var_ie *)(pframe + i);
+ if (i + sizeof(*pIE) + pIE->length > pkt_len)
+ break;
switch (pIE->element_id) {
7. More IE loop OOB reads — beacon and join paths
The same missing two-guard pattern turned up in three more IE parsing loops in the driver. All three read pIE->length from attacker-controlled frames without first checking that two bytes are available:
update_beacon_info() in core/rtw_wlan_util.c processes incoming Beacon frames. The loop advances by (pIE->length + 2) per iteration with only an i < len guard. A Beacon with a final IE where only the element ID byte is present (no length byte) causes the loop to read pIE->length one byte past the IE area. A malicious AP can trigger this against any station in range.
issue_assocreq() and join_cmd_hdl() in core/rtw_mlme_ext.c walk stored network IE data — filled earlier from the AP's Beacon and Probe Response frames. A malicious AP that sends a truncated final IE in its Beacon can plant a malformed IE buffer that triggers the OOB read later, when the driver processes the association or join.
All three were fixed with the same two-guard pattern already applied to OnAssocRsp(): break if fewer than sizeof(*pIE) bytes remain before casting, and break if the IE's declared data extends past the buffer end. The increment in update_beacon_info() was also corrected from (pIE->length + 2) to sizeof(*pIE) + pIE->length for consistency (Dan Carpenter's suggestion).
8. rtw_cfg80211_set_wpa_ie() — WPA IE one-byte overflow (local)
This one is different from the others: it is a local attack, not over-the-air. rtw_cfg80211_set_wpa_ie() in os_dep/ioctl_cfg80211.c handles WPA and WPA2 IE data supplied via NL80211_CMD_CONNECT. It copies the IE into supplicant_ie, a 256-byte array in struct security_priv:
memcpy(padapter->securitypriv.supplicant_ie, &pwpa[0], wpa_ielen + 2);
wpa_ielen is the raw IE length field — a u8, so it ranges from 0 to 255. When a local user issues a connect with a crafted WPA IE of length 255, wpa_ielen + 2 = 257 and the memcpy writes one byte past the end of the 256-byte buffer into the adjacent last_mic_err_time field.
The existing length check in rtw_parse_wpa_ie() does not prevent this. It compares *(wpa_ie+1) against (u8)(wpa_ie_len - 2). With wpa_ie_len = 257, the cast produces (u8)(255) = 255, which matches — so the check passes silently.
The fix adds an explicit bounds check before both the WPA and WPA2 copy paths:
+if (wpa_ielen + 2 > sizeof(padapter->securitypriv.supplicant_ie)) {
+ ret = -EINVAL;
+ goto exit;
+}
9. Patches
Three series went to the staging mailing list. Series 1 (v6) covered both the OnAuthClient() heap overflow and the missing frame length guards — the frame length patch followed after Dan Carpenter's review of the overflow fix. Series 2 (v2) fixed the HT_caps_handler() OOB write and the OnAssocRsp() IE loop OOB read. Series 3 (v3) fixed the remaining IE parsing loops in update_beacon_info(), issue_assocreq(), and join_cmd_hdl(), plus the one-byte WPA IE overflow in rtw_cfg80211_set_wpa_ie(), with Reviewed-by from Luka Gejak.
All changes are in drivers/staging/rtl8723bs/:
core/rtw_mlme_ext.c—OnAuthClient(): IE length check, frame length guardscore/rtw_wlan_util.c—HT_caps_handler(): IE length guardcore/rtw_mlme_ext.c—OnAssocRsp(): IE loop bounds checkscore/rtw_wlan_util.c—update_beacon_info(): IE loop bounds checks + increment fixcore/rtw_mlme_ext.c—issue_assocreq(),join_cmd_hdl(): IE loop bounds checksos_dep/ioctl_cfg80211.c—rtw_cfg80211_set_wpa_ie(): WPA/WPA2 IE size check