Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/bpf: test cases for callchain sensitive live stack tracking

- 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>

authored by

Eduard Zingerman and committed by
Alexei Starovoitov
fdcecdff 34c513be

+296
+2
tools/testing/selftests/bpf/prog_tests/verifier.c
··· 46 46 #include "verifier_ldsx.skel.h" 47 47 #include "verifier_leak_ptr.skel.h" 48 48 #include "verifier_linked_scalars.skel.h" 49 + #include "verifier_live_stack.skel.h" 49 50 #include "verifier_load_acquire.skel.h" 50 51 #include "verifier_loops1.skel.h" 51 52 #include "verifier_lwt.skel.h" ··· 185 184 void test_verifier_ldsx(void) { RUN(verifier_ldsx); } 186 185 void test_verifier_leak_ptr(void) { RUN(verifier_leak_ptr); } 187 186 void test_verifier_linked_scalars(void) { RUN(verifier_linked_scalars); } 187 + void test_verifier_live_stack(void) { RUN(verifier_live_stack); } 188 188 void test_verifier_loops1(void) { RUN(verifier_loops1); } 189 189 void test_verifier_lwt(void) { RUN(verifier_lwt); } 190 190 void test_verifier_map_in_map(void) { RUN(verifier_map_in_map); }
+294
tools/testing/selftests/bpf/progs/verifier_live_stack.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ 3 + 4 + #include <linux/bpf.h> 5 + #include <bpf/bpf_helpers.h> 6 + #include "bpf_misc.h" 7 + 8 + struct { 9 + __uint(type, BPF_MAP_TYPE_HASH); 10 + __uint(max_entries, 1); 11 + __type(key, int); 12 + __type(value, long long); 13 + } map SEC(".maps"); 14 + 15 + SEC("socket") 16 + __log_level(2) 17 + __msg("(0) frame 0 insn 2 +written -8") 18 + __msg("(0) frame 0 insn 1 +live -24") 19 + __msg("(0) frame 0 insn 1 +written -8") 20 + __msg("(0) frame 0 insn 0 +live -8,-24") 21 + __msg("(0) frame 0 insn 0 +written -8") 22 + __msg("(0) live stack update done in 2 iterations") 23 + __naked void simple_read_simple_write(void) 24 + { 25 + asm volatile ( 26 + "r1 = *(u64 *)(r10 - 8);" 27 + "r2 = *(u64 *)(r10 - 24);" 28 + "*(u64 *)(r10 - 8) = r1;" 29 + "r0 = 0;" 30 + "exit;" 31 + ::: __clobber_all); 32 + } 33 + 34 + SEC("socket") 35 + __log_level(2) 36 + __msg("(0) frame 0 insn 1 +live -8") 37 + __not_msg("(0) frame 0 insn 1 +written") 38 + __msg("(0) live stack update done in 2 iterations") 39 + __msg("(0) frame 0 insn 1 +live -16") 40 + __msg("(0) frame 0 insn 1 +written -32") 41 + __msg("(0) live stack update done in 2 iterations") 42 + __naked void read_write_join(void) 43 + { 44 + asm volatile ( 45 + "call %[bpf_get_prandom_u32];" 46 + "if r0 > 42 goto 1f;" 47 + "r0 = *(u64 *)(r10 - 8);" 48 + "*(u64 *)(r10 - 32) = r0;" 49 + "*(u64 *)(r10 - 40) = r0;" 50 + "exit;" 51 + "1:" 52 + "r0 = *(u64 *)(r10 - 16);" 53 + "*(u64 *)(r10 - 32) = r0;" 54 + "exit;" 55 + :: __imm(bpf_get_prandom_u32) 56 + : __clobber_all); 57 + } 58 + 59 + SEC("socket") 60 + __log_level(2) 61 + __msg("2: (25) if r0 > 0x2a goto pc+1") 62 + __msg("7: (95) exit") 63 + __msg("(0) frame 0 insn 2 +written -16") 64 + __msg("(0) live stack update done in 2 iterations") 65 + __msg("7: (95) exit") 66 + __not_msg("(0) frame 0 insn 2") 67 + __msg("(0) live stack update done in 1 iterations") 68 + __naked void must_write_not_same_slot(void) 69 + { 70 + asm volatile ( 71 + "call %[bpf_get_prandom_u32];" 72 + "r1 = -8;" 73 + "if r0 > 42 goto 1f;" 74 + "r1 = -16;" 75 + "1:" 76 + "r2 = r10;" 77 + "r2 += r1;" 78 + "*(u64 *)(r2 + 0) = r0;" 79 + "exit;" 80 + :: __imm(bpf_get_prandom_u32) 81 + : __clobber_all); 82 + } 83 + 84 + SEC("socket") 85 + __log_level(2) 86 + __msg("(0) frame 0 insn 0 +written -8,-16") 87 + __msg("(0) live stack update done in 2 iterations") 88 + __msg("(0) frame 0 insn 0 +written -8") 89 + __msg("(0) live stack update done in 2 iterations") 90 + __naked void must_write_not_same_type(void) 91 + { 92 + asm volatile ( 93 + "*(u64*)(r10 - 8) = 0;" 94 + "r2 = r10;" 95 + "r2 += -8;" 96 + "r1 = %[map] ll;" 97 + "call %[bpf_map_lookup_elem];" 98 + "if r0 != 0 goto 1f;" 99 + "r0 = r10;" 100 + "r0 += -16;" 101 + "1:" 102 + "*(u64 *)(r0 + 0) = 42;" 103 + "exit;" 104 + : 105 + : __imm(bpf_get_prandom_u32), 106 + __imm(bpf_map_lookup_elem), 107 + __imm_addr(map) 108 + : __clobber_all); 109 + } 110 + 111 + SEC("socket") 112 + __log_level(2) 113 + __msg("(2,4) frame 0 insn 4 +written -8") 114 + __msg("(2,4) live stack update done in 2 iterations") 115 + __msg("(0) frame 0 insn 2 +written -8") 116 + __msg("(0) live stack update done in 2 iterations") 117 + __naked void caller_stack_write(void) 118 + { 119 + asm volatile ( 120 + "r1 = r10;" 121 + "r1 += -8;" 122 + "call write_first_param;" 123 + "exit;" 124 + ::: __clobber_all); 125 + } 126 + 127 + static __used __naked void write_first_param(void) 128 + { 129 + asm volatile ( 130 + "*(u64 *)(r1 + 0) = 7;" 131 + "r0 = 0;" 132 + "exit;" 133 + ::: __clobber_all); 134 + } 135 + 136 + SEC("socket") 137 + __log_level(2) 138 + /* caller_stack_read() function */ 139 + __msg("2: .12345.... (85) call pc+4") 140 + __msg("5: .12345.... (85) call pc+1") 141 + __msg("6: 0......... (95) exit") 142 + /* read_first_param() function */ 143 + __msg("7: .1........ (79) r0 = *(u64 *)(r1 +0)") 144 + __msg("8: 0......... (95) exit") 145 + /* update for callsite at (2) */ 146 + __msg("(2,7) frame 0 insn 7 +live -8") 147 + __msg("(2,7) live stack update done in 2 iterations") 148 + __msg("(0) frame 0 insn 2 +live -8") 149 + __msg("(0) live stack update done in 2 iterations") 150 + /* update for callsite at (5) */ 151 + __msg("(5,7) frame 0 insn 7 +live -16") 152 + __msg("(5,7) live stack update done in 2 iterations") 153 + __msg("(0) frame 0 insn 5 +live -16") 154 + __msg("(0) live stack update done in 2 iterations") 155 + __naked void caller_stack_read(void) 156 + { 157 + asm volatile ( 158 + "r1 = r10;" 159 + "r1 += -8;" 160 + "call read_first_param;" 161 + "r1 = r10;" 162 + "r1 += -16;" 163 + "call read_first_param;" 164 + "exit;" 165 + ::: __clobber_all); 166 + } 167 + 168 + static __used __naked void read_first_param(void) 169 + { 170 + asm volatile ( 171 + "r0 = *(u64 *)(r1 + 0);" 172 + "exit;" 173 + ::: __clobber_all); 174 + } 175 + 176 + SEC("socket") 177 + __flag(BPF_F_TEST_STATE_FREQ) 178 + __log_level(2) 179 + /* read_first_param2() function */ 180 + __msg(" 9: .1........ (79) r0 = *(u64 *)(r1 +0)") 181 + __msg("10: .......... (b7) r0 = 0") 182 + __msg("11: 0......... (05) goto pc+0") 183 + __msg("12: 0......... (95) exit") 184 + /* 185 + * The purpose of the test is to check that checkpoint in 186 + * read_first_param2() stops path traversal. This will only happen if 187 + * verifier understands that fp[0]-8 at insn (12) is not alive. 188 + */ 189 + __msg("12: safe") 190 + __msg("processed 20 insns") 191 + __naked void caller_stack_pruning(void) 192 + { 193 + asm volatile ( 194 + "call %[bpf_get_prandom_u32];" 195 + "if r0 == 42 goto 1f;" 196 + "r0 = %[map] ll;" 197 + "1:" 198 + "*(u64 *)(r10 - 8) = r0;" 199 + "r1 = r10;" 200 + "r1 += -8;" 201 + /* 202 + * fp[0]-8 is either pointer to map or a scalar, 203 + * preventing state pruning at checkpoint created for call. 204 + */ 205 + "call read_first_param2;" 206 + "exit;" 207 + : 208 + : __imm(bpf_get_prandom_u32), 209 + __imm_addr(map) 210 + : __clobber_all); 211 + } 212 + 213 + static __used __naked void read_first_param2(void) 214 + { 215 + asm volatile ( 216 + "r0 = *(u64 *)(r1 + 0);" 217 + "r0 = 0;" 218 + /* 219 + * Checkpoint at goto +0 should fire, 220 + * as caller stack fp[0]-8 is not alive at this point. 221 + */ 222 + "goto +0;" 223 + "exit;" 224 + ::: __clobber_all); 225 + } 226 + 227 + SEC("socket") 228 + __flag(BPF_F_TEST_STATE_FREQ) 229 + __failure 230 + __msg("R1 type=scalar expected=map_ptr") 231 + __naked void caller_stack_pruning_callback(void) 232 + { 233 + asm volatile ( 234 + "r0 = %[map] ll;" 235 + "*(u64 *)(r10 - 8) = r0;" 236 + "r1 = 2;" 237 + "r2 = loop_cb ll;" 238 + "r3 = r10;" 239 + "r3 += -8;" 240 + "r4 = 0;" 241 + /* 242 + * fp[0]-8 is either pointer to map or a scalar, 243 + * preventing state pruning at checkpoint created for call. 244 + */ 245 + "call %[bpf_loop];" 246 + "r0 = 42;" 247 + "exit;" 248 + : 249 + : __imm(bpf_get_prandom_u32), 250 + __imm(bpf_loop), 251 + __imm_addr(map) 252 + : __clobber_all); 253 + } 254 + 255 + static __used __naked void loop_cb(void) 256 + { 257 + asm volatile ( 258 + /* 259 + * Checkpoint at function entry should not fire, as caller 260 + * stack fp[0]-8 is alive at this point. 261 + */ 262 + "r6 = r2;" 263 + "r1 = *(u64 *)(r6 + 0);" 264 + "*(u64*)(r10 - 8) = 7;" 265 + "r2 = r10;" 266 + "r2 += -8;" 267 + "call %[bpf_map_lookup_elem];" 268 + /* 269 + * This should stop verifier on a second loop iteration, 270 + * but only if verifier correctly maintains that fp[0]-8 271 + * is still alive. 272 + */ 273 + "*(u64 *)(r6 + 0) = 0;" 274 + "r0 = 0;" 275 + "exit;" 276 + : 277 + : __imm(bpf_map_lookup_elem), 278 + __imm(bpf_get_prandom_u32) 279 + : __clobber_all); 280 + } 281 + 282 + /* 283 + * Because of a bug in verifier.c:compute_postorder() 284 + * the program below overflowed traversal queue in that function. 285 + */ 286 + SEC("socket") 287 + __naked void syzbot_postorder_bug1(void) 288 + { 289 + asm volatile ( 290 + "r0 = 0;" 291 + "if r0 != 0 goto -1;" 292 + "exit;" 293 + ::: __clobber_all); 294 + }