summaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorAlexei Starovoitov <ast@kernel.org>2026-11-02 00:10:40 +0000
committerAlexei Starovoitov <ast@kernel.org>2026-11-02 00:10:41 +0000
commit714b7112017fc7f8b2afabccf562acdb12d2ea7d (patch)
treef49c1ce081d19372c077ffb4e227753a3eecbd6e /net
parent1d55411e8b677c67de7a20618d95dc114796de26 (diff)
parentee36c9aa44ef300cd651074bd97552b229337f85 (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.c3
-rw-r--r--net/core/filter.c133
-rw-r--r--net/core/lwt_bpf.c53
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);