Merge branch 'keep-dynamic-inner-array-lookups-nullable'

Nuoqi Gui says:

====================
Keep dynamic inner array lookups nullable

An ARRAY_OF_MAPS can use an array created with BPF_F_INNER_MAP as its
inner map template. The flag allows a concrete inner array with a
different max_entries value to replace the template.

The verifier currently uses the template's max_entries to elide
nullness for a constant-key lookup through the inner map pointer. At
runtime, the lookup uses the concrete inner array's max_entries instead.
The verifier can therefore accept an unchecked dereference even though
the runtime helper returns NULL.

Patch 1 keeps lookups through BPF_F_INNER_MAP array templates nullable.
Patch 2 adds a verifier regression test for the unchecked dereference.

Before the fix, the regression program is accepted and the runtime
reproducer triggers a NULL dereference. With the fix, both programs are
rejected with an invalid map_value_or_null access.

Tested by compiling kernel/bpf/verifier.o and
verifier_map_in_map.bpf.o, and by running the regression program and
runtime reproducer in QEMU before and after the fix.

Signed-off-by: Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
Acked-by: Jiri Olsa <jolsa@kernel.org>
---
v1->v2:
- Update the can_elide_value_nullness() comment to match the changed
  parameter (const struct bpf_map *map).

v1: https://patch.msgid.link/20260604151153.2488051-1-gnq25@mails.tsinghua.edu.cn

To: Alexei Starovoitov <ast@kernel.org>
To: Daniel Borkmann <daniel@iogearbox.net>
To: Andrii Nakryiko <andrii@kernel.org>
Cc: Daniel Xu <dxu@dxuuu.xyz>
Cc: Eduard Zingerman <eddyz87@gmail.com>
Cc: John Fastabend <john.fastabend@gmail.com>
Cc: Martin KaFai Lau <martin.lau@linux.dev>
Cc: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Cc: Song Liu <song@kernel.org>
Cc: Yonghong Song <yonghong.song@linux.dev>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Ihor Solodrai <isolodrai@meta.com>
Cc: bpf@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-kselftest@vger.kernel.org

---
====================

Link: https://patch.msgid.link/20260607-f01-v2-v2-0-da48453146e8@mails.tsinghua.edu.cn
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
This commit is contained in:
Kumar Kartikeya Dwivedi 2026-06-08 13:31:52 +02:00
commit af5cb68eed
No known key found for this signature in database
GPG Key ID: 472D377B63542F83
2 changed files with 49 additions and 6 deletions

View File

@ -8179,7 +8179,7 @@ static int get_constant_map_key(struct bpf_verifier_env *env,
return 0;
}
static bool can_elide_value_nullness(enum bpf_map_type type);
static bool can_elide_value_nullness(const struct bpf_map *map);
static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
struct bpf_call_arg_meta *meta,
@ -8298,7 +8298,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
err = check_helper_mem_access(env, reg, argno_from_reg(regno), key_size, BPF_READ, false, NULL);
if (err)
return err;
if (can_elide_value_nullness(meta->map.ptr->map_type)) {
if (can_elide_value_nullness(meta->map.ptr)) {
err = get_constant_map_key(env, reg, key_size, &meta->const_map_key);
if (err < 0) {
meta->const_map_key = -1;
@ -10068,13 +10068,16 @@ static void update_loop_inline_state(struct bpf_verifier_env *env, u32 subprogno
state->callback_subprogno == subprogno);
}
/* Returns whether or not the given map type can potentially elide
/* Returns whether or not the given map can potentially elide
* lookup return value nullness check. This is possible if the key
* is statically known.
*/
static bool can_elide_value_nullness(enum bpf_map_type type)
static bool can_elide_value_nullness(const struct bpf_map *map)
{
switch (type) {
if (map->map_flags & BPF_F_INNER_MAP)
return false;
switch (map->map_type) {
case BPF_MAP_TYPE_ARRAY:
case BPF_MAP_TYPE_PERCPU_ARRAY:
return true;
@ -10414,7 +10417,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
}
if (func_id == BPF_FUNC_map_lookup_elem &&
can_elide_value_nullness(meta.map.ptr->map_type) &&
can_elide_value_nullness(meta.map.ptr) &&
meta.const_map_key >= 0 &&
meta.const_map_key < meta.map.ptr->max_entries)
ret_flag &= ~PTR_MAYBE_NULL;

View File

@ -18,6 +18,20 @@ struct {
});
} map_in_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
__uint(max_entries, 1);
__type(key, int);
__type(value, int);
__array(values, struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(map_flags, BPF_F_INNER_MAP);
__uint(max_entries, 8);
__type(key, int);
__type(value, long);
});
} map_in_map_dyn SEC(".maps");
SEC("socket")
__description("map in map access")
__success __success_unpriv __retval(0)
@ -45,6 +59,32 @@ l0_%=: r0 = 0; \
: __clobber_all);
}
SEC("socket")
__description("map in map dynamic inner array lookup is nullable")
__failure __msg("invalid mem access 'map_value_or_null'")
__naked void map_in_map_dynamic_inner_array_lookup_is_nullable(void)
{
asm volatile (" \
r1 = 0; \
*(u32*)(r10 - 4) = r1; \
r2 = r10; \
r2 += -4; \
r1 = %[map_in_map_dyn] ll; \
call %[bpf_map_lookup_elem]; \
if r0 == 0 goto l0_%=; \
*(u32*)(r10 - 8) = 4; \
r2 = r10; \
r2 += -8; \
r1 = r0; \
call %[bpf_map_lookup_elem]; \
r0 = *(u64 *)(r0 + 0); \
l0_%=: exit; \
" :
: __imm(bpf_map_lookup_elem),
__imm_addr(map_in_map_dyn)
: __clobber_all);
}
SEC("xdp")
__description("map in map state pruning")
__success __msg("processed 15 insns")