[PATCH RFC] netfilter: nf_conntrack_bridge: handle vlan-tagged packets in refrag

"syzbot" <[email protected]>
Newsgroups dev.linux.lists.syzbot
Message-ID <[email protected]>
When a fragmented packet arrives at a bridge port with a hardware VLAN tag,
the bridge's RX VLAN offload populates skb->vlan_tci and sets skb->protocol
to the inner payload protocol (e.g., ETH_P_IP). If the packet is
defragmented in the NF_BR_PRE_ROUTING hook, the maximum fragment size is
recorded in the bridge control block.

Later, if the bridge's VLAN filtering logic detects a protocol mismatch
(e.g., skb->vlan_proto differs from br->vlan_proto), it pushes the VLAN tag
back into the packet payload using vlan_insert_tag_set_proto(). This
changes skb->protocol to the VLAN protocol (e.g., ETH_P_8021AD).

When the packet reaches the NF_BR_POST_ROUTING hook, nf_ct_bridge_post()
determines that the packet needs to be refragmented and calls
nf_ct_bridge_refrag(). However, nf_ct_bridge_refrag() expects skb->protocol
to be IPv4 or IPv6. Since it is now a VLAN protocol, the switch statement
hits the default case, triggering a WARN_ON_ONCE and dropping the packet:

WARNING: net/bridge/netfilter/nf_conntrack_bridge.c:356 at
nf_ct_bridge_refrag
...
Call Trace:
 <TASK>
 nf_hook_entry_hookfn include/linux/netfilter.h:158 [inline]
 nf_hook_slow+0xc5/0x220 net/netfilter/core.c:619
 nf_hook include/linux/netfilter.h:273 [inline]
 NF_HOOK+0x23e/0x3f0 include/linux/netfilter.h:316
 br_forward_finish+0xd3/0x130 net/bridge/br_forward.c:66
 NF_HOOK+0x360/0x3f0 include/linux/netfilter.h:318
 __br_forward+0x397/0x540 net/bridge/br_forward.c:115
 deliver_clone net/bridge/br_forward.c:131 [inline]
 maybe_deliver+0xb5/0x160 net/bridge/br_forward.c:191
 br_flood+0x316/0x690 net/bridge/br_forward.c:238
 br_handle_frame_finish+0x10f8/0x1930 net/bridge/br_input.c:229

To fix this, update nf_ct_bridge_refrag() to handle packets where the VLAN
tag has been pushed into the payload. If the protocol is a VLAN protocol,
use vlan_get_protocol_offset_inline() to extract the inner protocol and
pull the VLAN tags so skb->data points directly to the IP header, as
expected by the fragmentation routines.

Additionally, because the MAC header is no longer exactly ETH_HLEN bytes
when VLAN tags are pushed into the payload, expand the mac array in struct
nf_bridge_frag_data to 32 bytes and add a mac_len field to dynamically
track the actual length. Update nf_ct_bridge_frag_save() and
nf_ct_bridge_frag_restore() to save and restore the correct number of
bytes, pushing any extra bytes beyond ETH_HLEN to ensure skb->data
correctly points to the start of the MAC header before transmission.

Fixes: 3c171f496ef5 ("netfilter: bridge: add connection tracking system")
Assisted-by: Gemini:gemini-3.1-pro-preview syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=a8ba738fe2db6b4bb27f
Link: https://syzkaller.appspot.com/ai_job?id=4867c9de-cc2a-406b-a01c-4321a64f38ec
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..4ff36e0f3 100644
--- a/include/linux/netfilter_bridge.h
+++ b/include/linux/netfilter_bridge.h
@@ -6,7 +6,8 @@
 #include <linux/skbuff.h>
 
 struct nf_bridge_frag_data {
-	char    mac[ETH_HLEN];
+	char mac[32];
+	u8 mac_len;
 	bool    vlan_present;
 	u16     vlan_tci;
 	__be16  vlan_proto;
diff --git a/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c
index 58a33d038..a2a4b821a 100644
--- a/net/bridge/netfilter/nf_conntrack_bridge.c
+++ b/net/bridge/netfilter/nf_conntrack_bridge.c
@@ -330,7 +330,13 @@ 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->mac_len;
+	if (WARN_ON_ONCE(data->mac_len < ETH_HLEN))
+		data->mac_len = ETH_HLEN;
+	if (WARN_ON_ONCE(data->mac_len > sizeof(data->mac)))
+		data->mac_len = sizeof(data->mac);
+	skb_copy_from_linear_data_offset(skb, -data->mac_len, data->mac,
+					 data->mac_len);
 }
 
 static unsigned int
@@ -340,10 +346,30 @@ nf_ct_bridge_refrag(struct sk_buff *skb, const struct nf_hook_state *state,
 				  struct sk_buff *))
 {
 	struct nf_bridge_frag_data data;
+	__be16 inner_proto = skb->protocol;
+	int depth;
 
 	if (!BR_INPUT_SKB_CB(skb)->frag_max_size)
 		return NF_ACCEPT;
 
+	if (eth_type_vlan(inner_proto)) {
+		int pull_len;
+
+		inner_proto = vlan_get_protocol_offset_inline(
+			skb, inner_proto, skb_mac_offset(skb), &depth);
+		if (!inner_proto)
+			return NF_DROP;
+
+		pull_len = depth + skb_mac_offset(skb);
+		if (pull_len > 0) {
+			if (!pskb_may_pull(skb, pull_len))
+				return NF_DROP;
+
+			skb_pull(skb, pull_len);
+		}
+		skb->protocol = inner_proto;
+	}
+
 	nf_ct_bridge_frag_save(skb, &data);
 	switch (skb->protocol) {
 	case htons(ETH_P_IP):
@@ -366,7 +392,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 +402,11 @@ 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;
 
 	return 0;
 }


base-commit: 8cd9520d35a6c38db6567e97dd93b1f11f185dc6
-- 
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).

See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at [email protected].
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.