summaryrefslogtreecommitdiff
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
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>
-rw-r--r--Documentation/bpf/kfuncs.rst7
-rw-r--r--include/linux/bpf.h10
-rw-r--r--include/linux/bpf_verifier.h8
-rw-r--r--include/linux/btf.h1
-rw-r--r--kernel/bpf/arena.c16
-rw-r--r--kernel/bpf/btf.c19
-rw-r--r--kernel/bpf/cgroup.c26
-rw-r--r--kernel/bpf/stackmap.c33
-rw-r--r--kernel/bpf/verifier.c66
-rw-r--r--net/bpf/test_run.c3
-rw-r--r--net/core/filter.c133
-rw-r--r--net/core/lwt_bpf.c53
-rw-r--r--tools/testing/selftests/bpf/prog_tests/arena_mmap.c37
-rw-r--r--tools/testing/selftests/bpf/prog_tests/freplace_args.c92
-rw-r--r--tools/testing/selftests/bpf/progs/freplace_args.c53
-rw-r--r--tools/testing/selftests/bpf/progs/freplace_args_tgt.c70
16 files changed, 564 insertions, 63 deletions
diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index 73578a4c3b1f..901c37504217 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -407,6 +407,13 @@ referenced kptr (by invoking bpf_kptr_xchg). If not, the verifier fails the
loading of the BPF program until no lingering references remain in all possible
explored states of the program.
+A pointer to a struct in the arguments of a KF_ACQUIRE kfunc accepts only an
+object of that type. The same applies to the argument that a KF_RELEASE kfunc
+releases. For other arguments a kfunc also accepts memory of the program, if the
+struct has only scalar members. A struct without members, struct __sk_buff and
+struct xdp_md never accept memory. A KF_ACQUIRE kfunc which wants such memory
+has to pair the pointer with a __sz argument.
+
2.5.2 KF_RET_NULL flag
----------------------
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 1a8c3e514284..0b44bcb4f56e 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1752,12 +1752,22 @@ enum bpf_func_context {
BPF_FUNC_CTX_CNT,
};
+struct bpf_subprog_arg_info {
+ enum bpf_arg_type arg_type;
+ union {
+ u32 mem_size;
+ u32 btf_id;
+ };
+};
+
struct bpf_func_info_aux {
u16 linkage;
bool unreliable;
bool called[BPF_FUNC_CTX_CNT];
bool verified[BPF_FUNC_CTX_CNT];
bool called_with_pkt_ptrs;
+ /* what the callers of a global function are checked against */
+ struct bpf_subprog_arg_info args[MAX_BPF_FUNC_REG_ARGS];
};
enum bpf_jit_poke_reason {
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 8852c173c53e..33dc3d402057 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -923,14 +923,6 @@ struct bpf_func_ptr {
/* the subprog that a bpf_func_ptr pointed to was removed as dead code */
#define BPF_FUNC_PTR_DELETED ((u32)-1)
-struct bpf_subprog_arg_info {
- enum bpf_arg_type arg_type;
- union {
- u32 mem_size;
- u32 btf_id;
- };
-};
-
enum priv_stack_mode {
PRIV_STACK_UNKNOWN,
NO_PRIV_STACK,
diff --git a/include/linux/btf.h b/include/linux/btf.h
index a18254b7521b..87fad4076f0e 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -631,6 +631,7 @@ s32 btf_find_dtor_kfunc(struct btf *btf, u32 btf_id);
int register_btf_id_dtor_kfuncs(const struct btf_id_dtor_kfunc *dtors, u32 add_cnt,
struct module *owner);
struct btf_struct_meta *btf_find_struct_meta(const struct btf *btf, u32 btf_id);
+bool btf_is_projection_type(const char *pname);
bool btf_is_projection_of(const char *pname, const char *tname);
bool btf_is_prog_ctx_type(struct bpf_verifier_log *log, const struct btf *btf,
const struct btf_type *t, enum bpf_prog_type prog_type,
diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index 3c61c181a6fa..0b1ec3306ea4 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -634,7 +634,7 @@ static unsigned long arena_get_unmapped_area(struct file *filp, unsigned long ad
if (pgoff)
return -EINVAL;
- if (len > SZ_4G)
+ if (len > (u64)map->max_entries * PAGE_SIZE)
return -E2BIG;
/* if user_vm_start was specified at arena creation time */
@@ -650,11 +650,11 @@ static unsigned long arena_get_unmapped_area(struct file *filp, unsigned long ad
ret = mm_get_unmapped_area(filp, addr, len * 2, 0, flags);
if (IS_ERR_VALUE(ret))
return ret;
+ if (!ret)
+ /* user_vm_start == 0 means that the user range is not set yet */
+ return -EINVAL;
if ((ret >> 32) == ((ret + len - 1) >> 32))
return ret;
- if (WARN_ON_ONCE(arena->user_vm_start))
- /* checks at map creation time should prevent this */
- return -EFAULT;
return round_up(ret, SZ_4G);
}
@@ -679,7 +679,7 @@ static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)
return -EBUSY;
/* Earlier checks should prevent this */
- if (WARN_ON_ONCE(vma->vm_end - vma->vm_start > SZ_4G || vma->vm_pgoff))
+ if (WARN_ON_ONCE(vma_pages(vma) > map->max_entries || vma->vm_pgoff))
return -EFAULT;
if (remember_vma(arena, vma))
@@ -988,6 +988,8 @@ static int arena_free_range(struct bpf_arena *arena, long uaddr, long page_cnt)
static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt, bool sleepable)
{
+ /* The first mmap() may be setting the user range right now */
+ u64 user_vm_start = READ_ONCE(arena->user_vm_start);
struct mem_cgroup *new_memcg, *old_memcg;
u64 full_uaddr, uaddr_end;
struct arena_free_span *s;
@@ -995,8 +997,8 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
/* only aligned lower 32-bit are relevant */
uaddr = (u32)uaddr;
uaddr &= PAGE_MASK;
- full_uaddr = clear_lo32(arena->user_vm_start) + uaddr;
- if (full_uaddr < arena->user_vm_start)
+ full_uaddr = clear_lo32(user_vm_start) + uaddr;
+ if (!user_vm_start || full_uaddr < user_vm_start)
return;
uaddr_end = min(arena->user_vm_end, full_uaddr + (page_cnt << PAGE_SHIFT));
if (full_uaddr >= uaddr_end)
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 1be30487a2fa..8bf39098ba97 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -6590,6 +6590,15 @@ static int find_kern_ctx_type_id(enum bpf_prog_type prog_type)
return ctx_type->type;
}
+/*
+ * A projection type is the view that programs have of a kernel object with
+ * another layout: see btf_is_projection_of() for the two types behind them.
+ */
+bool btf_is_projection_type(const char *pname)
+{
+ return strcmp(pname, "__sk_buff") == 0 || strcmp(pname, "xdp_md") == 0;
+}
+
bool btf_is_projection_of(const char *pname, const char *tname)
{
if (strcmp(pname, "__sk_buff") == 0 && strcmp(tname, "sk_buff") == 0)
@@ -8483,9 +8492,8 @@ static int btf_check_func_type_match(struct bpf_verifier_log *log,
}
/* This is an optional check to make program writing easier.
* Compare names of structs and report an error to the user.
- * btf_prepare_func_args() already checked that t2 struct
- * is a context type. btf_prepare_func_args() will check
- * later that t1 struct is a context type as well.
+ * check_ext_args() compares what btf_prepare_func_args()
+ * derives for the two arguments.
*/
s1 = btf_name_by_offset(btf1, t1->name_off);
s2 = btf_name_by_offset(btf2, t2->name_off);
@@ -9019,6 +9027,11 @@ skip_pointer:
return err;
sub->arg_slot_cnt = slots_used;
+ /* check_ext_args() needs them after env is freed */
+ if (is_global)
+ memcpy(subprog_aux(env, subprog)->args, sub->args,
+ sizeof(subprog_aux(env, subprog)->args));
+
sub->args_cached = true;
return 0;
diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c
index ce82963bcfc7..57a785241d42 100644
--- a/kernel/bpf/cgroup.c
+++ b/kernel/bpf/cgroup.c
@@ -1279,9 +1279,14 @@ static struct bpf_prog_list *find_detach_entry(struct hlist_head *progs,
return ERR_PTR(-ENOENT);
/* to maintain backward compatibility NONE and OVERRIDE cgroups
- * allow detaching with invalid FD (prog==NULL) in legacy mode
+ * allow detaching with invalid FD (prog==NULL) in legacy mode.
+ * Without the FD of the attached program the caller needs
+ * CAP_NET_ADMIN or CAP_SYS_ADMIN in the initial user namespace.
*/
- return hlist_entry(progs->first, typeof(*pl), node);
+ pl = hlist_entry(progs->first, typeof(*pl), node);
+ if (pl->prog != prog && !bpf_net_capable())
+ return ERR_PTR(-EPERM);
+ return pl;
}
if (!prog && !link)
@@ -1785,7 +1790,7 @@ int __cgroup_bpf_run_filter_skb(struct sock *sk,
struct sk_buff *skb,
enum cgroup_bpf_attach_type atype)
{
- unsigned int offset = -skb_network_offset(skb);
+ int offset = skb_network_offset(skb);
struct sock *save_sk;
void *saved_data_end;
struct cgroup *cgrp;
@@ -1797,7 +1802,15 @@ int __cgroup_bpf_run_filter_skb(struct sock *sk,
cgrp = sock_cgroup_ptr(&sk->sk_cgrp_data);
save_sk = skb->sk;
skb->sk = sk;
- __skb_push(skb, offset);
+
+ /*
+ * The program sees the packet from its network header on, on
+ * whichever side of skb->data that header lies.
+ */
+ if (offset > 0)
+ __skb_pull(skb, offset);
+ else
+ __skb_push(skb, -offset);
/* compute pointers for the bpf prog */
bpf_compute_and_save_data_end(skb, &saved_data_end);
@@ -1839,7 +1852,10 @@ int __cgroup_bpf_run_filter_skb(struct sock *sk,
ret = -EFAULT;
}
bpf_restore_data_end(skb, saved_data_end);
- __skb_pull(skb, offset);
+ if (offset > 0)
+ __skb_push(skb, offset);
+ else
+ __skb_pull(skb, -offset);
skb->sk = save_sk;
return ret;
diff --git a/kernel/bpf/stackmap.c b/kernel/bpf/stackmap.c
index f7e8d766d128..a19126f5db02 100644
--- a/kernel/bpf/stackmap.c
+++ b/kernel/bpf/stackmap.c
@@ -522,12 +522,14 @@ static int stackid_init(struct stackid *stackid, struct bpf_map *map,
u32 skip = flags & BPF_F_SKIP_FIELD_MASK;
u32 max_depth;
+ max_depth = stack_map_calculate_max_depth(map->value_size, stack_map_data_size(map), flags);
+ /* trace_nr can be above max_depth and max_depth below skip: clamp first */
+ trace_nr = min_t(u32, trace_nr, max_depth);
if (trace_nr <= skip)
/* skipping more than usable stack trace */
return -EFAULT;
- max_depth = stack_map_calculate_max_depth(map->value_size, stack_map_data_size(map), flags);
- stackid->nr = min_t(u32, trace_nr - skip, max_depth - skip);
+ stackid->nr = trace_nr - skip;
stackid->len = stackid->nr * sizeof(u64);
stackid->ips = trace->ip + skip;
stackid->hash = jhash2((const u32 *)stackid->ips, stackid->len / sizeof(u32), 0);
@@ -642,7 +644,8 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,
/* couldn't fetch the stack trace */
return -EFAULT;
- err = stackid_fastpath(&stackid, map, trace, trace->nr, flags);
+ /* the entry is released already, an NMI can change trace->nr */
+ err = stackid_fastpath(&stackid, map, trace, READ_ONCE(trace->nr), flags);
if (err != -ENOENT)
return err;
@@ -822,14 +825,20 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task,
crosstask, false, 0);
}
- if (unlikely(!trace) || trace->nr < skip) {
- if (may_fault)
- rcu_read_unlock();
- preempt_enable();
- goto err_fault;
- }
+ if (unlikely(!trace))
+ goto err_unlock;
- trace_nr = callchain_store(trace, trace->nr, buf, elem_size, flags);
+ /*
+ * trace->nr can be above max_depth. The per-CPU entry is released
+ * already: a tracing program or an NMI which takes a callchain in the
+ * same context before the copy below reuses it. And the unwinders of
+ * PowerPC store context markers on top of max_depth.
+ */
+ trace_nr = min_t(u32, READ_ONCE(trace->nr), max_depth);
+ if (trace_nr < skip)
+ goto err_unlock;
+
+ trace_nr = callchain_store(trace, trace_nr, buf, elem_size, flags);
/* trace should not be dereferenced after this point */
if (may_fault)
@@ -838,6 +847,10 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task,
return callchain_finalize(buf, size, trace_nr, elem_size, flags, may_fault);
+err_unlock:
+ if (may_fault)
+ rcu_read_unlock();
+ preempt_enable();
err_fault:
err = -EFAULT;
clear:
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index a90bc6c4033a..217bcfa6ebfe 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -13574,7 +13574,12 @@ static int resolve_func_arg_type(struct bpf_verifier_env *env,
if (base_type(*arg_type) != ARG_PTR_TO_BTF_ID)
return 0;
+ /*
+ * A kfunc that releases an object, or that takes a reference to one,
+ * needs the object itself: memory of the program cannot stand in for it.
+ */
if (!is_kfunc(meta) || arg_type_is_release(*arg_type) ||
+ is_kfunc_acquire(meta) ||
base_type(reg->type) == PTR_TO_BTF_ID ||
reg2btf_ids[base_type(reg->type)])
return 0;
@@ -13585,7 +13590,15 @@ static int resolve_func_arg_type(struct bpf_verifier_env *env,
ref_t = btf_type_skip_modifiers(btf, ref_id, &ref_id);
ref_tname = btf_name_by_offset(btf, ref_t->name_off);
- if (!btf_type_is_scalar_struct(env, btf, ref_t))
+ /*
+ * A kfunc that takes a projection type (ie. xdp_md) casts it to the
+ * underlying type. So memory of the program cannot stand in for it,
+ * even if the projection type holds only scalars. A struct without
+ * members (ie. iou_ctx) is a handle for an object of another type.
+ */
+ if (btf_is_projection_type(ref_tname) ||
+ !btf_type_is_scalar_struct(env, btf, ref_t) ||
+ !btf_type_vlen(ref_t))
return 0;
resolve_ret = btf_resolve_size(btf, ref_t, arg_size);
@@ -14340,9 +14353,13 @@ static int process_arg_ptr_to_btf_id(struct bpf_verifier_env *env, struct bpf_re
/* If kfunc is accepting a projection type (ie. __sk_buff), it cannot
* actually use it -- it must cast to the underlying type. So we allow
- * caller to pass in the underlying type.
+ * caller to pass in the underlying type. Only kernel BTF has that type:
+ * a type of the same name in the BTF of the program is program memory.
+ * And only a pointer to the start of the object points to that type.
*/
- taking_projection = is_kfunc(meta) && btf_is_projection_of(arg_tname, reg_tname);
+ taking_projection = is_kfunc(meta) && btf_is_kernel(reg_btf) &&
+ !reg->var_off.value &&
+ btf_is_projection_of(arg_tname, reg_tname);
if (!taking_projection && !struct_same) {
verbose(env, "%s %s expected pointer to %s %s but %s has a pointer to %s %s\n",
meta->func_name, reg_arg_name(env, argno),
@@ -22510,6 +22527,37 @@ static bool func_has_rcu_read_lock(const struct bpf_prog *prog, int subprog)
!sub_aux->called[BPF_FUNC_CTX_NO_RCU_LOCK];
}
+/*
+ * The callers of a global function are checked against the arguments that its
+ * BTF describes, and an extension is verified with those of its own BTF. It
+ * must not expect more than the callers of the function provide.
+ *
+ * While an extension is loaded, bpf_check_attach_target() runs before its
+ * arguments are known. check_attach_btf_id() does the check when they are.
+ */
+static int check_ext_args(struct bpf_verifier_log *log, const struct bpf_prog *prog,
+ const struct bpf_prog *tgt_prog, int subprog)
+{
+ const struct bpf_subprog_arg_info *tgt = tgt_prog->aux->func_info_aux[subprog].args;
+ const struct bpf_subprog_arg_info *ext = prog->aux->func_info_aux[0].args;
+ int i;
+
+ for (i = 0; i < MAX_BPF_FUNC_REG_ARGS && ext[i].arg_type != ARG_UNUSED; i++) {
+ /* An extension may accept NULL where the callers never pass it. */
+ if ((ext[i].arg_type == tgt[i].arg_type ||
+ ext[i].arg_type == (tgt[i].arg_type | PTR_MAYBE_NULL)) &&
+ (base_type(ext[i].arg_type) == ARG_PTR_TO_MEM ?
+ ext[i].mem_size <= tgt[i].mem_size : ext[i].btf_id == tgt[i].btf_id))
+ continue;
+
+ bpf_log(log, "Extension program's argument in R%d is incompatible with original\n",
+ i + 1);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
int bpf_check_attach_target(struct bpf_verifier_log *log,
const struct bpf_prog *prog,
const struct bpf_prog *tgt_prog,
@@ -22797,7 +22845,8 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
return -EINVAL;
}
if (prog_extension &&
- btf_check_type_match(log, prog, btf, t))
+ (btf_check_type_match(log, prog, btf, t) ||
+ check_ext_args(log, prog, tgt_prog, subprog)))
return -EINVAL;
t = btf_attach_func_proto(log, btf, btf_id);
if (!t || !btf_type_is_func_proto(t))
@@ -23017,6 +23066,15 @@ static int check_attach_btf_id(struct bpf_verifier_env *env)
*/
env->ops = bpf_verifier_ops[tgt_prog->type];
prog->expected_attach_type = tgt_prog->expected_attach_type;
+
+ /* only now it is known what the arguments of prog stand for */
+ ret = btf_prepare_func_args(env, 0);
+ if (ret)
+ return ret;
+ ret = check_ext_args(&env->log, prog, tgt_prog,
+ find_tgt_subprog(tgt_prog, btf_id));
+ if (ret)
+ return ret;
}
/* store info about the attachment target that will be used later */
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);
diff --git a/tools/testing/selftests/bpf/prog_tests/arena_mmap.c b/tools/testing/selftests/bpf/prog_tests/arena_mmap.c
new file mode 100644
index 000000000000..2babc9ecf182
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/arena_mmap.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <sys/mman.h>
+
+#define ARENA_PAGES 8
+
+static void test_refused(int pages, int flags, int err)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, opts, .map_flags = BPF_F_MMAPABLE);
+ void *area;
+ int fd;
+
+ fd = bpf_map_create(BPF_MAP_TYPE_ARENA, "arena", 0, 0, ARENA_PAGES, &opts);
+ if (fd == -EOPNOTSUPP) {
+ test__skip();
+ return;
+ }
+ if (!ASSERT_GE(fd, 0, "bpf_map_create"))
+ return;
+
+ area = mmap(NULL, pages * getpagesize(), PROT_READ | PROT_WRITE,
+ MAP_SHARED | flags, fd, 0);
+ if (ASSERT_EQ(area, MAP_FAILED, "mmap"))
+ ASSERT_EQ(errno, err, "errno");
+ else
+ munmap(area, pages * getpagesize());
+ close(fd);
+}
+
+void test_arena_mmap(void)
+{
+ if (test__start_subtest("long"))
+ test_refused(ARENA_PAGES + 1, 0, E2BIG);
+ if (test__start_subtest("address_0"))
+ test_refused(ARENA_PAGES, MAP_FIXED, EINVAL);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/freplace_args.c b/tools/testing/selftests/bpf/prog_tests/freplace_args.c
new file mode 100644
index 000000000000..d1930af23fa0
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/freplace_args.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include "freplace_args.skel.h"
+#include "freplace_args_tgt.skel.h"
+
+/*
+ * An extension must not expect more of its arguments than the callers of the
+ * function that it replaces provide. The progs are loaded and attached to each
+ * other. None of them runs.
+ */
+static const struct test {
+ const char *name;
+ const char *ext; /* prog of freplace_args.c */
+ const char *tgt; /* prog of freplace_args_tgt.c */
+ const char *func; /* function of tgt that ext is loaded for */
+ const char *func2; /* function of tgt that ext is attached to */
+ int bad_reg; /* argument that is refused, 0: none */
+} tests[] = {
+ { "same_nonnull", "ext_mem_nonnull", "tc_prog", "mem_nonnull" },
+ { "same_trusted", "ext_task_trusted", "tc_prog", "task_trusted" },
+ { "smaller_struct", "ext_mem_smaller", "tc_prog", "mem_smaller" },
+ { "larger_struct", "ext_mem_larger", "tc_prog", "mem_larger", NULL, 1 },
+ { "nullable_for_nonnull", "ext_mem", "tc_prog", "mem_nonnull" },
+ { "nonnull_for_nullable", "ext_mem_nonnull", "tc_prog", "mem", NULL, 2 },
+ { "trusted_for_mem", "ext_task_trusted", "tc_prog", "task_mem", NULL, 1 },
+ { "arg_ctx_for_ctx_type", "ext_skb_ctx", "tc_prog", "skb_ctx" },
+ { "mem_for_arg_ctx", "ext_tp_mem", "tp_prog", "tagged_ctx", NULL, 1 },
+ { "attach_nullable_to_nonnull", "ext_mem", "tc_prog", "mem", "mem_nonnull" },
+ { "attach_nonnull_to_nullable", "ext_mem_nonnull", "tc_prog", "mem_nonnull", "mem", 2 },
+};
+
+static void run_test(struct freplace_args_tgt *tgt_skel, const struct test *t)
+{
+ struct bpf_program *ext, *tgt;
+ struct freplace_args *skel;
+ char log[4096], msg[128];
+ struct bpf_link *link;
+ int err;
+
+ skel = freplace_args__open();
+ if (!ASSERT_OK_PTR(skel, "open"))
+ return;
+
+ ext = bpf_object__find_program_by_name(skel->obj, t->ext);
+ tgt = bpf_object__find_program_by_name(tgt_skel->obj, t->tgt);
+ if (!ASSERT_OK_PTR(ext, "ext") || !ASSERT_OK_PTR(tgt, "tgt"))
+ goto out;
+
+ log[0] = '\0';
+ bpf_program__set_autoload(ext, true);
+ /* libbpf prints the log of a load that fails and must not */
+ if (t->bad_reg && !t->func2)
+ bpf_program__set_log_buf(ext, log, sizeof(log));
+ err = bpf_program__set_attach_target(ext, bpf_program__fd(tgt), t->func);
+ if (!ASSERT_OK(err, "set_attach_target"))
+ goto out;
+
+ err = freplace_args__load(skel);
+ if (t->bad_reg && !t->func2) {
+ snprintf(msg, sizeof(msg),
+ "Extension program's argument in R%d is incompatible with original",
+ t->bad_reg);
+ if (ASSERT_ERR(err, "load"))
+ ASSERT_HAS_SUBSTR(log, msg, "log");
+ goto out;
+ }
+ if (!ASSERT_OK(err, "load") || !t->func2)
+ goto out;
+
+ link = bpf_program__attach_freplace(ext, bpf_program__fd(tgt), t->func2);
+ ASSERT_EQ(libbpf_get_error(link), t->bad_reg ? -EINVAL : 0, "attach");
+ bpf_link__destroy(link);
+out:
+ freplace_args__destroy(skel);
+}
+
+void test_freplace_args(void)
+{
+ struct freplace_args_tgt *tgt_skel;
+ int i;
+
+ tgt_skel = freplace_args_tgt__open_and_load();
+ if (!ASSERT_OK_PTR(tgt_skel, "tgt_open_and_load"))
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ if (test__start_subtest(tests[i].name))
+ run_test(tgt_skel, &tests[i]);
+ }
+
+ freplace_args_tgt__destroy(tgt_skel);
+} \ No newline at end of file
diff --git a/tools/testing/selftests/bpf/progs/freplace_args.c b/tools/testing/selftests/bpf/progs/freplace_args.c
new file mode 100644
index 000000000000..6e8f16bd5d8e
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/freplace_args.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+/* In freplace_args_tgt.c each of them has 16 bytes. */
+struct buf { __u64 v[2]; };
+struct larger { __u64 v[4]; };
+struct smaller { __u64 v[1]; };
+struct tp_ctx { __u64 v[2]; };
+
+SEC("?freplace")
+int ext_mem(int a, struct buf *p)
+{
+ return 0;
+}
+
+SEC("?freplace")
+int ext_mem_nonnull(int a, struct buf *p __arg_nonnull)
+{
+ return 0;
+}
+
+SEC("?freplace")
+int ext_mem_larger(struct larger *p)
+{
+ return 0;
+}
+
+SEC("?freplace")
+int ext_mem_smaller(struct smaller *p)
+{
+ return 0;
+}
+
+SEC("?freplace")
+int ext_task_trusted(struct task_struct *t __arg_trusted)
+{
+ return 0;
+}
+
+SEC("?freplace")
+int ext_skb_ctx(struct __sk_buff *skb __arg_ctx)
+{
+ return 0;
+}
+
+SEC("?freplace")
+int ext_tp_mem(struct tp_ctx *p)
+{
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/freplace_args_tgt.c b/tools/testing/selftests/bpf/progs/freplace_args_tgt.c
new file mode 100644
index 000000000000..4f1d1d3d2891
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/freplace_args_tgt.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+/* In freplace_args.c 'larger' has 32 bytes and 'smaller' has 8 bytes. */
+struct buf { __u64 v[2]; };
+struct larger { __u64 v[2]; };
+struct smaller { __u64 v[2]; };
+struct tp_ctx { __u64 v[2]; };
+
+__weak int mem(int a, struct buf *p)
+{
+ return 0;
+}
+
+__weak int mem_nonnull(int a, struct buf *p __arg_nonnull)
+{
+ return 0;
+}
+
+__weak int mem_larger(struct larger *p)
+{
+ return 0;
+}
+
+__weak int mem_smaller(struct smaller *p)
+{
+ return 0;
+}
+
+/* No tag: memory of the size of the struct. */
+__weak int task_mem(struct task_struct *t)
+{
+ return 0;
+}
+
+__weak int task_trusted(struct task_struct *t __arg_trusted)
+{
+ return 0;
+}
+
+__weak int skb_ctx(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+SEC("tc")
+int tc_prog(struct __sk_buff *skb)
+{
+ struct smaller smaller = {};
+ struct larger larger = {};
+ struct buf buf = {};
+
+ return mem(0, &buf) + mem_nonnull(0, &buf) + mem_larger(&larger) + mem_smaller(&smaller) +
+ task_mem(NULL) + task_trusted(bpf_get_current_task_btf()) + skb_ctx(skb);
+}
+
+/* In a tracepoint prog the tag makes a context of any struct. */
+__weak int tagged_ctx(struct tp_ctx *ctx __arg_ctx)
+{
+ return 0;
+}
+
+SEC("tracepoint")
+int tp_prog(void *ctx)
+{
+ return tagged_ctx(ctx);
+}
+
+char _license[] SEC("license") = "GPL";