aboutsummaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-09-30 14:05:59 -0700
committerJakub Kicinski <kuba@kernel.org>2026-09-30 14:06:00 -0700
commitbe35a3e003941fc25b4e72d8d4fd44f8a98ac1af (patch)
tree7f4b26d1bde1d388ddd773a005e8dc131ff2fb65 /net
parent99b43ede9e355ba35244cc9470bf1819774ce39d (diff)
parent6f63e919fe1e335b8abcb3a28bfd4804a98d875a (diff)
Merge tag 'wireless-2026-09-30' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless
Johannes Berg says: ==================== Still more fixes coming in, notably: - ath11k: avoid running out of stations on HW restart - mac80211: - drop too large fragmented MPDUs - mesh path handling fixes - validation improvements - reject CSA with bad 320 MHz bandwidth - cfg80211: fix RTS for single radio devices * tag 'wireless-2026-09-30' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless: (27 commits) wifi: mac80211: fix slab-out-of-bounds read in ieee80211_monitor_select_queue() wifi: mac80211: reject invalid 320 MHz CSA bandwidth wifi: mac80211: set info->band for 802.3 encap offload frames wifi: mac80211: prevent AP VLAN tx from other interfaces wifi: cfg80211: preserve hidden-group beacon IE ownership wifi: mac80211: keep fallback association elements alive wifi: mac80211: shut down RX BA session timer on teardown wifi: mac80211: validate TX status rate metadata wifi: ath9k_htc: bound TX aggregation to MAX_TX_BUF_SIZE wifi: ath9k: reject short WMI command responses wifi: ath9k: Clean up device initialisation guards wifi: ath11k: reset ar->num_stations on hardware start wifi: cfg80211: fix RTS threshold setting for single-radio PHY wifi: mac80211: handle empty FILS association request payload wifi: mac80211: minstrel_ht: validate fixed rate index wifi: p54: validate firmware record lengths wifi: mac80211: fix mesh fast xmit path deletion UAF wifi: mac80211: drop oversized fragments to avoid extra_len overflow wifi: wlcore: Fix runtime PM leak in wlcore_remove() wifi: mac80211: drain PS delivery work during station teardown ... ==================== Link: https://patch.msgid.link/20260930124440.224799-3-johannes@sipsolutions.net Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Diffstat (limited to 'net')
-rw-r--r--net/mac80211/agg-rx.c2
-rw-r--r--net/mac80211/chan.c50
-rw-r--r--net/mac80211/drop.h1
-rw-r--r--net/mac80211/fils_aead.c8
-rw-r--r--net/mac80211/ieee80211_i.h2
-rw-r--r--net/mac80211/iface.c4
-rw-r--r--net/mac80211/mesh_pathtbl.c30
-rw-r--r--net/mac80211/mlme.c4
-rw-r--r--net/mac80211/rc80211_minstrel_ht.c89
-rw-r--r--net/mac80211/rx.c6
-rw-r--r--net/mac80211/spectmgmt.c4
-rw-r--r--net/mac80211/sta_info.c6
-rw-r--r--net/mac80211/status.c70
-rw-r--r--net/mac80211/tx.c45
-rw-r--r--net/wireless/nl80211.c11
-rw-r--r--net/wireless/scan.c22
16 files changed, 293 insertions, 61 deletions
diff --git a/net/mac80211/agg-rx.c b/net/mac80211/agg-rx.c
index 9629e00069a1..01d6703767c6 100644
--- a/net/mac80211/agg-rx.c
+++ b/net/mac80211/agg-rx.c
@@ -102,7 +102,7 @@ void __ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid,
if (!tid_rx)
return;
- timer_delete_sync(&tid_rx->session_timer);
+ timer_shutdown_sync(&tid_rx->session_timer);
/* make sure ieee80211_sta_reorder_release() doesn't re-arm the timer */
spin_lock_bh(&tid_rx->reorder_lock);
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 75bb204ad743..9cba79507767 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -231,6 +231,13 @@ int ieee80211_chanctx_refcount(struct ieee80211_local *local,
return num;
}
+static bool
+ieee80211_chanctx_has_replace_partner(struct ieee80211_chanctx *ctx)
+{
+ return ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED &&
+ ctx->replace_ctx;
+}
+
static int ieee80211_num_chanctx(struct ieee80211_local *local, int radio_idx)
{
struct ieee80211_chanctx *ctx;
@@ -1405,6 +1412,7 @@ void ieee80211_link_unreserve_chanctx(struct ieee80211_link_data *link)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_chanctx *ctx = link->reserved_chanctx;
+ struct ieee80211_chanctx *old_ctx = NULL;
lockdep_assert_wiphy(sdata->local->hw.wiphy);
@@ -1418,6 +1426,7 @@ void ieee80211_link_unreserve_chanctx(struct ieee80211_link_data *link)
if (WARN_ON(!ctx->replace_ctx))
return;
+ old_ctx = ctx->replace_ctx;
WARN_ON(ctx->replace_ctx->replace_state !=
IEEE80211_CHANCTX_WILL_BE_REPLACED);
WARN_ON(ctx->replace_ctx->replace_ctx != ctx);
@@ -1428,6 +1437,11 @@ void ieee80211_link_unreserve_chanctx(struct ieee80211_link_data *link)
list_del_rcu(&ctx->list);
kfree_rcu(ctx, rcu_head);
+
+ if (ieee80211_chanctx_refcount(sdata->local,
+ old_ctx) == 0)
+ ieee80211_free_chanctx(sdata->local, old_ctx,
+ false);
} else {
ieee80211_free_chanctx(sdata->local, ctx, false);
}
@@ -1707,7 +1721,8 @@ ieee80211_link_use_reserved_reassign(struct ieee80211_link_data *link)
ieee80211_check_fast_xmit_iface(sdata);
- if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
+ if (ieee80211_chanctx_refcount(local, old_ctx) == 0 &&
+ !ieee80211_chanctx_has_replace_partner(old_ctx))
ieee80211_free_chanctx(local, old_ctx, false);
ieee80211_recalc_chanctx_min_def(local, new_ctx);
@@ -1772,7 +1787,8 @@ out:
}
static bool
-ieee80211_link_has_in_place_reservation(struct ieee80211_link_data *link)
+ieee80211_link_has_in_place_reservation(struct ieee80211_link_data *link,
+ struct ieee80211_chanctx *ctx)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_chanctx *old_ctx, *new_ctx;
@@ -1782,6 +1798,9 @@ ieee80211_link_has_in_place_reservation(struct ieee80211_link_data *link)
new_ctx = link->reserved_chanctx;
old_ctx = ieee80211_link_get_chanctx(link);
+ if (new_ctx != ctx)
+ return false;
+
if (!old_ctx)
return false;
@@ -1794,6 +1813,12 @@ ieee80211_link_has_in_place_reservation(struct ieee80211_link_data *link)
if (new_ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
return false;
+ if (new_ctx->replace_ctx != old_ctx)
+ return false;
+
+ if (old_ctx->replace_ctx != new_ctx)
+ return false;
+
return true;
}
@@ -1823,7 +1848,8 @@ static int ieee80211_chsw_switch_vifs(struct ieee80211_local *local,
}
for_each_chanctx_user_reserved(local, ctx, &iter) {
- if (!ieee80211_link_has_in_place_reservation(iter.link))
+ if (!ieee80211_link_has_in_place_reservation(iter.link,
+ ctx))
continue;
old_ctx = ieee80211_link_get_chanctx(iter.link);
@@ -1925,7 +1951,9 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
for_each_chanctx_user_assigned(local, ctx->replace_ctx, &iter) {
n_assigned++;
- if (iter.link && iter.link->reserved_chanctx) {
+ if (iter.link &&
+ ieee80211_link_has_in_place_reservation(iter.link,
+ ctx)) {
n_reserved++;
if (iter.link->reserved_ready)
n_ready++;
@@ -1950,7 +1978,8 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
use_reserved:
ctx->conf.radar_enabled = false;
for_each_chanctx_user_reserved(local, ctx, &iter) {
- if (ieee80211_link_has_in_place_reservation(iter.link) &&
+ if (ieee80211_link_has_in_place_reservation(iter.link,
+ ctx) &&
!iter.link->reserved_ready)
return -EAGAIN;
@@ -1991,7 +2020,8 @@ use_reserved:
}
for_each_chanctx_user_reserved(local, ctx, &iter) {
- if (!ieee80211_link_has_in_place_reservation(iter.link))
+ if (!ieee80211_link_has_in_place_reservation(iter.link,
+ ctx))
continue;
ieee80211_chan_bw_change(local,
@@ -2040,7 +2070,7 @@ use_reserved:
struct ieee80211_bss_conf *link_conf = link->conf;
u64 changed = 0;
- if (!ieee80211_link_has_in_place_reservation(link))
+ if (!ieee80211_link_has_in_place_reservation(link, ctx))
continue;
rcu_assign_pointer(link_conf->chanctx_conf,
@@ -2091,7 +2121,8 @@ use_reserved:
for_each_chanctx_user_reserved(local, ctx, &iter) {
struct ieee80211_link_data *link = iter.link;
- if (WARN_ON(ieee80211_link_has_in_place_reservation(link)))
+ if (WARN_ON(ieee80211_link_has_in_place_reservation(link,
+ ctx)))
continue;
if (!link->reserved_ready)
@@ -2176,7 +2207,8 @@ void __ieee80211_link_release_channel(struct ieee80211_link_data *link,
}
ieee80211_assign_link_chanctx(link, NULL, false);
- if (ieee80211_chanctx_refcount(local, ctx) == 0)
+ if (ieee80211_chanctx_refcount(local, ctx) == 0 &&
+ !ieee80211_chanctx_has_replace_partner(ctx))
ieee80211_free_chanctx(local, ctx, skip_idle_recalc);
link->radar_required = false;
diff --git a/net/mac80211/drop.h b/net/mac80211/drop.h
index f06a8aa905c5..f433ee17cce5 100644
--- a/net/mac80211/drop.h
+++ b/net/mac80211/drop.h
@@ -108,6 +108,7 @@ typedef unsigned int __bitwise ieee80211_rx_result;
R(RX_DROP_U_UNHANDLED_PREQ) \
R(RX_DROP_U_UNHANDLED_MGMT_STYPE) \
R(RX_DROP_U_NO_LINK) \
+ R(RX_DROP_U_DEFRAG_OVERFLOW) \
/* this line for the trailing \ - add before this */
/* having two enums allows for checking ieee80211_rx_result use with sparse */
diff --git a/net/mac80211/fils_aead.c b/net/mac80211/fils_aead.c
index 293590976489..9c4f00d61981 100644
--- a/net/mac80211/fils_aead.c
+++ b/net/mac80211/fils_aead.c
@@ -89,6 +89,14 @@ static int aes_siv_encrypt(const u8 *key, size_t key_len,
if (res)
return res;
+ /* With an empty plaintext there is no ciphertext to produce and
+ * the S2V result is the complete output.
+ */
+ if (!plain_len) {
+ memcpy(out, v, AES_BLOCK_SIZE);
+ return 0;
+ }
+
/* Use a temporary buffer of the plaintext to handle need for
* overwriting this during AES-CTR.
*/
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index d05f59467399..9374c2942489 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2475,7 +2475,7 @@ static inline bool ieee80211_require_encrypted_assoc(__le16 fc,
/* sta_out needs to be checked for ERR_PTR() before using */
int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb,
- struct sta_info **sta_out);
+ struct sta_info **sta_out, bool bss);
static inline void
ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 889c32fd8de1..88942317fec8 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -998,8 +998,8 @@ static u16 ieee80211_monitor_select_queue(struct net_device *dev,
len_rthdr = ieee80211_get_radiotap_len(skb->data);
hdr = (struct ieee80211_hdr *)(skb->data + len_rthdr);
- if (skb->len < len_rthdr + 2 ||
- skb->len < len_rthdr + ieee80211_hdrlen(hdr->frame_control))
+ if (skb_headlen(skb) < len_rthdr + 2 ||
+ skb_headlen(skb) < len_rthdr + ieee80211_hdrlen(hdr->frame_control))
return 0; /* doesn't matter, frame will be dropped */
return ieee80211_select_queue_80211(sdata, skb, hdr);
diff --git a/net/mac80211/mesh_pathtbl.c b/net/mac80211/mesh_pathtbl.c
index 03171cf00855..07a21dfa8674 100644
--- a/net/mac80211/mesh_pathtbl.c
+++ b/net/mac80211/mesh_pathtbl.c
@@ -577,6 +577,12 @@ void mesh_fast_tx_cache(struct ieee80211_sub_if_data *sdata,
goto unlock_sta;
spin_lock(&cache->walk_lock);
+ if ((READ_ONCE(mpath->flags) & MESH_PATH_DELETED) ||
+ (mppath && (READ_ONCE(mppath->flags) & MESH_PATH_DELETED))) {
+ kfree(entry);
+ goto unlock_cache;
+ }
+
prev = rhashtable_lookup_get_insert_fast(&cache->rht,
&entry->rhash,
fast_tx_rht_params);
@@ -694,8 +700,10 @@ struct mesh_path *mesh_path_add(struct ieee80211_sub_if_data *sdata,
return ERR_PTR(-ENOSPC);
new_mpath = mesh_path_new(sdata, dst, GFP_ATOMIC);
- if (!new_mpath)
+ if (!new_mpath) {
+ atomic_dec(&sdata->u.mesh.mpaths);
return ERR_PTR(-ENOMEM);
+ }
tbl = &sdata->u.mesh.mesh_paths;
spin_lock_bh(&tbl->walk_lock);
@@ -708,6 +716,7 @@ struct mesh_path *mesh_path_add(struct ieee80211_sub_if_data *sdata,
if (mpath) {
kfree(new_mpath);
+ atomic_dec(&sdata->u.mesh.mpaths);
if (IS_ERR(mpath))
return mpath;
@@ -733,10 +742,15 @@ int mpp_path_add(struct ieee80211_sub_if_data *sdata,
if (is_multicast_ether_addr(dst))
return -EOPNOTSUPP;
+ if (!atomic_add_unless(&sdata->u.mesh.mpaths, 1, MESH_MAX_MPATHS))
+ return -ENOSPC;
+
new_mpath = mesh_path_new(sdata, dst, GFP_ATOMIC);
- if (!new_mpath)
+ if (!new_mpath) {
+ atomic_dec(&sdata->u.mesh.mpaths);
return -ENOMEM;
+ }
memcpy(new_mpath->mpp, mpp, ETH_ALEN);
tbl = &sdata->u.mesh.mpp_paths;
@@ -749,10 +763,12 @@ int mpp_path_add(struct ieee80211_sub_if_data *sdata,
hlist_add_head_rcu(&new_mpath->walk_list, &tbl->walk_head);
spin_unlock_bh(&tbl->walk_lock);
- if (ret)
+ if (ret) {
kfree(new_mpath);
- else
+ atomic_dec(&sdata->u.mesh.mpaths);
+ } else {
mesh_fast_tx_flush_addr(sdata, dst);
+ }
sdata->u.mesh.mpp_paths_generation++;
return ret;
@@ -798,7 +814,8 @@ static void mesh_path_free_rcu(struct mesh_table *tbl,
struct ieee80211_sub_if_data *sdata = mpath->sdata;
spin_lock_bh(&mpath->state_lock);
- mpath->flags |= MESH_PATH_RESOLVING | MESH_PATH_DELETED;
+ WRITE_ONCE(mpath->flags,
+ mpath->flags | MESH_PATH_RESOLVING | MESH_PATH_DELETED);
mesh_gate_del(tbl, mpath);
spin_unlock_bh(&mpath->state_lock);
timer_shutdown_sync(&mpath->timer);
@@ -812,6 +829,9 @@ static void __mesh_path_del(struct mesh_table *tbl, struct mesh_path *mpath)
{
hlist_del_rcu(&mpath->walk_list);
rhashtable_remove_fast(&tbl->rhead, &mpath->rhash, mesh_rht_params);
+ spin_lock_bh(&mpath->state_lock);
+ WRITE_ONCE(mpath->flags, mpath->flags | MESH_PATH_DELETED);
+ spin_unlock_bh(&mpath->state_lock);
if (tbl == &mpath->sdata->u.mesh.mpp_paths)
mesh_fast_tx_flush_addr(mpath->sdata, mpath->dst);
else
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index f51167f0fc46..3c31c16860ab 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -5877,6 +5877,7 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ;
bool is_s1g = cbss->channel->band == NL80211_BAND_S1GHZ;
const struct cfg80211_bss_ies *bss_ies = NULL;
+ struct ieee802_11_elems *bss_elems = NULL;
struct ieee80211_supported_band *sband;
struct ieee802_11_elems *elems;
u16 capab_info;
@@ -6007,7 +6008,6 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
(is_5ghz && link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_VHT &&
(!elems->vht_cap_elem || !elems->vht_operation)))) {
const struct cfg80211_bss_ies *ies;
- struct ieee802_11_elems *bss_elems;
rcu_read_lock();
ies = rcu_dereference(cbss->ies);
@@ -6069,7 +6069,6 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
"AP bug: VHT operation missing from AssocResp\n");
}
}
- kfree(bss_elems);
}
/*
@@ -6342,6 +6341,7 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
ret = true;
out:
kfree(elems);
+ kfree(bss_elems);
kfree(bss_ies);
return ret;
}
diff --git a/net/mac80211/rc80211_minstrel_ht.c b/net/mac80211/rc80211_minstrel_ht.c
index b73ef3adfcc5..45e7a7ea7690 100644
--- a/net/mac80211/rc80211_minstrel_ht.c
+++ b/net/mac80211/rc80211_minstrel_ht.c
@@ -7,6 +7,7 @@
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/debugfs.h>
+#include <linux/limits.h>
#include <linux/random.h>
#include <linux/moduleparam.h>
#include <linux/ieee80211.h>
@@ -1193,6 +1194,54 @@ minstrel_ht_update_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi)
mi->sample_time = jiffies;
}
+/*
+ * Check whether an HT/VHT rate from a TX status entry maps to an entry
+ * in the minstrel_ht rate tables. Values that cannot be represented
+ * there must not be used for indexing mi->groups[] and the MCS groups.
+ */
+static bool
+minstrel_ht_txstat_rate_valid(struct ieee80211_tx_rate *rate)
+{
+ unsigned int bw;
+
+ if (!(rate->flags & (IEEE80211_TX_RC_MCS | IEEE80211_TX_RC_VHT_MCS)))
+ return true;
+
+ if (rate->flags & IEEE80211_TX_RC_MCS) {
+ /* minstrel_ht supports up to MINSTREL_MAX_STREAMS streams */
+ return rate->idx < MINSTREL_MAX_STREAMS * 8;
+ }
+
+ /* minstrel_ht has no VHT groups for 160 MHz and wider */
+ bw = !!(rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH) +
+ 2 * !!(rate->flags & IEEE80211_TX_RC_80_MHZ_WIDTH);
+ if ((rate->flags & IEEE80211_TX_RC_160_MHZ_WIDTH) || bw > BW_80)
+ return false;
+
+ return ieee80211_rate_get_vht_nss(rate) <= MINSTREL_MAX_STREAMS &&
+ ieee80211_rate_get_vht_mcs(rate) < MCS_GROUP_RATES;
+}
+
+static bool
+minstrel_ht_ri_txstat_rate_valid(struct rate_info *rate)
+{
+ if (!(rate->flags & (RATE_INFO_FLAGS_MCS | RATE_INFO_FLAGS_VHT_MCS)))
+ return true;
+
+ if (rate->flags & RATE_INFO_FLAGS_MCS) {
+ /* minstrel_ht supports up to MINSTREL_MAX_STREAMS streams */
+ return rate->mcs < MINSTREL_MAX_STREAMS * 8;
+ }
+
+ /* minstrel_ht has VHT groups only for 20/40/80 MHz */
+ if (rate->bw != RATE_INFO_BW_20 && rate->bw != RATE_INFO_BW_40 &&
+ rate->bw != RATE_INFO_BW_80)
+ return false;
+
+ return rate->nss <= MINSTREL_MAX_STREAMS &&
+ rate->mcs < MCS_GROUP_RATES;
+}
+
static bool
minstrel_ht_txstat_valid(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
struct ieee80211_tx_rate *rate)
@@ -1205,9 +1254,8 @@ minstrel_ht_txstat_valid(struct minstrel_priv *mp, struct minstrel_ht_sta *mi,
if (!rate->count)
return false;
- if (rate->flags & IEEE80211_TX_RC_MCS ||
- rate->flags & IEEE80211_TX_RC_VHT_MCS)
- return true;
+ if (rate->flags & (IEEE80211_TX_RC_MCS | IEEE80211_TX_RC_VHT_MCS))
+ return minstrel_ht_txstat_rate_valid(rate);
for (i = 0; i < ARRAY_SIZE(mp->cck_rates); i++)
if (rate->idx == mp->cck_rates[i])
@@ -1235,9 +1283,9 @@ minstrel_ht_ri_txstat_valid(struct minstrel_priv *mp,
if (!rate_status->try_count)
return false;
- if (rate_status->rate_idx.flags & RATE_INFO_FLAGS_MCS ||
- rate_status->rate_idx.flags & RATE_INFO_FLAGS_VHT_MCS)
- return true;
+ if (rate_status->rate_idx.flags &
+ (RATE_INFO_FLAGS_MCS | RATE_INFO_FLAGS_VHT_MCS))
+ return minstrel_ht_ri_txstat_rate_valid(&rate_status->rate_idx);
for (i = 0; i < ARRAY_SIZE(mp->cck_rates); i++) {
if (rate_status->rate_idx.legacy ==
@@ -1947,14 +1995,39 @@ minstrel_ht_alloc(struct ieee80211_hw *hw)
}
#ifdef CONFIG_MAC80211_DEBUGFS
+static int minstrel_ht_fixed_rate_idx_get(void *data, u64 *val)
+{
+ *val = *(u32 *)data;
+ return 0;
+}
+
+static int minstrel_ht_fixed_rate_idx_set(void *data, u64 val)
+{
+ u32 idx = val;
+
+ /* U32_MAX is the default and keeps fixed rate processing disabled */
+ if (val != U32_MAX &&
+ (val > U16_MAX ||
+ MI_RATE_GROUP(idx) >= ARRAY_SIZE(minstrel_mcs_groups) ||
+ MI_RATE_IDX(idx) >= MCS_GROUP_RATES))
+ return -EINVAL;
+
+ *(u32 *)data = idx;
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(minstrel_ht_fixed_rate_idx_fops,
+ minstrel_ht_fixed_rate_idx_get,
+ minstrel_ht_fixed_rate_idx_set, "%llu\n");
+
static void minstrel_ht_add_debugfs(struct ieee80211_hw *hw, void *priv,
struct dentry *debugfsdir)
{
struct minstrel_priv *mp = priv;
mp->fixed_rate_idx = (u32) -1;
- debugfs_create_u32("fixed_rate_idx", S_IRUGO | S_IWUGO, debugfsdir,
- &mp->fixed_rate_idx);
+ debugfs_create_file("fixed_rate_idx", S_IRUGO | S_IWUGO, debugfsdir,
+ &mp->fixed_rate_idx, &minstrel_ht_fixed_rate_idx_fops);
}
#endif
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 5e26be8e27d8..7ead509304eb 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -2499,6 +2499,12 @@ ieee80211_rx_h_defragment(struct ieee80211_rx_data *rx)
}
skb_pull(rx->skb, ieee80211_hdrlen(fc));
+ if (unlikely((u32)entry->extra_len + rx->skb->len > U16_MAX)) {
+ I802_DEBUG_INC(rx->local->rx_handlers_drop_defrag);
+ __skb_queue_purge(&entry->skb_list);
+ return RX_DROP_U_DEFRAG_OVERFLOW;
+ }
+
__skb_queue_tail(&entry->skb_list, rx->skb);
entry->last_frag = frag;
entry->extra_len += rx->skb->len;
diff --git a/net/mac80211/spectmgmt.c b/net/mac80211/spectmgmt.c
index 880f4625775d..f5e4970869b6 100644
--- a/net/mac80211/spectmgmt.c
+++ b/net/mac80211/spectmgmt.c
@@ -111,8 +111,8 @@ validate_chandef_by_ht_vht_oper(struct ieee80211_sub_if_data *sdata,
switch (chan_width) {
case NL80211_CHAN_WIDTH_320:
- WARN_ON(1);
- break;
+ chandef->chan = NULL;
+ return;
case NL80211_CHAN_WIDTH_160:
vht_oper.chan_width = IEEE80211_VHT_CHANWIDTH_80MHZ;
vht_oper.center_freq_seg1_idx = vht_oper.center_freq_seg0_idx;
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index fdf00cbf49d8..e81cd155197b 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -137,6 +137,8 @@ static void __cleanup_single_sta(struct sta_info *sta)
struct ieee80211_local *local = sdata->local;
struct ps_data *ps;
+ cancel_work_sync(&sta->drv_deliver_wk);
+
if (test_sta_flag(sta, WLAN_STA_PS_STA) ||
test_sta_flag(sta, WLAN_STA_PS_DRIVER) ||
test_sta_flag(sta, WLAN_STA_PS_DELIVER)) {
@@ -166,8 +168,6 @@ static void __cleanup_single_sta(struct sta_info *sta)
if (ieee80211_vif_is_mesh(&sdata->vif))
mesh_sta_cleanup(sta);
- cancel_work_sync(&sta->drv_deliver_wk);
-
/*
* Destroy aggregation state here. It would be nice to wait for the
* driver to finish aggregation stop and then clean up, but for now
@@ -1582,6 +1582,8 @@ static void __sta_info_destroy_part2(struct sta_info *sta, bool recalc)
sta->dead = true;
+ cancel_work_sync(&sta->drv_deliver_wk);
+
local->num_sta--;
local->sta_generation++;
diff --git a/net/mac80211/status.c b/net/mac80211/status.c
index 3d811f652603..414979e2a1d9 100644
--- a/net/mac80211/status.c
+++ b/net/mac80211/status.c
@@ -1164,6 +1164,73 @@ void ieee80211_tx_status_skb(struct ieee80211_hw *hw, struct sk_buff *skb)
}
EXPORT_SYMBOL(ieee80211_tx_status_skb);
+/*
+ * Check whether the HT/VHT rate information in a TX status entry is
+ * valid for its encoding. This is not specific to any rate control
+ * algorithm; all status consumers rely on the values being sane.
+ */
+static bool ieee80211_tx_status_rate_info_valid(const struct rate_info *rate)
+{
+ if (rate->flags & RATE_INFO_FLAGS_MCS)
+ return rate->mcs < 32;
+
+ if (rate->flags & RATE_INFO_FLAGS_VHT_MCS)
+ return rate->nss >= 1 && rate->nss <= 8 && rate->mcs <= 11;
+
+ return true;
+}
+
+static bool
+ieee80211_tx_status_tx_rate_valid(const struct ieee80211_tx_rate *rate)
+{
+ if (rate->flags & IEEE80211_TX_RC_MCS)
+ return rate->idx < 32;
+
+ if (rate->flags & IEEE80211_TX_RC_VHT_MCS)
+ return ieee80211_rate_get_vht_mcs(rate) <= 11;
+
+ return true;
+}
+
+/*
+ * Drop TX status rate entries that don't describe a valid rate. The
+ * status information is used by rate control and by other mac80211
+ * code, and a malformed entry must not be able to corrupt state beyond
+ * the driver that reported it.
+ */
+static void
+ieee80211_tx_status_drop_invalid_rates(struct ieee80211_tx_status *status)
+{
+ int i;
+
+ for (i = 0; i < status->n_rates; i++) {
+ struct ieee80211_rate_status *rs = &status->rates[i];
+
+ if (ieee80211_tx_status_rate_info_valid(&rs->rate_idx))
+ continue;
+
+ rs->try_count = 0;
+ memset(&rs->rate_idx, 0, sizeof(rs->rate_idx));
+ }
+
+ if (!status->info)
+ return;
+
+ for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) {
+ struct ieee80211_tx_rate *rate;
+
+ rate = &status->info->status.rates[i];
+ if (rate->idx < 0)
+ break;
+
+ if (ieee80211_tx_status_tx_rate_valid(rate))
+ continue;
+
+ rate->idx = -1;
+ rate->count = 0;
+ }
+}
+
void ieee80211_tx_status_ext(struct ieee80211_hw *hw,
struct ieee80211_tx_status *status)
{
@@ -1176,6 +1243,8 @@ void ieee80211_tx_status_ext(struct ieee80211_hw *hw,
bool acked, noack_success, ack_signal_valid;
u16 tx_time_est;
+ ieee80211_tx_status_drop_invalid_rates(status);
+
if (pubsta) {
sta = container_of(pubsta, struct sta_info, sta);
@@ -1300,6 +1369,7 @@ void ieee80211_tx_rate_update(struct ieee80211_hw *hw,
.sta = pubsta,
};
+ ieee80211_tx_status_drop_invalid_rates(&status);
rate_control_tx_status(local, &status);
if (ieee80211_hw_check(&local->hw, HAS_RATE_CONTROL))
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 814399989b5e..d51f810dd2c5 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -2091,7 +2091,7 @@ static bool ieee80211_validate_radiotap_len(struct sk_buff *skb)
(struct ieee80211_radiotap_header *)skb->data;
/* check for not even having the fixed radiotap header part */
- if (unlikely(skb->len < sizeof(struct ieee80211_radiotap_header)))
+ if (unlikely(skb_headlen(skb) < sizeof(struct ieee80211_radiotap_header)))
return false; /* too short to be possibly valid */
/* is it a header version we can trust to find length from? */
@@ -2099,7 +2099,7 @@ static bool ieee80211_validate_radiotap_len(struct sk_buff *skb)
return false; /* only version 0 is supported */
/* does the skb contain enough to deliver on the alleged length? */
- if (unlikely(skb->len < ieee80211_get_radiotap_len(skb->data)))
+ if (unlikely(skb_headlen(skb) < ieee80211_get_radiotap_len(skb->data)))
return false; /* skb too short for claimed rt header extent */
return true;
@@ -2388,13 +2388,13 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb,
skb_set_network_header(skb, len_rthdr);
skb_set_transport_header(skb, len_rthdr);
- if (skb->len < len_rthdr + 2)
+ if (skb_headlen(skb) < len_rthdr + 2)
goto fail;
hdr = (struct ieee80211_hdr *)(skb->data + len_rthdr);
hdrlen = ieee80211_hdrlen(hdr->frame_control);
- if (skb->len < len_rthdr + hdrlen)
+ if (skb_headlen(skb) < len_rthdr + hdrlen)
goto fail;
/*
@@ -2402,7 +2402,7 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb,
* carrying a rfc1042 header
*/
if (ieee80211_is_data(hdr->frame_control) &&
- skb->len >= len_rthdr + hdrlen + sizeof(rfc1042_header) + 2) {
+ skb_headlen(skb) >= len_rthdr + hdrlen + sizeof(rfc1042_header) + 2) {
u8 *payload = (u8 *)hdr + hdrlen;
if (ether_addr_equal(payload, rfc1042_header))
@@ -2532,7 +2532,7 @@ static inline bool ieee80211_is_tdls_setup(struct sk_buff *skb)
int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb,
- struct sta_info **sta_out)
+ struct sta_info **sta_out, bool bss)
{
struct sta_info *sta;
@@ -2553,7 +2553,10 @@ int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata,
*sta_out = ERR_PTR(-ENOENT);
return 0;
}
- sta = sta_info_get_bss(sdata, skb->data);
+ if (bss)
+ sta = sta_info_get_bss(sdata, skb->data);
+ else
+ sta = sta_info_get(sdata, skb->data);
break;
#ifdef CONFIG_MAC80211_MESH
case NL80211_IFTYPE_MESH_POINT:
@@ -4419,7 +4422,8 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb,
ieee80211_mesh_xmit_fast(sdata, skb, ctrl_flags))
goto out;
- if (ieee80211_lookup_ra_sta(sdata, skb, &sta))
+ if (ieee80211_lookup_ra_sta(sdata, skb, &sta,
+ skb->protocol == sdata->control_port_protocol))
goto out_free;
if (IS_ERR(sta))
@@ -4731,6 +4735,7 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata,
{
struct ieee80211_tx_info *info;
struct ieee80211_local *local = sdata->local;
+ struct ieee80211_chanctx_conf *chanctx_conf;
struct tid_ampdu_tx *tid_tx = NULL;
struct sk_buff *seg, *next;
unsigned int skbs = 0, len = 0;
@@ -4780,6 +4785,16 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata,
sdata = container_of(sdata->bss,
struct ieee80211_sub_if_data, u.ap);
+ /* MLD transmissions must not rely on the band */
+ if (!ieee80211_vif_is_mld(&sdata->vif)) {
+ chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf);
+ if (unlikely(!chanctx_conf)) {
+ kfree_skb_list(skb);
+ return;
+ }
+ info->band = chanctx_conf->def.chan->band;
+ }
+
info->flags |= IEEE80211_TX_CTL_HW_80211_ENCAP;
info->control.vif = &sdata->vif;
@@ -4848,7 +4863,10 @@ static void __ieee80211_subif_start_xmit_8023(struct sk_buff *skb,
rcu_read_lock();
- if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) {
+ if (unlikely(sdata->control_port_protocol == ehdr->h_proto))
+ goto skip_offload;
+
+ if (ieee80211_lookup_ra_sta(sdata, skb, &sta, false)) {
kfree_skb(skb);
goto out;
}
@@ -4869,8 +4887,7 @@ static void __ieee80211_subif_start_xmit_8023(struct sk_buff *skb,
link = &sdata->deflink;
key = rcu_dereference(link->default_multicast_key);
} else if (unlikely(IS_ERR_OR_NULL(sta) || !sta->uploaded ||
- !test_sta_flag(sta, WLAN_STA_AUTHORIZED) ||
- sdata->control_port_protocol == ehdr->h_proto)) {
+ !test_sta_flag(sta, WLAN_STA_AUTHORIZED))) {
goto skip_offload;
} else {
key = rcu_dereference(sta->ptk[sta->ptk_idx]);
@@ -4931,7 +4948,7 @@ ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata,
rcu_read_lock();
- if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) {
+ if (ieee80211_lookup_ra_sta(sdata, skb, &sta, false)) {
kfree_skb(skb);
skb = ERR_PTR(-EINVAL);
goto out;
@@ -5002,7 +5019,7 @@ static bool ieee80211_tx_pending_skb(struct ieee80211_local *local,
}
result = ieee80211_tx(sdata, NULL, skb, true);
} else if (info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) {
- if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) {
+ if (ieee80211_lookup_ra_sta(sdata, skb, &sta, true)) {
dev_kfree_skb(skb);
return true;
}
@@ -6639,7 +6656,7 @@ int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev,
* AF_PACKET
*/
rcu_read_lock();
- err = ieee80211_lookup_ra_sta(sdata, skb, &sta);
+ err = ieee80211_lookup_ra_sta(sdata, skb, &sta, true);
if (err) {
dev_kfree_skb(skb);
rcu_read_unlock();
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 9fafd7673d85..ddce61ec016a 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -4563,12 +4563,13 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
rdev->wiphy.retry_long = retry_long;
if (changed & WIPHY_PARAM_FRAG_THRESHOLD)
rdev->wiphy.frag_threshold = frag_threshold;
- if ((changed & WIPHY_PARAM_RTS_THRESHOLD) &&
- old_radio_rts_threshold) {
+ if (changed & WIPHY_PARAM_RTS_THRESHOLD) {
rdev->wiphy.rts_threshold = rts_threshold;
- for (i = 0 ; i < rdev->wiphy.n_radio; i++)
- rdev->wiphy.radio_cfg[i].rts_threshold =
- rdev->wiphy.rts_threshold;
+ if (old_radio_rts_threshold) {
+ for (i = 0; i < rdev->wiphy.n_radio; i++)
+ rdev->wiphy.radio_cfg[i].rts_threshold =
+ rdev->wiphy.rts_threshold;
+ }
}
if (changed & WIPHY_PARAM_COVERAGE_CLASS)
rdev->wiphy.coverage_class = coverage_class;
diff --git a/net/wireless/scan.c b/net/wireless/scan.c
index caa9c6495f20..d35c826c4b1a 100644
--- a/net/wireless/scan.c
+++ b/