]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
wifi: cfg80211/mac80211: correctly parse S1G beacon optional elements
authorLachlan Hodges <lachlan.hodges@morsemicro.com>
Tue, 3 Jun 2025 05:35:38 +0000 (15:35 +1000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 19 Jun 2025 13:32:19 +0000 (15:32 +0200)
[ Upstream commit 1e1f706fc2ce90eaaf3480b3d5f27885960d751c ]

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>
Signed-off-by: Sasha Levin <sashal@kernel.org>
include/linux/ieee80211.h
net/mac80211/mlme.c
net/mac80211/scan.c
net/wireless/scan.c

index 777f6aa8efa7b2e0fb114a67f07669ee7f80d19a..d07c1f0ad3de33e330b6d25ebb59c4a38d709098 100644 (file)
 
 /* bits unique to S1G beacon */
 #define IEEE80211_S1G_BCN_NEXT_TBTT    0x100
+#define IEEE80211_S1G_BCN_CSSID                0x200
+#define IEEE80211_S1G_BCN_ANO          0x400
 
 /* see 802.11ah-2016 9.9 NDP CMAC frames */
 #define IEEE80211_S1G_1MHZ_NDP_BITS    25
 
 #define IEEE80211_ANO_NETTYPE_WILD              15
 
-/* bits unique to S1G beacon */
-#define IEEE80211_S1G_BCN_NEXT_TBTT    0x100
-
 /* control extension - for IEEE80211_FTYPE_CTL | IEEE80211_STYPE_CTL_EXT */
 #define IEEE80211_CTL_EXT_POLL         0x2000
 #define IEEE80211_CTL_EXT_SPR          0x3000
@@ -627,6 +626,42 @@ static inline bool ieee80211_is_s1g_beacon(__le16 fc)
               cpu_to_le16(IEEE80211_FTYPE_EXT | IEEE80211_STYPE_S1G_BEACON);
 }
 
+/**
+ * ieee80211_s1g_has_next_tbtt - check if IEEE80211_S1G_BCN_NEXT_TBTT
+ * @fc: frame control bytes in little-endian byteorder
+ * Return: whether or not the frame contains the variable-length
+ *     next TBTT field
+ */
+static inline bool ieee80211_s1g_has_next_tbtt(__le16 fc)
+{
+       return ieee80211_is_s1g_beacon(fc) &&
+               (fc & cpu_to_le16(IEEE80211_S1G_BCN_NEXT_TBTT));
+}
+
+/**
+ * ieee80211_s1g_has_ano - check if IEEE80211_S1G_BCN_ANO
+ * @fc: frame control bytes in little-endian byteorder
+ * Return: whether or not the frame contains the variable-length
+ *     ANO field
+ */
+static inline bool ieee80211_s1g_has_ano(__le16 fc)
+{
+       return ieee80211_is_s1g_beacon(fc) &&
+               (fc & cpu_to_le16(IEEE80211_S1G_BCN_ANO));
+}
+
+/**
+ * ieee80211_s1g_has_cssid - check if IEEE80211_S1G_BCN_CSSID
+ * @fc: frame control bytes in little-endian byteorder
+ * Return: whether or not the frame contains the variable-length
+ *     compressed SSID field
+ */
+static inline bool ieee80211_s1g_has_cssid(__le16 fc)
+{
+       return ieee80211_is_s1g_beacon(fc) &&
+               (fc & cpu_to_le16(IEEE80211_S1G_BCN_CSSID));
+}
+
 /**
  * ieee80211_is_s1g_short_beacon - check if frame is an S1G short beacon
  * @fc: frame control bytes in little-endian byteorder
@@ -1245,16 +1280,40 @@ struct ieee80211_ext {
                        u8 change_seq;
                        u8 variable[0];
                } __packed s1g_beacon;
-               struct {
-                       u8 sa[ETH_ALEN];
-                       __le32 timestamp;
-                       u8 change_seq;
-                       u8 next_tbtt[3];
-                       u8 variable[0];
-               } __packed s1g_short_beacon;
        } u;
 } __packed __aligned(2);
 
+/**
+ * ieee80211_s1g_optional_len - determine length of optional S1G beacon fields
+ * @fc: frame control bytes in little-endian byteorder
+ * Return: total length in bytes of the optional fixed-length fields
+ *
+ * S1G beacons may contain up to three optional fixed-length fields that
+ * precede the variable-length elements. Whether these fields are present
+ * is indicated by flags in the frame control field.
+ *
+ * From IEEE 802.11-2024 section 9.3.4.3:
+ *  - Next TBTT field may be 0 or 3 bytes
+ *  - Short SSID field may be 0 or 4 bytes
+ *  - Access Network Options (ANO) field may be 0 or 1 byte
+ */
+static inline size_t
+ieee80211_s1g_optional_len(__le16 fc)
+{
+       size_t len = 0;
+
+       if (ieee80211_s1g_has_next_tbtt(fc))
+               len += 3;
+
+       if (ieee80211_s1g_has_cssid(fc))
+               len += 4;
+
+       if (ieee80211_s1g_has_ano(fc))
+               len += 1;
+
+       return len;
+}
+
 #define IEEE80211_TWT_CONTROL_NDP                      BIT(0)
 #define IEEE80211_TWT_CONTROL_RESP_MODE                        BIT(1)
 #define IEEE80211_TWT_CONTROL_NEG_TYPE_BROADCAST       BIT(3)
