From 1395a676ec15a0a02a2a6d86602324f2d5fd41d5 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 23 Jul 2026 14:42:45 +0000 Subject: vxlan: re-fetch eth header after route_shortcircuit() Before route_shortcircuit(), the eth header pointer is cached from eth_hdr(skb). Inside route_shortcircuit(), pskb_may_pull() can be called, which may reallocate skb->head. In this case, returning to vxlan_xmit() leaves the cached eth pointer pointing to freed memory, leading to a use-after-free when dereferencing eth->h_dest. Fix this by updating eth = eth_hdr(skb) after calling route_shortcircuit(). Fixes: ae8840825605 ("VXLAN: Allow L2 redirection with L3 switching") Cc: stable@vger.kernel.org Signed-off-by: Eric Dumazet Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260723144249.759100-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index d834a4865aec..a05654a55bd6 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -2796,6 +2796,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); } -- cgit v1.2.3 From 760d36e737f2b3867762f42af36c663f55babcc4 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 23 Jul 2026 14:42:46 +0000 Subject: vxlan: unclone skb head before modifying eth header in route_shortcircuit() When route_shortcircuit() performs L3 short-circuit routing, it modifies the Ethernet header of the skb in-place: 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); If the incoming skb is cloned (for example by packet sockets, tcpdump, or dev_queue_xmit), modifying the Ethernet header without uncloning can corrupt the packet header for other readers holding a reference to the cloned skb. Ensure the skb header is writable and unshared by calling skb_cow_head(skb, 0) prior to updating the Ethernet header. If skb_cow_head() fails, abort short-circuiting and return false to allow standard packet processing fallback. Fixes: e4f67addf158 ("add DOVE extensions for VXLAN") Cc: stable@vger.kernel.org Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260723144249.759100-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index a05654a55bd6..e831fe203442 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -2163,6 +2163,10 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb) diff = !ether_addr_equal(eth_hdr(skb)->h_dest, n->ha); 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); -- cgit v1.2.3 From 8eca411347e1d38964f9ed2c8d3b6ab0e7e4473d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 23 Jul 2026 14:42:47 +0000 Subject: vxlan: use neigh_ha_snapshot() in route_shortcircuit() The neighbour hardware address n->ha can be updated asynchronously by the neighbour subsystem, protected by n->ha_lock seqlock. Reading n->ha without holding the seqlock loop can lead to torn reads or reading a partially updated MAC address. Use neigh_ha_snapshot() in route_shortcircuit() to safely copy n->ha under read_seqbegin()/read_seqretry() lock protection before using it. Note that arp_reduce() and neigh_reduce() seem to have the same issue left for future patches. Fixes: e4f67addf158 ("add DOVE extensions for VXLAN") Cc: stable@vger.kernel.org Signed-off-by: Eric Dumazet Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260723144249.759100-4-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index e831fe203442..be3c2bc2cd9a 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -2159,9 +2159,11 @@ 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); @@ -2169,7 +2171,7 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb) } 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; -- cgit v1.2.3 From 26bb2dd0a8839617e2c79ffbbe1923f8e4bab9fb Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 23 Jul 2026 14:42:48 +0000 Subject: vxlan: use pskb_network_may_pull() in route_shortcircuit() route_shortcircuit() currently calls pskb_may_pull(skb, sizeof(struct iphdr)) (or ipv6hdr), which checks if bytes are available starting from skb->data. However, in vxlan_xmit(), skb->data points to the MAC header, so skb_network_offset(skb) is ETH_HLEN (14 bytes). Using pskb_may_pull(skb, 20) only checks 20 bytes from skb->data (which is 14 bytes MAC header + 6 bytes of IP header), leaving the rest of the IP header potentially un-pulled in non-linear frags. Subsequent dereferences of ip_hdr(skb)->daddr can read beyond the pulled linear buffer length. Fix this by using pskb_network_may_pull(), which adds skb_network_offset(skb) to the length check to ensure the full network header is present in the linear buffer. Fixes: e4f67addf158 ("add DOVE extensions for VXLAN") Cc: stable@vger.kernel.org Signed-off-by: Eric Dumazet Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260723144249.759100-5-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index be3c2bc2cd9a..2163e2687db0 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -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); -- cgit v1.2.3 From b9553558b48db54ac9273e6b98d7263ef5c1a329 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 23 Jul 2026 14:42:49 +0000 Subject: vxlan: use pskb_network_may_pull() for transmit path header pulls In vxlan_xmit(), arp_reduce(), and vxlan_mdb_entry_skb_get(), pskb_may_pull() was being called to verify the availability of network layer headers (ARP, IPv6/ND, IP/IPv6 MDB keys). However, during transmit skb->data points to the MAC header, so skb_network_offset(skb) is ETH_HLEN (14 bytes). Using pskb_may_pull(skb, len) only checks len bytes from skb->data rather than skb_network_offset(skb) + len, which can leave part of the network header in non-linear frags. Replace these remaining pskb_may_pull() calls with pskb_network_may_pull() to properly account for the MAC header offset. Fixes: e4f67addf158 ("add DOVE extensions for VXLAN") Fixes: f564f45c4518 ("vxlan: add ipv6 proxy support") Fixes: 0f83e69f44bf ("vxlan: Add MDB data path support") Signed-off-by: Eric Dumazet Cc: stable@vger.kernel.org Reviewed-by: Vadim Fedorenko Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260723144249.759100-6-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 6 +++--- drivers/net/vxlan/vxlan_mdb.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 2163e2687db0..1ded27768a97 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -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); @@ -2763,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); diff --git a/drivers/net/vxlan/vxlan_mdb.c b/drivers/net/vxlan/vxlan_mdb.c index af7a0d7f95a5..9a9038ae90c1 100644 --- a/drivers/net/vxlan/vxlan_mdb.c +++ b/drivers/net/vxlan/vxlan_mdb.c @@ -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; -- cgit v1.2.3