bpf: Recognize __nullable on pointer members of kernel structs

A pointer member of a kernel struct that the verifier derives as trusted
or RCU-protected is assumed to be non-NULL, so a load through it is a
plain dereference. The BTF_TYPE_SAFE_*_OR_NULL lists exist for members
known to be NULL at times, but they live in the verifier and have to be
extended by hand.

Teach btf_struct_walk() to report the "nullable" BTF type tag on a
pointer member as PTR_MAYBE_NULL. The tag combines with any other tag
on the member; it only says that the pointer may be NULL. The verifier
keeps the flag where NULL would otherwise be assumed away: on trusted
and RCU results, which a program dereferences as they are, and on
per-CPU results, which the per-CPU helpers shift without a check. Such
a pointer has to be tested before it is dereferenced or passed on.
Plain and untrusted results are probed anyway and lose the flag, as the
BTF_TYPE_SAFE_*_OR_NULL lists never applied to them either, so a tagged
member of an existing struct asks no more of the programs that read it
than a list entry does, and the tag can take the place of the lists.

The next patch uses the tag on __arena members to decide whether the
injected address translation has to preserve NULL.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20261019132203.3034698-7-memxor@gmail.com
This commit is contained in:
Kumar Kartikeya Dwivedi 2026-10-19 15:21:48 +02:00 committed by Alexei Starovoitov
parent 10832993c9
commit bc7c282468
No known key found for this signature in database
2 changed files with 26 additions and 2 deletions

View File

@ -3569,7 +3569,8 @@ static int btf_type_tag_walk(const struct btf *btf,
return 0;
}
bool btf_type_is_arena_ptr(const struct btf *btf, const struct btf_type *t)
/* Whether the pointee of pointer type @t carries the type tag @name */
static bool btf_ptr_has_type_tag(const struct btf *btf, const struct btf_type *t, const char *name)
{
if (!btf_type_is_ptr(t))
return false;
@ -3578,13 +3579,18 @@ bool btf_type_is_arena_ptr(const struct btf *btf, const struct btf_type *t)
t = btf_type_by_id(btf, t->type)) {
if (!btf_type_is_type_tag(t) || btf_type_kflag(t))
continue;
if (!strcmp(__btf_name_by_offset(btf, t->name_off), "arena"))
if (!strcmp(__btf_name_by_offset(btf, t->name_off), name))
return true;
}
return false;
}
bool btf_type_is_arena_ptr(const struct btf *btf, const struct btf_type *t)
{
return btf_ptr_has_type_tag(btf, t, "arena");
}
static int btf_find_kptr(const struct btf *btf, const struct btf_type *t,
u32 off, int sz, struct btf_field_info *info, u32 field_mask)
{
@ -7973,6 +7979,14 @@ static int btf_struct_walk(struct bpf_verifier_log *log, const struct btf *btf,
id = ctx.id;
stype = ctx.t;
/*
* __nullable combines with any of the above: it only says
* that the pointer may be NULL. The caller decides whether
* that matters for the register type it derives.
*/
if (btf_ptr_has_type_tag(btf, mtype, "nullable"))
tmp_flag |= PTR_MAYBE_NULL;
if (btf_type_is_struct(stype)) {
*next_btf_id = id;
*flag |= tmp_flag;

View File

@ -6754,6 +6754,16 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
clear_trusted_flags(&flag);
}
/*
* A __nullable member only matters where NULL would otherwise be
* assumed away, which is anywhere but on a plain or an untrusted
* pointer: those are probed on a dereference, which tolerates NULL, and
* the BTF_TYPE_SAFE_*_OR_NULL lists never applied to them either. A
* per-CPU pointer counts, per_cpu_ptr() does not check for NULL.
*/
if (bpf_may_fault_on_deref(PTR_TO_BTF_ID | (flag & ~PTR_MAYBE_NULL)))
flag &= ~PTR_MAYBE_NULL;
if (atype == BPF_READ && value_regno >= 0) {
ret = mark_btf_ld_reg(env, regs, value_regno, ret, reg->btf, btf_id, flag);
if (ret < 0)