On Mon, Oct 5, 2026 at 8:23 AM Yiyang Chen
<[email protected]> wrote:
>
> A global subprogram parameter tagged __arg_trusted is documented to
> accept only the PTR_TRUSTED flavor of PTR_TO_BTF_ID, but the call-site
> check also accepts a bare PTR_TO_BTF_ID and an MEM_RCU one.
>
> check_reg_type() resolves the accepted set from the base argument type
> alone and compares only MEM_RDONLY and PTR_MAYBE_NULL, so the PTR_TRUSTED
> bit of arg_type is never consulted. The trusted/RCU enforcement in
> check_func_arg() is gated on is_kfunc(meta), which is false for a
> subprogram call, so that block is skipped for __arg_trusted arguments.
>
> The callee is then validated with PTR_TRUSTED set on the register while
> the caller passed a pointer that is neither referenced nor trusted.
> bpf_may_fault_on_deref() is false for PTR_TRUSTED, so the dereference
> becomes a raw load instead of a BPF_PROBE_MEM probe, is_trusted_reg()
> kfuncs accept the pointer, and the callee can pass it on to a kfunc that
> would have rejected it at the original call site.
>
> Reject a PTR_TO_BTF_ID that is neither referenced nor trusted when the
> argument is marked PTR_TRUSTED. A referenced register is accepted, as in
> is_trusted_reg(). PTR_MAYBE_NULL is not counted as an unsafe modifier
> when __arg_nullable declares it, so trusted-and-nullable arguments keep
> working. The kfunc path is unchanged.
>
> Fixes: e2b3c4ff5d183da6d1863c2321413406a2752e7a ("bpf: add __arg_trusted 
> global func arg tag")
> Signed-off-by: Yiyang Chen <[email protected]>
> ---
>  kernel/bpf/verifier.c | 21 +++++++++++++++++++++
>  1 file changed, 21 insertions(+)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index fd3c0206bd67d..fe5edbef85a16 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -9597,6 +9597,27 @@ static int check_func_arg(struct bpf_verifier_env 
> *env, u32 arg, u32 slot, u32 p
>                         }
>                 }
>
> +               /* A __arg_trusted argument requires a referenced or trusted
> +                * pointer. btf_ptr_types also matches a bare PTR_TO_BTF_ID 
> and
> +                * an MEM_RCU one, but neither is referenced or trusted, so 
> the
> +                * callee would be verified with PTR_TRUSTED while the caller
> +                * passed something that is not. PTR_MAYBE_NULL is not counted
> +                * as unsafe when __arg_nullable declares it, because
> +                * bpf_type_has_unsafe_modifiers() treats that flag as unsafe.
> +                */
> +               if ((arg_type & PTR_TRUSTED) && base_type(reg->type) == 
> PTR_TO_BTF_ID &&
> +                   !reg_is_referenced(env, reg)) {
> +                       u32 flags = type_flag(reg->type);
> +
> +                       if (!(flags & BPF_REG_TRUSTED_MODIFIERS) ||
> +                           (flags & ~(BPF_REG_TRUSTED_MODIFIERS |
> +                                      (arg_type & PTR_MAYBE_NULL)))) {
> +                               verbose(env, "%s must be referenced or 
> trusted\n",
> +                                       reg_arg_name(env, argno));
> +                               return -EINVAL;
> +                       }
> +               }
> +

Could we avoid adding a second provenance check and make the existing
kfunc check contract-driven instead?

Kfunc ARG_PTR_TO_BTF_ID arguments implicitly require trusted or
referenced provenance through the is_kfunc(meta) condition. Global
subprogs express the same requirement explicitly with PTR_TRUSTED.
Likewise, __nullable and __arg_nullable permit PTR_MAYBE_NULL,
which should not by itself make the provenance invalid.

First, encode the kfunc requirement in its generated prototype:

if (base_type(arg_type) == ARG_PTR_TO_BTF_ID) {
        arg_type |= PTR_TRUSTED;

        /* MEM_RCU denotes an accepted alternative provenance. */
        if (is_kfunc_rcu(meta))
                arg_type |= MEM_RCU;
}

The common check can then be driven entirely by the argument contract:

reg_type = reg->type & ~(arg_type & PTR_MAYBE_NULL);

if ((arg_type & PTR_TRUSTED) &&
    (!is_trusted_reg_type(env, reg, reg_type) ||
     bpf_type_has_unsafe_modifiers(reg_type))) {
        if (!(arg_type & MEM_RCU)) {
                /* must be referenced or trusted */
                return -EINVAL;
        }
        if (!is_rcu_reg(reg)) {
                /* must be an RCU pointer */
                return -EINVAL;
        }
}

is_trusted_reg_type() would contain the existing
is_trusted_reg() logic, but use the supplied normalized type for its
type and modifier checks while still using the original register ID for
reference lookup.

resolve_func_arg_type() should continue dropping PTR_TRUSTED when
it changes ARG_PTR_TO_BTF_ID into ARG_PTR_TO_MEM; that is a
different, fixed-size memory-buffer contract.

This also lets a kfunc __nullable argument accept
PTR_TRUSTED | PTR_MAYBE_NULL, as its existing contract specifies.
Please add corresponding kfunc coverage and adjust the claim that the
kfunc path is unchanged.

>                 if (is_kfunc(meta) && (!is_trusted_reg(env, reg) ||
>                                        
> bpf_type_has_unsafe_modifiers(reg->type))) {
>                         if (!(arg_type & MEM_RCU)) {
>
> --
> 2.43.0
>
>

Reply via email to