Re: [PATCH RFC] netfilter: bridge: fix VLAN protocol mismatch in nf_ct_bridge_refrag

Aleksandr Nogikh <[email protected]>
Newsgroups dev.linux.lists.syzbot
Message-ID <CANp29Y6BPvZTngiKH4iUxTx4g17MX1SvJ6SDa-y3ycF2UYEiDA@mail.gmail.com>
Let's try again.

#syz reject

On Thu, May 21, 2026 at 1:14 AM 'syzbot' via
syzkaller-upstream-moderation
<[email protected]> wrote:
>
> The root cause of this bug is that nf_ct_bridge_refrag() expects
> skb->protocol to be ETH_P_IP or ETH_P_IPV6, and skb->data to point to
> the network header. However, if a VLAN tag was pushed into the payload
> during bridge forwarding (e.g., due to a VLAN protocol mismatch in
> br_allowed_ingress() or tc rules), skb->protocol becomes ETH_P_8021Q (or
> ETH_P_8021AD), and skb->data points to the VLAN tag instead of the
> network header. This causes nf_ct_bridge_refrag() to hit the default
> case in the switch statement and trigger a WARN_ON_ONCE(1). Furthermore,
> even if the switch statement was bypassed, nf_br_ip_fragment() would
> read the VLAN tag as the IP header, leading to corruption or crashes.
> Additionally, nf_ct_bridge_frag_save() and nf_ct_bridge_frag_restore()
> assume the MAC header is exactly ETH_HLEN (14 bytes) long, failing to
> save and restore the VLAN tag in the payload, which causes the fragments
> to lose the VLAN tag.
>
> To fix this, we need to properly handle packets with VLAN tags in the
> payload during refragmentation. We expand struct nf_bridge_frag_data to
> store up to 32 bytes of the MAC header, along with the actual mac_len
> and the original skb->protocol. In nf_ct_bridge_frag_save(), we save the
> entire MAC header (including any VLAN tags) and the original protocol.
> In nf_ct_bridge_refrag(), if the MAC header is larger than ETH_HLEN
> (indicating a VLAN tag in the payload), we temporarily push skb->data to
> the MAC header to use vlan_get_protocol() to find the inner protocol
> (e.g., ETH_P_IP), and then pull skb->data to the network header. This
> ensures nf_br_ip_fragment() sees the correct protocol and IP header.
> Finally, in nf_ct_bridge_frag_restore(), we restore the entire saved MAC
> header and the original protocol, carefully adjusting skb->data and
> skb->mac_header so that br_dev_queue_push_xmit() works correctly.
>
> Fixes: 3c171f496ef5 ("netfilter: bridge: add connection tracking system")
> Assisted-by: Gemini:gemini-3.1-pro-preview
> Reported-by: [email protected]
> Closes: https://syzkaller.appspot.com/bug?extid=a8ba738fe2db6b4bb27f
> Link: https://syzkaller.appspot.com/ai_job?id=d2874cff-e6fe-4bb5-807f-dd0552282866
> To: <[email protected]>
> To: <[email protected]>
> To: "David S. Miller" <[email protected]>
> To: "Eric Dumazet" <[email protected]>
> To: "Florian Westphal" <[email protected]>
> To: "Ido Schimmel" <[email protected]>
> To: "Jakub Kicinski" <[email protected]>
> To: <[email protected]>
> To: <[email protected]>
> To: "Paolo Abeni" <[email protected]>
> To: "Pablo Neira Ayuso" <[email protected]>
> To: "Nikolay Aleksandrov" <[email protected]>
> Cc: "Simon Horman" <[email protected]>
> Cc: <[email protected]>
> Cc: "Phil Sutter" <[email protected]>
>
> ---
> diff --git a/include/linux/netfilter_bridge.h b/include/linux/netfilter_bridge.h
> index 743475ca7..7919223ba 100644
> --- a/include/linux/netfilter_bridge.h
> +++ b/include/linux/netfilter_bridge.h
> @@ -6,10 +6,12 @@
>  #include <linux/skbuff.h>
>
>  struct nf_bridge_frag_data {
> -       char    mac[ETH_HLEN];
> +       char mac[32];
>         bool    vlan_present;
>         u16     vlan_tci;
>         __be16  vlan_proto;
> +       u16 mac_len;
> +       __be16 orig_proto;
>  };
>
>  #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
> diff --git a/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c
> index 58a33d038..a2d85e799 100644
> --- a/net/bridge/netfilter/nf_conntrack_bridge.c
> +++ b/net/bridge/netfilter/nf_conntrack_bridge.c
> @@ -6,6 +6,7 @@
>  #include <linux/netfilter_bridge.h>
>  #include <linux/module.h>
>  #include <linux/skbuff.h>
> +#include <linux/if_vlan.h>
>  #include <linux/icmp.h>
>  #include <linux/sysctl.h>
>  #include <net/route.h>
> @@ -320,8 +321,8 @@ static unsigned int nf_ct_bridge_in(void *priv, struct sk_buff *skb,
>         return NF_ACCEPT;
>  }
>
> -static void nf_ct_bridge_frag_save(struct sk_buff *skb,
> -                                  struct nf_bridge_frag_data *data)
> +static int nf_ct_bridge_frag_save(struct sk_buff *skb,
> +                                 struct nf_bridge_frag_data *data)
>  {
>         if (skb_vlan_tag_present(skb)) {
>                 data->vlan_present = true;
> @@ -330,7 +331,12 @@ static void nf_ct_bridge_frag_save(struct sk_buff *skb,
>         } else {
>                 data->vlan_present = false;
>         }
> -       skb_copy_from_linear_data_offset(skb, -ETH_HLEN, data->mac, ETH_HLEN);
> +       data->mac_len = skb->network_header - skb->mac_header;
> +       if (data->mac_len > sizeof(data->mac))
> +               return -E2BIG;
> +       memcpy(data->mac, skb_mac_header(skb), data->mac_len);
> +       data->orig_proto = skb->protocol;
> +       return 0;
>  }
>
>  static unsigned int
> @@ -340,11 +346,23 @@ nf_ct_bridge_refrag(struct sk_buff *skb, const struct nf_hook_state *state,
>                                   struct sk_buff *))
>  {
>         struct nf_bridge_frag_data data;
> +       int err;
>
>         if (!BR_INPUT_SKB_CB(skb)->frag_max_size)
>                 return NF_ACCEPT;
>
> -       nf_ct_bridge_frag_save(skb, &data);
> +       err = nf_ct_bridge_frag_save(skb, &data);
> +       if (err)
> +               return NF_DROP;
> +
> +       if (data.mac_len > ETH_HLEN) {
> +               unsigned int mac_offset = skb->data - skb_mac_header(skb);
> +
> +               skb_push(skb, mac_offset);
> +               skb->protocol = vlan_get_protocol(skb);
> +               skb_pull(skb, data.mac_len);
> +       }
> +
>         switch (skb->protocol) {
>         case htons(ETH_P_IP):
>                 nf_br_ip_fragment(state->net, state->sk, skb, &data, output);
> @@ -366,7 +384,7 @@ static int nf_ct_bridge_frag_restore(struct sk_buff *skb,
>  {
>         int err;
>
> -       err = skb_cow_head(skb, ETH_HLEN);
> +       err = skb_cow_head(skb, data->mac_len);
>         if (err) {
>                 kfree_skb(skb);
>                 return -ENOMEM;
> @@ -376,8 +394,12 @@ static int nf_ct_bridge_frag_restore(struct sk_buff *skb,
>         else if (skb_vlan_tag_present(skb))
>                 __vlan_hwaccel_clear_tag(skb);
>
> -       skb_copy_to_linear_data_offset(skb, -ETH_HLEN, data->mac, ETH_HLEN);
> +       skb_copy_to_linear_data_offset(skb, -data->mac_len, data->mac,
> +                                      data->mac_len);
> +       skb_push(skb, data->mac_len - ETH_HLEN);
>         skb_reset_mac_header(skb);
> +       skb->mac_header -= ETH_HLEN;
> +       skb->protocol = data->orig_proto;
>
>         return 0;
>  }
>
>
> base-commit: 5200f5f493f79f14bbdc349e402a40dfb32f23c8
> --
> This is an AI-generated patch subject to moderation.
> Reply with '#syz upstream' to send it to the mailing list.
> Reply with '#syz reject' to reject it.
>
> See https://github.com/google/syzkaller/blob/master/docs/syzbot_ai_patches.md for more information.
>
> --
> You received this message because you are subscribed to the Google Groups "syzkaller-upstream-moderation" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
> To view this discussion visit https://groups.google.com/d/msgid/syzkaller-upstream-moderation/00558653-6963-4e86-b4b4-3c0d4ee1977f%40mail.kernel.org.
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.