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

From: Anastasios Papagiannis

Date: Mon Aug 31 2026 - 05:48:52 EST


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.

This patch adds support to dereference pointers marked as
PTR_TO_BTF_ID|PTR_TRUSTED|PTR_MAYBE_NULL without the need for a NULL
check. We define that those pointers can fault on dereference, so
reads need to happen through BPF_PROBE_MEM. If the dereferenced pointer
is another BTF pointer, we clear trusted flags and mark the result 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.

To summarise, before that patch, reads to PTR_TO_BTF_ID|PTR_UNTRUSTED
are allowed through BPF_PROBE_MEM and reads to
PTR_TO_BTF_ID|PTR_TRUSTED are normal loads. This patch allows
PTR_TO_BTF_ID|PTR_TRUSTED|PTR_MAYBE_NULL to happen through
BPF_PROBE_MEM.

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.

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 ae9f606539f4..d3d7254cc1f6 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1326,6 +1326,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)
{
/*
@@ -1333,7 +1338,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 8f585ceb2cd5..386401fe051d 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6116,6 +6116,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.
@@ -6591,7 +6600,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) {
--
2.55.0