diff options
| author | Alexei Starovoitov <ast@kernel.org> | 2026-11-02 00:10:40 +0000 |
|---|---|---|
| committer | Alexei Starovoitov <ast@kernel.org> | 2026-11-02 00:10:41 +0000 |
| commit | 714b7112017fc7f8b2afabccf562acdb12d2ea7d (patch) | |
| tree | f49c1ce081d19372c077ffb4e227753a3eecbd6e /net | |
| parent | 1d55411e8b677c67de7a20618d95dc114796de26 (diff) | |
| parent | ee36c9aa44ef300cd651074bd97552b229337f85 (diff) | |
Merge branch 'bpf-fixes-for-the-verifier-arena-skb-and-lwt-helpers'
Alexei Starovoitov says:
====================
bpf: Fixes for the verifier, arena, skb and lwt helpers
From: Alexei Starovoitov <ast@kernel.org>
These patches fix bugs in BPF:
patches area
------- ----------------------------------------------------------------
01-02 stack traces: the limit of the trace length
03-07 arena: the range of mmap() and of bpf_arena_free_pages()
08-12 arguments of kfuncs and of extension progs
13 cgroup detach: the permission check
14 cgroup skb progs: the network offset
15 bpf_skb_adjust_room(): the limit of gso_size
16-24 lwt and seg6local End.BPF progs: pushed headers, skb->cb, dst
25-27 skb offsets when a helper adds or removes room
10 of the patches are from Chris Mason and Josef Bacik. Patches 07 and 12
are selftests.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
====================
Link: https://patch.msgid.link/20261101190535.1454146-1-alexei.starovoitov@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Diffstat (limited to 'net')
| -rw-r--r-- | net/bpf/test_run.c | 3 | ||||
| -rw-r--r-- | net/core/filter.c | 133 | ||||
| -rw-r--r-- | net/core/lwt_bpf.c | 53 |
3 files changed, 163 insertions, 26 deletions
diff --git a/net/bpf/test_run.c b/net/bpf/test_run.c index 3043a7393c29..d6c9dc62182e 100644 --- a/net/bpf/test_run.c +++ b/net/bpf/test_run.c @@ -1210,7 +1210,8 @@ int bpf_prog_test_run_skb(struct bpf_prog *prog, const union bpf_attr *kattr, skb->ip_summed = CHECKSUM_COMPLETE; } - if (prog->type == BPF_PROG_TYPE_LWT_XMIT) { + if (prog->type == BPF_PROG_TYPE_LWT_IN || + prog->type == BPF_PROG_TYPE_LWT_XMIT) { if (!ipv6_mod_enabled()) { pr_warn_once("Please test this program with IPv6 enabled kernel\n"); ret = -EOPNOTSUPP; diff --git a/net/core/filter.c b/net/core/filter.c index 303c0c44dc2c..9245009d9f1e 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -3369,6 +3369,10 @@ static const struct bpf_func_proto bpf_skb_vlan_pop_proto = { static int bpf_skb_generic_push(struct sk_buff *skb, u32 off, u32 len) { + /* The bytes in front of @off are moved with skb->data. */ + if (unlikely(off > skb->len || !pskb_may_pull(skb, off))) + return -ENOMEM; + /* Caller already did skb_cow() with meta_len+len as headroom, * so no need to do it here. */ @@ -3389,9 +3393,10 @@ static int bpf_skb_generic_pop(struct sk_buff *skb, u32 off, u32 len) void *old_data; /* skb_ensure_writable() is not needed here, as we're - * already working on an uncloned skb. + * already working on an uncloned skb. The first test + * keeps off + len from wrapping. */ - if (unlikely(!pskb_may_pull(skb, off + len))) + if (unlikely(off > skb->len || !pskb_may_pull(skb, off + len))) return -ENOMEM; old_data = skb->data; @@ -3402,9 +3407,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 +3436,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 +3445,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 +3453,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; @@ -3744,6 +3774,25 @@ static int bpf_skb_net_grow(struct sk_buff *skb, u32 off, u32 len_diff, return 0; } +/* + * Code that went by the inner transport header while skb->encapsulation was set + * goes by the transport header from now on. So take the inner one over, unless + * it is the smaller one, as it is if nobody has set it. Do not leave the result + * in front of @off, where the bytes were removed. + */ +static void bpf_skb_clear_encap(struct sk_buff *skb, u32 off) +{ + int thoff; + + if (!skb->encapsulation) + return; + + thoff = max(skb->inner_transport_header, skb->transport_header) - + skb_headroom(skb); + skb_set_transport_header(skb, max_t(int, thoff, off)); + skb->encapsulation = 0; +} + static int bpf_skb_net_shrink(struct sk_buff *skb, u32 off, u32 len_diff, u64 flags) { @@ -3762,6 +3811,14 @@ static int bpf_skb_net_shrink(struct sk_buff *skb, u32 off, u32 len_diff, return -ENOTSUPP; } + /* + * The MSS is raised by len_diff. It has 16 bits, and GSO_BY_FRAGS is + * not a size. + */ + if (skb_is_gso(skb) && !(flags & BPF_F_ADJ_ROOM_FIXED_GSO) && + skb_shinfo(skb)->gso_size + len_diff >= GSO_BY_FRAGS) + return -ENOTSUPP; + ret = skb_unclone(skb, GFP_ATOMIC); if (unlikely(ret < 0)) return ret; @@ -3817,8 +3874,7 @@ static int bpf_skb_net_shrink(struct sk_buff *skb, u32 off, u32 len_diff, SKB_GSO_IPXIP4 | SKB_GSO_IPXIP6 | SKB_GSO_ESP))) - if (skb->encapsulation) - skb->encapsulation = 0; + bpf_skb_clear_encap(skb, off); } /* Header must be checked, and gso_segs recomputed. */ @@ -3826,10 +3882,9 @@ static int bpf_skb_net_shrink(struct sk_buff *skb, u32 off, u32 len_diff, shinfo->gso_segs = 0; } else { /* For non-GSO packets, clear encapsulation if decap flags are set */ - if ((flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK | - BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK)) && - skb->encapsulation) - skb->encapsulation = 0; + if (flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK | + BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK)) + bpf_skb_clear_encap(skb, off); } return 0; @@ -6916,14 +6971,22 @@ static int bpf_push_seg6_encap(struct sk_buff *skb, u32 type, void *hdr, u32 len int err; struct ipv6_sr_hdr *srh = (struct ipv6_sr_hdr *)hdr; + /* With ipv6.disable=1 the netns has no seg6 data and no IPv6 tables. */ + if (!ipv6_mod_enabled()) + return -EAFNOSUPPORT; + if (!seg6_validate_srh(srh, len, false)) return -EINVAL; + /* + * The encap mode names IPv6 as the inner protocol, and neither mode + * updates skb->protocol. + */ + if (skb->protocol != htons(ETH_P_IPV6)) + return -EBADMSG; + switch (type) { case BPF_LWT_ENCAP_SEG6_INLINE: - if (skb->protocol != htons(ETH_P_IPV6)) - return -EBADMSG; - err = seg6_do_srh_inline(skb, srh); break; case BPF_LWT_ENCAP_SEG6: @@ -7012,12 +7075,13 @@ BPF_CALL_4(bpf_lwt_seg6_store_bytes, struct sk_buff *, skb, u32, offset, this_cpu_ptr(&seg6_bpf_srh_states); struct ipv6_sr_hdr *srh = srh_state->srh; void *srh_tlvs, *srh_end, *ptr; - int srhoff = 0; + int srhoff; lockdep_assert_held(&srh_state->bh_lock); if (srh == NULL) return -EINVAL; + srhoff = (unsigned char *)srh - skb->data; srh_tlvs = (void *)((char *)srh + ((srh->first_segment + 1) << 4)); srh_end = (void *)((char *)srh + sizeof(*srh) + srh_state->hdrlen); @@ -7030,8 +7094,7 @@ BPF_CALL_4(bpf_lwt_seg6_store_bytes, struct sk_buff *, skb, u32, offset, if (unlikely(bpf_try_make_writable(skb, offset + len))) return -EFAULT; - if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) - return -EINVAL; + /* The head may have moved. */ srh_state->srh = (struct ipv6_sr_hdr *)(skb->data + srhoff); memcpy(skb->data + offset, from, len); @@ -7147,16 +7210,18 @@ 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; - int srhoff = 0; + int srhoff; int ret; lockdep_assert_held(&srh_state->bh_lock); if (unlikely(srh == NULL)) return -EINVAL; + srhoff = (unsigned char *)srh - skb->data; srh_tlvs = (void *)((unsigned char *)srh + sizeof(*srh) + ((srh->first_segment + 1) << 4)); srh_end = (void *)((unsigned char *)srh + sizeof(*srh) + @@ -7167,26 +7232,46 @@ BPF_CALL_3(bpf_lwt_seg6_adjust_srh, struct sk_buff *, skb, u32, offset, return -EFAULT; if (unlikely(len < 0 && (void *)((char *)ptr - len) > srh_end)) return -EFAULT; + /* The length field of the SRH has 8 bits, in units of 8 bytes. */ + 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)); - if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) - return -EINVAL; + /* The bytes in front of offset have moved together with skb->data. */ srh_state->srh = (struct ipv6_sr_hdr *)(skb->data + srhoff); srh_state->hdrlen += len; srh_state->valid = false; diff --git a/net/core/lwt_bpf.c b/net/core/lwt_bpf.c index da49364ec63d..774fad4e9fbf 100644 --- a/net/core/lwt_bpf.c +++ b/net/core/lwt_bpf.c @@ -85,6 +85,32 @@ static int run_lwt_bpf(struct sk_buff *skb, struct bpf_lwt_prog *lwt, return ret; } +/* + * skb->cb still describes the IP header that was the first one before the + * prog ran. The option offsets in IPCB(skb)->opt are wrong for a pushed + * header, and a header of the other family needs the other layout. iif + * stays: behind a VRF skb->skb_iif is the L3 master and not the port. + * bpf_lwt_push_encap() cannot do this, because bpf_prog_run_save_cb() can + * restore skb->cb after the prog. + */ +static void bpf_lwt_reset_cb(struct sk_buff *skb, __be16 proto) +{ + int iif; + + if (skb->protocol == proto) { + if (proto == htons(ETH_P_IP)) + memset(&IPCB(skb)->opt, 0, sizeof(IPCB(skb)->opt)); + } else if (skb->protocol == htons(ETH_P_IP)) { + iif = IP6CB(skb)->iif; + memset(IPCB(skb), 0, sizeof(*IPCB(skb))); + IPCB(skb)->iif = iif; + } else { + iif = IPCB(skb)->iif; + memset(IP6CB(skb), 0, sizeof(*IP6CB(skb))); + IP6CB(skb)->iif = iif; + } +} + static int bpf_lwt_input_reroute(struct sk_buff *skb) { enum skb_drop_reason reason; @@ -102,7 +128,7 @@ static int bpf_lwt_input_reroute(struct sk_buff *skb) dev_put(dev); } else if (skb->protocol == htons(ETH_P_IPV6)) { skb_dst_drop(skb); - if (IS_ENABLED(CONFIG_IPV6)) { + if (ipv6_mod_enabled()) { ip6_route_input(skb); err = skb_dst(skb)->error; } else { @@ -124,6 +150,8 @@ err: static int bpf_input(struct sk_buff *skb) { struct dst_entry *dst = skb_dst(skb); + __be16 proto = skb->protocol; + unsigned int len = skb->len; struct bpf_lwt *bpf; int ret; @@ -132,8 +160,21 @@ static int bpf_input(struct sk_buff *skb) ret = run_lwt_bpf(skb, &bpf->in, dst, NO_REDIRECT); if (ret < 0) return ret; + /* An lwt_in prog can change the length only with a push. */ + if (skb->len != len) + bpf_lwt_reset_cb(skb, proto); if (ret == BPF_LWT_REROUTE) return bpf_lwt_input_reroute(skb); + /* + * orig_input() is for the old protocol only, see bpf_xmit(). + * It also needs a dst of the old family. BPF_LWT_ENCAP_SEG6 + * puts an IPv6 dst into the skb. + */ + if (skb->protocol != proto || + skb_dst(skb)->ops->family != dst->ops->family) { + kfree_skb(skb); + return -EINVAL; + } } if (unlikely(!dst->lwtstate->orig_input)) { @@ -311,6 +352,8 @@ static int bpf_xmit(struct sk_buff *skb) case BPF_REDIRECT: return LWTUNNEL_XMIT_DONE; case BPF_LWT_REROUTE: + /* An lwt_xmit prog can also write to the options. */ + bpf_lwt_reset_cb(skb, proto); return bpf_lwt_xmit_reroute(skb); default: return ret; @@ -649,6 +692,14 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) if (ingress) skb_postpush_rcsum(skb, iph, len); skb_reset_network_header(skb); + /* + * The new header goes over the MAC header. On ingress move it first. + * On egress the packet gets a new one, so forget the old one. + */ + if (ingress) + skb_mac_header_rebuild(skb); + else + skb_unset_mac_header(skb); if (is_udp_tunnel) { size_t iph_sz = ipv4 ? iph->ihl * 4 : sizeof(struct ipv6hdr); |
