From 0fcb3f11a93d740e7f9ae1aeb48fe4d25838be18 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Sun, 1 Nov 2026 19:05:28 +0000 Subject: [PATCH] bpf: Move the MAC header in bpf_lwt_seg6_adjust_srh() bpf_skb_net_hdr_push() and bpf_skb_net_hdr_pop() change skb->data and skb->mac_header by len. That is correct in a tc prog, where skb->data is the MAC header. But bpf_lwt_seg6_adjust_srh() calls them in an End.BPF prog, where skb->data is the network header. The MAC header does not move: before push | free | MAC header | IPv6 | SRH | payload ^ head ^ mac_header ^ data today | free | MAC h| IPv6 | SRH + len | payload ^ head ^ mac_header ^ data this patch | free | MAC header | IPv6 | SRH + len | payload ^ head ^ mac_header ^ data Users of skb_mac_header() get incorrect bytes until __dev_queue_xmit() sets skb->mac_header again. skb_cow_head() reserves only len bytes. Thus the 16-bit offset skb->mac_header can wrap. A push also changes the "not set" value. In both cases skb_mac_header() points after the skb. Move the MAC header too. Pass len + skb->mac_len to skb_cow_head(). Keep the "not set" value. The second Fixes tag is for this case. Since that commit an End.BPF prog can get an skb with skb->mac_header not set. Fixes: fe94cc290f53 ("bpf: Add IPv6 Segment Routing helpers") Fixes: 20a081b7984c ("seg6: add VRF support for SRv6 End.DT6 behavior") Signed-off-by: Chris Mason Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20261101190535.1454146-21-alexei.starovoitov@gmail.com --- net/core/filter.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/net/core/filter.c b/net/core/filter.c index d634850a38c7..482f8132d9ca 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -7159,6 +7159,7 @@ BPF_CALL_3(bpf_lwt_seg6_adjust_srh, struct sk_buff *, skb, u32, offset, { struct seg6_bpf_srh_state *srh_state = this_cpu_ptr(&seg6_bpf_srh_states); + bool mac_set = skb_mac_header_was_set(skb); struct ipv6_sr_hdr *srh = srh_state->srh; void *srh_end, *srh_tlvs, *ptr; struct ipv6hdr *hdr; @@ -7184,20 +7185,38 @@ BPF_CALL_3(bpf_lwt_seg6_adjust_srh, struct sk_buff *, skb, u32, offset, if (unlikely(len > (U8_MAX << 3) - srh_state->hdrlen)) return -EFAULT; + /* + * skb->data is the network header here. The MAC header is in front of + * it, so bpf_skb_net_hdr_push() and bpf_skb_net_hdr_pop() do not move + * it with the other headers. + */ if (len > 0) { - ret = skb_cow_head(skb, len); + ret = skb_cow_head(skb, len + skb->mac_len); if (unlikely(ret < 0)) return ret; + /* The push can write over the MAC header, so move it first. */ + if (mac_set) + memmove(skb->data - len - skb->mac_len, + skb_mac_header(skb), skb->mac_len); ret = bpf_skb_net_hdr_push(skb, offset, len); } else { ret = bpf_skb_net_hdr_pop(skb, offset, -1 * len); + /* The pop has added -len to skb->mac_header. */ + if (likely(!ret) && mac_set) + memmove(skb->data - skb->mac_len, + skb_mac_header(skb) + len, skb->mac_len); } bpf_compute_data_pointers(skb); if (unlikely(ret < 0)) return ret; + if (mac_set) + skb_set_mac_header(skb, -skb->mac_len); + else + skb_unset_mac_header(skb); + hdr = (struct ipv6hdr *)skb->data; hdr->payload_len = htons(skb->len - sizeof(struct ipv6hdr));