[PATCH net-next] net: xdp: don't assume an Ethernet header in generic XDP
From: Jiayuan Chen
Date: Thu Aug 13 2026 - 07:33:02 EST
Kenneth Lee reported a bug [1]:
skb_vlan_push got skb with skb->data not at mac header (offset 14)
WARNING: net/core/skbuff.c:6474 at skb_vlan_push+0x5ec/0x8a0
____bpf_skb_vlan_push net/core/filter.c:3239 [inline]
bpf_skb_vlan_push+0x216/0x8b0 net/core/filter.c:3229
bpf_prog_run_data_pointers+0x17c/0x240 include/linux/filter.h:917
tcf_bpf_act+0x31c/0x940 net/sched/act_bpf.c:50
tcf_action_exec+0x2c1/0x720 net/sched/act_api.c:1147
tcf_classify+0x6e4/0x1080 net/sched/cls_api.c:1860
tc_run+0x31c/0x5d0 net/core/dev.c:4411
sch_handle_ingress net/core/dev.c:4486 [inline]
__netif_receive_skb_core+0x141b/0x2ec0 net/core/dev.c:6054
bpf_prog_run_generic_xdp() just assumes xdp->data is an Ethernet header:
eth = (struct ethhdr *)xdp->data;
orig_host = ether_addr_equal_64bits(eth->h_dest, skb->dev->dev_addr);
orig_bcast = is_multicast_ether_addr_64bits(eth->h_dest);
orig_eth_type = eth->h_proto;
But an L3 device (ARPHRD_NONE, ARPHRD_TUNNEL, ...) has no L2 header at
all - its mac_len is 0 and xdp->data is really the L3 header, so those
orig_* values are garbage.
When that garbage comparison says the header changed, we then do:
__skb_push(skb, ETH_HLEN);
skb->pkt_type = PACKET_HOST;
skb->protocol = eth_type_trans(skb, skb->dev);
eth_type_trans() resets mac_header and pulls ETH_HLEN back, so skb->data
does not move at all, but we now have mac_len == 0 while
skb_mac_header(skb) == skb->data - ETH_HLEN.
tc ingress relies on mac_len == skb->data - skb_mac_header(skb) to get
skb->data onto the mac header, so __skb_push(skb, skb->mac_len) pushes
nothing, the program runs with skb->data ETH_HLEN past the mac header,
and bpf_skb_vlan_push() hits the WARN_ONCE above. skb->protocol also
ends up parsed from uninitialised headroom.
So skip the Ethernet part entirely when the skb has no ETH_HLEN sized L2
header. The program still runs and its action is still returned.
Refusing to attach XDP to L3 devices instead would not help: mac_len is a
property of the skb, not of the netdev, so an ARPHRD_ETHER device can see
mac_len == 0 too.
[1]: https://lore.kernel.org/bpf/20260812043643.808295-1-kennethbwlee@xxxxxxxxx/
Fixes: 22b6034323fd ("net, xdp: Update pkt_type if generic XDP changes unicast MAC")
Reported-by: Kenneth Lee <kennethbwlee@xxxxxxxxx>
Signed-off-by: Jiayuan Chen <jiayuan.chen@xxxxxxxxx>
---
net/core/dev.c | 42 ++++++++++++++++++++++++++++--------------
1 file changed, 28 insertions(+), 14 deletions(-)
diff --git a/net/core/dev.c b/net/core/dev.c
index b390c2edfb33..5c395460854f 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -5489,11 +5489,12 @@ u32 bpf_prog_run_generic_xdp(struct sk_buff *skb, struct xdp_buff *xdp,
{
void *orig_data, *orig_data_end, *hard_start;
struct netdev_rx_queue *rxqueue;
- bool orig_bcast, orig_host;
+ bool orig_bcast = false, orig_host = false;
+ __be16 orig_eth_type = 0;
u32 mac_len, frame_sz;
- __be16 orig_eth_type;
struct ethhdr *eth;
u32 metalen, act;
+ bool has_eth;
int off;
/* The XDP program wants to see the packet starting at the MAC
@@ -5519,10 +5520,21 @@ u32 bpf_prog_run_generic_xdp(struct sk_buff *skb, struct xdp_buff *xdp,
orig_data_end = xdp->data_end;
orig_data = xdp->data;
- eth = (struct ethhdr *)xdp->data;
- orig_host = ether_addr_equal_64bits(eth->h_dest, skb->dev->dev_addr);
- orig_bcast = is_multicast_ether_addr_64bits(eth->h_dest);
- orig_eth_type = eth->h_proto;
+
+ /* xdp->data only points at an Ethernet header if this skb actually
+ * carries one. Devices with a different link layer (mac_len == 0
+ * for ARPHRD_NONE/TUNNEL/RAWIP/PPP/..., IPOIB_ENCAP_LEN for IPoIB)
+ * have nothing to inspect here, and must not have skb->mac_header
+ * relocated by the ETH_HLEN fixup below.
+ */
+ has_eth = mac_len == ETH_HLEN;
+ if (has_eth) {
+ eth = (struct ethhdr *)xdp->data;
+ orig_host = ether_addr_equal_64bits(eth->h_dest,
+ skb->dev->dev_addr);
+ orig_bcast = is_multicast_ether_addr_64bits(eth->h_dest);
+ orig_eth_type = eth->h_proto;
+ }
act = bpf_prog_run_xdp(xdp_prog, xdp);
@@ -5554,14 +5566,16 @@ u32 bpf_prog_run_generic_xdp(struct sk_buff *skb, struct xdp_buff *xdp,
skb->data_len = 0;
/* check if XDP changed eth hdr such SKB needs update */
- eth = (struct ethhdr *)xdp->data;
- if ((orig_eth_type != eth->h_proto) ||
- (orig_host != ether_addr_equal_64bits(eth->h_dest,
- skb->dev->dev_addr)) ||
- (orig_bcast != is_multicast_ether_addr_64bits(eth->h_dest))) {
- __skb_push(skb, ETH_HLEN);
- skb->pkt_type = PACKET_HOST;
- skb->protocol = eth_type_trans(skb, skb->dev);
+ if (has_eth) {
+ eth = (struct ethhdr *)xdp->data;
+ if ((orig_eth_type != eth->h_proto) ||
+ (orig_host != ether_addr_equal_64bits(eth->h_dest,
+ skb->dev->dev_addr)) ||
+ (orig_bcast != is_multicast_ether_addr_64bits(eth->h_dest))) {
+ __skb_push(skb, ETH_HLEN);
+ skb->pkt_type = PACKET_HOST;
+ skb->protocol = eth_type_trans(skb, skb->dev);
+ }
}
/* Redirect/Tx gives L2 packet, code that will reuse skb must __skb_pull
--
2.43.0