]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
selftests/bpf: test cases for callchain sensitive live stack tracking
authorEduard Zingerman <eddyz87@gmail.com>
Fri, 19 Sep 2025 02:18:45 +0000 (19:18 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Fri, 19 Sep 2025 16:27:24 +0000 (09:27 -0700)
- simple propagation of read/write marks;
- joining read/write marks from conditional branches;
- avoid must_write marks in when same instruction accesses different
  stack offsets on different execution paths;
- avoid must_write marks in case same instruction accesses stack
  and non-stack pointers on different execution paths;
- read/write marks propagation to outer stack frame;
- independent read marks for different callchains ending with the same
  function;
- bpf_calls_callback() dependent logic in
  liveness.c:bpf_stack_slot_alive().

Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20250918-callchain-sensitive-liveness-v3-12-c3cd27bacc60@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
tools/testing/selftests/bpf/prog_tests/verifier.c
tools/testing/selftests/bpf/progs/verifier_live_stack.c [new file with mode: 0644]

index e35c216dbaf21cf05a88475a247ef91f60d424ed..28e81161e6fca9efa69aa42b137d300c541f6da1 100644 (file)
@@ -46,6 +46,7 @@
 #include "verifier_ldsx.skel.h"
 #include "verifier_leak_ptr.skel.h"
 #include "verifier_linked_scalars.skel.h"
+#include "verifier_live_stack.skel.h"
 #include "verifier_load_acquire.skel.h"
 #include "verifier_loops1.skel.h"
 #include "verifier_lwt.skel.h"
@@ -184,6 +185,7 @@ void test_verifier_ld_ind(void)               { RUN(verifier_ld_ind); }
 void test_verifier_ldsx(void)                  { RUN(verifier_ldsx); }
 void test_verifier_leak_ptr(void)             { RUN(verifier_leak_ptr); }
 void test_verifier_linked_scalars(void)       { RUN(verifier_linked_scalars); }
+void test_verifier_live_stack(void)           { RUN(verifier_live_stack); }
 void test_verifier_loops1(void)               { RUN(verifier_loops1); }
 void test_verifier_lwt(void)                  { RUN(verifier_lwt); }
 void test_verifier_map_in_map(void)           { RUN(verifier_map_in_map); }
diff --git a/tools/testing/selftests/bpf/progs/verifier_live_stack.c b/tools/testing/selftests/bpf/progs/verifier_live_stack.c
new file mode 100644 (file)
index 0000000..c0e8085
--- /dev/null
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+struct {
+       __uint(type, BPF_MAP_TYPE_HASH);
+       __uint(max_entries, 1);
+       __type(key, int);
+       __type(value, long long);
+} map SEC(".maps");
+
+SEC("socket")
+__log_level(2)
+__msg("(0) frame 0 insn 2 +written -8")
+__msg("(0) frame 0 insn 1 +live -24")
+__msg("(0) frame 0 insn 1 +written -8")
+__msg("(0) frame 0 insn 0 +live -8,-24")
+__msg("(0) frame 0 insn 0 +written -8")
+__msg("(0) live stack update done in 2 iterations")
+__naked void simple_read_simple_write(void)
+{
+       asm volatile (
+       "r1 = *(u64 *)(r10 - 8);"
+       "r2 = *(u64 *)(r10 - 24);"
+       "*(u64 *)(r10 - 8) = r1;"
+       "r0 = 0;"
+       "exit;"
+       ::: __clobber_all);
+}
+
+SEC("socket")
+__log_level(2)
+__msg("(0) frame 0 insn 1 +live -8")
+__not_msg("(0) frame 0 insn 1 +written")
+__msg("(0) live stack update done in 2 iterations")
+__msg("(0) frame 0 insn 1 +live -16")
+__msg("(0) frame 0 insn 1 +written -32")
+__msg("(0) live stack update done in 2 iterations")
+__naked void read_write_join(void)
+{
+       asm volatile (
+       "call %[bpf_get_prandom_u32];"
+       "if r0 > 42 goto 1f;"
+       "r0 = *(u64 *)(r10 - 8);"
+       "*(u64 *)(r10 - 32) = r0;"
+       "*(u64 *)(r10 - 40) = r0;"
+       "exit;"
+"1:"
+       "r0 = *(u64 *)(r10 - 16);"
+       "*(u64 *)(r10 - 32) = r0;"
+       "exit;"
+       :: __imm(bpf_get_prandom_u32)
+       : __clobber_all);
+}
+
+SEC("socket")
+__log_level(2)
+__msg("2: (25) if r0 > 0x2a goto pc+1")
+__msg("7: (95) exit")
+__msg("(0) frame 0 insn 2 +written -16")
+__msg("(0) live stack update done in 2 iterations")
+__msg("7: (95) exit")
+__not_msg("(0) frame 0 insn 2")
+__msg("(0) live stack update done in 1 iterations")
+__naked void must_write_not_same_slot(void)
+{
+       asm volatile (
+       "call %[bpf_get_prandom_u32];"
+       "r1 = -8;"
+       "if r0 > 42 goto 1f;"
+       "r1 = -16;"
+"1:"
+       "r2 = r10;"
+       "r2 += r1;"
+       "*(u64 *)(r2 + 0) = r0;"
+       "exit;"
+       :: __imm(bpf_get_prandom_u32)
+       : __clobber_all);
+}
+
+SEC("socket")
+__log_level(2)
+__msg("(0) frame 0 insn 0 +written -8,-16")
+__msg("(0) live stack update done in 2 iterations")
+__msg("(0) frame 0 insn 0 +written -8")
+__msg("(0) live stack update done in 2 iterations")
+__naked void must_write_not_same_type(void)
+{
+       asm volatile (
+       "*(u64*)(r10 - 8) = 0;"
+       "r2 = r10;"
+       "r2 += -8;"
+       "r1 = %[map] ll;"
+       "call %[bpf_map_lookup_elem];"
+       "if r0 != 0 goto 1f;"
+       "r0 = r10;"
+       "r0 += -16;"
+"1:"
+       "*(u64 *)(r0 + 0) = 42;"
+       "exit;"
+       :
+        : __imm(bpf_get_prandom_u32),
+         __imm(bpf_map_lookup_elem),
+         __imm_addr(map)
+       : __clobber_all);
+}
+
+SEC("socket")
+__log_level(2)
+__msg("(2,4) frame 0 insn 4 +written -8")
+__msg("(2,4) live stack update done in 2 iterations")
+__msg("(0) frame 0 insn 2 +written -8")
+__msg("(0) live stack update done in 2 iterations")
+__naked void caller_stack_write(void)
+{
+       asm volatile (
+       "r1 = r10;"
+       "r1 += -8;"
+       "call write_first_param;"
+       "exit;"
+       ::: __clobber_all);
+}
+
+static __used __naked void write_first_param(void)
+{
+       asm volatile (
+       "*(u64 *)(r1 + 0) = 7;"
+       "r0 = 0;"
+       "exit;"
+       ::: __clobber_all);
+}
+
+SEC("socket")
+__log_level(2)
+/* caller_stack_read() function */
+__msg("2: .12345.... (85) call pc+4")
+__msg("5: .12345.... (85) call pc+1")
+__msg("6: 0......... (95) exit")
+/* read_first_param() function */
+__msg("7: .1........ (79) r0 = *(u64 *)(r1 +0)")
+__msg("8: 0......... (95) exit")
+/* update for callsite at (2) */
+__msg("(2,7) frame 0 insn 7 +live -8")
+__msg("(2,7) live stack update done in 2 iterations")
+__msg("(0) frame 0 insn 2 +live -8")
+__msg("(0) live stack update done in 2 iterations")
+/* update for callsite at (5) */
+__msg("(5,7) frame 0 insn 7 +live -16")
+__msg("(5,7) live stack update done in 2 iterations")
+__msg("(0) frame 0 insn 5 +live -16")
+__msg("(0) live stack update done in 2 iterations")
+__naked void caller_stack_read(void)
+{
+       asm volatile (
+       "r1 = r10;"
+       "r1 += -8;"
+       "call read_first_param;"
+       "r1 = r10;"
+       "r1 += -16;"
+       "call read_first_param;"
+       "exit;"
+       ::: __clobber_all);
+}
+
+static __used __naked void read_first_param(void)
+{
+       asm volatile (
+       "r0 = *(u64 *)(r1 + 0);"
+       "exit;"
+       ::: __clobber_all);
+}
+
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__log_level(2)
+/* read_first_param2() function */
+__msg(" 9: .1........ (79) r0 = *(u64 *)(r1 +0)")
+__msg("10: .......... (b7) r0 = 0")
+__msg("11: 0......... (05) goto pc+0")
+__msg("12: 0......... (95) exit")
+/*
+ * The purpose of the test is to check that checkpoint in
+ * read_first_param2() stops path traversal. This will only happen if
+ * verifier understands that fp[0]-8 at insn (12) is not alive.
+ */
+__msg("12: safe")
+__msg("processed 20 insns")
+__naked void caller_stack_pruning(void)
+{
+       asm volatile (
+       "call %[bpf_get_prandom_u32];"
+       "if r0 == 42 goto 1f;"
+       "r0 = %[map] ll;"
+"1:"
+       "*(u64 *)(r10 - 8) = r0;"
+       "r1 = r10;"
+       "r1 += -8;"
+       /*
+        * fp[0]-8 is either pointer to map or a scalar,
+        * preventing state pruning at checkpoint created for call.
+        */
+       "call read_first_param2;"
+       "exit;"
+       :
+       : __imm(bpf_get_prandom_u32),
+         __imm_addr(map)
+       : __clobber_all);
+}
+
+static __used __naked void read_first_param2(void)
+{
+       asm volatile (
+       "r0 = *(u64 *)(r1 + 0);"
+       "r0 = 0;"
+       /*
+        * Checkpoint at goto +0 should fire,
+        * as caller stack fp[0]-8 is not alive at this point.
+        */
+       "goto +0;"
+       "exit;"
+       ::: __clobber_all);
+}
+
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure
+__msg("R1 type=scalar expected=map_ptr")
+__naked void caller_stack_pruning_callback(void)
+{
+       asm volatile (
+       "r0 = %[map] ll;"
+       "*(u64 *)(r10 - 8) = r0;"
+       "r1 = 2;"
+       "r2 = loop_cb ll;"
+       "r3 = r10;"
+       "r3 += -8;"
+       "r4 = 0;"
+       /*
+        * fp[0]-8 is either pointer to map or a scalar,
+        * preventing state pruning at checkpoint created for call.
+        */
+       "call %[bpf_loop];"
+       "r0 = 42;"
+       "exit;"
+       :
+       : __imm(bpf_get_prandom_u32),
+         __imm(bpf_loop),
+         __imm_addr(map)
+       : __clobber_all);
+}
+
+static __used __naked void loop_cb(void)
+{
+       asm volatile (
+       /*
+        * Checkpoint at function entry should not fire, as caller
+        * stack fp[0]-8 is alive at this point.
+        */
+       "r6 = r2;"
+       "r1 = *(u64 *)(r6 + 0);"
+       "*(u64*)(r10 - 8) = 7;"
+       "r2 = r10;"
+       "r2 += -8;"
+       "call %[bpf_map_lookup_elem];"
+       /*
+        * This should stop verifier on a second loop iteration,
+        * but only if verifier correctly maintains that fp[0]-8
+        * is still alive.
+        */
+       "*(u64 *)(r6 + 0) = 0;"
+       "r0 = 0;"
+       "exit;"
+       :
+       : __imm(bpf_map_lookup_elem),
+         __imm(bpf_get_prandom_u32)
+       : __clobber_all);
+}
+
+/*
+ * Because of a bug in verifier.c:compute_postorder()
+ * the program below overflowed traversal queue in that function.
+ */
+SEC("socket")
+__naked void syzbot_postorder_bug1(void)
+{
+       asm volatile (
+       "r0 = 0;"
+       "if r0 != 0 goto -1;"
+       "exit;"
+       ::: __clobber_all);
+}