Thread (9 messages) 9 messages, 3 authors, 11h ago

Re: [PATCH v2 net] vlan: fix skb_under_panic and races when toggling HW VLAN offload

From: xietangxin <hidden>
Date: 2026-07-24 08:13:18


On 7/24/2026 11:54 AM, Eric Dumazet wrote:
quoted hunk ↗ jump to hunk
Toggling hardware VLAN TX offload (NETIF_F_HW_VLAN_CTAG_TX or
NETIF_F_HW_VLAN_STAG_TX) on a lower device invokes vlan_transfer_features(),
which dynamically changed vlandev->hard_header_len.

This causes two issues:
1. Lockless TX paths (e.g. packet_snd in af_packet.c, ip6_finish_output2)
   read dev->hard_header_len without holding RTNL lock. Mutating
   hard_header_len dynamically under RTNL creates a data race where upper
   layers reserve insufficient headroom based on a stale hard_header_len,
   resulting in skb_under_panic when vlan_dev_hard_header() is called.
2. In addition, vlan_transfer_features() updated hard_header_len without
   updating header_ops, causing a mismatch between allocated headroom
   and header creation.

Always setting dev->hard_header_len = real_dev->hard_header_len and
dev->needed_headroom = real_dev->needed_headroom + VLAN_HLEN unconditionally
ensures:
- dev->hard_header_len remains 100% static and immutable at real_dev->hard_header_len,
  eliminating all dynamic runtime updates and data races on hard_header_len.
- Upper layers allocating skbs via LL_RESERVED_SPACE() will always reserve
  sufficient headroom for software VLAN tag insertion (real_dev->hard_header_len +
  real_dev->needed_headroom + VLAN_HLEN).
- AF_PACKET SOCK_RAW network header offsets remain correctly aligned at
  real_dev->hard_header_len.
- vlan_header_ops is used unconditionally.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: Tangxin Xie <redacted>
Closes: https://lore.kernel.org/netdev/99d678ae-c7b2-4b44-b534-b8320679deb3@h-partners.com/ (local)
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
v2: apply the VLAN_HLEN provision on needed_headroom
v1: https://lore.kernel.org/netdev/20260723092203.5b1470f5@kernel.org/T/#t (local)

 net/8021q/vlan_dev.c | 37 +++++--------------------------------
 1 file changed, 5 insertions(+), 32 deletions(-)
diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c
index ec2569b3f8dac629027b4344bc89402decf026d1..aaf2051a434cfef30d52e0434a9c372142f49195 100644
--- a/net/8021q/vlan_dev.c
+++ b/net/8021q/vlan_dev.c
@@ -502,26 +502,6 @@ static const struct header_ops vlan_header_ops = {
	.parse_protocol = vlan_parse_protocol,
 };

-static int vlan_passthru_hard_header(struct sk_buff *skb, struct net_device *dev,
-					 unsigned short type,
-					 const void *daddr, const void *saddr,
-					 unsigned int len)
-{
-	struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
-	struct net_device *real_dev = vlan->real_dev;
-
-	if (saddr == NULL)
-		saddr = dev->dev_addr;
-
-	return dev_hard_header(skb, real_dev, type, daddr, saddr, len);
-}
-
-static const struct header_ops vlan_passthru_header_ops = {
-	.create	 = vlan_passthru_hard_header,
-	.parse	 = eth_header_parse,
-	.parse_protocol = vlan_parse_protocol,
-};
-
 static const struct device_type vlan_type = {
	.name	= "vlan",
 };
@@ -580,14 +560,9 @@ static int vlan_dev_init(struct net_device *dev)
	dev->fcoe_ddp_xid = real_dev->fcoe_ddp_xid;
 #endif

-	dev->needed_headroom = real_dev->needed_headroom;
-	if (vlan_hw_offload_capable(real_dev->features, vlan->vlan_proto)) {
-		dev->header_ops 	 = &vlan_passthru_header_ops;
-		dev->hard_header_len = real_dev->hard_header_len;
-	} else {
-		dev->header_ops 	 = &vlan_header_ops;
-		dev->hard_header_len = real_dev->hard_header_len + VLAN_HLEN;
-	}
+	dev->needed_headroom = real_dev->needed_headroom + VLAN_HLEN;
+	dev->header_ops 	 = &vlan_header_ops;
+	dev->hard_header_len = real_dev->hard_header_len;

	dev->netdev_ops = &vlan_netdev_ops;
