[PATCH] wifi: mac80211: bound S1G TIM PVB walk to the TIM element

Bryam Vargas posted 1 patch 1 day, 21 hours ago
include/linux/ieee80211-s1g.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
[PATCH] wifi: mac80211: bound S1G TIM PVB walk to the TIM element
Posted by Bryam Vargas 1 day, 21 hours ago
ieee80211_s1g_check_tim() parses the S1G Partial Virtual Bitmap (PVB) of a
received TIM element. The TIM is handed in as the element payload:
ieee802_11_parse_elems_full() stores elems->tim = elem->data and
elems->tim_len = elem->datalen (net/mac80211/parse.c), so the valid bytes
are [tim, tim + tim_len).

When walking the encoded blocks the function passes the walker an end
sentinel of (const u8 *)tim + tim_len + 2, i.e. two bytes past the end of
the element. ieee80211_s1g_find_target_block() loops while (ptr + 1 <= end)
and dereferences ptr (and the per-mode ieee80211_s1g_len_*() helpers read
*ptr), so it can read up to two bytes beyond the TIM element -- an
out-of-bounds read of adjacent skb/heap data when the TIM is the last
element in the frame. The +2 appears to account for the element id/len
header, but tim already points past that header at the element payload, so
the addend is wrong.

Pass the correct element end, (const u8 *)tim + tim_len.

Fixes: e0c47c6229c2 ("wifi: mac80211: support parsing S1G TIM PVB")
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
---
Everything below the --- is dropped by git am.

Class / impact: CWE-125 out-of-bounds read (CWE-193 off-by-two end
sentinel). An S1G (802.11ah) station that has the PS_NULLFUNC_STACK
software path processes the TIM of every beacon from its AP
(ieee80211_rx_mgmt_beacon() -> ieee80211_check_tim(..., s1g=true) ->
ieee80211_s1g_check_tim()). A beacon -- which is unauthenticated and can be
spoofed over the air by anything in radio range -- carrying a short S1G TIM
element placed last in the frame makes the PVB walker read up to two bytes
past the element. The bytes only influence the boolean "is my AID buffered"
result, so this is a robustness/hardening fix (no leak to userspace, no
corruption); KASAN flags it as a slab out-of-bounds read.

Affected: introduced this merge window by e0c47c6229c2; the buggy +2 is
present at mainline v7.1-rc6 and at torvalds/master 8e65320d (2026-06-06).
It is not in any released stable tree yet, so no Cc: stable here -- but note
"[PATCH AUTOSEL 6.17-6.12] wifi: mac80211: support parsing S1G TIM PVB" is
proposing to backport the feature (with this bug) to 6.12..6.17; if that
lands this fix should ride along.

A/B verification:

1) Full-system KASAN (x86-64, KASAN_GENERIC+INLINE, kasan.fault=report).
   A test module calls the real in-kernel ieee80211_s1g_check_tim() on a TIM
   kmalloc()'d at exactly tim_len bytes, with a crafted single-mode PVB block
   whose block offset never matches the target so the walk runs to the
   element end. Without this patch:

     BUG: KASAN: slab-out-of-bounds in ieee80211_s1g_find_target_block
     Read of size 1 at addr ffff88810340d345 by task insmod
      ieee80211_s1g_find_target_block
      <test caller>
     The buggy address is located 0 bytes to the right of
      allocated 5-byte region [ffff88810340d340, ffff88810340d345)
      cache kmalloc-8

   A well-formed PVB (block offset matches the target AID) produces no
   report. With this patch the crafted case produces no report.

2) ABI-invariant userspace AddressSanitizer extraction of the verbatim
   walker (-m64 and -m32): both report

     heap-buffer-overflow READ of size 1 ... 0 bytes after 5-byte region
       in ieee80211_s1g_find_target_block

   without the patch; clean with the patch and clean on a well-formed PVB.

 include/linux/ieee80211-s1g.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/linux/ieee80211-s1g.h b/include/linux/ieee80211-s1g.h
index 22dde4cbc1b0..3f9626ad3d97 100644
--- a/include/linux/ieee80211-s1g.h
+++ b/include/linux/ieee80211-s1g.h
@@ -556,7 +556,7 @@ static inline bool ieee80211_s1g_check_tim(const struct ieee80211_tim_ie *tim,
 	 */
 	err = ieee80211_s1g_find_target_block(&enc_blk, &target_aid,
 					      tim->virtual_map,
-					      (const u8 *)tim + tim_len + 2);
+					      (const u8 *)tim + tim_len);
 	if (err)
 		return false;