NFQUEUE can suspend traversal of a duplicated packet while the task-local
duplication guard is set. The output call then returns and clears the guard
before a later verdict resumes hook traversal.

Save the guard in the queue entry and restore it while reinjection continues
hook traversal. Preserve and restore the verdict task's prior state, including
nested synchronous reinjection.

Fixes: cd58bcd9787e ("netfilter: xt_TEE: have cloned packet travel through 
Xtables too")
Cc: [email protected]
Reported-by: Vega <[email protected]>
Assisted-by: LLM
Co-developed-by: Luxing Yin <[email protected]>
Signed-off-by: Luxing Yin <[email protected]>
Signed-off-by: Zihan Xi <[email protected]>
---
 include/net/netfilter/nf_queue.h |  1 +
 net/netfilter/nf_queue.c         |  1 +
 net/netfilter/nfnetlink_queue.c  | 15 ++++++++++++---
 3 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/include/net/netfilter/nf_queue.h b/include/net/netfilter/nf_queue.h
index fc3e81c07364..039622d48905 100644
--- a/include/net/netfilter/nf_queue.h
+++ b/include/net/netfilter/nf_queue.h
@@ -23,6 +23,7 @@ struct nf_queue_entry {
        struct net_device       *physout;
 #endif
        struct nf_hook_state    state;
+       bool                    in_nf_duplicate;
        bool                    nf_ct_is_unconfirmed;
        u16                     size; /* sizeof(entry) + saved route keys */
 
diff --git a/net/netfilter/nf_queue.c b/net/netfilter/nf_queue.c
index 73363ceedebe..44f182eba953 100644
--- a/net/netfilter/nf_queue.c
+++ b/net/netfilter/nf_queue.c
@@ -219,6 +219,7 @@ static int __nf_queue(struct sk_buff *skb, const struct 
nf_hook_state *state,
        *entry = (struct nf_queue_entry) {
                .skb    = skb,
                .skb_dev = skb->dev,
+               .in_nf_duplicate = current->in_nf_duplicate,
                .state  = *state,
                .hook_index = index,
                .size   = sizeof(*entry) + route_key_size,
diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c
index a3bc00280051..9a8f55498fd5 100644
--- a/net/netfilter/nfnetlink_queue.c
+++ b/net/netfilter/nfnetlink_queue.c
@@ -43,6 +43,7 @@
 #include <net/sock.h>
 #include <net/tcp_states.h>
 #include <net/netfilter/nf_queue.h>
+#include <net/net_debug.h>
 #include <net/netns/generic.h>
 
 #include <linux/atomic.h>
@@ -368,17 +369,24 @@ static void nf_reinject(struct nf_queue_entry *entry, 
unsigned int verdict)
        unsigned int i;
        int err;
        u8 pf;
+       bool old_in_nf_duplicate;
+
+       DEBUG_NET_WARN_ONCE(current->in_nf_duplicate,
+                           "%s called with duplication guard set", __func__);
+       local_bh_disable();
+       old_in_nf_duplicate = current->in_nf_duplicate;
 
        net = entry->state.net;
        pf = entry->state.pf;
 
+       current->in_nf_duplicate = entry->in_nf_duplicate;
        hooks = nf_hook_entries_head(net, pf, entry->state.hook);
 
        i = entry->hook_index;
        if (!hooks || i >= hooks->num_hook_entries) {
                kfree_skb_reason(skb, SKB_DROP_REASON_NETFILTER_DROP);
                nf_queue_entry_free(entry);
-               return;
+               goto out;
        }
 
        hook_entry = &hooks->hooks[i];
@@ -401,9 +409,7 @@ static void nf_reinject(struct nf_queue_entry *entry, 
unsigned int verdict)
        switch (verdict & NF_VERDICT_MASK) {
        case NF_ACCEPT:
        case NF_STOP:
-               local_bh_disable();
                entry->state.okfn(entry->state.net, entry->state.sk, skb);
-               local_bh_enable();
                break;
        case NF_QUEUE:
                err = nf_queue(skb, &entry->state, i, verdict);
@@ -417,6 +423,9 @@ static void nf_reinject(struct nf_queue_entry *entry, 
unsigned int verdict)
        }
 
        nf_queue_entry_free(entry);
+out:
+       current->in_nf_duplicate = old_in_nf_duplicate;
+       local_bh_enable();
 }
 
 /* return true if the entry has an unconfirmed conntrack attached that isn't 
owned by us
-- 
2.43.0

Reply via email to