Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next] net: xdp: don't assume an Ethernet header in generic XDP
@ 2026-08-13 11:32 Jiayuan Chen
  0 siblings, 0 replies; only message in thread
From: Jiayuan Chen @ 2026-08-13 11:32 UTC (permalink / raw)
  To: netdev
  Cc: Jiayuan Chen, Kenneth Lee, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Alexei Starovoitov,
	Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
	Stanislav Fomichev, Kuniyuki Iwashima, Hangbin Liu, Krishna Kumar,
	Martin Karsten, Toke Høiland-Jørgensen, Martin Willi,
	linux-kernel, bpf

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@snu.ac.kr/

Fixes: 22b6034323fd ("net, xdp: Update pkt_type if generic XDP changes unicast MAC")
Reported-by: Kenneth Lee <kennethbwlee@snu.ac.kr>
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
---
 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


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-08-13 11:32 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-13 11:32 [PATCH net-next] net: xdp: don't assume an Ethernet header in generic XDP Jiayuan Chen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox