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, so a program has to check such a pointer before dereferencing it or passing it to a kfunc that needs a trusted argument. Untrusted results are probed anyway and lose the flag, as does anything walked from a map pointer. 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 --- kernel/bpf/btf.c | 18 ++++++++++++++++-- kernel/bpf/verifier.c | 9 +++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index b09c04ccdc6d..bcbd49c7ca0b 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -3557,7 +3557,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; @@ -3566,13 +3567,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) { @@ -7961,6 +7967,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; diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 2f190abe3448..70fd44a6e1c5 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -6721,6 +6721,13 @@ 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, on trusted and RCU pointers; the rest are probed. + */ + if (!(flag & (PTR_TRUSTED | MEM_RCU))) + 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) @@ -6785,6 +6792,8 @@ static int check_ptr_to_map_access(struct bpf_verifier_env *env, ret = btf_struct_access(&env->log, &map_reg, off, size, atype, &btf_id, &flag, NULL); if (ret < 0) return ret; + /* Nothing walked from a map is trusted, a __nullable member changes nothing. */ + flag &= ~PTR_MAYBE_NULL; if (value_regno >= 0) { ret = mark_btf_ld_reg(env, regs, value_regno, ret, btf_vmlinux, btf_id, flag); -- 2.53.0-Meta