wifi: mac80211: keep paired old chanctx alive

An in-place replacement keeps reciprocal replace_ctx pointers between
the old WILL_BE_REPLACED chanctx and the new REPLACES_OTHER chanctx.
However, the early free paths only consider assigned and reserved link
users when deciding whether to free the old chanctx.

That lets the old chanctx be freed too early while the replacement
partner still points back to it, either when the last assigned link is
released or when that link successfully reassigns to another existing
context. Later unreserve and switch-finalization paths can then follow a
stale replace_ctx pointer.

Keep a paired old chanctx alive until the replacement pair has been torn
down. Apply the same check to both __ieee80211_link_release_channel()
and ieee80211_link_use_reserved_reassign(). If the replacement is later
abandoned, ieee80211_link_unreserve_chanctx() already tears down the
pairing before freeing the old chanctx once no users remain.

Fixes: 5bcae31d9c ("mac80211: implement multi-vif in-place reservations")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
Link: https://patch.msgid.link/b9b582b0db3814f641648b8886bb50f9d68f93ac.1785730815.git.zhilinz@nebusec.ai
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
Zhiling Zou 2026-08-03 12:35:10 +08:00 committed by Johannes Berg
parent 437f3727b4
commit 3412e9a800

View File

@ -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);
@ -2192,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;