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