@@ -1029,10 +1004,8 @@ static void vlan_transfer_features(struct net_device *dev,
	netif_inherit_tso_max(vlandev, dev);

-	if (vlan_hw_offload_capable(dev->features, vlan->vlan_proto))
-		vlandev->hard_header_len = dev->hard_header_len;
-	else
-		vlandev->hard_header_len = dev->hard_header_len + VLAN_HLEN;
+	vlandev->needed_headroom = dev->needed_headroom + VLAN_HLEN;
+	vlandev->hard_header_len = dev->hard_header_len;

 #if IS_ENABLED(CONFIG_FCOE)
	vlandev->fcoe_ddp_xid = dev->fcoe_ddp_xid;
--
2.55.0.229.g6434b31f56-goog
Hi Eric,

While testing this backport in our local environment, we hit a similar
skb_under_panic issue when layering macvlan on top of a VLAN device.

macvlan does't sync vlan's need_headroom, so skb head align to 16 bytes.
when skb arrive vlan, call vlan_header_ops -> vlan_dev_hard_header,
push VLAN_HLEN 4 + ETH_LEN 14 to skb, oops.

Call trace
==========
[  103.088286] skbuff: skb_under_panic: text:ffff800080d2159c len:114 put:14 head:ffff0000d2055800 data:ffff0000d20557fe tail:0x70 end:0x6c0 dev:macvlan
[  103.088344] ------------[ cut here ]------------
[  103.088346] kernel BUG at net/core/skbuff.c:214!
[  103.088350] Internal error: Oops - BUG: 00000000f2000800 [#1]  SMP
[  103.098003] CPU: 3 UID: 0 PID: 160 Comm: kworker/3:3 Kdump: loaded Not tainted 7.2.0-rc4+ #56 PREEMPTLAZY
[  103.099007] Hardware name: QEMU KVM Virtual Machine, BIOS 0.0.0 02/06/2015
[  103.099725] Workqueue: mld mld_ifc_work
[  103.100169] pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[  103.100898] pc : skb_panic+0x58/0x68
[  103.101310] lr : skb_panic+0x58/0x68
[  103.101738] sp : ffff800083d1b950
[  103.109858] Call trace:
[  103.110161]  skb_panic+0x58/0x68 (P)
[  103.110578]  skb_push+0x58/0x60
[  103.110971]  eth_header+0x3c/0xe0
[  103.111359]  vlan_dev_hard_header+0x7c/0x1a8 [8021q]
[  103.111904]  macvlan_hard_header+0x38/0x60 [macvlan]
[  103.112455]  neigh_resolve_output.part.0+0xa8/0x168
[  103.113081]  neigh_resolve_output+0x4c/0x90
[  103.113639]  ip6_finish_output2+0x2c0/0x858
[  103.114182]  ip6_finish_output+0x23c/0x398
[  103.114741]  ip6_output+0x90/0x250
[  103.115211]  NF_HOOK.constprop.0+0x54/0x118
[  103.115699]  mld_sendpack+0x1cc/0x3d0
[  103.116202]  mld_send_cr+0x1cc/0x318
[  103.116696]  mld_ifc_work+0x38/0x128


Reproducer
==========
ip link add veth0 type veth peer name veth1
ip link set veth0 up
ip link set veth1 up

ip link add link veth0 name vlan_test type vlan id 10 reorder_hdr off
ip link set vlan_test up

# macvlan does't sync vlan's need_headroom,
ip link add link vlan_test name macvlan type macvlan
ip link set macvlan up

ethtool -K veth0 tx-vlan-hw-insert off

# skb_under_panic
ip -6 addr add fe80::1/64 dev macvlan

-- 
Best regards,
Tangxin Xie
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help