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