mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +02:00
ieee80211_process_rx_twt_action() only partially validates a received
S1G TWT setup frame before queueing it.
An individual agreement can therefore reach ieee80211_s1g_rx_twt_setup()
with twt->length too short for the full struct ieee80211_twt_params.
The individual path passes twt to drv_add_twt_setup(). Both the tracepoint
and the driver callback consume the complete parameters block, not merely
req_type. Do not pass a short individual agreement to the driver.
Broadcast agreements remain unchanged because they are rejected locally
after accessing only req_type.
Fixes: f5a4c24e68 ("mac80211: introduce individual TWT support in AP mode")
Assisted-by: Codex:gpt-5
Assisted-by: Claude:opus-4.8
Signed-off-by: Zhao Li <enderaoelyther@gmail.com>
Link: https://patch.msgid.link/20260723010928.76551-1-enderaoelyther@gmail.com
[edit commit message to not overclaim lack of validation nor
understate driver impact]
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
235 lines
6.7 KiB
C
235 lines
6.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* S1G handling
|
|
* Copyright(c) 2020 Adapt-IP
|
|
* Copyright (C) 2023, 2026 Intel Corporation
|
|
*/
|
|
#include <linux/ieee80211.h>
|
|
#include <net/mac80211.h>
|
|
#include "ieee80211_i.h"
|
|
#include "driver-ops.h"
|
|
|
|
void ieee80211_s1g_sta_rate_init(struct sta_info *sta)
|
|
{
|
|
/* avoid indicating legacy bitrates for S1G STAs */
|
|
sta->deflink.tx_stats.last_rate.flags |= IEEE80211_TX_RC_S1G_MCS;
|
|
sta->deflink.rx_stats.last_rate =
|
|
STA_STATS_FIELD(TYPE, STA_STATS_RATE_TYPE_S1G);
|
|
}
|
|
|
|
bool ieee80211_s1g_is_twt_setup(struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
|
|
|
|
if (likely(!ieee80211_is_action(mgmt->frame_control)))
|
|
return false;
|
|
|
|
if (likely(mgmt->u.action.category != WLAN_CATEGORY_S1G))
|
|
return false;
|
|
|
|
return mgmt->u.action.action_code == WLAN_S1G_TWT_SETUP;
|
|
}
|
|
|
|
static void
|
|
ieee80211_s1g_send_twt_setup(struct ieee80211_sub_if_data *sdata, const u8 *da,
|
|
const u8 *bssid, struct ieee80211_twt_setup *twt)
|
|
{
|
|
int len = IEEE80211_MIN_ACTION_SIZE(s1g) + 3 + twt->length;
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct ieee80211_mgmt *mgmt;
|
|
struct sk_buff *skb;
|
|
|
|
skb = dev_alloc_skb(local->hw.extra_tx_headroom + len);
|
|
if (!skb)
|
|
return;
|
|
|
|
skb_reserve(skb, local->hw.extra_tx_headroom);
|
|
mgmt = skb_put_zero(skb, len);
|
|
mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
|
|
IEEE80211_STYPE_ACTION);
|
|
memcpy(mgmt->da, da, ETH_ALEN);
|
|
memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
|
|
memcpy(mgmt->bssid, bssid, ETH_ALEN);
|
|
|
|
mgmt->u.action.category = WLAN_CATEGORY_S1G;
|
|
mgmt->u.action.action_code = WLAN_S1G_TWT_SETUP;
|
|
memcpy(mgmt->u.action.s1g.variable, twt, 3 + twt->length);
|
|
|
|
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT |
|
|
IEEE80211_TX_INTFL_MLME_CONN_TX |
|
|
IEEE80211_TX_CTL_REQ_TX_STATUS;
|
|
ieee80211_tx_skb(sdata, skb);
|
|
}
|
|
|
|
static void
|
|
ieee80211_s1g_send_twt_teardown(struct ieee80211_sub_if_data *sdata,
|
|
const u8 *da, const u8 *bssid, u8 flowid)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct ieee80211_mgmt *mgmt;
|
|
struct sk_buff *skb;
|
|
u8 *id;
|
|
|
|
skb = dev_alloc_skb(local->hw.extra_tx_headroom +
|
|
IEEE80211_MIN_ACTION_SIZE(s1g) + 1);
|
|
if (!skb)
|
|
return;
|
|
|
|
skb_reserve(skb, local->hw.extra_tx_headroom);
|
|
mgmt = skb_put_zero(skb, IEEE80211_MIN_ACTION_SIZE(s1g) + 1);
|
|
mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
|
|
IEEE80211_STYPE_ACTION);
|
|
memcpy(mgmt->da, da, ETH_ALEN);
|
|
memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
|
|
memcpy(mgmt->bssid, bssid, ETH_ALEN);
|
|
|
|
mgmt->u.action.category = WLAN_CATEGORY_S1G;
|
|
mgmt->u.action.action_code = WLAN_S1G_TWT_TEARDOWN;
|
|
id = (u8 *)mgmt->u.action.s1g.variable;
|
|
*id = flowid;
|
|
|
|
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT |
|
|
IEEE80211_TX_CTL_REQ_TX_STATUS;
|
|
ieee80211_tx_skb(sdata, skb);
|
|
}
|
|
|
|
static void
|
|
ieee80211_s1g_rx_twt_setup(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_mgmt *mgmt = (void *)skb->data;
|
|
struct ieee80211_twt_setup *twt = (void *)mgmt->u.action.s1g.variable;
|
|
struct ieee80211_twt_params *twt_agrt = (void *)twt->params;
|
|
|
|
if (!(twt->control & IEEE80211_TWT_CONTROL_NEG_TYPE_BROADCAST) &&
|
|
twt->length < sizeof(twt->control) + sizeof(*twt_agrt))
|
|
return;
|
|
|
|
twt_agrt->req_type &= cpu_to_le16(~IEEE80211_TWT_REQTYPE_REQUEST);
|
|
|
|
/* broadcast TWT not supported yet */
|
|
if (twt->control & IEEE80211_TWT_CONTROL_NEG_TYPE_BROADCAST) {
|
|
twt_agrt->req_type &=
|
|
~cpu_to_le16(IEEE80211_TWT_REQTYPE_SETUP_CMD);
|
|
twt_agrt->req_type |=
|
|
le16_encode_bits(TWT_SETUP_CMD_REJECT,
|
|
IEEE80211_TWT_REQTYPE_SETUP_CMD);
|
|
goto out;
|
|
}
|
|
|
|
/* TWT Information not supported yet */
|
|
twt->control |= IEEE80211_TWT_CONTROL_RX_DISABLED;
|
|
|
|
drv_add_twt_setup(sdata->local, sdata, &sta->sta, twt);
|
|
out:
|
|
ieee80211_s1g_send_twt_setup(sdata, mgmt->sa, sdata->vif.addr, twt);
|
|
}
|
|
|
|
static void
|
|
ieee80211_s1g_rx_twt_teardown(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
|
|
|
|
drv_twt_teardown_request(sdata->local, sdata, &sta->sta,
|
|
mgmt->u.action.s1g.variable[0]);
|
|
}
|
|
|
|
static void
|
|
ieee80211_s1g_tx_twt_setup_fail(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
|
|
struct ieee80211_twt_setup *twt = (void *)mgmt->u.action.s1g.variable;
|
|
struct ieee80211_twt_params *twt_agrt = (void *)twt->params;
|
|
u8 flowid = le16_get_bits(twt_agrt->req_type,
|
|
IEEE80211_TWT_REQTYPE_FLOWID);
|
|
|
|
drv_twt_teardown_request(sdata->local, sdata, &sta->sta, flowid);
|
|
|
|
ieee80211_s1g_send_twt_teardown(sdata, mgmt->sa, sdata->vif.addr,
|
|
flowid);
|
|
}
|
|
|
|
void ieee80211_s1g_rx_twt_action(struct ieee80211_sub_if_data *sdata,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct sta_info *sta;
|
|
|
|
lockdep_assert_wiphy(local->hw.wiphy);
|
|
|
|
sta = sta_info_get_bss(sdata, mgmt->sa);
|
|
if (!sta)
|
|
return;
|
|
|
|
switch (mgmt->u.action.action_code) {
|
|
case WLAN_S1G_TWT_SETUP:
|
|
ieee80211_s1g_rx_twt_setup(sdata, sta, skb);
|
|
break;
|
|
case WLAN_S1G_TWT_TEARDOWN:
|
|
ieee80211_s1g_rx_twt_teardown(sdata, sta, skb);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ieee80211_s1g_status_twt_action(struct ieee80211_sub_if_data *sdata,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct sta_info *sta;
|
|
|
|
lockdep_assert_wiphy(local->hw.wiphy);
|
|
|
|
sta = sta_info_get_bss(sdata, mgmt->da);
|
|
if (!sta)
|
|
return;
|
|
|
|
switch (mgmt->u.action.action_code) {
|
|
case WLAN_S1G_TWT_SETUP:
|
|
/* process failed twt setup frames */
|
|
ieee80211_s1g_tx_twt_setup_fail(sdata, sta, skb);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ieee80211_s1g_cap_to_sta_s1g_cap(struct ieee80211_sub_if_data *sdata,
|
|
const struct ieee80211_s1g_cap *s1g_cap_ie,
|
|
struct link_sta_info *link_sta)
|
|
{
|
|
struct ieee80211_sta_s1g_cap *s1g_cap = &link_sta->pub->s1g_cap;
|
|
|
|
memset(s1g_cap, 0, sizeof(*s1g_cap));
|
|
|
|
memcpy(s1g_cap->cap, s1g_cap_ie->capab_info, sizeof(s1g_cap->cap));
|
|
memcpy(s1g_cap->nss_mcs, s1g_cap_ie->supp_mcs_nss,
|
|
sizeof(s1g_cap->nss_mcs));
|
|
|
|
s1g_cap->s1g = true;
|
|
|
|
/* Maximum MPDU length is 1 bit for S1G */
|
|
if (s1g_cap->cap[3] & S1G_CAP3_MAX_MPDU_LEN) {
|
|
link_sta->pub->agg.max_amsdu_len =
|
|
IEEE80211_MAX_MPDU_LEN_VHT_7991;
|
|
} else {
|
|
link_sta->pub->agg.max_amsdu_len =
|
|
IEEE80211_MAX_MPDU_LEN_VHT_3895;
|
|
}
|
|
|
|
ieee80211_sta_recalc_aggregates(&link_sta->sta->sta);
|
|
}
|
|
|
|
bool ieee80211_s1g_use_ndp_ba(const struct ieee80211_sub_if_data *sdata,
|
|
const struct sta_info *sta)
|
|
{
|
|
return sdata->vif.cfg.s1g &&
|
|
ieee80211_hw_check(&sdata->local->hw, SUPPORTS_NDP_BLOCKACK) &&
|
|
(sta && sta->sta.deflink.s1g_cap.s1g);
|
|
}
|