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;