Re: [PATCH bpf-next v3 1/2] bpf: Reject MEM_ALLOC BTF accesses past object bounds
From: Amery Hung
Date: Mon Jul 06 2026 - 17:43:51 EST
On Tue, Jun 30, 2026 at 1:45 AM Yiyang Chen
<chenyy23@xxxxxxxxxxxxxxxxxxxxx> wrote:
>
> BTF struct walks relax the struct-size check for accesses through a
> trailing flexible array. That is valid for ordinary BTF type walking, but
> PTR_TO_BTF_ID | MEM_ALLOC values point to objects allocated with the static
> BTF type size.
>
> When walking a MEM_ALLOC object, reject the access before applying the
> flexible-array relaxation if the access range extends past the struct size.
> Apply the same policy to struct ID matching so kfunc and kptr type checks
> do not walk past the allocated object bounds either.
>
> Fixes: 958cf2e273f0 ("bpf: Introduce bpf_obj_new")
> Fixes: 36d8bdf75a93 ("bpf: Add alloc/xchg/direct_access support for local percpu kptr")
> Signed-off-by: Yiyang Chen <chenyy23@xxxxxxxxxxxxxxxxxxxxx>
Reviewed-by: Amery Hung <ameryhung@xxxxxxxxx>
> ---
> include/linux/bpf.h | 2 +-
> kernel/bpf/btf.c | 17 +++++++++++------
> kernel/bpf/verifier.c | 11 +++++++----
> 3 files changed, 19 insertions(+), 11 deletions(-)
>
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 7719f6528..314d606dc 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -3146,7 +3146,7 @@ int btf_struct_access(struct bpf_verifier_log *log,
> bool btf_struct_ids_match(struct bpf_verifier_log *log,
> const struct btf *btf, u32 id, int off,
> const struct btf *need_btf, u32 need_type_id,
> - bool strict);
> + bool strict, bool walk_flex_arrays);
>
> int btf_distill_func_proto(struct bpf_verifier_log *log,
> struct btf *btf,
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index 64572f85e..dff5c0d91 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -7108,7 +7108,7 @@ enum bpf_struct_walk_result {
> static int btf_struct_walk(struct bpf_verifier_log *log, const struct btf *btf,
> const struct btf_type *t, int off, int size,
> u32 *next_btf_id, enum bpf_type_flag *flag,
> - const char **field_name)
> + const char **field_name, bool walk_flex_arrays)
> {
> u32 i, moff, mtrue_end, msize = 0, total_nelems = 0;
> const struct btf_type *mtype, *elem_type = NULL;
> @@ -7135,11 +7135,14 @@ static int btf_struct_walk(struct bpf_verifier_log *log, const struct btf *btf,
> *flag |= PTR_UNTRUSTED;
>
> if (off + size > t->size) {
> + struct btf_array *array_elem;
> +
> + if (!walk_flex_arrays)
> + goto error;
> +
> /* If the last element is a variable size array, we may
> * need to relax the rule.
> */
> - struct btf_array *array_elem;
> -
> if (vlen == 0)
> goto error;
>
> @@ -7404,7 +7407,8 @@ int btf_struct_access(struct bpf_verifier_log *log,
>
> t = btf_type_by_id(btf, id);
> do {
> - err = btf_struct_walk(log, btf, t, off, size, &id, &tmp_flag, field_name);
> + err = btf_struct_walk(log, btf, t, off, size, &id, &tmp_flag,
> + field_name, !type_is_alloc(reg->type));
>
> switch (err) {
> case WALK_PTR:
> @@ -7463,7 +7467,7 @@ bool btf_types_are_same(const struct btf *btf1, u32 id1,
> bool btf_struct_ids_match(struct bpf_verifier_log *log,
> const struct btf *btf, u32 id, int off,
> const struct btf *need_btf, u32 need_type_id,
> - bool strict)
> + bool strict, bool walk_flex_arrays)
> {
> const struct btf_type *type;
> enum bpf_type_flag flag = 0;
> @@ -7482,7 +7486,8 @@ bool btf_struct_ids_match(struct bpf_verifier_log *log,
> type = btf_type_by_id(btf, id);
> if (!type)
> return false;
> - err = btf_struct_walk(log, btf, type, off, 1, &id, &flag, NULL);
> + err = btf_struct_walk(log, btf, type, off, 1, &id, &flag, NULL,
> + walk_flex_arrays);
> if (err != WALK_STRUCT)
> return false;
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 25aea4271..4d3bb7156 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -4379,7 +4379,8 @@ static int map_kptr_match_type(struct bpf_verifier_env *env,
> */
> if (!btf_struct_ids_match(&env->log, reg->btf, reg->btf_id, reg->var_off.value,
> kptr_field->kptr.btf, kptr_field->kptr.btf_id,
> - kptr_field->type != BPF_KPTR_UNREF))
> + kptr_field->type != BPF_KPTR_UNREF,
> + !type_is_alloc(reg->type)))
> goto bad_type;
> return 0;
> bad_type:
> @@ -7970,7 +7971,7 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
>
> if (!btf_struct_ids_match(&env->log, reg->btf, reg->btf_id,
> reg->var_off.value, btf_vmlinux, *arg_btf_id,
> - strict_type_match)) {
> + strict_type_match, !type_is_alloc(reg->type))) {
> verbose(env, "%s is of type %s but %s is expected\n",
> reg_arg_name(env, argno),
> btf_type_name(reg->btf, reg->btf_id),
> @@ -11429,7 +11430,8 @@ static int process_kf_arg_ptr_to_btf_id(struct bpf_verifier_env *env,
> reg_ref_t = btf_type_skip_modifiers(reg_btf, reg_ref_id, ®_ref_id);
> reg_ref_tname = btf_name_by_offset(reg_btf, reg_ref_t->name_off);
> struct_same = btf_struct_ids_match(&env->log, reg_btf, reg_ref_id, reg->var_off.value,
> - meta->btf, ref_id, strict_type_match);
> + meta->btf, ref_id, strict_type_match,
> + !type_is_alloc(reg->type));
> /* 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.
> @@ -11876,7 +11878,8 @@ __process_kf_arg_ptr_to_graph_node(struct bpf_verifier_env *env,
> et = btf_type_by_id(field->graph_root.btf, field->graph_root.value_btf_id);
> t = btf_type_by_id(reg->btf, reg->btf_id);
> if (!btf_struct_ids_match(&env->log, reg->btf, reg->btf_id, 0, field->graph_root.btf,
> - field->graph_root.value_btf_id, true)) {
> + field->graph_root.value_btf_id, true,
> + !type_is_alloc(reg->type))) {
> verbose(env, "operation on %s expects arg#1 %s at offset=%d "
> "in struct %s, but arg is at offset=%d in struct %s\n",
> btf_field_type_name(head_field_type),
> --
> 2.34.1
>
>