Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

wifi: cfg80211/mac80211: correctly parse S1G beacon optional elements

S1G beacons are not traditional beacons but a type of extension frame.
Extension frames contain the frame control and duration fields, followed
by zero or more optional fields before the frame body. These optional
fields are distinct from the variable length elements.

The presence of optional fields is indicated in the frame control field.
To correctly locate the elements offset, the frame control must be parsed
to identify which optional fields are present. Currently, mac80211 parses
S1G beacons based on fixed assumptions about the frame layout, without
inspecting the frame control field. This can result in incorrect offsets
to the "variable" portion of the frame.

Properly parse S1G beacon frames by using the field lengths defined in
IEEE 802.11-2024, section 9.3.4.3, ensuring that the elements offset is
calculated accurately.

Fixes: 9eaffe5078ca ("cfg80211: convert S1G beacon to scan results")
Fixes: cd418ba63f0c ("mac80211: convert S1G beacon to scan results")
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
Link: https://patch.msgid.link/20250603053538.468562-1-lachlan.hodges@morsemicro.com
Signed-off-by: Johannes Berg <johannes.berg@intel.com>

authored by

Lachlan Hodges and committed by
Johannes Berg
1e1f706f 1b98f357

+83 -32
+69 -10
include/linux/ieee80211.h
··· 111 111 112 112 /* bits unique to S1G beacon */ 113 113 #define IEEE80211_S1G_BCN_NEXT_TBTT 0x100 114 + #define IEEE80211_S1G_BCN_CSSID 0x200 115 + #define IEEE80211_S1G_BCN_ANO 0x400 114 116 115 117 /* see 802.11ah-2016 9.9 NDP CMAC frames */ 116 118 #define IEEE80211_S1G_1MHZ_NDP_BITS 25 ··· 154 152 #define IEEE80211_NDP_2M_PREQ_RTYPE_S 36 155 153 156 154 #define IEEE80211_ANO_NETTYPE_WILD 15 157 - 158 - /* bits unique to S1G beacon */ 159 - #define IEEE80211_S1G_BCN_NEXT_TBTT 0x100 160 155 161 156 /* control extension - for IEEE80211_FTYPE_CTL | IEEE80211_STYPE_CTL_EXT */ 162 157 #define IEEE80211_CTL_EXT_POLL 0x2000 ··· 624 625 return (fc & cpu_to_le16(IEEE80211_FCTL_FTYPE | 625 626 IEEE80211_FCTL_STYPE)) == 626 627 cpu_to_le16(IEEE80211_FTYPE_EXT | IEEE80211_STYPE_S1G_BEACON); 628 + } 629 + 630 + /** 631 + * ieee80211_s1g_has_next_tbtt - check if IEEE80211_S1G_BCN_NEXT_TBTT 632 + * @fc: frame control bytes in little-endian byteorder 633 + * Return: whether or not the frame contains the variable-length 634 + * next TBTT field 635 + */ 636 + static inline bool ieee80211_s1g_has_next_tbtt(__le16 fc) 637 + { 638 + return ieee80211_is_s1g_beacon(fc) && 639 + (fc & cpu_to_le16(IEEE80211_S1G_BCN_NEXT_TBTT)); 640 + } 641 + 642 + /** 643 + * ieee80211_s1g_has_ano - check if IEEE80211_S1G_BCN_ANO 644 + * @fc: frame control bytes in little-endian byteorder 645 + * Return: whether or not the frame contains the variable-length 646 + * ANO field 647 + */ 648 + static inline bool ieee80211_s1g_has_ano(__le16 fc) 649 + { 650 + return ieee80211_is_s1g_beacon(fc) && 651 + (fc & cpu_to_le16(IEEE80211_S1G_BCN_ANO)); 652 + } 653 + 654 + /** 655 + * ieee80211_s1g_has_cssid - check if IEEE80211_S1G_BCN_CSSID 656 + * @fc: frame control bytes in little-endian byteorder 657 + * Return: whether or not the frame contains the variable-length 658 + * compressed SSID field 659 + */ 660 + static inline bool ieee80211_s1g_has_cssid(__le16 fc) 661 + { 662 + return ieee80211_is_s1g_beacon(fc) && 663 + (fc & cpu_to_le16(IEEE80211_S1G_BCN_CSSID)); 627 664 } 628 665 629 666 /** ··· 1280 1245 u8 change_seq; 1281 1246 u8 variable[0]; 1282 1247 } __packed s1g_beacon; 1283 - struct { 1284 - u8 sa[ETH_ALEN]; 1285 - __le32 timestamp; 1286 - u8 change_seq; 1287 - u8 next_tbtt[3]; 1288 - u8 variable[0]; 1289 - } __packed s1g_short_beacon; 1290 1248 } u; 1291 1249 } __packed __aligned(2); 1250 + 1251 + /** 1252 + * ieee80211_s1g_optional_len - determine length of optional S1G beacon fields 1253 + * @fc: frame control bytes in little-endian byteorder 1254 + * Return: total length in bytes of the optional fixed-length fields 1255 + * 1256 + * S1G beacons may contain up to three optional fixed-length fields that 1257 + * precede the variable-length elements. Whether these fields are present 1258 + * is indicated by flags in the frame control field. 1259 + * 1260 + * From IEEE 802.11-2024 section 9.3.4.3: 1261 + * - Next TBTT field may be 0 or 3 bytes 1262 + * - Short SSID field may be 0 or 4 bytes 1263 + * - Access Network Options (ANO) field may be 0 or 1 byte 1264 + */ 1265 + static inline size_t 1266 + ieee80211_s1g_optional_len(__le16 fc) 1267 + { 1268 + size_t len = 0; 1269 + 1270 + if (ieee80211_s1g_has_next_tbtt(fc)) 1271 + len += 3; 1272 + 1273 + if (ieee80211_s1g_has_cssid(fc)) 1274 + len += 4; 1275 + 1276 + if (ieee80211_s1g_has_ano(fc)) 1277 + len += 1; 1278 + 1279 + return len; 1280 + } 1292 1281 1293 1282 #define IEEE80211_TWT_CONTROL_NDP BIT(0) 1294 1283 #define IEEE80211_TWT_CONTROL_RESP_MODE BIT(1)
+2 -5
net/mac80211/mlme.c
··· 7220 7220 bssid = ieee80211_get_bssid(hdr, len, sdata->vif.type); 7221 7221 if (ieee80211_is_s1g_beacon(mgmt->frame_control)) { 7222 7222 struct ieee80211_ext *ext = (void *) mgmt; 7223 - 7224 - if (ieee80211_is_s1g_short_beacon(ext->frame_control)) 7225 - variable = ext->u.s1g_short_beacon.variable; 7226 - else 7227 - variable = ext->u.s1g_beacon.variable; 7223 + variable = ext->u.s1g_beacon.variable + 7224 + ieee80211_s1g_optional_len(ext->frame_control); 7228 7225 } 7229 7226 7230 7227 baselen = (u8 *) variable - (u8 *) mgmt;
+5 -6
net/mac80211/scan.c
··· 276 276 struct ieee80211_mgmt *mgmt = (void *)skb->data; 277 277 struct ieee80211_bss *bss; 278 278 struct ieee80211_channel *channel; 279 + struct ieee80211_ext *ext; 279 280 size_t min_hdr_len = offsetof(struct ieee80211_mgmt, 280 281 u.probe_resp.variable); 281 282 ··· 286 285 return; 287 286 288 287 if (ieee80211_is_s1g_beacon(mgmt->frame_control)) { 289 - if (ieee80211_is_s1g_short_beacon(mgmt->frame_control)) 290 - min_hdr_len = offsetof(struct ieee80211_ext, 291 - u.s1g_short_beacon.variable); 292 - else 293 - min_hdr_len = offsetof(struct ieee80211_ext, 294 - u.s1g_beacon); 288 + ext = (struct ieee80211_ext *)mgmt; 289 + min_hdr_len = 290 + offsetof(struct ieee80211_ext, u.s1g_beacon.variable) + 291 + ieee80211_s1g_optional_len(ext->frame_control); 295 292 } 296 293 297 294 if (skb->len < min_hdr_len)
+7 -11
net/wireless/scan.c
··· 3250 3250 const u8 *ie; 3251 3251 size_t ielen; 3252 3252 u64 tsf; 3253 + size_t s1g_optional_len; 3253 3254 3254 3255 if (WARN_ON(!mgmt)) 3255 3256 return NULL; ··· 3265 3264 3266 3265 if (ieee80211_is_s1g_beacon(mgmt->frame_control)) { 3267 3266 ext = (void *) mgmt; 3268 - if (ieee80211_is_s1g_short_beacon(mgmt->frame_control)) 3269 - min_hdr_len = offsetof(struct ieee80211_ext, 3270 - u.s1g_short_beacon.variable); 3271 - else 3272 - min_hdr_len = offsetof(struct ieee80211_ext, 3273 - u.s1g_beacon.variable); 3267 + s1g_optional_len = 3268 + ieee80211_s1g_optional_len(ext->frame_control); 3269 + min_hdr_len = 3270 + offsetof(struct ieee80211_ext, u.s1g_beacon.variable) + 3271 + s1g_optional_len; 3274 3272 } else { 3275 3273 /* same for beacons */ 3276 3274 min_hdr_len = offsetof(struct ieee80211_mgmt, ··· 3285 3285 const struct ieee80211_s1g_bcn_compat_ie *compat; 3286 3286 const struct element *elem; 3287 3287 3288 - if (ieee80211_is_s1g_short_beacon(mgmt->frame_control)) 3289 - ie = ext->u.s1g_short_beacon.variable; 3290 - else 3291 - ie = ext->u.s1g_beacon.variable; 3292 - 3288 + ie = ext->u.s1g_beacon.variable + s1g_optional_len; 3293 3289 elem = cfg80211_find_elem(WLAN_EID_S1G_BCN_COMPAT, ie, ielen); 3294 3290 if (!elem) 3295 3291 return NULL;