Re: [PATCH bpf-next v3 15/18] bpf: Classify kfunc pointer arguments from BTF, resolve type against the register
Amery Hung <[email protected]> Sun, 2 Aug 2026 14:17:01 -0700
| Newsgroups | dev.linux.lists.sashiko-reviews,org.kernel.vger.bpf |
|---|---|
| Message-ID | <CAMB2axMVAk70e8ktN6_kr_8Y6N1yV8gTfJ4JyhDooGwg46j19w@mail.gmail.com> |
On Sat, Aug 1, 2026 at 1:22=E2=80=AFAM <[email protected]> wrote: > > Thank you for your contribution! Sashiko AI review found 1 potential issu= e(s) to consider: > - [Medium] Allowing PTR_TO_BTF_ID as a KF_ARG_PTR_TO_MEM argument is eith= er broken or fundamentally unsafe due to unvalidated access flags. > -- > > --- Patch [15]: [PATCH bpf-next v3 15/18] bpf: Classify kfunc pointer arg= uments from BTF, resolve type against the register --- > commit d5adf1f80d7ac5827091492c5099bcb0c05e15d1 > Author: Amery Hung <[email protected]> > > bpf: Classify kfunc pointer arguments from BTF, resolve type against the = register > > This commit changes kfunc pointer argument classification to rely strictl= y > on BTF rather than register state. It introduces support for passing a > PTR_TO_BTF_ID as a fixed-size memory buffer when pointing to a scalar str= uct. > > > The new design now accepts one previously rejected case: passing > > PTR_TO_BTF_ID to a pointer to scalar w/o a following __sz/__szk. Th= e > > argument will be classified as KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE. = The > > PTR_TO_BTF_ID register will go through check_mem_reg() -> > > check_helper_mem_access() -> check_ptr_to_btf_access(), and by defa= ult only > > read is allowed. > > [Severity: Medium] > Will this newly accepted case always fail verification? > > While the commit message states that "by default only read is allowed," > the call to check_mem_reg() for KF_ARG_PTR_TO_MEM arguments > unconditionally demands BPF_READ | BPF_WRITE access. This causes > check_ptr_to_btf_access() to reject standard kernel pointers with -EACCES= . I see the problem in my wording. So it should be: For a pointer to scalar arg, a kernel btf id will be rejected unless explicitly granted by btf_struct_access(); a program allocated btf id will be allowed. > > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > > index 1ccf3b764c515..9045369ba5694 100644 > > --- a/kernel/bpf/verifier.c > > +++ b/kernel/bpf/verifier.c > > @@ -11405,37 +11414,37 @@ get_kfunc_ptr_arg_type(struct bpf_verifier_en= v *env, > [ ... ] > > - /* This is the catch all argument type of register types supporte= d by > > - * check_helper_mem_access. However, we only allow when argument = type is > > - * pointer to scalar, or struct composed (recursively) of scalars= . When > > - * arg_mem_size is true, the pointer can be void *. > > + /* A pointer to a struct without a size argument is classified as= KF_ARG_PTR_TO_BTF_ID */ > > + if (btf_type_is_struct(ref_t)) > > + return KF_ARG_PTR_TO_BTF_ID; > > + > > + /* > > + * Otherwise this is a fixed-size memory buffer supported by > > + * check_helper_mem_access(): a pointer to a scalar or a struct o= f > > + * scalars. The access size is derived from the pointed-to BTF ty= pe. > > */ > > - if (!btf_type_is_scalar(ref_t) && !__btf_type_is_scalar_struct(en= v, meta->btf, ref_t, 0) && > > - (arg_mem_size ? !btf_type_is_void(ref_t) : 1)) { > > - verbose(env, "%s pointer type %s %s must point to %sscala= r, or struct with scalar\n", > > - reg_arg_name(env, argno), > > - btf_type_str(ref_t), ref_tname, arg_mem_size ? "v= oid, " : ""); > > + if (!btf_type_is_scalar(ref_t) && > > + !__btf_type_is_scalar_struct(env, meta->btf, ref_t, 0)) { > > + verbose(env, "%s pointer type %s %s must point to scalar,= or struct with scalar\n", > > + reg_arg_name(env, argno), btf_type_str(ref_t), re= f_tname); > > return -EINVAL; > > } > > - return arg_mem_size ? KF_ARG_PTR_TO_MEM : KF_ARG_PTR_TO_MEM | MEM= _FIXED_SIZE; > > + return KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE; > > } > > [Severity: Medium] > When get_kfunc_ptr_arg_type() returns KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE = for > a PTR_TO_BTF_ID, or when check_kfunc_args() falls through to KF_ARG_PTR_T= O_MEM, > it verifies the argument as a memory buffer: > > kernel/bpf/verifier.c:check_kfunc_args() { > ... > case KF_ARG_PTR_TO_MEM: > if (kf_arg_type & MEM_FIXED_SIZE) { > resolve_ret =3D btf_resolve_size(btf, ref= _t, &type_size); > ... > ret =3D check_mem_reg(env, reg, argno, ty= pe_size, BPF_READ | BPF_WRITE, meta); > ... > } > > Because check_mem_reg() is passed BPF_READ | BPF_WRITE, it requires write > access. When this goes through check_helper_mem_access() and reaches > check_ptr_to_btf_access(), it will be rejected because atype !=3D BPF_REA= D: > > kernel/bpf/verifier.c:check_ptr_to_btf_access() { > ... > if (atype !=3D BPF_READ && !type_is_ptr_alloc_obj(reg->ty= pe)) { > verbose(env, "only read is supported\n"); > return -EACCES; > } > ... > } > > Could this unconditionally break the newly introduced feature for standar= d > (non-allocated) kernel pointers? Also, if the BPF_READ | BPF_WRITE flag w= ere > simply changed to BPF_READ to pass verification, could it introduce a > vulnerability since the verifier cannot guarantee that the underlying kfu= nc > will not write to the buffer? > > -- > Sashiko AI review =C2=B7 https://sashiko.dev/#/patchset/20260801074633.15= [email protected]?part=3D15