bpf: Move the transport header with the moved headers

bpf_skb_net_hdr_pop() removes len bytes at offset off.
bpf_skb_net_hdr_push() does the opposite.

           skb->data       off
  before   | A             | removed | B            |
  after              | A             | B            |
                     skb->data       off

mac_header and network_header move with A. transport_header moves only when
it is equal to network_header. inner_transport_header never moves. This is
correct only for a header in B. In bpf_lwt_seg6_adjust_srh()
transport_header is in A. bpf_skb_adjust_room() can remove both transport
headers. With an incorrect header offset, the stack can drop the skb or
read outside of it.

Also move transport_header with A when it is in A. Set it to offset off
when it is in the removed bytes. In BPF_ADJ_ROOM_MAC mode it is then equal
to network_header. Do not unset it, because skb_gso_validate_mac_len() uses
it without a check. After a removal, set inner_transport_header to off when
it is not in B and skb->encapsulation is set. A has only outer headers.

Fixes: fe94cc290f ("bpf: Add IPv6 Segment Routing helpers")
Fixes: 868d523535 ("bpf: add bpf_skb_adjust_room encap flags")
Signed-off-by: Chris Mason <mason@kernel.org>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20261101190535.1454146-26-alexei.starovoitov@gmail.com
This commit is contained in:
Chris Mason 2026-11-01 19:05:33 +00:00 committed by Alexei Starovoitov
parent 95f1d5e4ab
commit a9d08fe60f
No known key found for this signature in database

View File

@ -3402,9 +3402,25 @@ static int bpf_skb_generic_pop(struct sk_buff *skb, u32 off, u32 len)
return 0;
}
/*
* bpf_skb_net_hdr_push() inserts bytes at @off, bpf_skb_net_hdr_pop() removes
* bytes at @off. Both move the bytes in front of @off together with skb->data,
* and leave the rest of the packet where it is. Return true if the transport
* header is in the part that moves. A transport header that is equal to the
* network header stays equal to it.
*/
static bool bpf_skb_trans_hdr_moves(const struct sk_buff *skb, u32 off)
{
if (skb->transport_header == skb->network_header)
return true;
return skb_transport_header_was_set(skb) &&
(u32)skb_transport_offset(skb) < off;
}
static int bpf_skb_net_hdr_push(struct sk_buff *skb, u32 off, u32 len)
{
bool trans_same = skb->transport_header == skb->network_header;
bool trans_moves = bpf_skb_trans_hdr_moves(skb, off);
int ret;
/* There's no need for __skb_push()/__skb_pull() pair to
@ -3415,8 +3431,8 @@ static int bpf_skb_net_hdr_push(struct sk_buff *skb, u32 off, u32 len)
if (likely(!ret)) {
skb->mac_header -= len;
skb->network_header -= len;
if (trans_same)
skb->transport_header = skb->network_header;
if (trans_moves)
skb->transport_header -= len;
}
return ret;
@ -3424,7 +3440,7 @@ static int bpf_skb_net_hdr_push(struct sk_buff *skb, u32 off, u32 len)
static int bpf_skb_net_hdr_pop(struct sk_buff *skb, u32 off, u32 len)
{
bool trans_same = skb->transport_header == skb->network_header;
bool trans_moves = bpf_skb_trans_hdr_moves(skb, off);
int ret;
/* Same here, __skb_push()/__skb_pull() pair not needed. */
@ -3432,8 +3448,17 @@ static int bpf_skb_net_hdr_pop(struct sk_buff *skb, u32 off, u32 len)
if (likely(!ret)) {
skb->mac_header += len;
skb->network_header += len;
if (trans_same)
skb->transport_header = skb->network_header;
if (trans_moves) {
skb->transport_header += len;
} else if (skb_transport_header_was_set(skb) &&
skb_transport_offset(skb) < (int)off) {
/* It was in the removed bytes or before skb->data. */
skb_set_transport_header(skb, off);
}
/* Do not leave the inner transport header in front of @off. */
if (skb->encapsulation &&
skb_inner_transport_offset(skb) < (int)off)
skb_set_inner_transport_header(skb, off);
}
return ret;