Merge branch 'vxlan-fixes-for-skb-header-pulling-cloning-and-concurrency-in-tx-path'

Eric Dumazet says:

====================
vxlan: fixes for skb header pulling, cloning, and concurrency in TX path

While working on RTNL-less fill_info for vxlan, Sashiko found annoying
pre-existing issues, adding noise to an already complex work.

This series addresses some of them in VXLAN transmit path,
primarily within route_shortcircuit(), header validation, and neighbour
lookup.

Patch 1 fixes a potential use-after-free in vxlan_xmit() caused by caching
the Ethernet header pointer ('eth') before calling route_shortcircuit(),
which can reallocate skb->head via pskb_may_pull().

Patch 2 calls skb_cow_head() in route_shortcircuit() before modifying the
Ethernet header in-place, preventing packet header corruption when the skb
is cloned (e.g., by packet sockets, tcpdump, or dev_queue_xmit).

Patch 3 replaces direct reads of n->ha in route_shortcircuit() with
neigh_ha_snapshot() to safely snapshot the neighbour hardware address
under seqlock protection, avoiding potential torn reads during
asynchronous updates.

Patch 4 changes route_shortcircuit() to use pskb_network_may_pull() instead
of pskb_may_pull(). Since skb->data points to the MAC header on transmit
(skb_network_offset(skb) == ETH_HLEN), pskb_may_pull() was only checking
6 bytes into the IP header, leaving the remainder un-pulled in non-linear
frags.

Patch 5 applies pskb_network_may_pull() to the remaining transmit-path
header pull checks in arp_reduce(), ND solicitation proxy checks, and
MDB entry lookup, where skb->data similarly points to the Ethernet header.
====================

Link: https://patch.msgid.link/20260723144249.759100-1-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2026-07-27 15:15:18 -07:00
2 changed files with 16 additions and 9 deletions

View File

@@ -1850,7 +1850,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
if (dev->flags & IFF_NOARP)
goto out;
if (!pskb_may_pull(skb, arp_hdr_len(dev))) {
if (!pskb_network_may_pull(skb, arp_hdr_len(dev))) {
dev_dstats_tx_dropped(dev);
vxlan_vnifilter_count(vxlan, vni, NULL,
VXLAN_VNI_STATS_TX_DROPS, 0);
@@ -2111,7 +2111,7 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
{
struct iphdr *pip;
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
if (!pskb_network_may_pull(skb, sizeof(struct iphdr)))
return false;
pip = ip_hdr(skb);
n = neigh_lookup(&arp_tbl, &pip->daddr, dev);
@@ -2137,7 +2137,7 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
*/
if (!ipv6_mod_enabled())
return false;
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr)))
return false;
pip6 = ipv6_hdr(skb);
n = neigh_lookup(&nd_tbl, &pip6->daddr, dev);
@@ -2159,13 +2159,19 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
}
if (n) {
u8 haddr[ETH_ALEN];
bool diff;
diff = !ether_addr_equal(eth_hdr(skb)->h_dest, n->ha);
neigh_ha_snapshot(haddr, n, dev);
diff = !ether_addr_equal_unaligned(eth_hdr(skb)->h_dest, haddr);
if (diff) {
if (skb_cow_head(skb, 0)) {
neigh_release(n);
return false;
}
memcpy(eth_hdr(skb)->h_source, eth_hdr(skb)->h_dest,
dev->addr_len);
memcpy(eth_hdr(skb)->h_dest, n->ha, dev->addr_len);
memcpy(eth_hdr(skb)->h_dest, haddr, dev->addr_len);
}
neigh_release(n);
return diff;
@@ -2757,8 +2763,8 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev)
return arp_reduce(dev, skb, vni);
#if IS_ENABLED(CONFIG_IPV6)
else if (ntohs(eth->h_proto) == ETH_P_IPV6 &&
pskb_may_pull(skb, sizeof(struct ipv6hdr) +
sizeof(struct nd_msg)) &&
pskb_network_may_pull(skb, sizeof(struct ipv6hdr) +
sizeof(struct nd_msg)) &&
ipv6_hdr(skb)->nexthdr == IPPROTO_ICMPV6) {
struct nd_msg *m = (struct nd_msg *)(ipv6_hdr(skb) + 1);
@@ -2796,6 +2802,7 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev)
(ntohs(eth->h_proto) == ETH_P_IP ||
ntohs(eth->h_proto) == ETH_P_IPV6)) {
did_rsc = route_shortcircuit(dev, skb);
eth = eth_hdr(skb);
if (did_rsc)
f = vxlan_find_mac_tx(vxlan, eth->h_dest, vni);
}

View File

@@ -1631,7 +1631,7 @@ struct vxlan_mdb_entry *vxlan_mdb_entry_skb_get(struct vxlan_dev *vxlan,
switch (skb->protocol) {
case htons(ETH_P_IP):
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
if (!pskb_network_may_pull(skb, sizeof(struct iphdr)))
return NULL;
group.dst.sa.sa_family = AF_INET;
group.dst.sin.sin_addr.s_addr = ip_hdr(skb)->daddr;
@@ -1640,7 +1640,7 @@ struct vxlan_mdb_entry *vxlan_mdb_entry_skb_get(struct vxlan_dev *vxlan,
break;
#if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6):
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr)))
return NULL;
group.dst.sa.sa_family = AF_INET6;
group.dst.sin6.sin6_addr = ipv6_hdr(skb)->daddr;