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>
This commit is contained in:
Alexei Starovoitov 2026-11-02 00:10:40 +00:00
commit 714b711201
No known key found for this signature in database
16 changed files with 564 additions and 63 deletions

View File

@ -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 loading of the BPF program until no lingering references remain in all possible
explored states of the program. 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 2.5.2 KF_RET_NULL flag
---------------------- ----------------------

View File

@ -1752,12 +1752,22 @@ enum bpf_func_context {
BPF_FUNC_CTX_CNT, 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 { struct bpf_func_info_aux {
u16 linkage; u16 linkage;
bool unreliable; bool unreliable;
bool called[BPF_FUNC_CTX_CNT]; bool called[BPF_FUNC_CTX_CNT];
bool verified[BPF_FUNC_CTX_CNT]; bool verified[BPF_FUNC_CTX_CNT];
bool called_with_pkt_ptrs; 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 { enum bpf_jit_poke_reason {

View File

@ -923,14 +923,6 @@ struct bpf_func_ptr {
/* the subprog that a bpf_func_ptr pointed to was removed as dead code */ /* the subprog that a bpf_func_ptr pointed to was removed as dead code */
#define BPF_FUNC_PTR_DELETED ((u32)-1) #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 { enum priv_stack_mode {
PRIV_STACK_UNKNOWN, PRIV_STACK_UNKNOWN,
NO_PRIV_STACK, NO_PRIV_STACK,

View File

@ -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, int register_btf_id_dtor_kfuncs(const struct btf_id_dtor_kfunc *dtors, u32 add_cnt,
struct module *owner); struct module *owner);
struct btf_struct_meta *btf_find_struct_meta(const struct btf *btf, u32 btf_id); 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_projection_of(const char *pname, const char *tname);
bool btf_is_prog_ctx_type(struct bpf_verifier_log *log, const struct btf *btf, 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, const struct btf_type *t, enum bpf_prog_type prog_type,

View File

@ -634,7 +634,7 @@ static unsigned long arena_get_unmapped_area(struct file *filp, unsigned long ad
if (pgoff) if (pgoff)
return -EINVAL; return -EINVAL;
if (len > SZ_4G) if (len > (u64)map->max_entries * PAGE_SIZE)
return -E2BIG; return -E2BIG;
/* if user_vm_start was specified at arena creation time */ /* 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); ret = mm_get_unmapped_area(filp, addr, len * 2, 0, flags);
if (IS_ERR_VALUE(ret)) if (IS_ERR_VALUE(ret))
return 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)) if ((ret >> 32) == ((ret + len - 1) >> 32))
return ret; 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); 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; return -EBUSY;
/* Earlier checks should prevent this */ /* 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; return -EFAULT;
if (remember_vma(arena, vma)) 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) 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; struct mem_cgroup *new_memcg, *old_memcg;
u64 full_uaddr, uaddr_end; u64 full_uaddr, uaddr_end;
struct arena_free_span *s; 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 */ /* only aligned lower 32-bit are relevant */
uaddr = (u32)uaddr; uaddr = (u32)uaddr;
uaddr &= PAGE_MASK; uaddr &= PAGE_MASK;
full_uaddr = clear_lo32(arena->user_vm_start) + uaddr; full_uaddr = clear_lo32(user_vm_start) + uaddr;
if (full_uaddr < arena->user_vm_start) if (!user_vm_start || full_uaddr < user_vm_start)
return; return;
uaddr_end = min(arena->user_vm_end, full_uaddr + (page_cnt << PAGE_SHIFT)); uaddr_end = min(arena->user_vm_end, full_uaddr + (page_cnt << PAGE_SHIFT));
if (full_uaddr >= uaddr_end) if (full_uaddr >= uaddr_end)

View File

@ -6590,6 +6590,15 @@ static int find_kern_ctx_type_id(enum bpf_prog_type prog_type)
return ctx_type->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) bool btf_is_projection_of(const char *pname, const char *tname)
{ {
if (strcmp(pname, "__sk_buff") == 0 && strcmp(tname, "sk_buff") == 0) 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. /* This is an optional check to make program writing easier.
* Compare names of structs and report an error to the user. * Compare names of structs and report an error to the user.
* btf_prepare_func_args() already checked that t2 struct * check_ext_args() compares what btf_prepare_func_args()
* is a context type. btf_prepare_func_args() will check * derives for the two arguments.
* later that t1 struct is a context type as well.
*/ */
s1 = btf_name_by_offset(btf1, t1->name_off); s1 = btf_name_by_offset(btf1, t1->name_off);
s2 = btf_name_by_offset(btf2, t2->name_off); s2 = btf_name_by_offset(btf2, t2->name_off);
@ -9019,6 +9027,11 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
return err; return err;
sub->arg_slot_cnt = slots_used; 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; sub->args_cached = true;
return 0; return 0;

View File

@ -1279,9 +1279,14 @@ static struct bpf_prog_list *find_detach_entry(struct hlist_head *progs,
return ERR_PTR(-ENOENT); return ERR_PTR(-ENOENT);
/* to maintain backward compatibility NONE and OVERRIDE cgroups /* 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) if (!prog && !link)
@ -1785,7 +1790,7 @@ int __cgroup_bpf_run_filter_skb(struct sock *sk,
struct sk_buff *skb, struct sk_buff *skb,
enum cgroup_bpf_attach_type atype) enum cgroup_bpf_attach_type atype)
{ {
unsigned int offset = -skb_network_offset(skb); int offset = skb_network_offset(skb);
struct sock *save_sk; struct sock *save_sk;
void *saved_data_end; void *saved_data_end;
struct cgroup *cgrp; struct cgroup *cgrp;
@ -1797,7 +1802,15 @@ int __cgroup_bpf_run_filter_skb(struct sock *sk,
cgrp = sock_cgroup_ptr(&sk->sk_cgrp_data); cgrp = sock_cgroup_ptr(&sk->sk_cgrp_data);
save_sk = skb->sk; save_sk = skb->sk;
skb->sk = 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 */ /* compute pointers for the bpf prog */
bpf_compute_and_save_data_end(skb, &saved_data_end); 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; ret = -EFAULT;
} }
bpf_restore_data_end(skb, saved_data_end); 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; skb->sk = save_sk;
return ret; return ret;

View File

@ -522,12 +522,14 @@ static int stackid_init(struct stackid *stackid, struct bpf_map *map,
u32 skip = flags & BPF_F_SKIP_FIELD_MASK; u32 skip = flags & BPF_F_SKIP_FIELD_MASK;
u32 max_depth; 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) if (trace_nr <= skip)
/* skipping more than usable stack trace */ /* skipping more than usable stack trace */
return -EFAULT; return -EFAULT;
max_depth = stack_map_calculate_max_depth(map->value_size, stack_map_data_size(map), flags); stackid->nr = trace_nr - skip;
stackid->nr = min_t(u32, trace_nr - skip, max_depth - skip);
stackid->len = stackid->nr * sizeof(u64); stackid->len = stackid->nr * sizeof(u64);
stackid->ips = trace->ip + skip; stackid->ips = trace->ip + skip;
stackid->hash = jhash2((const u32 *)stackid->ips, stackid->len / sizeof(u32), 0); 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 */ /* couldn't fetch the stack trace */
return -EFAULT; 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) if (err != -ENOENT)
return err; return err;
@ -822,14 +825,20 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task,
crosstask, false, 0); crosstask, false, 0);
} }
if (unlikely(!trace) || trace->nr < skip) { if (unlikely(!trace))
if (may_fault) goto err_unlock;
rcu_read_unlock();
preempt_enable();
goto err_fault;
}
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 */ /* trace should not be dereferenced after this point */
if (may_fault) 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); 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_fault:
err = -EFAULT; err = -EFAULT;
clear: clear:

View File

@ -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) if (base_type(*arg_type) != ARG_PTR_TO_BTF_ID)
return 0; 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) || if (!is_kfunc(meta) || arg_type_is_release(*arg_type) ||
is_kfunc_acquire(meta) ||
base_type(reg->type) == PTR_TO_BTF_ID || base_type(reg->type) == PTR_TO_BTF_ID ||
reg2btf_ids[base_type(reg->type)]) reg2btf_ids[base_type(reg->type)])
return 0; 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_t = btf_type_skip_modifiers(btf, ref_id, &ref_id);
ref_tname = btf_name_by_offset(btf, ref_t->name_off); 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; return 0;
resolve_ret = btf_resolve_size(btf, ref_t, arg_size); 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 /* 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 * 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) { if (!taking_projection && !struct_same) {
verbose(env, "%s %s expected pointer to %s %s but %s has a pointer to %s %s\n", 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), 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]; !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, int bpf_check_attach_target(struct bpf_verifier_log *log,
const struct bpf_prog *prog, const struct bpf_prog *prog,
const struct bpf_prog *tgt_prog, const struct bpf_prog *tgt_prog,
@ -22797,7 +22845,8 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
return -EINVAL; return -EINVAL;
} }
if (prog_extension && 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; return -EINVAL;
t = btf_attach_func_proto(log, btf, btf_id); t = btf_attach_func_proto(log, btf, btf_id);
if (!t || !btf_type_is_func_proto(t)) 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]; env->ops = bpf_verifier_ops[tgt_prog->type];
prog->expected_attach_type = tgt_prog->expected_attach_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 */ /* store info about the attachment target that will be used later */

