diff --git a/net/core/filter.c b/net/core/filter.c index 08168ea381c2..b5534dac9e6d 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -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;