netfilter: nft_fwd_netdev: use recursion counter in neigh egress path

nft_fwd_neigh can be used in egress chains (NF_NETDEV_EGRESS). When the
forwarding rule targets the same device or two devices forward to each
other, neigh_xmit() triggers dev_queue_xmit() which re-enters
nf_hook_egress(), causing infinite recursion and stack overflow.

Move the nf_get_nf_dup_skb_recursion() accessor and NF_RECURSION_LIMIT
to the shared header nf_dup_netdev.h as a static inline, so that
nft_fwd_netdev can use the recursion counter directly without exported
function call overhead. Guard neigh_xmit() with the same recursion
limit already used in nf_do_netdev_egress().

[ Updated to cache the nf_get_nf_dup_skb_recursion pointer. --pablo ]

Fixes: f87b9464d1 ("netfilter: nft_fwd_netdev: Support egress hook")
Reported-by: Xiang Mei <xmei5@asu.edu>
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
master
Weiming Shi 2026-04-27 14:34:50 +02:00 committed by Pablo Neira Ayuso
parent 0a0b35f0bf
commit 1d47b55b36
3 changed files with 21 additions and 16 deletions

View File

@ -3,10 +3,23 @@
#define _NF_DUP_NETDEV_H_
#include <net/netfilter/nf_tables.h>
#include <linux/netdevice.h>
#include <linux/sched.h>
void nf_dup_netdev_egress(const struct nft_pktinfo *pkt, int oif);
void nf_fwd_netdev_egress(const struct nft_pktinfo *pkt, int oif);
#define NF_RECURSION_LIMIT 2
static inline u8 *nf_get_nf_dup_skb_recursion(void)
{
#ifndef CONFIG_PREEMPT_RT
return this_cpu_ptr(&softnet_data.xmit.nf_dup_skb_recursion);
#else
return &current->net_xmit.nf_dup_skb_recursion;
#endif
}
struct nft_offload_ctx;
struct nft_flow_rule;

View File

@ -13,22 +13,6 @@
#include <net/netfilter/nf_tables_offload.h>
#include <net/netfilter/nf_dup_netdev.h>
#define NF_RECURSION_LIMIT 2
#ifndef CONFIG_PREEMPT_RT
static u8 *nf_get_nf_dup_skb_recursion(void)
{
return this_cpu_ptr(&softnet_data.xmit.nf_dup_skb_recursion);
}
#else
static u8 *nf_get_nf_dup_skb_recursion(void)
{
return &current->net_xmit.nf_dup_skb_recursion;
}
#endif
static void nf_do_netdev_egress(struct sk_buff *skb, struct net_device *dev,
enum nf_dev_hooks hook)
{

View File

@ -95,6 +95,7 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr,
struct nft_regs *regs,
const struct nft_pktinfo *pkt)
{
u8 *nf_dup_skb_recursion = nf_get_nf_dup_skb_recursion();
struct nft_fwd_neigh *priv = nft_expr_priv(expr);
void *addr = &regs->data[priv->sreg_addr];
int oif = regs->data[priv->sreg_dev];
@ -153,6 +154,11 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr,
goto out;
}
if (*nf_dup_skb_recursion > NF_RECURSION_LIMIT) {
verdict = NF_DROP;
goto out;
}
dev = dev_get_by_index_rcu(nft_net(pkt), oif);
if (dev == NULL) {
verdict = NF_DROP;
@ -170,7 +176,9 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr,
skb->dev = dev;
skb_clear_tstamp(skb);
(*nf_dup_skb_recursion)++;
neigh_xmit(neigh_table, dev, addr, skb);
(*nf_dup_skb_recursion)--;
out:
regs->verdict.code = verdict;
}