View File

@ -1210,7 +1210,8 @@ int bpf_prog_test_run_skb(struct bpf_prog *prog, const union bpf_attr *kattr,
skb->ip_summed = CHECKSUM_COMPLETE; 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()) { if (!ipv6_mod_enabled()) {
pr_warn_once("Please test this program with IPv6 enabled kernel\n"); pr_warn_once("Please test this program with IPv6 enabled kernel\n");
ret = -EOPNOTSUPP; ret = -EOPNOTSUPP;

View File

@ -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) 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, /* Caller already did skb_cow() with meta_len+len as headroom,
* so no need to do it here. * 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; void *old_data;
/* skb_ensure_writable() is not needed here, as we're /* 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; return -ENOMEM;
old_data = skb->data; old_data = skb->data;
@ -3402,9 +3407,25 @@ static int bpf_skb_generic_pop(struct sk_buff *skb, u32 off, u32 len)
return 0; 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) 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; int ret;
/* There's no need for __skb_push()/__skb_pull() pair to /* 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)) { if (likely(!ret)) {
skb->mac_header -= len; skb->mac_header -= len;
skb->network_header -= len; skb->network_header -= len;
if (trans_same) if (trans_moves)
skb->transport_header = skb->network_header; skb->transport_header -= len;
} }
return ret; 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) 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; int ret;
/* Same here, __skb_push()/__skb_pull() pair not needed. */ /* 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)) { if (likely(!ret)) {
skb->mac_header += len; skb->mac_header += len;
skb->network_header += len; skb->network_header += len;
if (trans_same) if (trans_moves) {
skb->transport_header = skb->network_header; 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; return ret;
@ -3744,6 +3774,25 @@ static int bpf_skb_net_grow(struct sk_buff *skb, u32 off, u32 len_diff,
return 0; 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, static int bpf_skb_net_shrink(struct sk_buff *skb, u32 off, u32 len_diff,
u64 flags) u64 flags)
{ {
@ -3762,6 +3811,14 @@ static int bpf_skb_net_shrink(struct sk_buff *skb, u32 off, u32 len_diff,
return -ENOTSUPP; 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); ret = skb_unclone(skb, GFP_ATOMIC);
if (unlikely(ret < 0)) if (unlikely(ret < 0))
return ret; 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_IPXIP4 |
SKB_GSO_IPXIP6 | SKB_GSO_IPXIP6 |
SKB_GSO_ESP))) SKB_GSO_ESP)))
if (skb->encapsulation) bpf_skb_clear_encap(skb, off);
skb->encapsulation = 0;
} }
/* Header must be checked, and gso_segs recomputed. */ /* 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; shinfo->gso_segs = 0;
} else { } else {
/* For non-GSO packets, clear encapsulation if decap flags are set */ /* For non-GSO packets, clear encapsulation if decap flags are set */
if ((flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK | if (flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK |
BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK)) && BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK))
skb->encapsulation) bpf_skb_clear_encap(skb, off);
skb->encapsulation = 0;
} }
return 0; return 0;
@ -6916,14 +6971,22 @@ static int bpf_push_seg6_encap(struct sk_buff *skb, u32 type, void *hdr, u32 len
int err; int err;
struct ipv6_sr_hdr *srh = (struct ipv6_sr_hdr *)hdr; 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)) if (!seg6_validate_srh(srh, len, false))
return -EINVAL; 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) { switch (type) {
case BPF_LWT_ENCAP_SEG6_INLINE: case BPF_LWT_ENCAP_SEG6_INLINE:
if (skb->protocol != htons(ETH_P_IPV6))
return -EBADMSG;
err = seg6_do_srh_inline(skb, srh); err = seg6_do_srh_inline(skb, srh);
break; break;
case BPF_LWT_ENCAP_SEG6: 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); this_cpu_ptr(&seg6_bpf_srh_states);
struct ipv6_sr_hdr *srh = srh_state->srh; struct ipv6_sr_hdr *srh = srh_state->srh;
void *srh_tlvs, *srh_end, *ptr; void *srh_tlvs, *srh_end, *ptr;
int srhoff = 0; int srhoff;
lockdep_assert_held(&srh_state->bh_lock); lockdep_assert_held(&srh_state->bh_lock);
if (srh == NULL) if (srh == NULL)
return -EINVAL; return -EINVAL;
srhoff = (unsigned char *)srh - skb->data;
srh_tlvs = (void *)((char *)srh + ((srh->first_segment + 1) << 4)); srh_tlvs = (void *)((char *)srh + ((srh->first_segment + 1) << 4));
srh_end = (void *)((char *)srh + sizeof(*srh) + srh_state->hdrlen); 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))) if (unlikely(bpf_try_make_writable(skb, offset + len)))
return -EFAULT; return -EFAULT;
if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) /* The head may have moved. */
return -EINVAL;
srh_state->srh = (struct ipv6_sr_hdr *)(skb->data + srhoff); srh_state->srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
memcpy(skb->data + offset, from, len); 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 = struct seg6_bpf_srh_state *srh_state =
this_cpu_ptr(&seg6_bpf_srh_states); this_cpu_ptr(&seg6_bpf_srh_states);
bool mac_set = skb_mac_header_was_set(skb);
struct ipv6_sr_hdr *srh = srh_state->srh; struct ipv6_sr_hdr *srh = srh_state->srh;
void *srh_end, *srh_tlvs, *ptr; void *srh_end, *srh_tlvs, *ptr;
struct ipv6hdr *hdr; struct ipv6hdr *hdr;
int srhoff = 0; int srhoff;
int ret; int ret;
lockdep_assert_held(&srh_state->bh_lock); lockdep_assert_held(&srh_state->bh_lock);
if (unlikely(srh == NULL)) if (unlikely(srh == NULL))
return -EINVAL; return -EINVAL;
srhoff = (unsigned char *)srh - skb->data;
srh_tlvs = (void *)((unsigned char *)srh + sizeof(*srh) + srh_tlvs = (void *)((unsigned char *)srh + sizeof(*srh) +
((srh->first_segment + 1) << 4)); ((srh->first_segment + 1) << 4));
srh_end = (void *)((unsigned char *)srh + sizeof(*srh) + 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; return -EFAULT;
if (unlikely(len < 0 && (void *)((char *)ptr - len) > srh_end)) if (unlikely(len < 0 && (void *)((char *)ptr - len) > srh_end))
return -EFAULT; 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) { if (len > 0) {
ret = skb_cow_head(skb, len); ret = skb_cow_head(skb, len + skb->mac_len);
if (unlikely(ret < 0)) if (unlikely(ret < 0))
return ret; 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); ret = bpf_skb_net_hdr_push(skb, offset, len);
} else { } else {
ret = bpf_skb_net_hdr_pop(skb, offset, -1 * len); 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); bpf_compute_data_pointers(skb);
if (unlikely(ret < 0)) if (unlikely(ret < 0))
return ret; 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 = (struct ipv6hdr *)skb->data;
hdr->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); hdr->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) /* The bytes in front of offset have moved together with skb->data. */
return -EINVAL;
srh_state->srh = (struct ipv6_sr_hdr *)(skb->data + srhoff); srh_state->srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
srh_state->hdrlen += len; srh_state->hdrlen += len;
srh_state->valid = false; srh_state->valid = false;

