From 3a1445c729ac073d1497170c97f667606d81bcf5 Mon Sep 17 00:00:00 2001 From: Haoze Xie Date: Fri, 29 May 2026 19:49:42 -0400 Subject: [PATCH] netfilter: nf_queue: hold bridge skb->dev while queued ANBZ: #38395 commit e196115ec330a18de415bdb9f5071aa9f08e53ce upstream. commit 950d809f154dca04e5fbe5d3c8b9c5e44769cd57 stable. br_pass_frame_up() rewrites skb->dev from the ingress port to the bridge master before queueing bridge LOCAL_IN packets. NFQUEUE only holds references on state.in/out and bridge physdevs, so a queued bridge packet can retain a freed bridge master in skb->dev until reinjection. When the verdict is reinjected later, br_netif_receive_skb() re-enters the receive path with skb->dev still pointing at the freed bridge master, triggering a use-after-free. Store skb->dev in the queue entry, hold a reference on it for the queue lifetime, and use the saved device when dropping queued packets during NETDEV_DOWN handling. [backport-note] PatchPilot-Conflict-Type: context_drift, divergent_fix PatchPilot-Conflict-Files: net/netfilter/nf_queue.c PatchPilot-Resolution: net/netfilter/nf_queue.c: Use upstream skb_dev refcount handling and drop local NULL guards. Fixes: ac2863445686 ("netfilter: bridge: add nf_afinfo to enable queuing to userspace") Cc: stable@kernel.org Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Xin Liu Signed-off-by: Haoze Xie Signed-off-by: Ren Wei Signed-off-by: Pablo Neira Ayuso Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman Fixes: CVE-2026-52912 Assisted-by: PatchPilot Signed-off-by: D. Wythe --- include/net/netfilter/nf_queue.h | 1 + net/netfilter/nf_queue.c | 16 +++++++--------- net/netfilter/nfnetlink_queue.c | 2 ++ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/include/net/netfilter/nf_queue.h b/include/net/netfilter/nf_queue.h index b1d43894296a..6b4de68a762e 100644 --- a/include/net/netfilter/nf_queue.h +++ b/include/net/netfilter/nf_queue.h @@ -12,6 +12,7 @@ struct nf_queue_entry { struct list_head list; struct sk_buff *skb; + struct net_device *skb_dev; unsigned int id; unsigned int hook_index; /* index in hook_entries->hook[] */ #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) diff --git a/net/netfilter/nf_queue.c b/net/netfilter/nf_queue.c index bb8607ff94bc..3e21edbd6063 100644 --- a/net/netfilter/nf_queue.c +++ b/net/netfilter/nf_queue.c @@ -60,10 +60,9 @@ static void nf_queue_entry_release_refs(struct nf_queue_entry *entry) struct nf_hook_state *state = &entry->state; /* Release those devices we held, or Alexey will kill me. */ - if (state->in) - dev_put(state->in); - if (state->out) - dev_put(state->out); + dev_put(entry->skb_dev); + dev_put(state->in); + dev_put(state->out); if (state->sk) nf_queue_sock_put(state->sk); @@ -107,10 +106,9 @@ bool nf_queue_entry_get_refs(struct nf_queue_entry *entry) if (state->sk && !refcount_inc_not_zero(&state->sk->sk_refcnt)) return false; - if (state->in) - dev_hold(state->in); - if (state->out) - dev_hold(state->out); + dev_hold(entry->skb_dev); + dev_hold(state->in); + dev_hold(state->out); #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) if (entry->physin) @@ -212,11 +210,11 @@ 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, .state = *state, .hook_index = index, .size = sizeof(*entry) + route_key_size, }; - __nf_queue_entry_init_physdevs(entry); if (!nf_queue_entry_get_refs(entry)) { diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index 72d30922ed29..44f58e1b534e 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -913,6 +913,8 @@ dev_cmp(struct nf_queue_entry *entry, unsigned long ifindex) if (physinif == ifindex || physoutif == ifindex) return 1; #endif + if (entry->skb_dev && entry->skb_dev->ifindex == ifindex) + return 1; if (entry->state.in) if (entry->state.in->ifindex == ifindex) return 1; -- Gitee