Re: [BUG] net/bridge: out-of-bounds in br_forward()

Pablo Neira Ayuso <[email protected]>
Newsgroups gmane.linux.network.bridge
Message-ID <apBPj741ai10Wajf__16502.4143572495$1787842546$gmane$org@chamomile>
On Thu, Aug 27, 2026 at 04:29:28PM +0200, Florian Westphal wrote:
> co <[email protected]> wrote:
> > We found a bug reachable in:
> > 
> >     path    net/bridge/netfilter
> >     crash   out-of-bounds in br_forward()
> >     commit  7b5344954050 ("Merge tag 'nf-26-08-10' of git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf")
> 
> I'm looking into these reports, I think this is the same
> underlying bug as the other report; a variant of
> 
> ccb9fd4b8753 ("netfilter: revalidate bridge ports")
> 
> That bug uses nfnetlink_queue for RCU escape, the other two reports
> use defrag engine.

I started this patch. I think this can still happen with native
nfnetlink_queue support for the bridge family?

diff --git a/net/bridge/netfilter/nft_reject_bridge.c b/net/bridge/netfilter/nft_reject_bridge.c
index cd2b04236a99..b3a51ba72e9c 100644
--- a/net/bridge/netfilter/nft_reject_bridge.c
+++ b/net/bridge/netfilter/nft_reject_bridge.c
@@ -44,7 +44,7 @@ static void nft_reject_br_push_etherhdr(struct sk_buff *oldskb,
  */
 static void nft_reject_br_send_v4_tcp_reset(struct net *net,
                                            struct sk_buff *oldskb,
-                                           const struct net_device *dev,
+                                           struct net_bridge_port *port,
                                            int hook)
 {
        struct sk_buff *nskb;
@@ -55,12 +55,12 @@ static void nft_reject_br_send_v4_tcp_reset(struct net *net,
 
        nft_reject_br_push_etherhdr(oldskb, nskb);
 
-       br_forward(br_port_get_rcu(dev), nskb, false, true);
+       br_forward(port, nskb, false, true);
 }
 
 static void nft_reject_br_send_v4_unreach(struct net *net,
                                          struct sk_buff *oldskb,
-                                         const struct net_device *dev,
+                                         struct net_bridge_port *port,
                                          int hook, u8 code)
 {
        struct sk_buff *nskb;
@@ -71,12 +71,12 @@ static void nft_reject_br_send_v4_unreach(struct net *net,
 
        nft_reject_br_push_etherhdr(oldskb, nskb);
 
-       br_forward(br_port_get_rcu(dev), nskb, false, true);
+       br_forward(port, nskb, false, true);
 }
 
 static void nft_reject_br_send_v6_tcp_reset(struct net *net,
                                            struct sk_buff *oldskb,
-                                           const struct net_device *dev,
+                                           struct net_bridge_port *port,
                                            int hook)
 {
        struct sk_buff *nskb;
@@ -87,13 +87,13 @@ static void nft_reject_br_send_v6_tcp_reset(struct net *net,
 
        nft_reject_br_push_etherhdr(oldskb, nskb);
 
-       br_forward(br_port_get_rcu(dev), nskb, false, true);
+       br_forward(port, nskb, false, true);
 }
 
 
 static void nft_reject_br_send_v6_unreach(struct net *net,
                                          struct sk_buff *oldskb,
-                                         const struct net_device *dev,
+                                         struct net_bridge_port *port,
                                          int hook, u8 code)
 {
        struct sk_buff *nskb;
@@ -104,37 +104,47 @@ static void nft_reject_br_send_v6_unreach(struct net *net,
 
        nft_reject_br_push_etherhdr(oldskb, nskb);
 
-       br_forward(br_port_get_rcu(dev), nskb, false, true);
+       br_forward(port, nskb, false, true);
 }
 
 static void nft_reject_bridge_eval(const struct nft_expr *expr,
                                   struct nft_regs *regs,
                                   const struct nft_pktinfo *pkt)
 {
-       struct nft_reject *priv = nft_expr_priv(expr);
        const unsigned char *dest = eth_hdr(pkt->skb)->h_dest;
+       struct nft_reject *priv = nft_expr_priv(expr);
+       const struct net_device *dev = nft_in(pkt);
+       struct net_bridge_port *port;
 
        if (is_broadcast_ether_addr(dest) ||
            is_multicast_ether_addr(dest))
                goto out;
 
+       if (!dev || !netif_is_bridge_port(dev) ||
+           netdev_master_upper_dev_get_rcu((struct net_device *)dev))
+               goto out;
+
+       port = br_port_get_rcu(dev);
+       if (!port)
+               goto out;
+
        switch (eth_hdr(pkt->skb)->h_proto) {
        case htons(ETH_P_IP):
                switch (priv->type) {
                case NFT_REJECT_ICMP_UNREACH:
                        nft_reject_br_send_v4_unreach(nft_net(pkt), pkt->skb,
-                                                     nft_in(pkt),
+                                                     port,
                                                      nft_hook(pkt),
                                                      priv->icmp_code);
                        break;
                case NFT_REJECT_TCP_RST:
                        nft_reject_br_send_v4_tcp_reset(nft_net(pkt), pkt->skb,
-                                                       nft_in(pkt),
+                                                       port,
                                                        nft_hook(pkt));
                        break;
                case NFT_REJECT_ICMPX_UNREACH:
                        nft_reject_br_send_v4_unreach(nft_net(pkt), pkt->skb,
-                                                     nft_in(pkt),
+                                                     port,
                                                      nft_hook(pkt),
                                                      nft_reject_icmp_code(priv->icmp_code));
                        break;
@@ -144,18 +154,18 @@ static void nft_reject_bridge_eval(const struct nft_expr *expr,
                switch (priv->type) {
                case NFT_REJECT_ICMP_UNREACH:
                        nft_reject_br_send_v6_unreach(nft_net(pkt), pkt->skb,
-                                                     nft_in(pkt),
+                                                     port,
                                                      nft_hook(pkt),
                                                      priv->icmp_code);
                        break;
                case NFT_REJECT_TCP_RST:
                        nft_reject_br_send_v6_tcp_reset(nft_net(pkt), pkt->skb,
-                                                       nft_in(pkt),
+                                                       port,
                                                        nft_hook(pkt));
                        break;
                case NFT_REJECT_ICMPX_UNREACH:
                        nft_reject_br_send_v6_unreach(nft_net(pkt), pkt->skb,
-                                                     nft_in(pkt),
+                                                     port,
                                                      nft_hook(pkt),
                                                      nft_reject_icmpv6_code(priv->icmp_code));
                        break;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.