[PATCH nf 1/1] netfilter: nf_dup: prevent asynchronous duplicate recursion

From: Zihan Xi

Date: Sat Aug 29 2026 - 01:19:42 EST


nf_dup_ipv4() and nf_dup_ipv6() use current->in_nf_duplicate to keep
duplicated packets from being duplicated again while ip_local_out() or
ip6_local_out() walks netfilter hooks. The task flag is cleared as soon
as the output function returns.

NFQUEUE can retain a duplicate and return from the output hook. A later
NF_ACCEPT verdict resumes the same skb at the following hook from the
verdict task, after in_nf_duplicate has been cleared. A later TEE target
or dup expression can then duplicate it again. With an earlier queue
hook and a later duplication hook, one packet can sustain an unbounded
packet generation loop.

Record the duplication state in the cloned skb as well as the task. The
skb flag survives queuing, reinjection, and skb metadata copies, so an
asynchronously resumed duplicate cannot enter either IPv4 or IPv6
duplication helper again. Copy the flag through nf_copy() so fragments
retain the same state. Keep the task flag for the nested xtables jumpstack.

Fixes: cd58bcd9787e ("netfilter: xt_TEE: have cloned packet travel through Xtables too")
Cc: stable@xxxxxxxxxxxxxxx
Reported-by: Vega <vega@xxxxxxxxxx>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Zihan Xi <zihanx@xxxxxxxxxx>
---
include/linux/skbuff.h | 7 +++++++
net/ipv4/netfilter/nf_dup_ipv4.c | 3 ++-
net/ipv6/netfilter/nf_dup_ipv6.c | 3 ++-
3 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 5522716df8ff..f5c7b7b1cede 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -812,6 +812,7 @@ enum skb_tstamp_type {
* @redirected: packet was redirected by packet classifier
* @from_ingress: packet was redirected from the ingress path
* @nf_skip_egress: packet shall skip nf egress - see netfilter_netdev.h
+ * @nf_duplicated: packet was generated by a netfilter duplication action
* @peeked: this packet has been seen already, so stats have been
* done for it, don't do them again
* @nf_trace: netfilter packet trace flag
@@ -1023,6 +1024,9 @@ struct sk_buff {
#ifdef CONFIG_NETFILTER_SKIP_EGRESS
__u8 nf_skip_egress:1;
#endif
+#if IS_ENABLED(CONFIG_NF_DUP_IPV4) || IS_ENABLED(CONFIG_NF_DUP_IPV6)
+ __u8 nf_duplicated:1;
+#endif
#ifdef CONFIG_SKB_DECRYPTED
__u8 decrypted:1;
#endif
@@ -5200,6 +5204,9 @@ static inline void nf_copy(struct sk_buff *dst, const struct sk_buff *src)
nf_conntrack_put(skb_nfct(dst));
#endif
dst->slow_gro = src->slow_gro;
+#if IS_ENABLED(CONFIG_NF_DUP_IPV4) || IS_ENABLED(CONFIG_NF_DUP_IPV6)
+ dst->nf_duplicated = src->nf_duplicated;
+#endif
__nf_copy(dst, src, true);
}

diff --git a/net/ipv4/netfilter/nf_dup_ipv4.c b/net/ipv4/netfilter/nf_dup_ipv4.c
index 9a773502f10a..8fe31a0db063 100644
--- a/net/ipv4/netfilter/nf_dup_ipv4.c
+++ b/net/ipv4/netfilter/nf_dup_ipv4.c
@@ -54,7 +54,7 @@ void nf_dup_ipv4(struct net *net, struct sk_buff *skb, unsigned int hooknum,
struct iphdr *iph;

local_bh_disable();
- if (current->in_nf_duplicate)
+ if (current->in_nf_duplicate || skb->nf_duplicated)
goto out;
/*
* Copy the skb, and route the copy. Will later return %XT_CONTINUE for
@@ -86,6 +86,7 @@ void nf_dup_ipv4(struct net *net, struct sk_buff *skb, unsigned int hooknum,
--iph->ttl;

if (nf_dup_ipv4_route(net, skb, gw, oif)) {
+ skb->nf_duplicated = 1;
current->in_nf_duplicate = true;
ip_local_out(net, skb->sk, skb);
current->in_nf_duplicate = false;
diff --git a/net/ipv6/netfilter/nf_dup_ipv6.c b/net/ipv6/netfilter/nf_dup_ipv6.c
index 6da3102b7c1b..e0fdb43d7d3d 100644
--- a/net/ipv6/netfilter/nf_dup_ipv6.c
+++ b/net/ipv6/netfilter/nf_dup_ipv6.c
@@ -48,7 +48,7 @@ void nf_dup_ipv6(struct net *net, struct sk_buff *skb, unsigned int hooknum,
const struct in6_addr *gw, int oif)
{
local_bh_disable();
- if (current->in_nf_duplicate)
+ if (current->in_nf_duplicate || skb->nf_duplicated)
goto out;
skb = pskb_copy(skb, GFP_ATOMIC);
if (skb == NULL)
@@ -64,6 +64,7 @@ void nf_dup_ipv6(struct net *net, struct sk_buff *skb, unsigned int hooknum,
--iph->hop_limit;
}
if (nf_dup_ipv6_route(net, skb, gw, oif)) {
+ skb->nf_duplicated = 1;
current->in_nf_duplicate = true;
ip6_local_out(net, skb->sk, skb);
current->in_nf_duplicate = false;
--
2.43.0