Re: [PATCH bpf-next v5 4/7] bpf: Allow reads through trusted-or-null BTF pointers

From: Kumar Kartikeya Dwivedi

Date: Tue Sep 08 2026 - 06:41:56 EST


On Mon Sep 7, 2026 at 6:52 PM CEST, Anastasios Papagiannis wrote:
> Currently, a trusted-or-null pointer
> (i.e. PTR_TO_BTF_ID|PTR_TRUSTED|PTR_MAYBE_NULL) has to be checked
> for NULL before it can be dereferenced. Marking a field from
> PTR_TO_BTF_ID typing to trusted-or-null can reject programs that
> previously dereferenced the pointer directly. This is useful as we
> need to mark new fields as trusted in order to pass those as arguments
> to kfuncs.
>
> Allow reads through pointers marked as
> PTR_TO_BTF_ID|PTR_TRUSTED|PTR_MAYBE_NULL without an explicit NULL
> check. Treat these pointers as potentially faulting so the reads happen
> through BPF_PROBE_MEM. If a read produces another BTF pointer, clear its
> trusted flags and mark it as PTR_UNTRUSTED.
>
> This applies only to reads. Other cases still require an explicit NULL
> check. After such a check, the pointer retains PTR_TRUSTED and can be
> used normally.
>
> The unchecked read path has two consequences:
>
> 1. It uses BPF_PROBE_MEM, which is slower than a normal load. An
> explicit NULL check refines the pointer to PTR_TRUSTED and allows a
> normal load.
>
> 2. A faulting read returns zero, which is indistinguishable from a
> legitimately zero-valued field. Programs that need to distinguish
> those cases must check the pointer before reading the field.
>
> The next patch updates current tests and also introduces more checks to
> ensure this change does not break anything.

We tried doing this before in
https://lore.kernel.org/bpf/20241104171959.2938862-2-memxor@xxxxxxxxx and it
got reverted, it broke all sorts of things and made everything more complex.

I would drop this hack and just fix the program. Given your earlier change to
annotate the field correctly, I am puzzled why you added this, and there isn't
any description anywhere explaining why.

Anyway, regardless of the reason, it's a bad idea and shouldn't be done. At some
point we will also tighten conditions around normal PTR_TO_BTF_ID and only allow
trusted pointers everywhere.

pw-bot: cr

>
> Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@xxxxxxxxx>
> ---
> include/linux/bpf_verifier.h | 9 ++++++++-
> kernel/bpf/verifier.c | 12 +++++++++++-
> 2 files changed, 19 insertions(+), 2 deletions(-)
>
> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index 9727df5af83a..4f032ad83c67 100644
> --- a/include/linux/bpf_verifier.h
> +++ b/include/linux/bpf_verifier.h
> @@ -1339,6 +1339,11 @@ static inline bool bpf_is_ptr_to_mem_or_btf_id(enum bpf_reg_type type)
> }
> }
>
> +static inline bool bpf_is_trusted_or_null_btf_ptr(enum bpf_reg_type type)
> +{
> + return type == (PTR_TO_BTF_ID | PTR_TRUSTED | PTR_MAYBE_NULL);
> +}
> +
> static inline bool bpf_may_fault_on_deref(enum bpf_reg_type type)
> {
> /*
> @@ -1346,7 +1351,9 @@ static inline bool bpf_may_fault_on_deref(enum bpf_reg_type type)
> * protection, that is, the ones bpf_convert_ctx_accesses() has to
> * turn a BPF_LDX into a BPF_PROBE_MEM one for.
> */
> - return type == PTR_TO_BTF_ID || (type_flag(type) & PTR_UNTRUSTED);
> + return type == PTR_TO_BTF_ID ||
> + (type_flag(type) & PTR_UNTRUSTED) ||
> + bpf_is_trusted_or_null_btf_ptr(type);
> }
>
> static inline bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 9e79750e2480..b5186e664aea 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -6168,6 +6168,15 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
> if (ret != PTR_TO_BTF_ID) {
> /* just mark; */
>
> + } else if (bpf_is_trusted_or_null_btf_ptr(reg->type)) {
> + /*
> + * An unchecked load through a trusted-or-NULL pointer is
> + * fault-protected. Any pointer derived from that load must be
> + * untrusted, as a fault produces a NULL value.
> + */
> + clear_trusted_flags(&flag);
> + flag |= PTR_UNTRUSTED;
> +
> } else if (type_flag(reg->type) & PTR_UNTRUSTED) {
> /* If this is an untrusted pointer, all pointers formed by walking it
> * also inherit the untrusted flag.
> @@ -6644,7 +6653,8 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
> if (!err && t == BPF_READ && value_regno >= 0)
> mark_reg_unknown(env, regs, value_regno);
> } else if (base_type(reg->type) == PTR_TO_BTF_ID &&
> - !type_may_be_null(reg->type)) {
> + (!type_may_be_null(reg->type) ||
> + (t == BPF_READ && bpf_is_trusted_or_null_btf_ptr(reg->type)))) {
> err = check_ptr_to_btf_access(env, regs, reg, argno, off, size, t,
> value_regno);
> } else if (reg->type == CONST_PTR_TO_MAP) {