index 8fa9b9dd46118463ab98236455a3d82e4061f34e..16bb3db67eaac0742af6c76c8a526166996d48d6 100644 (file)
@@ -6728,11 +6728,8 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
        bssid = ieee80211_get_bssid(hdr, len, sdata->vif.type);
        if (ieee80211_is_s1g_beacon(mgmt->frame_control)) {
                struct ieee80211_ext *ext = (void *) mgmt;
-
-               if (ieee80211_is_s1g_short_beacon(ext->frame_control))
-                       variable = ext->u.s1g_short_beacon.variable;
-               else
-                       variable = ext->u.s1g_beacon.variable;
+               variable = ext->u.s1g_beacon.variable +
+                          ieee80211_s1g_optional_len(ext->frame_control);
        }
 
        baselen = (u8 *) variable - (u8 *) mgmt;
index adb88c06b5982237f5022a8103e8bc974109679d..ce6d5857214eba6017f03c54974231484e1da300 100644 (file)
@@ -260,6 +260,7 @@ void ieee80211_scan_rx(struct ieee80211_local *local, struct sk_buff *skb)
        struct ieee80211_mgmt *mgmt = (void *)skb->data;
        struct ieee80211_bss *bss;
        struct ieee80211_channel *channel;
+       struct ieee80211_ext *ext;
        size_t min_hdr_len = offsetof(struct ieee80211_mgmt,
                                      u.probe_resp.variable);
 
@@ -269,12 +270,10 @@ void ieee80211_scan_rx(struct ieee80211_local *local, struct sk_buff *skb)
                return;
 
        if (ieee80211_is_s1g_beacon(mgmt->frame_control)) {
-               if (ieee80211_is_s1g_short_beacon(mgmt->frame_control))
-                       min_hdr_len = offsetof(struct ieee80211_ext,
-                                              u.s1g_short_beacon.variable);
-               else
-                       min_hdr_len = offsetof(struct ieee80211_ext,
-                                              u.s1g_beacon);
+               ext = (struct ieee80211_ext *)mgmt;
+               min_hdr_len =
+                       offsetof(struct ieee80211_ext, u.s1g_beacon.variable) +
+                       ieee80211_s1g_optional_len(ext->frame_control);
        }
 
        if (skb->len < min_hdr_len)
index f0dd1f448d4d42c6326ffbe550bf4e6885673d7b..d80ab1725f28ddb4d7be7990832a1565b3ef3192 100644 (file)
@@ -3213,6 +3213,7 @@ cfg80211_inform_bss_frame_data(struct wiphy *wiphy,
        const u8 *ie;
        size_t ielen;
        u64 tsf;
+       size_t s1g_optional_len;
 
        if (WARN_ON(!mgmt))
                return NULL;
@@ -3227,12 +3228,11 @@ cfg80211_inform_bss_frame_data(struct wiphy *wiphy,
 
        if (ieee80211_is_s1g_beacon(mgmt->frame_control)) {
                ext = (void *) mgmt;
-               if (ieee80211_is_s1g_short_beacon(mgmt->frame_control))
-                       min_hdr_len = offsetof(struct ieee80211_ext,
-                                              u.s1g_short_beacon.variable);
-               else
-                       min_hdr_len = offsetof(struct ieee80211_ext,
-                                              u.s1g_beacon.variable);
+               s1g_optional_len =
+                       ieee80211_s1g_optional_len(ext->frame_control);
+               min_hdr_len =
+                       offsetof(struct ieee80211_ext, u.s1g_beacon.variable) +
+                       s1g_optional_len;
        } else {
                /* same for beacons */
                min_hdr_len = offsetof(struct ieee80211_mgmt,
@@ -3248,11 +3248,7 @@ cfg80211_inform_bss_frame_data(struct wiphy *wiphy,
                const struct ieee80211_s1g_bcn_compat_ie *compat;
                const struct element *elem;
 
-               if (ieee80211_is_s1g_short_beacon(mgmt->frame_control))
-                       ie = ext->u.s1g_short_beacon.variable;
-               else
-                       ie = ext->u.s1g_beacon.variable;
-
+               ie = ext->u.s1g_beacon.variable + s1g_optional_len;
                elem = cfg80211_find_elem(WLAN_EID_S1G_BCN_COMPAT, ie, ielen);
                if (!elem)
                        return NULL;