View File

@ -85,6 +85,32 @@ static int run_lwt_bpf(struct sk_buff *skb, struct bpf_lwt_prog *lwt,
return ret; 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) static int bpf_lwt_input_reroute(struct sk_buff *skb)
{ {
enum skb_drop_reason reason; enum skb_drop_reason reason;
@ -102,7 +128,7 @@ static int bpf_lwt_input_reroute(struct sk_buff *skb)
dev_put(dev); dev_put(dev);
} else if (skb->protocol == htons(ETH_P_IPV6)) { } else if (skb->protocol == htons(ETH_P_IPV6)) {
skb_dst_drop(skb); skb_dst_drop(skb);
if (IS_ENABLED(CONFIG_IPV6)) { if (ipv6_mod_enabled()) {
ip6_route_input(skb); ip6_route_input(skb);
err = skb_dst(skb)->error; err = skb_dst(skb)->error;
} else { } else {
@ -124,6 +150,8 @@ static int bpf_lwt_input_reroute(struct sk_buff *skb)
static int bpf_input(struct sk_buff *skb) static int bpf_input(struct sk_buff *skb)
{ {
struct dst_entry *dst = skb_dst(skb); struct dst_entry *dst = skb_dst(skb);
__be16 proto = skb->protocol;
unsigned int len = skb->len;
struct bpf_lwt *bpf; struct bpf_lwt *bpf;
int ret; int ret;
@ -132,8 +160,21 @@ static int bpf_input(struct sk_buff *skb)
ret = run_lwt_bpf(skb, &bpf->in, dst, NO_REDIRECT); ret = run_lwt_bpf(skb, &bpf->in, dst, NO_REDIRECT);
if (ret < 0) if (ret < 0)
return ret; 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) if (ret == BPF_LWT_REROUTE)
return bpf_lwt_input_reroute(skb); 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)) { if (unlikely(!dst->lwtstate->orig_input)) {
@ -311,6 +352,8 @@ static int bpf_xmit(struct sk_buff *skb)
case BPF_REDIRECT: case BPF_REDIRECT:
return LWTUNNEL_XMIT_DONE; return LWTUNNEL_XMIT_DONE;
case BPF_LWT_REROUTE: 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); return bpf_lwt_xmit_reroute(skb);
default: default:
return ret; return ret;
@ -649,6 +692,14 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress)
if (ingress) if (ingress)
skb_postpush_rcsum(skb, iph, len); skb_postpush_rcsum(skb, iph, len);
skb_reset_network_header(skb); 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) { if (is_udp_tunnel) {
size_t iph_sz = ipv4 ? iph->ihl * 4 : sizeof(struct ipv6hdr); size_t iph_sz = ipv4 ? iph->ihl * 4 : sizeof(struct ipv6hdr);

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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";

View File

@ -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";