]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
selftests/bpf: Add test cases with CONST_PTR_TO_MAP null checks
authorIhor Solodrai <isolodrai@meta.com>
Mon, 9 Jun 2025 18:30:24 +0000 (11:30 -0700)
committerAndrii Nakryiko <andrii@kernel.org>
Mon, 9 Jun 2025 23:42:04 +0000 (16:42 -0700)
A test requires the following to happen:
  * CONST_PTR_TO_MAP value is checked for null
  * the code in the null branch fails verification

Add test cases:
* direct global map_ptr comparison to null
* lookup inner map, then two checks (the first transforms
  map_value_or_null into map_ptr)
* lookup inner map, spill-fill it, then check for null
* use an array of ringbufs to recreate a common coding pattern [1]

[1] https://lore.kernel.org/bpf/CAEf4BzZNU0gX_sQ8k8JaLe1e+Veth3Rk=4x7MDhv=hQxvO8EDw@mail.gmail.com/

Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Ihor Solodrai <isolodrai@meta.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20250609183024.359974-4-isolodrai@meta.com
tools/testing/selftests/bpf/progs/verifier_map_in_map.c

index 7d088ba99ea5d51210f46f3e303ae7801caf8c0b..16b761e510f0ddede9d437cd84efef2b4f345172 100644 (file)
@@ -139,4 +139,122 @@ __naked void on_the_inner_map_pointer(void)
        : __clobber_all);
 }
 
+SEC("socket")
+__description("map_ptr is never null")
+__success
+__naked void map_ptr_is_never_null(void)
+{
+       asm volatile ("                                 \
+       r0 = 0;                                         \
+       r1 = %[map_in_map] ll;                          \
+       if r1 != 0 goto l0_%=;                          \
+       r10 = 42;                                       \
+l0_%=: exit;                                           \
+"      :
+       : __imm(bpf_map_lookup_elem),
+         __imm_addr(map_in_map)
+       : __clobber_all);
+}
+
+SEC("socket")
+__description("map_ptr is never null inner")
+__success
+__naked void map_ptr_is_never_null_inner(void)
+{
+       asm volatile ("                                 \
+       r1 = 0;                                         \
+       *(u32*)(r10 - 4) = r1;                          \
+       r2 = r10;                                       \
+       r2 += -4;                                       \
+       r1 = %[map_in_map] ll;                          \
+       call %[bpf_map_lookup_elem];                    \
+       if r0 == 0 goto l0_%=;                          \
+       if r0 != 0 goto l0_%=;                          \
+       r10 = 42;                                       \
+l0_%=:  exit;                                          \
+"      :
+       : __imm(bpf_map_lookup_elem),
+         __imm_addr(map_in_map)
+       : __clobber_all);
+}
+
+SEC("socket")
+__description("map_ptr is never null inner spill fill")
+__success
+__naked void map_ptr_is_never_null_inner_spill_fill(void)
+{
+       asm volatile ("                                 \
+       r1 = 0;                                         \
+       *(u32*)(r10 - 4) = r1;                          \
+       r2 = r10;                                       \
+       r2 += -4;                                       \
+       r1 = %[map_in_map] ll;                          \
+       call %[bpf_map_lookup_elem];                    \
+       if r0 != 0 goto l0_%=;                          \
+       exit;                                           \
+l0_%=: *(u64 *)(r10 -16) = r0;                         \
+       r1 = *(u64 *)(r10 -16);                         \
+       if r1 == 0 goto l1_%=;                          \
+       exit;                                           \
+l1_%=: r10 = 42;                                       \
+       exit;                                           \
+"      :
+       : __imm(bpf_map_lookup_elem),
+         __imm_addr(map_in_map)
+       : __clobber_all);
+}
+
+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_RINGBUF);
+               __uint(max_entries, 64 * 1024);
+       });
+} rb_in_map SEC(".maps");
+
+struct rb_ctx {
+       void *rb;
+       struct bpf_dynptr dptr;
+};
+
+static __always_inline struct rb_ctx __rb_event_reserve(__u32 sz)
+{
+       struct rb_ctx rb_ctx = {};
+       void *rb;
+       __u32 cpu = bpf_get_smp_processor_id();
+       __u32 rb_slot = cpu & 1;
+
+       rb = bpf_map_lookup_elem(&rb_in_map, &rb_slot);
+       if (!rb)
+               return rb_ctx;
+
+       rb_ctx.rb = rb;
+       bpf_ringbuf_reserve_dynptr(rb, sz, 0, &rb_ctx.dptr);
+
+       return rb_ctx;
+}
+
+static __noinline void __rb_event_submit(struct rb_ctx *ctx)
+{
+       if (!ctx->rb)
+               return;
+
+       /* If the verifier (incorrectly) concludes that ctx->rb can be
+        * NULL at this point, we'll get "BPF_EXIT instruction in main
+        * prog would lead to reference leak" error
+        */
+       bpf_ringbuf_submit_dynptr(&ctx->dptr, 0);
+}
+
+SEC("socket")
+int map_ptr_is_never_null_rb(void *ctx)
+{
+       struct rb_ctx event_ctx = __rb_event_reserve(256);
+       __rb_event_submit(&event_ctx);
+       return 0;
+}
+
 char _license[] SEC("license") = "GPL";