Re: [PATCH] wifi: mac80211: validate S1G beacon length before RX
From: Johannes Berg
Date: Thu Jun 11 2026 - 08:10:23 EST
On Thu, 2026-06-11 at 00:27 +0800, Zhao Li wrote:
> S1G beacons are extension frames, so ieee80211_hdrlen() only guarantees
> the extension header before the generic RX path starts dispatching the
> frame.
>
> The RX path can then reach helpers and interface handling code that read
> regular 802.11 header address fields, which are not present at those
> offsets in an S1G beacon.
>
> Pull the complete S1G beacon fixed header, including optional fixed
> fields indicated by frame control, before generic RX dispatch.
>
> Also make ieee80211_get_bssid() length-safe for S1G beacons and avoid
> regular-header address reads for S1G frames in accept/interface/MLO
> address handling. Skip extension frames in duplicate detection for the
> same reason, since that path consumes the regular sequence-control field.
This is all true, but all of the below seems far too complicated a fix?
Also seems like you should probably disclose some LLM usage, unless
you're going to tell me you wrote all this code yourself?
> @@ -4487,12 +4490,17 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx)
> struct ieee80211_hdr *hdr = (void *)skb->data;
> struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
> u8 *bssid = ieee80211_get_bssid(hdr, skb->len, sdata->vif.type);
> - bool multicast = is_multicast_ether_addr(hdr->addr1) ||
> - ieee80211_is_s1g_beacon(hdr->frame_control);
> + bool s1g = ieee80211_is_s1g_beacon(hdr->frame_control);
> + bool multicast;
> static const u8 nan_network_id[ETH_ALEN] __aligned(2) = {
> 0x51, 0x6F, 0x9A, 0x01, 0x00, 0x00
> };
>
> + if (s1g)
no need to introduce the 's1g' variable, and it sounds weird anyway
because s1g uses other frames too, not just beacons
> @@ -5175,11 +5183,13 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,
> }
>
> /* Store a copy of the pre-translated link addresses for SW crypto */
> - if (unlikely(is_unicast_ether_addr(hdr->addr1) &&
> + if (unlikely(!ieee80211_is_s1g_beacon(hdr->frame_control) &&
> + is_unicast_ether_addr(hdr->addr1) &&
> !ieee80211_is_data(hdr->frame_control)))
> memcpy(rx->link_addrs, &hdr->addrs, 3 * ETH_ALEN);
>
> if (unlikely(rx->sta && rx->sta->sta.mlo) &&
> + !ieee80211_is_s1g_beacon(hdr->frame_control) &&
> is_unicast_ether_addr(hdr->addr1) &&
> !ieee80211_is_probe_resp(hdr->frame_control) &&
> !ieee80211_is_beacon(hdr->frame_control)) {
This seems very ... specific, and doing the same thing twice also seems
odd. While not great, I'd probably advocate for a goto or just doing the
invoke() separately for s1g beacons.
> @@ -5260,23 +5270,30 @@ static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx,
> {
> struct link_sta_info *link_sta;
> struct ieee80211_hdr *hdr = (void *)skb->data;
> + u8 *sta_addr = hdr->addr2;
> struct sta_info *sta;
> int link_id = -1;
>
> + if (ieee80211_is_s1g_beacon(hdr->frame_control)) {
> + sta_addr = ieee80211_get_bssid(hdr, skb->len, rx->sdata->vif.type);
> + if (!sta_addr)
> + return false;
> + }
That one seems even weirder - especially in the face of your *other*
changes that attempt to never access hdr-> without making sure it's
actually the right format ... you still create a pointer to addr2 here.
It's valid since you never use it, but it's also weird because it pretty
much looks like hdr->addr2 _is_ OK at the whole function level.
> +
> /*
> * Look up link station first, in case there's a
> * chance that they might have a link address that
> * is identical to the MLD address, that way we'll
> * have the link information if needed.
> */
> - link_sta = link_sta_info_get_bss(rx->sdata, hdr->addr2);
> + link_sta = link_sta_info_get_bss(rx->sdata, sta_addr);
Obviously, if things work today, we didn't really need the link_sta for
these frames, and that makes some sense since there's no MLO and it's
just ieee80211_rx_mgmt_beacon() basically. Probably better to just skip
this entirely and handle s1g beacons separately.
> if (link_sta) {
> sta = link_sta->sta;
> link_id = link_sta->link_id;
> } else {
> struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
>
> - sta = sta_info_get_bss(rx->sdata, hdr->addr2);
> + sta = sta_info_get_bss(rx->sdata, sta_addr);
> if (status->link_valid) {
> link_id = status->link_id;
> } else if (ieee80211_vif_is_mld(&rx->sdata->vif) &&
> @@ -5347,6 +5364,12 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
> return;
> }
>
> + if (ieee80211_is_s1g_beacon(fc) &&
> + !pskb_may_pull(skb, ieee80211_s1g_beacon_min_len(fc))) {
> + dev_kfree_skb(skb);
> + return;
> + }
I'm fairly certain this still leaves things (e.g.
ieee80211_rx_mgmt_beacon()) wrong if the driver ever reports an s1g
beacon as a frag skb.
I think much better to just treat this frame like mgmt frames and
linearize it earlier in the function along with mgmt frames etc. Still
need to check the length, but we could even do that there as well,
rather than this late.
johannes