xref: /linux/tools/testing/selftests/bpf/progs/verifier_live_stack.c (revision a8471eb0747f083ae5bde949e667d3d799d65cf7)
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 "../../../include/linux/filter.h"
7 #include "bpf_misc.h"
8 
9 char _license[] SEC("license") = "GPL";
10 struct {
11 	__uint(type, BPF_MAP_TYPE_HASH);
12 	__uint(max_entries, 1);
13 	__type(key, int);
14 	__type(value, long long);
15 } map SEC(".maps");
16 
17 struct {
18 	__uint(type, BPF_MAP_TYPE_ARRAY);
19 	__uint(max_entries, 1);
20 	__type(key, __u32);
21 	__type(value, __u64);
22 } array_map_8b SEC(".maps");
23 
24 SEC("socket")
25 __log_level(2)
26 __msg("0: (79) r1 = *(u64 *)(r10 -8)        ; use: fp0-8")
27 __msg("1: (79) r2 = *(u64 *)(r10 -24)       ; use: fp0-24")
28 __msg("2: (7b) *(u64 *)(r10 -8) = r1        ; def: fp0-8")
29 __naked void simple_read_simple_write(void)
30 {
31 	asm volatile (
32 	"r1 = *(u64 *)(r10 - 8);"
33 	"r2 = *(u64 *)(r10 - 24);"
34 	"*(u64 *)(r10 - 8) = r1;"
35 	"r0 = 0;"
36 	"exit;"
37 	::: __clobber_all);
38 }
39 
40 SEC("socket")
41 __log_level(2)
42 __msg("2: (79) r0 = *(u64 *)(r10 -8)        ; use: fp0-8")
43 __msg("6: (79) r0 = *(u64 *)(r10 -16)       ; use: fp0-16")
44 __naked void read_write_join(void)
45 {
46 	asm volatile (
47 	"call %[bpf_get_prandom_u32];"
48 	"if r0 > 42 goto 1f;"
49 	"r0 = *(u64 *)(r10 - 8);"
50 	"*(u64 *)(r10 - 32) = r0;"
51 	"*(u64 *)(r10 - 40) = r0;"
52 	"exit;"
53 "1:"
54 	"r0 = *(u64 *)(r10 - 16);"
55 	"*(u64 *)(r10 - 32) = r0;"
56 	"exit;"
57 	:: __imm(bpf_get_prandom_u32)
58 	: __clobber_all);
59 }
60 
61 SEC("socket")
62 __log_level(2)
63 __msg("stack use/def subprog#0 must_write_not_same_slot (d0,cs0):")
64 /*
65  * 'r2 += r1' adds a scalar, so the offset is lost (off_cnt == 0): no def,
66  * but the write conservatively marks the whole frame as may_def.
67  */
68 __msg("6: (7b) *(u64 *)(r2 +0) = r0         ; may_def: fp0-8..-{{(512|2048)}}")
69 __msg("Program dump")
70 __naked void must_write_not_same_slot(void)
71 {
72 	asm volatile (
73 	"call %[bpf_get_prandom_u32];"
74 	"r1 = -8;"
75 	"if r0 > 42 goto 1f;"
76 	"r1 = -16;"
77 "1:"
78 	"r2 = r10;"
79 	"r2 += r1;"
80 	"*(u64 *)(r2 + 0) = r0;"
81 	"exit;"
82 	:: __imm(bpf_get_prandom_u32)
83 	: __clobber_all);
84 }
85 
86 SEC("socket")
87 __log_level(2)
88 __msg("stack use/def subprog#0 may_write_two_precise_offsets (d0,cs0):")
89 /*
90  * r1 is fp-8 or fp-16, with both offsets tracked precisely (off_cnt == 2).
91  * A write through it cannot set def, but marks both candidate slots as may_def.
92  */
93 __msg("6: (7b) *(u64 *)(r1 +0) = r0         ; may_def: fp0-8 fp0-16")
94 __naked void may_write_two_precise_offsets(void)
95 {
96 	asm volatile (
97 	"call %[bpf_get_prandom_u32];"
98 	"r1 = r10;"
99 	"if r0 > 42 goto 1f;"
100 	"r1 += -8;"
101 	"goto 2f;"
102 "1:"
103 	"r1 += -16;"
104 "2:"
105 	"*(u64 *)(r1 + 0) = r0;"
106 	"exit;"
107 	:: __imm(bpf_get_prandom_u32)
108 	: __clobber_all);
109 }
110 
111 SEC("socket")
112 __log_level(2)
113 __msg("0: (7a) *(u64 *)(r10 -8) = 0         ; def: fp0-8")
114 __msg("5: (85) call bpf_map_lookup_elem#1   ; use: fp0-8h")
115 __naked void must_write_not_same_type(void)
116 {
117 	asm volatile (
118 	"*(u64*)(r10 - 8) = 0;"
119 	"r2 = r10;"
120 	"r2 += -8;"
121 	"r1 = %[map] ll;"
122 	"call %[bpf_map_lookup_elem];"
123 	"if r0 != 0 goto 1f;"
124 	"r0 = r10;"
125 	"r0 += -16;"
126 "1:"
127 	"*(u64 *)(r0 + 0) = 42;"
128 	"exit;"
129 	:
130         : __imm(bpf_get_prandom_u32),
131 	  __imm(bpf_map_lookup_elem),
132 	  __imm_addr(map)
133 	: __clobber_all);
134 }
135 
136 SEC("socket")
137 __log_level(2)
138 /* Callee writes fp[0]-8: the def is summarized as may_def at the call site */
139 __msg("stack use/def subprog#0 caller_stack_write (d0,cs0):")
140 __msg("2: (85) call pc+1                    ; may_def: fp0-8")
141 __msg("stack use/def subprog#1 write_first_param (d1,cs2):")
142 __msg("4: (7a) *(u64 *)(r1 +0) = 7          ; def: fp0-8 may_def: fp0-8")
143 __naked void caller_stack_write(void)
144 {
145 	asm volatile (
146 	"r1 = r10;"
147 	"r1 += -8;"
148 	"call write_first_param;"
149 	"exit;"
150 	::: __clobber_all);
151 }
152 
153 static __used __naked void write_first_param(void)
154 {
155 	asm volatile (
156 	"*(u64 *)(r1 + 0) = 7;"
157 	"r0 = 0;"
158 	"exit;"
159 	::: __clobber_all);
160 }
161 
162 struct cnt8 { long v; };
163 
164 __noinline int global_noop_arg(struct cnt8 *p)
165 {
166 	return 0;
167 }
168 
169 SEC("socket")
170 __log_level(2)
171 /* Call to a global function: the argument is described by its declared type. */
172 __msg("stack use/def subprog#0 global_callee_arg (d0,cs0):")
173 __msg("3: (85) call pc+{{.*}}; use: fp0-8 def: fp0-8 may_def: fp0-8")
174 __naked void global_callee_arg(void)
175 {
176 	asm volatile (
177 	"*(u64 *)(r10 - 8) = 0;"
178 	"r1 = r10;"
179 	"r1 += -8;"
180 	"call global_noop_arg;"
181 	"exit;"
182 	::: __clobber_all);
183 }
184 
185 __noinline int global_int_arg(int x)
186 {
187 	return 0;
188 }
189 
190 SEC("socket")
191 __success __log_level(2)
192 __msg("stack use/def subprog#0 global_noop_int_arg (d0,cs0):")
193 __msg_next("  0: (bf) r1 = r10{{$}}")
194 __msg_next("  1: (07) r1 += -8{{$}}")
195 __msg_next("  2: (1f) r1 -= r10{{$}}")
196 __msg_next("  3: (85) call pc+1{{$}}")
197 __msg_next("  4: (95) exit")
198 __naked void global_noop_int_arg(void)
199 {
200 	asm volatile (
201 	"r1 = r10;"
202 	"r1 += -8;"
203 	"r1 -= r10;"		/* This is a scalar now. */
204 	"call global_int_arg;"	/* And should not be tracked as stack effect. */
205 	"exit;"
206 	::: __clobber_all);
207 }
208 
209 static char global_dynptr_buf[8];
210 
211 __noinline int global_noop_dynptr_arg(struct bpf_dynptr *p)
212 {
213 	return 0;
214 }
215 
216 SEC("socket")
217 __success __log_level(2)
218 /* A global dynptr argument may read/write only its 16 bytes, with no definite write. */
219 __msg("stack use/def subprog#0 global_callee_dynptr_arg (d0,cs0):")
220 __msg("  7: (bf) r1 = r10{{$}}")
221 __msg_next("  8: (07) r1 += -32{{$}}")
222 __msg_next("  9: (85) call pc+1{{ +}}; use: fp0-24 fp0-32 may_def: fp0-24 fp0-32{{$}}")
223 __msg_next(" 10: (95) exit{{$}}")
224 __naked void global_callee_dynptr_arg(void)
225 {
226 	asm volatile (
227 	"r1 = %[global_dynptr_buf] ll;"
228 	"r2 = 8;"
229 	"r3 = 0;"
230 	"r4 = r10;"
231 	"r4 += -32;"
232 	"call %[bpf_dynptr_from_mem];"
233 	"r1 = r10;"
234 	"r1 += -32;"
235 	"call global_noop_dynptr_arg;"
236 	"exit;"
237 	:: __imm_addr(global_dynptr_buf), __imm(bpf_dynptr_from_mem)
238 	: __clobber_all);
239 }
240 
241 struct task_struct { int pid; };
242 
243 __noinline int global_noop_btf_arg(struct task_struct *p __arg_untrusted)
244 {
245 	return 0;
246 }
247 
248 SEC("socket")
249 __success __log_level(2)
250 /* Untrusted BTF pointer arguments need no caller stack state. */
251 __msg("stack use/def subprog#0 global_callee_btf_arg (d0,cs0):")
252 __msg_next("  0: (7a) *(u64 *)(r10 -8) = 0{{.*}}")
253 __msg_next("  1: (bf) r1 = r10{{$}}")
254 __msg_next("  2: (07) r1 += -8{{$}}")
255 __msg_next("  3: (85) call pc+1{{$}}")
256 __msg_next("  4: (95) exit{{$}}")
257 __naked void global_callee_btf_arg(void)
258 {
259 	asm volatile (
260 	"*(u64 *)(r10 - 8) = 0;"
261 	"r1 = r10;"
262 	"r1 += -8;"
263 	"call global_noop_btf_arg;"
264 	"exit;"
265 	::: __clobber_all);
266 }
267 
268 /*
269  * Cross-frame imprecise write: imprecise_frame_writer() receives a pointer
270  * into the caller's frame (frame 0) but conditionally replaces it with a
271  * pointer into its own frame (frame 1). At the store the two are joined into
272  * ARG_IMPRECISE (frame is unknown, mask = {0,1}), so the offset is dropped and
273  * the whole of both candidate frames is conservatively marked as may_def.
274  */
275 SEC("socket")
276 __log_level(2)
277 /* The callee's write into the caller frame is summarized at the call site. */
278 __msg("stack use/def subprog#0 imprecise_frame_write (d0,cs0):")
279 __msg("2: (85) call pc+2                    ; may_def: fp0-8..-{{(512|2048)}}")
280 __msg("stack use/def subprog#1 imprecise_frame_writer (d1,cs2):")
281 __msg("12: (7b) *(u64 *)(r1 +0) = r2         ; may_def: fp1-8..-{{(512|2048)}} fp0-8..-{{(512|2048)}}")
282 __naked void imprecise_frame_write(void)
283 {
284 	asm volatile (
285 	"r1 = r10;"
286 	"r1 += -8;"
287 	"call imprecise_frame_writer;"
288 	"r0 = 0;"
289 	"exit;"
290 	::: __clobber_all);
291 }
292 
293 static __used __naked void imprecise_frame_writer(void)
294 {
295 	asm volatile (
296 	"r6 = r1;"			/* save arg: pointer into frame 0 */
297 	"call %[bpf_get_prandom_u32];"
298 	"r1 = r6;"			/* r1 = frame0-8 (arg) */
299 	"if r0 == 0 goto 1f;"
300 	"r1 = r10;"
301 	"r1 += -8;"			/* r1 = frame1-8 (own frame) */
302 "1:"
303 	"r2 = 0;"
304 	"*(u64 *)(r1 + 0) = r2;"	/* join frame0-8 | frame1-8 -> imprecise */
305 	"r0 = 0;"
306 	"exit;"
307 	:: __imm(bpf_get_prandom_u32)
308 	: __clobber_all);
309 }
310 
311 SEC("socket")
312 __log_level(2)
313 __msg("stack use/def subprog#0 caller_stack_read (d0,cs0):")
314 __msg("2: (85) call pc+{{.*}}                   ; use: fp0-8{{$}}")
315 __msg("5: (85) call pc+{{.*}}                   ; use: fp0-16{{$}}")
316 __msg("stack use/def subprog#1 read_first_param (d1,cs2):")
317 __msg("7: (79) r0 = *(u64 *)(r1 +0)         ; use: fp0-8{{$}}")
318 __msg("8: (95) exit")
319 __msg("stack use/def subprog#1 read_first_param (d1,cs5):")
320 __msg("7: (79) r0 = *(u64 *)(r1 +0)         ; use: fp0-16{{$}}")
321 __msg("8: (95) exit")
322 __naked void caller_stack_read(void)
323 {
324 	asm volatile (
325 	"r1 = r10;"
326 	"r1 += -8;"
327 	"call read_first_param;"
328 	"r1 = r10;"
329 	"r1 += -16;"
330 	"call read_first_param;"
331 	"exit;"
332 	::: __clobber_all);
333 }
334 
335 static __used __naked void read_first_param(void)
336 {
337 	asm volatile (
338 	"r0 = *(u64 *)(r1 + 0);"
339 	"exit;"
340 	::: __clobber_all);
341 }
342 
343 SEC("socket")
344 __success
345 __naked void arg_track_join_convergence(void)
346 {
347 	asm volatile (
348 	"r1 = 1;"
349 	"r2 = 2;"
350 	"call arg_track_join_convergence_subprog;"
351 	"r0 = 0;"
352 	"exit;"
353 	::: __clobber_all);
354 }
355 
356 static __used __naked void arg_track_join_convergence_subprog(void)
357 {
358 	asm volatile (
359 	"if r1 == 0 goto 1f;"
360 	"r0 = r1;"
361 	"goto 2f;"
362 "1:"
363 	"r0 = r2;"
364 "2:"
365 	"r0 = 0;"
366 	"exit;"
367 	::: __clobber_all);
368 }
369 
370 SEC("socket")
371 __flag(BPF_F_TEST_STATE_FREQ)
372 __log_level(2)
373 /* fp0-8 consumed at insn 9, dead by insn 11. stack_def at insn 4 kills slots 0,1. */
374 __msg("4: (7b) *(u64 *)(r10 -8) = r0        ; def: fp0-8")
375 /* stack_use at call site: callee reads fp0-8, slots 0,1 live */
376 __msg("7: (85) call pc+{{.*}}               ; use: fp0-8")
377 /* read_first_param2: no caller stack live inside callee after first read */
378 __msg("9: (79) r0 = *(u64 *)(r1 +0)         ; use: fp0-8")
379 __msg("10: (b7) r0 = 0{{$}}")
380 __msg("11: (05) goto pc+0{{$}}")
381 __msg("12: (95) exit")
382 /*
383  * Checkpoint at goto +0 fires because fp0-8 is dead → state pruning.
384  */
385 __msg("12: safe")
386 __naked void caller_stack_pruning(void)
387 {
388 	asm volatile (
389 	"call %[bpf_get_prandom_u32];"
390 	"if r0 == 42 goto 1f;"
391 	"r0 = %[map] ll;"
392 "1:"
393 	"*(u64 *)(r10 - 8) = r0;"
394 	"r1 = r10;"
395 	"r1 += -8;"
396 	/*
397 	 * fp[0]-8 is either pointer to map or a scalar,
398 	 * preventing state pruning at checkpoint created for call.
399 	 */
400 	"call read_first_param2;"
401 	"exit;"
402 	:
403 	: __imm(bpf_get_prandom_u32),
404 	  __imm_addr(map)
405 	: __clobber_all);
406 }
407 
408 static __used __naked void read_first_param2(void)
409 {
410 	asm volatile (
411 	"r0 = *(u64 *)(r1 + 0);"
412 	"r0 = 0;"
413 	/*
414 	 * Checkpoint at goto +0 should fire,
415 	 * as caller stack fp[0]-8 is not alive at this point.
416 	 */
417 	"goto +0;"
418 	"exit;"
419 	::: __clobber_all);
420 }
421 
422 SEC("socket")
423 __flag(BPF_F_TEST_STATE_FREQ)
424 __failure
425 __msg("Possibly NULL pointer passed to trusted R1")
426 __naked void caller_stack_pruning_callback(void)
427 {
428 	asm volatile (
429 	"r0 = %[map] ll;"
430 	"*(u64 *)(r10 - 8) = r0;"
431 	"r1 = 2;"
432 	"r2 = loop_cb ll;"
433 	"r3 = r10;"
434 	"r3 += -8;"
435 	"r4 = 0;"
436 	/*
437 	 * fp[0]-8 is either pointer to map or a scalar,
438 	 * preventing state pruning at checkpoint created for call.
439 	 */
440 	"call %[bpf_loop];"
441 	"r0 = 42;"
442 	"exit;"
443 	:
444 	: __imm(bpf_get_prandom_u32),
445 	  __imm(bpf_loop),
446 	  __imm_addr(map)
447 	: __clobber_all);
448 }
449 
450 static __used __naked void loop_cb(void)
451 {
452 	asm volatile (
453 	/*
454 	 * Checkpoint at function entry should not fire, as caller
455 	 * stack fp[0]-8 is alive at this point.
456 	 */
457 	"r6 = r2;"
458 	"r1 = *(u64 *)(r6 + 0);"
459 	"*(u64*)(r10 - 8) = 7;"
460 	"r2 = r10;"
461 	"r2 += -8;"
462 	"call %[bpf_map_lookup_elem];"
463 	/*
464 	 * This should stop verifier on a second loop iteration,
465 	 * but only if verifier correctly maintains that fp[0]-8
466 	 * is still alive.
467 	 */
468 	"*(u64 *)(r6 + 0) = 0;"
469 	"r0 = 0;"
470 	"exit;"
471 	:
472 	: __imm(bpf_map_lookup_elem),
473 	  __imm(bpf_get_prandom_u32)
474 	: __clobber_all);
475 }
476 
477 /*
478  * Because of a bug in verifier.c:compute_postorder()
479  * the program below overflowed traversal queue in that function.
480  */
481 SEC("socket")
482 __naked void syzbot_postorder_bug1(void)
483 {
484 	asm volatile (
485 	"r0 = 0;"
486 	"if r0 != 0 goto -1;"
487 	"exit;"
488 	::: __clobber_all);
489 }
490 
491 struct {
492         __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
493         __uint(max_entries, 1);
494         __type(key, __u32);
495         __type(value, __u32);
496 } map_array SEC(".maps");
497 
498 SEC("socket")
499 __failure __msg("invalid read from stack R2 off=-4096 size=8")
500 __flag(BPF_F_TEST_STATE_FREQ)
501 __naked unsigned long caller_stack_write_tail_call(void)
502 {
503         asm volatile (
504 	"r6 = r1;"
505 	"*(u64 *)(r10 - 8) = -8;"
506         "call %[bpf_get_prandom_u32];"
507         "if r0 != 42 goto 1f;"
508         "goto 2f;"
509   "1:"
510         "*(u64 *)(r10 - 8) = -4096;"
511   "2:"
512         "r1 = r6;"
513         "r2 = r10;"
514         "r2 += -8;"
515         "call write_tail_call;"
516         "r1 = *(u64 *)(r10 - 8);"
517         "r2 = r10;"
518         "r2 += r1;"
519         "r0 = *(u64 *)(r2 + 0);"
520         "exit;"
521         :: __imm(bpf_get_prandom_u32)
522 	: __clobber_all);
523 }
524 
525 static __used __naked unsigned long write_tail_call(void)
526 {
527         asm volatile (
528         "r6 = r2;"
529         "r2 = %[map_array] ll;"
530         "r3 = 0;"
531         "call %[bpf_tail_call];"
532         "*(u64 *)(r6 + 0) = -16;"
533         "r0 = 0;"
534         "exit;"
535 	:
536 	: __imm(bpf_tail_call),
537           __imm_addr(map_array)
538         : __clobber_all);
539 }
540 
541 /* Test precise subprog stack access analysis.
542  * Caller passes fp-32 (SPI 3) to callee that only accesses arg+0 and arg+8
543  * (SPIs 3 and 2). Slots 0 and 1 should NOT be live at the call site.
544  *
545  * Insn layout:
546  *   0: *(u64*)(r10 - 8) = 0      write SPI 0
547  *   1: *(u64*)(r10 - 16) = 0     write SPI 1
548  *   2: *(u64*)(r10 - 24) = 0     write SPI 2
549  *   3: *(u64*)(r10 - 32) = 0     write SPI 3
550  *   4: r1 = r10
551  *   5: r1 += -32
552  *   6: call precise_read_two      passes fp-32 (SPI 3)
553  *   7: r0 = 0
554  *   8: exit
555  *
556  * At insn 6 only SPIs 2,3 should be live (slots 4-7, 0xf0).
557  * SPIs 0,1 are written but never read → dead.
558  */
559 SEC("socket")
560 __log_level(2)
561 __msg("6: (85) call pc+{{.*}}                   ; use: fp0-24 fp0-32{{$}}")
562 __naked void subprog_precise_stack_access(void)
563 {
564 	asm volatile (
565 	"*(u64 *)(r10 - 8) = 0;"
566 	"*(u64 *)(r10 - 16) = 0;"
567 	"*(u64 *)(r10 - 24) = 0;"
568 	"*(u64 *)(r10 - 32) = 0;"
569 	"r1 = r10;"
570 	"r1 += -32;"
571 	"call precise_read_two;"
572 	"r0 = 0;"
573 	"exit;"
574 	::: __clobber_all);
575 }
576 
577 /* Callee reads only at arg+0 (SPI 3) and arg+8 (SPI 2) */
578 static __used __naked void precise_read_two(void)
579 {
580 	asm volatile (
581 	"r0 = *(u64 *)(r1 + 0);"
582 	"r2 = *(u64 *)(r1 + 8);"
583 	"r0 = 0;"
584 	"exit;"
585 	::: __clobber_all);
586 }
587 
588 /* Test that multi-level subprog calls (callee passes arg-derived ptr
589  * to another BPF subprog) are analyzed precisely.
590  *
591  * Caller passes fp-32 (SPI 3). The callee forwards it to inner_callee.
592  * inner_callee only reads at offset 0 from the pointer.
593  * The analysis recurses into forward_to_inner -> inner_callee and
594  * determines only SPI 3 is accessed (slots 6-7, 0xc0), not all of SPIs 0-3.
595  *
596  * Insn layout:
597  *   0: *(u64*)(r10 - 8) = 0      write SPI 0
598  *   1: *(u64*)(r10 - 16) = 0     write SPI 1
599  *   2: *(u64*)(r10 - 24) = 0     write SPI 2
600  *   3: *(u64*)(r10 - 32) = 0     write SPI 3
601  *   4: r1 = r10
602  *   5: r1 += -32
603  *   6: call forward_to_inner      passes fp-32 (SPI 3)
604  *   7: r0 = 0
605  *   8: exit
606  */
607 SEC("socket")
608 __log_level(2)
609 __msg("6: (85) call pc+{{.*}}                   ; use: fp0-32{{$}}")
610 __naked void subprog_multilevel_conservative(void)
611 {
612 	asm volatile (
613 	"*(u64 *)(r10 - 8) = 0;"
614 	"*(u64 *)(r10 - 16) = 0;"
615 	"*(u64 *)(r10 - 24) = 0;"
616 	"*(u64 *)(r10 - 32) = 0;"
617 	"r1 = r10;"
618 	"r1 += -32;"
619 	"call forward_to_inner;"
620 	"r0 = 0;"
621 	"exit;"
622 	::: __clobber_all);
623 }
624 
625 /* Forwards arg to another subprog */
626 static __used __naked void forward_to_inner(void)
627 {
628 	asm volatile (
629 	"call inner_callee;"
630 	"r0 = 0;"
631 	"exit;"
632 	::: __clobber_all);
633 }
634 
635 static __used __naked void inner_callee(void)
636 {
637 	asm volatile (
638 	"r0 = *(u64 *)(r1 + 0);"
639 	"r0 = 0;"
640 	"exit;"
641 	::: __clobber_all);
642 }
643 
644 /* Test multi-frame precision loss: callee consumes caller stack early,
645  * but static liveness keeps it live at pruning points inside callee.
646  *
647  * Caller stores map_ptr or scalar(42) at fp-8, then calls
648  * consume_and_call_inner. The callee reads fp0-8 at entry (consuming
649  * the slot), then calls do_nothing2. After do_nothing2 returns (a
650  * pruning point), fp-8 should be dead -- the read already happened.
651  * But because the call instruction's stack_use includes SPI 0, the
652  * static live_stack_before at insn 7 is 0x1, keeping fp-8 live inside
653  * the callee and preventing state pruning between the two paths.
654  *
655  * Insn layout:
656  *   0: call bpf_get_prandom_u32
657  *   1: if r0 == 42 goto pc+2    -> insn 4
658  *   2: r0 = map ll (ldimm64 part1)
659  *   3: (ldimm64 part2)
660  *   4: *(u64)(r10 - 8) = r0     fp-8 = map_ptr OR scalar(42)
661  *   5: r1 = r10
662  *   6: r1 += -8
663  *   7: call consume_and_call_inner
664  *   8: r0 = 0
665  *   9: exit
666  *
667  * At insn 7, live_stack_before = 0x3 (slots 0-1 live due to stack_use).
668  * At insn 8, live_stack_before = 0x0 (SPI 0 dead, caller doesn't need it).
669  */
670 SEC("socket")
671 __flag(BPF_F_TEST_STATE_FREQ)
672 __log_level(2)
673 __success
674 __msg(" 7: (85) call pc+{{.*}}                   ; use: fp0-8")
675 __msg(" 8: {{.*}} (b7)")
676 __naked void callee_consumed_caller_stack(void)
677 {
678 	asm volatile (
679 	"call %[bpf_get_prandom_u32];"
680 	"if r0 == 42 goto 1f;"
681 	"r0 = %[map] ll;"
682 "1:"
683 	"*(u64 *)(r10 - 8) = r0;"
684 	"r1 = r10;"
685 	"r1 += -8;"
686 	"call consume_and_call_inner;"
687 	"r0 = 0;"
688 	"exit;"
689 	:
690 	: __imm(bpf_get_prandom_u32),
691 	  __imm_addr(map)
692 	: __clobber_all);
693 }
694 
695 static __used __naked void consume_and_call_inner(void)
696 {
697 	asm volatile (
698 	"r0 = *(u64 *)(r1 + 0);"	/* read fp[0]-8 into caller-saved r0 */
699 	"call do_nothing2;"		/* inner call clobbers r0 */
700 	"r0 = 0;"
701 	"goto +0;"			/* checkpoint */
702 	"r0 = 0;"
703 	"goto +0;"			/* checkpoint */
704 	"r0 = 0;"
705 	"goto +0;"			/* checkpoint */
706 	"r0 = 0;"
707 	"goto +0;"			/* checkpoint */
708 	"exit;"
709 	::: __clobber_all);
710 }
711 
712 static __used __naked void do_nothing2(void)
713 {
714 	asm volatile (
715 	"r0 = 0;"
716 	"r0 = 0;"
717 	"r0 = 0;"
718 	"r0 = 0;"
719 	"r0 = 0;"
720 	"r0 = 0;"
721 	"r0 = 0;"
722 	"exit;"
723 	::: __clobber_all);
724 }
725 
726 /*
727  * Reproducer for unsound pruning when clean_verifier_state() promotes
728  * live STACK_ZERO bytes to STACK_MISC.
729  *
730  * Program shape:
731  * - Build key at fp-4:
732  *   - path A keeps key byte as STACK_ZERO;
733  *   - path B writes unknown byte making it STACK_MISC.
734  * - Branches merge at a prune point before map_lookup.
735  * - map_lookup on ARRAY map is value-sensitive to constant zero key:
736  *   - path A: const key 0 => PTR_TO_MAP_VALUE (non-NULL);
737  *   - path B: non-const key => PTR_TO_MAP_VALUE_OR_NULL.
738  * - Dereference lookup result without null check.
739  *
740  * Note this behavior won't trigger at fp-8, since the verifier will
741  * track 32-bit scalar spill differently as spilled_ptr.
742  *
743  * Correct verifier behavior: reject (path B unsafe).
744  * With blanket STACK_ZERO->STACK_MISC promotion on live slots, cached path A
745  * state can be generalized and incorrectly prune path B, making program load.
746  */
747 SEC("socket")
748 __flag(BPF_F_TEST_STATE_FREQ)
749 __failure __msg("R0 invalid mem access 'map_value_or_null'")
750 __naked void stack_zero_to_misc_unsound_array_lookup(void)
751 {
752 	asm volatile (
753 	/* key at fp-4: all bytes STACK_ZERO */
754 	"*(u32 *)(r10 - 4) = 0;"
755 	"call %[bpf_get_prandom_u32];"
756 	/* fall-through (path A) explored first */
757 	"if r0 != 0 goto l_nonconst%=;"
758 	/* path A: keep key constant zero */
759 	"goto l_lookup%=;"
760 "l_nonconst%=:"
761 	/* path B: key byte turns to STACK_MISC, key no longer const */
762 	"*(u8 *)(r10 - 4) = r0;"
763 "l_lookup%=:"
764 	/* value-sensitive lookup */
765 	"r2 = r10;"
766 	"r2 += -4;"
767 	"r1 = %[array_map_8b] ll;"
768 	"call %[bpf_map_lookup_elem];"
769 	/* unsafe when lookup result is map_value_or_null */
770 	"r0 = *(u64 *)(r0 + 0);"
771 	"exit;"
772 	:
773 	: __imm(bpf_get_prandom_u32),
774 	  __imm(bpf_map_lookup_elem),
775 	  __imm_addr(array_map_8b)
776 	: __clobber_all);
777 }
778 
779 /*
780  * Subprog variant of stack_zero_to_misc_unsound_array_lookup.
781  *
782  * Check unsound pruning when a callee modifies the caller's
783  * stack through a pointer argument.
784  *
785  * Program shape:
786  *   main:
787  *     *(u32)(fp - 4) = 0            key = 0 (all bytes STACK_ZERO)
788  *     r1 = fp - 4
789  *     call maybe_clobber_key        may overwrite key[0] with scalar
790  *     <-- prune point: two states meet here -->
791  *     r2 = fp - 4
792  *     r1 = array_map_8b
793  *     call bpf_map_lookup_elem      value-sensitive on const-zero key
794  *     r0 = *(u64)(r0 + 0)           deref without null check
795  *     exit
796  *
797  *   maybe_clobber_key(r1):
798  *     r6 = r1                       save &key
799  *     call bpf_get_prandom_u32
800  *     if r0 == 0 goto skip          path A: key stays STACK_ZERO
801  *     *(u8)(r6 + 0) = r0            path B: key[0] becomes STACK_MISC
802  *   skip:
803  *     r0 = 0
804  *     exit
805  *
806  * Path A: const-zero key => array lookup => PTR_TO_MAP_VALUE => deref OK.
807  * Path B: non-const key  => array lookup => PTR_TO_MAP_VALUE_OR_NULL => UNSAFE.
808  *
809  * If the cleaner collapses STACK_ZERO -> STACK_MISC for the live key
810  * slot, path A's cached state matches path B, pruning the unsafe path.
811  *
812  * Correct verifier behaviour: reject.
813  */
814 SEC("socket")
815 __flag(BPF_F_TEST_STATE_FREQ)
816 __failure __msg("R0 invalid mem access 'map_value_or_null'")
817 __naked void subprog_stack_zero_to_misc_unsound(void)
818 {
819 	asm volatile (
820 	/* key at fp-4: all bytes STACK_ZERO */
821 	"*(u32 *)(r10 - 4) = 0;"
822 	/* subprog may clobber key[0] with a scalar byte */
823 	"r1 = r10;"
824 	"r1 += -4;"
825 	"call maybe_clobber_key;"
826 	/* value-sensitive array lookup */
827 	"r2 = r10;"
828 	"r2 += -4;"
829 	"r1 = %[array_map_8b] ll;"
830 	"call %[bpf_map_lookup_elem];"
831 	/* unsafe when result is map_value_or_null (path B) */
832 	"r0 = *(u64 *)(r0 + 0);"
833 	"exit;"
834 	:
835 	: __imm(bpf_map_lookup_elem),
836 	  __imm_addr(array_map_8b)
837 	: __clobber_all);
838 }
839 
840 static __used __naked void maybe_clobber_key(void)
841 {
842 	asm volatile (
843 	"r6 = r1;"
844 	"call %[bpf_get_prandom_u32];"
845 	/* path A (r0==0): key stays STACK_ZERO, explored first */
846 	"if r0 == 0 goto 1f;"
847 	/* path B (r0!=0): overwrite key[0] with scalar */
848 	"*(u8 *)(r6 + 0) = r0;"
849 	"1:"
850 	"r0 = 0;"
851 	"exit;"
852 	:: __imm(bpf_get_prandom_u32)
853 	: __clobber_all);
854 }
855 
856 /*
857  * Demonstrate that subprog arg spill/reload breaks arg tracking,
858  * inflating caller stack liveness and preventing state pruning.
859  *
860  * modifier2(fp-24) has two paths: one writes a scalar to *(r1+8)
861  * = caller fp-16, the other leaves it as zero.  After modifier2
862  * returns, fp-16 is never read again — it is dead.
863  *
864  * spill_reload_reader2(fp-24) only reads caller fp-8 via
865  * *(r1+16), but it spills r1 across a helper call.  This
866  * breaks compute_subprog_arg_access(): the reload from callee
867  * stack cannot be connected back to arg1, so arg1 access goes
868  * "all (conservative)".  At the call site (r1 = fp-24, slot 5)
869  * apply_callee_stack_access() marks slots 0..5 as stack_use —
870  * pulling fp-16 (slots 2-3) into live_stack_before even though
871  * the reader never touches it.
872  *
873  * Result: at modifier2's return point two states with different
874  * fp-16 values cannot be pruned.
875  *
876  * With correct (or old dynamic) liveness fp-16 is dead at that
877  * point and the states prune → "6: safe" appears in the log.
878  */
879 SEC("socket")
880 __flag(BPF_F_TEST_STATE_FREQ)
881 __log_level(2)
882 __success
883 __msg("6: safe")
884 __naked void spill_reload_inflates_stack_liveness(void)
885 {
886 	asm volatile (
887 	/* struct at fp-24: { ctx; ptr; tail; } */
888 	"*(u64 *)(r10 - 24) = r1;"		/* fp-24 = ctx */
889 	"*(u64 *)(r10 - 16) = r1;"		/* fp-16 = ctx (STACK_SPILL ptr) */
890 	"*(u64 *)(r10 - 8) = 0;"		/* fp-8  = tail */
891 	/* modifier2 writes different values to fp-16 on two paths */
892 	"r1 = r10;"
893 	"r1 += -24;"
894 	"call modifier2;"
895 	/* insn 6: prune point — two states with different fp-16
896 	 * path A: fp-16 = STACK_MISC  (scalar overwrote pointer)
897 	 * path B: fp-16 = STACK_SPILL (original ctx pointer)
898 	 * STACK_MISC does NOT subsume STACK_SPILL(ptr),
899 	 * so pruning fails unless fp-16 is cleaned (dead).
900 	 */
901 	"r1 = r10;"
902 	"r1 += -24;"
903 	"call spill_reload_reader2;"		/* reads fp-8 via *(r1+16) */
904 	"r0 = 0;"
905 	"exit;"
906 	::: __clobber_all);
907 }
908 
909 /* Two paths: one writes a scalar to *(r1+8) = caller fp-16,
910  * the other leaves it unchanged.  Both return 0 via separate
911  * exits to prevent pruning inside the subprog at the merge.
912  */
913 static __used __naked void modifier2(void)
914 {
915 	asm volatile (
916 	"r6 = r1;"
917 	"call %[bpf_get_prandom_u32];"
918 	"if r0 == 0 goto 1f;"
919 	"*(u64 *)(r6 + 8) = r0;"		/* fp-16 = random */
920 	"r0 = 0;"
921 	"exit;"					/* path A exit */
922 	"1:"
923 	"r0 = 0;"
924 	"exit;"					/* path B exit */
925 	:: __imm(bpf_get_prandom_u32)
926 	: __clobber_all);
927 }
928 
929 /* Receives r1 = caller fp-24.  Only reads *(r1+16) = fp-8.
930  * Spills r1 across a helper call → arg tracking goes conservative →
931  * slots 0..5 all appear used instead of just slot 1 (fp-8).
932  */
933 static __used __naked void spill_reload_reader2(void)
934 {
935 	asm volatile (
936 	"*(u64 *)(r10 - 8) = r1;"		/* spill arg1 */
937 	"call %[bpf_get_prandom_u32];"		/* clobbers r1-r5 */
938 	"r1 = *(u64 *)(r10 - 8);"		/* reload arg1 */
939 	"r0 = *(u64 *)(r1 + 16);"		/* read caller fp-8 */
940 	"r0 = 0;"
941 	"exit;"
942 	:: __imm(bpf_get_prandom_u32)
943 	: __clobber_all);
944 }
945 
946 /* BTF FUNC records are not generated for kfuncs referenced
947  * from inline assembly. These records are necessary for
948  * libbpf to link the program. The function below is a hack
949  * to ensure that BTF FUNC records are generated.
950  */
951 void __kfunc_btf_root(void)
952 {
953 	bpf_iter_num_new(0, 0, 0);
954 	bpf_iter_num_next(0);
955 	bpf_iter_num_destroy(0);
956 }
957 
958 /* Test that open-coded iterator kfunc arguments get precise stack
959  * liveness tracking. struct bpf_iter_num is 8 bytes (1 SPI).
960  *
961  * Insn layout:
962  *   0: *(u64*)(r10 - 8) = 0      write SPI 0 (dead)
963  *   1: *(u64*)(r10 - 16) = 0     write SPI 1 (dead)
964  *   2: r1 = r10
965  *   3: r1 += -24                 iter state at fp-24 (SPI 2)
966  *   4: r2 = 0
967  *   5: r3 = 10
968  *   6: call bpf_iter_num_new     defines SPI 2 (KF_ITER_NEW) → 0x0
969  *   7-8: r1 = fp-24
970  *   9: call bpf_iter_num_next    uses SPI 2 → 0x30
971  *  10: if r0 == 0 goto 2f
972  *  11: goto 1b
973  *  12-13: r1 = fp-24
974  *  14: call bpf_iter_num_destroy uses SPI 2 → 0x30
975  *  15: r0 = 0
976  *  16: exit
977  *
978  * At insn 6, SPI 2 is defined (KF_ITER_NEW initializes, doesn't read),
979  * so it kills liveness from successors. live_stack_before = 0x0.
980  * At insns 9 and 14, SPI 2 is used (iter_next/destroy read the state),
981  * so live_stack_before = 0x30.
982  */
983 SEC("socket")
984 __success __log_level(2)
985 __msg(" 6: (85) call bpf_iter_num_new{{.*}}          ; def: fp0-24 may_def: fp0-24{{$}}")
986 __msg(" 9: (85) call bpf_iter_num_next{{.*}}         ; use: fp0-24 may_def: fp0-24{{$}}")
987 __msg("14: (85) call bpf_iter_num_destroy{{.*}}      ; use: fp0-24 may_def: fp0-24{{$}}")
988 __naked void kfunc_iter_stack_liveness(void)
989 {
990 	asm volatile (
991 	"*(u64 *)(r10 - 8) = 0;"	/* SPI 0 - dead */
992 	"*(u64 *)(r10 - 16) = 0;"	/* SPI 1 - dead */
993 	"r1 = r10;"
994 	"r1 += -24;"
995 	"r2 = 0;"
996 	"r3 = 10;"
997 	"call %[bpf_iter_num_new];"
998 "1:"
999 	"r1 = r10;"
1000 	"r1 += -24;"
1001 	"call %[bpf_iter_num_next];"
1002 	"if r0 == 0 goto 2f;"
1003 	"goto 1b;"
1004 "2:"
1005 	"r1 = r10;"
1006 	"r1 += -24;"
1007 	"call %[bpf_iter_num_destroy];"
1008 	"r0 = 0;"
1009 	"exit;"
1010 	:: __imm(bpf_iter_num_new),
1011 	   __imm(bpf_iter_num_next),
1012 	   __imm(bpf_iter_num_destroy)
1013 	: __clobber_all);
1014 }
1015 
1016 /*
1017  * Test for soundness bug in static stack liveness analysis.
1018  *
1019  * The static pre-pass tracks FP-derived register offsets to determine
1020  * which stack slots are accessed. When a PTR_TO_STACK is spilled to
1021  * the stack and later reloaded, the reload (BPF_LDX) kills FP-derived
1022  * tracking, making subsequent accesses through the reloaded pointer
1023  * invisible to the static analysis.
1024  *
1025  * This causes the analysis to incorrectly mark SPI 0 as dead at the
1026  * merge point. clean_verifier_state() zeros it in the cached state,
1027  * and stacksafe() accepts the new state against STACK_INVALID,
1028  * enabling incorrect pruning.
1029  *
1030  * Path A (verified first): stores PTR_TO_MAP_VALUE in SPI 0
1031  * Path B (verified second): stores scalar 42 in SPI 0
1032  * After merge: reads SPI 0 through spilled/reloaded PTR_TO_STACK
1033  * and dereferences the result as a pointer.
1034  *
1035  * Correct behavior: reject (path B dereferences a scalar)
1036  * Bug behavior: accept (path B is incorrectly pruned)
1037  */
1038 SEC("socket")
1039 __flag(BPF_F_TEST_STATE_FREQ)
1040 __failure __msg("R0 invalid mem access 'scalar'")
1041 __naked void spill_ptr_liveness_type_confusion(void)
1042 {
1043 	asm volatile (
1044 	/* Map lookup to get PTR_TO_MAP_VALUE */
1045 	"r1 = %[map] ll;"
1046 	"*(u32 *)(r10 - 32) = 0;"
1047 	"r2 = r10;"
1048 	"r2 += -32;"
1049 	"call %[bpf_map_lookup_elem];"
1050 	"if r0 == 0 goto l_exit%=;"
1051 	/* r6 = PTR_TO_MAP_VALUE (callee-saved) */
1052 	"r6 = r0;"
1053 	/* Branch: fall-through (path A) verified first */
1054 	"call %[bpf_get_prandom_u32];"
1055 	"if r0 != 0 goto l_scalar%=;"
1056 	/* Path A: store map value ptr at SPI 0 */
1057 	"*(u64 *)(r10 - 8) = r6;"
1058 	"goto l_merge%=;"
1059 "l_scalar%=:"
1060 	/* Path B: store scalar at SPI 0 */
1061 	"r1 = 42;"
1062 	"*(u64 *)(r10 - 8) = r1;"
1063 "l_merge%=:"
1064 	/*
1065 	 * Spill PTR_TO_STACK{off=-8} to SPI 1, then reload.
1066 	 * Reload kills FP-derived tracking, hiding the
1067 	 * subsequent SPI 0 access from the static analysis.
1068 	 */
1069 	"r1 = r10;"
1070 	"r1 += -8;"
1071 	"*(u64 *)(r10 - 16) = r1;"
1072 	"goto +0;"			/* checkpoint */
1073 	"goto +0;"			/* checkpoint */
1074 	"goto +0;"			/* checkpoint */
1075 	"r1 = *(u64 *)(r10 - 16);"
1076 	/* Read SPI 0 through reloaded pointer */
1077 	"r0 = *(u64 *)(r1 + 0);"
1078 	/* Dereference: safe for map value (path A),
1079 	 * unsafe for scalar (path B).
1080 	 */
1081 	"r0 = *(u64 *)(r0 + 0);"
1082 	"exit;"
1083 "l_exit%=:"
1084 	"r0 = 0;"
1085 	"exit;"
1086 	:
1087 	: __imm(bpf_map_lookup_elem),
1088 	  __imm(bpf_get_prandom_u32),
1089 	  __imm_addr(map)
1090 	: __clobber_all);
1091 }
1092 
1093 /* === Tests for 4-byte stack slot liveness granularity === */
1094 
1095 /* Test that a 4-byte aligned write is stack_def and kills liveness.
1096  *
1097  *   0: *(u64 *)(r10 - 8) = 0      def slots 0,1 (full SPI 0)
1098  *   1: *(u32 *)(r10 - 8) = 0      def slot 1 (4-byte write kills slot 1)
1099  *   2: r0 = *(u64 *)(r10 - 8)     use slots 0,1
1100  *   3: r0 = 0
1101  *   4: exit
1102  *
1103  * At insn 1, the 4-byte write defines slot 1. Slot 0 still flows
1104  * backward from insn 2's read: live_stack_before = 0x1.
1105  */
1106 SEC("socket")
1107 __log_level(2)
1108 __msg("1: (62) *(u32 *)(r10 -8) = 0         ; def: fp0-8h")
1109 __naked void four_byte_write_kills_slot(void)
1110 {
1111 	asm volatile (
1112 	"*(u64 *)(r10 - 8) = 0;"
1113 	"*(u32 *)(r10 - 8) = 0;"
1114 	"r0 = *(u64 *)(r10 - 8);"
1115 	"r0 = 0;"
1116 	"exit;"
1117 	::: __clobber_all);
1118 }
1119 
1120 /* Test that a write to the upper half of an SPI is dead when only
1121  * the lower half is read. This was impossible at SPI granularity
1122  * where any read of the SPI kept the entire SPI live.
1123  *
1124  *   0: *(u32 *)(r10 - 8) = 0      def slot 1 (DEAD: never read)
1125  *   1: *(u32 *)(r10 - 4) = 0      def slot 0
1126  *   2: r0 = *(u32 *)(r10 - 4)     use slot 0 only
1127  *   3: r0 = 0
1128  *   4: exit
1129  *
1130  * At insn 0, nothing is live (0x0). Previously at SPI granularity,
1131  * the read at insn 2 would mark the full SPI 0 as live and the
1132  * 4-byte writes wouldn't count as def, so insn 0 would have had
1133  * SPI 0 live (0x1).
1134  */
1135 SEC("socket")
1136 __log_level(2)
1137 __msg("0: (62) *(u32 *)(r10 -8) = 0         ; def: fp0-8h")
1138 __msg("2: (61) r0 = *(u32 *)(r10 -4)        ; use: fp0-4h")
1139 __naked void dead_half_spi_write(void)
1140 {
1141 	asm volatile (
1142 	"*(u32 *)(r10 - 8) = 0;"
1143 	"*(u32 *)(r10 - 4) = 0;"
1144 	"r0 = *(u32 *)(r10 - 4);"
1145 	"r0 = 0;"
1146 	"exit;"
1147 	::: __clobber_all);
1148 }
1149 
1150 /* Test that a 4-byte read from the upper half of SPI 0 makes only
1151  * slot 1 live (0x2), not the full SPI (0x3).
1152  *
1153  *   0: *(u64 *)(r10 - 8) = 0      def slots 0,1
1154  *   1: r0 = *(u32 *)(r10 - 8)     use slot 1 only (upper half)
1155  *   2: r0 = 0
1156  *   3: exit
1157  *
1158  * At insn 1, live_stack_before = 0x2 (slot 1 only).
1159  */
1160 SEC("socket")
1161 __log_level(2)
1162 __msg("1: (61) r0 = *(u32 *)(r10 -8)        ; use: fp0-8h")
1163 __naked void four_byte_read_upper_half(void)
1164 {
1165 	asm volatile (
1166 	"*(u64 *)(r10 - 8) = 0;"
1167 	"r0 = *(u32 *)(r10 - 8);"
1168 	"r0 = 0;"
1169 	"exit;"
1170 	::: __clobber_all);
1171 }
1172 
1173 /* Test that a 2-byte write does NOT count as stack_def.
1174  * Sub-4-byte writes don't fully cover a 4-byte slot,
1175  * so liveness passes through.
1176  *
1177  *   0: *(u64 *)(r10 - 8) = 0      def slots 0,1
1178  *   1: *(u16 *)(r10 - 4) = 0      NOT stack_def (2 < 4 bytes)
1179  *   2: r0 = *(u32 *)(r10 - 4)     use slot 0
1180  *   3: r0 = 0
1181  *   4: exit
1182  *
1183  * At insn 1, slot 0 still live (0x1) because 2-byte write
1184  * didn't kill it.
1185  */
1186 SEC("socket")
1187 __log_level(2)
1188 __msg("0: (7a) *(u64 *)(r10 -8) = 0         ; def: fp0-8 may_def: fp0-8")
1189 /* 2-byte write only partially covers the upper half: may_def, but no def. */
1190 __msg("1: (6a) *(u16 *)(r10 -4) = 0         ; may_def: fp0-4h")
1191 __msg("2: (61) r0 = *(u32 *)(r10 -4)        ; use: fp0-4h")
1192 __naked void two_byte_write_no_kill(void)
1193 {
1194 	asm volatile (
1195 	"*(u64 *)(r10 - 8) = 0;"
1196 	"*(u16 *)(r10 - 4) = 0;"
1197 	"r0 = *(u32 *)(r10 - 4);"
1198 	"r0 = 0;"
1199 	"exit;"
1200 	::: __clobber_all);
1201 }
1202 
1203 /* Test that a 1-byte write does NOT count as stack_def.
1204  *
1205  *   0: *(u64 *)(r10 - 8) = 0      def slots 0,1
1206  *   1: *(u8 *)(r10 - 4) = 0       NOT stack_def (1 < 4 bytes)
1207  *   2: r0 = *(u32 *)(r10 - 4)     use slot 0
1208  *   3: r0 = 0
1209  *   4: exit
1210  *
1211  * At insn 1, slot 0 still live (0x1).
1212  */
1213 SEC("socket")
1214 __log_level(2)
1215 __msg("0: (7a) *(u64 *)(r10 -8) = 0         ; def: fp0-8")
1216 __msg("1: (72) *(u8 *)(r10 -4) = 0")
1217 __msg("2: (61) r0 = *(u32 *)(r10 -4)        ; use: fp0-4h")
1218 __naked void one_byte_write_no_kill(void)
1219 {
1220 	asm volatile (
1221 	"*(u64 *)(r10 - 8) = 0;"
1222 	"*(u8 *)(r10 - 4) = 0;"
1223 	"r0 = *(u32 *)(r10 - 4);"
1224 	"r0 = 0;"
1225 	"exit;"
1226 	::: __clobber_all);
1227 }
1228 
1229 /* Test stack access beyond fp-256 exercising the second bitmask word.
1230  * fp-264 is SPI 32, slots 64-65, which are bits 0-1 of live_stack[1].
1231  *
1232  *   0: *(u64 *)(r10 - 264) = 0     def slots 64,65
1233  *   1: r0 = *(u64 *)(r10 - 264)    use slots 64,65
1234  *   2: r0 = 0
1235  *   3: exit
1236  *
1237  * At insn 1, live_stack high word has bits 0,1 set: 0x3:0x0.
1238  */
1239 SEC("socket")
1240 __log_level(2)
1241 __msg("1: (79) r0 = *(u64 *)(r10 -264)      ; use: fp0-264")
1242 __naked void high_stack_second_bitmask_word(void)
1243 {
1244 	asm volatile (
1245 	"*(u64 *)(r10 - 264) = 0;"
1246 	"r0 = *(u64 *)(r10 - 264);"
1247 	"r0 = 0;"
1248 	"exit;"
1249 	::: __clobber_all);
1250 }
1251 
1252 /* Test that two separate 4-byte writes to each half of an SPI
1253  * together kill liveness for the full SPI.
1254  *
1255  *   0: *(u32 *)(r10 - 8) = 0      def slot 1 (upper half)
1256  *   1: *(u32 *)(r10 - 4) = 0      def slot 0 (lower half)
1257  *   2: r0 = *(u64 *)(r10 - 8)     use slots 0,1
1258  *   3: r0 = 0
1259  *   4: exit
1260  *
1261  * At insn 0: live_stack_before = 0x0 (both slots killed by insns 0,1).
1262  * At insn 1: live_stack_before = 0x2 (slot 1 still live, slot 0 killed here).
1263  */
1264 SEC("socket")
1265 __log_level(2)
1266 __msg("0: (62) *(u32 *)(r10 -8) = 0         ; def: fp0-8h")
1267 __msg("1: (62) *(u32 *)(r10 -4) = 0         ; def: fp0-4h")
1268 __naked void two_four_byte_writes_kill_full_spi(void)
1269 {
1270 	asm volatile (
1271 	"*(u32 *)(r10 - 8) = 0;"
1272 	"*(u32 *)(r10 - 4) = 0;"
1273 	"r0 = *(u64 *)(r10 - 8);"
1274 	"r0 = 0;"
1275 	"exit;"
1276 	::: __clobber_all);
1277 }
1278 
1279 /* Test that 4-byte writes on both branches kill a slot at the
1280  * join point. Previously at SPI granularity, a 4-byte write was
1281  * not stack_def, so liveness would flow backward through the
1282  * branch that only had a 4-byte write.
1283  *
1284  *   0: call bpf_get_prandom_u32
1285  *   1: if r0 != 0 goto 1f
1286  *   2: *(u64 *)(r10 - 8) = 0       path A: def slots 0,1
1287  *   3: goto 2f
1288  * 1:4: *(u32 *)(r10 - 4) = 0       path B: def slot 0
1289  * 2:5: r0 = *(u32 *)(r10 - 4)      use slot 0
1290  *   6: r0 = 0
1291  *   7: exit
1292  *
1293  * Both paths define slot 0 before the read. At insn 1 (branch),
1294  * live_stack_before = 0x0 because slot 0 is killed on both paths.
1295  */
1296 SEC("socket")
1297 __log_level(2)
1298 __msg("1: (55) if r0 != 0x0 goto pc+2")
1299 __msg("2: (7a) *(u64 *)(r10 -8) = 0         ; def: fp0-8")
1300 __msg("3: (05) goto pc+1")
1301 __msg("4: (62) *(u32 *)(r10 -4) = 0         ; def: fp0-4h")
1302 __msg("5: (61) r0 = *(u32 *)(r10 -4)        ; use: fp0-4h")
1303 __naked void both_branches_kill_slot(void)
1304 {
1305 	asm volatile (
1306 	"call %[bpf_get_prandom_u32];"
1307 	"if r0 != 0 goto 1f;"
1308 	"*(u64 *)(r10 - 8) = 0;"
1309 	"goto 2f;"
1310 "1:"
1311 	"*(u32 *)(r10 - 4) = 0;"
1312 "2:"
1313 	"r0 = *(u32 *)(r10 - 4);"
1314 	"r0 = 0;"
1315 	"exit;"
1316 	:: __imm(bpf_get_prandom_u32)
1317 	: __clobber_all);
1318 }
1319 
1320 /* Soundness: cleaning the dead upper half of an SPI must not
1321  * affect the live lower half's type information for pruning.
1322  *
1323  * Both halves of SPI 0 are written separately. Only the lower
1324  * half (slot 0) is used as a 4-byte map key. The upper half
1325  * (slot 1) is dead and cleaned to STACK_INVALID.
1326  *
1327  * Path A: key stays 0 (STACK_ZERO) → non-null array lookup
1328  * Path B: key byte turns STACK_MISC → may-null array lookup
1329  * Deref without null check: safe for A, unsafe for B.
1330  *
1331  * If half-SPI cleaning incorrectly corrupted the live half's
1332  * type info, path A's cached state could generalize and unsoundly
1333  * prune path B.
1334  *
1335  * Expected: reject (path B unsafe).
1336  */
1337 SEC("socket")
1338 __flag(BPF_F_TEST_STATE_FREQ)
1339 __failure __msg("R0 invalid mem access 'map_value_or_null'")
1340 __naked void half_spi_clean_preserves_stack_zero(void)
1341 {
1342 	asm volatile (
1343 	"*(u32 *)(r10 - 4) = 0;"           /* slot 0: STACK_ZERO */
1344 	"*(u32 *)(r10 - 8) = 0;"           /* slot 1: STACK_ZERO (dead) */
1345 	"call %[bpf_get_prandom_u32];"
1346 	"if r0 != 0 goto l_nonconst%=;"
1347 	"goto l_lookup%=;"
1348 "l_nonconst%=:"
1349 	"*(u8 *)(r10 - 4) = r0;"           /* slot 0: STACK_MISC */
1350 "l_lookup%=:"
1351 	"r2 = r10;"
1352 	"r2 += -4;"
1353 	"r1 = %[array_map_8b] ll;"
1354 	"call %[bpf_map_lookup_elem];"
1355 	"r0 = *(u64 *)(r0 + 0);"           /* unsafe if null */
1356 	"exit;"
1357 	:
1358 	: __imm(bpf_get_prandom_u32),
1359 	  __imm(bpf_map_lookup_elem),
1360 	  __imm_addr(array_map_8b)
1361 	: __clobber_all);
1362 }
1363 
1364 /*
1365  * Model of scx_lavd's pick_idle_cpu_at_cpdom iat block:
1366  * conditional block with helper call and temporary stack spill,
1367  * spill dead after merge.
1368  *
1369  * Path A (fall-through): spill r6 to fp-8 across helper call
1370  * Path B (branch taken): skip the block entirely
1371  * At merge (insn 6): fp-8 is dead (never read after merge)
1372  *
1373  * Static liveness marks fp-8 dead at merge. clean_verifier_state()
1374  * converts path A's STACK_SPILL to STACK_INVALID. Path B has
1375  * STACK_INVALID. stacksafe() matches -> path B pruned -> "6: safe".
1376  */
1377 SEC("socket")
1378 __flag(BPF_F_TEST_STATE_FREQ)
1379 __success
1380 __log_level(2)
1381 __msg("6: safe")
1382 __naked void dead_spill_at_merge_enables_pruning(void)
1383 {
1384 	asm volatile (
1385 	"call %[bpf_get_prandom_u32];"
1386 	"r6 = 7;"
1387 	"if r0 != 0 goto l_skip%=;"
1388 	/* conditional block: spill, call, reload */
1389 	"*(u64 *)(r10 - 8) = r6;"
1390 	"call %[bpf_get_prandom_u32];"
1391 	"r6 = *(u64 *)(r10 - 8);"
1392 "l_skip%=:"
1393 	/* fp-8 dead. Path B pruned here -> "6: safe" */
1394 	"r0 = r6;"
1395 	"exit;"
1396 	:
1397 	: __imm(bpf_get_prandom_u32)
1398 	: __clobber_all);
1399 }
1400 
1401 /*
1402  * FP-offset tracking loses precision on second ADD, killing all liveness.
1403  *
1404  * fp_off_insn_xfer() handles "FP itself + negative imm" precisely
1405  * (e.g. r6 = r10; r6 += -24 -> slot 5).  But any subsequent ADD/SUB
1406  * on a register that already has non-zero spis falls through to
1407  * spis_set_all(), because the code only handles the FP-itself case.
1408  *
1409  * A write through this imprecise register enters the non-zero-spis
1410  * branch of set_indirect_stack_access(), which OR's the all-ones
1411  * mask into stack_def.  The backward liveness equation
1412  *
1413  *   stack_in = (stack_out & ~stack_def) | stack_use
1414  *
1415  * sees ~ALL = 0, killing ALL slot liveness at that instruction.
1416  *
1417  * At the merge pruning point, live_stack_before is empty.
1418  * clean_verifier_state() marks fp-8 as STACK_INVALID.
1419  * stacksafe() skips STACK_INVALID (line "continue"), so pruning
1420  * succeeds regardless of the current state's fp-8 value.
1421  * Path B is pruned, its null deref is never explored.
1422  *
1423  * Correct behavior: reject (path B dereferences NULL).
1424  * Bug behavior: accept (path B pruned away).
1425  */
1426 SEC("socket")
1427 __flag(BPF_F_TEST_STATE_FREQ)
1428 __failure __msg("R1 invalid mem access 'scalar'")
1429 __naked void fp_add_loses_precision_kills_liveness(void)
1430 {
1431 	asm volatile (
1432 	"call %[bpf_get_prandom_u32];"
1433 	"if r0 != 0 goto l_pathB%=;"
1434 
1435 	/* Path A (fall-through, explored first): fp-8 = 0 */
1436 	"r1 = 0;"
1437 	"*(u64 *)(r10 - 8) = r1;"
1438 	"goto l_merge%=;"
1439 
1440 "l_pathB%=:"
1441 	/* Path B (explored second): fp-8 = 42 */
1442 	"r1 = 42;"
1443 	"*(u64 *)(r10 - 8) = r1;"
1444 
1445 "l_merge%=:"
1446 	/*
1447 	 * Create imprecise FP-derived register.
1448 	 * r6 = r10 - 24 gets precise slot 5.
1449 	 * r6 += 8 hits the else branch (spis non-zero, delta > 0)
1450 	 * and sets spis to ALL.  r6 is actually r10-16.
1451 	 */
1452 	"r6 = r10;"
1453 	"r6 += -24;"
1454 	"r6 += 8;"
1455 
1456 	/*
1457 	 * Write through imprecise r6.  Actually writes to fp-16
1458 	 * (does NOT touch fp-8), but liveness marks ALL slots
1459 	 * as stack_def, killing fp-8's liveness.
1460 	 */
1461 	"r7 = 0;"
1462 	"*(u64 *)(r6 + 0) = r7;"
1463 
1464 	/* Read fp-8: liveness says dead, but value is needed. */
1465 	"r2 = *(u64 *)(r10 - 8);"
1466 	"if r2 == 42 goto l_danger%=;"
1467 
1468 	/* r2 != 42 (path A: r2 == 0): safe exit */
1469 	"r0 = 0;"
1470 	"exit;"
1471 
1472 "l_danger%=:"
1473 	/* Only reachable from path B (r2 == 42): null deref */
1474 	"r1 = 0;"
1475 	"r0 = *(u64 *)(r1 + 0);"
1476 	"exit;"
1477 	:
1478 	: __imm(bpf_get_prandom_u32)
1479 	: __clobber_all);
1480 }
1481 
1482 SEC("socket")
1483 __flag(BPF_F_TEST_STATE_FREQ)
1484 __failure __msg("R1 invalid mem access 'scalar'")
1485 __naked void fp_spill_loses_precision_kills_liveness(void)
1486 {
1487 	asm volatile (
1488 	"call %[bpf_get_prandom_u32];"
1489 	"if r0 != 0 goto l_pathB%=;"
1490 
1491 	"r1 = 0;"
1492 	"*(u64 *)(r10 - 8) = r1;"
1493 	"goto l_merge%=;"
1494 
1495 "l_pathB%=:"
1496 	"r1 = 42;"
1497 	"*(u64 *)(r10 - 8) = r1;"
1498 
1499 "l_merge%=:"
1500 	"r6 = r10;"
1501 	"r6 += -64;"
1502 	"*(u64 *)(r10 - 160) = r6;"
1503 	"r6 = *(u64 *)(r10 - 160);"
1504 
1505 	"r7 = 0;"
1506 	"*(u64 *)(r6 + 0) = r7;"
1507 
1508 	"r2 = *(u64 *)(r10 - 8);"
1509 	"if r2 == 42 goto l_danger%=;"
1510 
1511 	"r0 = *(u64 *)(r10 - 56);"
1512 	"exit;"
1513 
1514 "l_danger%=:"
1515 	"r1 = 0;"
1516 	"r0 = *(u64 *)(r1 + 0);"
1517 	"exit;"
1518 	:
1519 	: __imm(bpf_get_prandom_u32)
1520 	: __clobber_all);
1521 }
1522 
1523 /* === Tests for frame-based AT_FP tracking === */
1524 
1525 /*
1526  * Test 1: conditional_stx_in_subprog
1527  * Subprog conditionally writes caller's slot.
1528  * Verify slot stays live (backward pass handles conditional def via CFG).
1529  *
1530  * Main writes fp-8=42, calls cond_writer(fp-8), reads fp-8.
1531  * cond_writer only writes on one path → parent_def only on that path.
1532  * The backward parent_live correctly keeps fp-8 live at entry
1533  * (conditional write doesn't kill liveness at the join).
1534  */
1535 SEC("socket")
1536 __log_level(2)
1537 /*
1538  * fp-8 live at call: callee conditionally writes it, so the slot is not killed
1539  * (no def), but the conditional write surfaces as may_def at the call site.
1540  */
1541 __msg("1: (7b) *(u64 *)(r10 -8) = r1        ; def: fp0-8 may_def: fp0-8")
1542 __msg("4: (85) call pc+2                    ; may_def: fp0-8")
1543 __msg("5: (79) r0 = *(u64 *)(r10 -8)        ; use: fp0-8")
1544 __naked void conditional_stx_in_subprog(void)
1545 {
1546 	asm volatile (
1547 	"r1 = 42;"
1548 	"*(u64 *)(r10 - 8) = r1;"
1549 	"r1 = r10;"
1550 	"r1 += -8;"
1551 	"call cond_writer;"
1552 	"r0 = *(u64 *)(r10 - 8);"
1553 	"exit;"
1554 	::: __clobber_all);
1555 }
1556 
1557 /* Conditionally writes to *(r1+0) */
1558 static __used __naked void cond_writer(void)
1559 {
1560 	asm volatile (
1561 	"r6 = r1;"
1562 	"call %[bpf_get_prandom_u32];"
1563 	"if r0 == 0 goto 1f;"
1564 	"*(u64 *)(r6 + 0) = r0;"
1565 	"1:"
1566 	"r0 = 0;"
1567 	"exit;"
1568 	:: __imm(bpf_get_prandom_u32)
1569 	: __clobber_all);
1570 }
1571 
1572 SEC("socket")
1573 __log_level(2)
1574 __msg("4: (85) call pc+{{.*}}                   ; use: fp0-16")
1575 __msg("7: (85) call pc+{{.*}}                   ; use: fp0-32")
1576 __naked void multiple_callsites_different_offsets(void)
1577 {
1578 	asm volatile (
1579 	"*(u64 *)(r10 - 16) = 0;"
1580 	"*(u64 *)(r10 - 32) = 0;"
1581 	"r1 = r10;"
1582 	"r1 += -16;"
1583 	"call read_first_param;"
1584 	"r1 = r10;"
1585 	"r1 += -32;"
1586 	"call read_first_param;"
1587 	"r0 = 0;"
1588 	"exit;"
1589 	::: __clobber_all);
1590 }
1591 
1592 /*
1593  * Test 3: nested_fp_passthrough
1594  * main→A→B, main's FP forwarded to B. B accesses main's stack.
1595  * Verify liveness propagates through.
1596  *
1597  * Main passes fp-32 to outer_forwarder, which passes it to inner_reader.
1598  * inner_reader reads at arg+0 (= main's fp-32).
1599  * parent_live propagates transitively: inner→outer→main.
1600  */
1601 SEC("socket")
1602 __log_level(2)
1603 /* At call to outer_forwarder: main's fp-32 (slots 6,7) should be live */
1604 __msg("6: (85) call pc+{{.*}}                   ; use: fp0-32")
1605 __naked void nested_fp_passthrough(void)
1606 {
1607 	asm volatile (
1608 	"*(u64 *)(r10 - 8) = 0;"
1609 	"*(u64 *)(r10 - 16) = 0;"
1610 	"*(u64 *)(r10 - 24) = 0;"
1611 	"*(u64 *)(r10 - 32) = 0;"
1612 	"r1 = r10;"
1613 	"r1 += -32;"
1614 	"call outer_forwarder;"
1615 	"r0 = 0;"
1616 	"exit;"
1617 	::: __clobber_all);
1618 }
1619 
1620 /* Forwards arg to inner_reader */
1621 static __used __naked void outer_forwarder(void)
1622 {
1623 	asm volatile (
1624 	"call inner_reader;"
1625 	"r0 = 0;"
1626 	"exit;"
1627 	::: __clobber_all);
1628 }
1629 
1630 static __used __naked void inner_reader(void)
1631 {
1632 	asm volatile (
1633 	"r0 = *(u64 *)(r1 + 0);"
1634 	"r0 = 0;"
1635 	"exit;"
1636 	::: __clobber_all);
1637 }
1638 
1639 /*
1640  * Test 4: callee_must_write_before_read
1641  * Callee unconditionally writes parent slot before reading.
1642  * Verify slot is NOT live at call site (parent_def kills it).
1643  */
1644 SEC("socket")
1645 __log_level(2)
1646 /* fp-8 NOT live at call: callee writes before reading (parent_def kills it) */
1647 __msg("2: .1........ (85) call pc+")
1648 __naked void callee_must_write_before_read(void)
1649 {
1650 	asm volatile (
1651 	"r1 = r10;"
1652 	"r1 += -8;"
1653 	"call write_then_read;"
1654 	"r0 = 0;"
1655 	"exit;"
1656 	::: __clobber_all);
1657 }
1658 
1659 /* Unconditionally writes *(r1+0), then reads it back */
1660 static __used __naked void write_then_read(void)
1661 {
1662 	asm volatile (
1663 	"r6 = r1;"
1664 	"r7 = 99;"
1665 	"*(u64 *)(r6 + 0) = r7;"
1666 	"r0 = *(u64 *)(r6 + 0);"
1667 	"r0 = 0;"
1668 	"exit;"
1669 	::: __clobber_all);
1670 }
1671 
1672 /*
1673  * Test 5: return_site_liveness_bleeding
1674  * Main calls subprog twice. Slot used after one call but not the other.
1675  * Context-insensitive: slot conservatively live at both.
1676  *
1677  * After first call: read fp-8.
1678  * After second call: don't read fp-8.
1679  * Since parent_live is per-subprog (not per call-site),
1680  * fp-8 is live at both call sites.
1681  */
1682 SEC("socket")
1683 __log_level(2)
1684 /* Both calls have fp-8 live due to context-insensitive parent_live */
1685 __msg("3: (85) call pc+{{.*}}                   ; use: fp0-8")
1686 __msg("7: (85) call pc+{{.*}}                   ; use: fp0-8")
1687 __naked void return_site_liveness_bleeding(void)
1688 {
1689 	asm volatile (
1690 	"*(u64 *)(r10 - 8) = 0;"
1691 	"r1 = r10;"
1692 	"r1 += -8;"
1693 	"call read_first_param;"
1694 	"r0 = *(u64 *)(r10 - 8);"
1695 	"r1 = r10;"
1696 	"r1 += -8;"
1697 	"call read_first_param;"
1698 	"r0 = 0;"
1699 	"exit;"
1700 	::: __clobber_all);
1701 }
1702 
1703 SEC("socket")
1704 __log_level(2)
1705 __msg("9: (85) call bpf_loop#181            ; use: fp0-16")
1706 __naked void callback_conditional_read_beyond_ctx(void)
1707 {
1708 	asm volatile (
1709 	"r1 = 42;"
1710 	"*(u64 *)(r10 - 8) = r1;"
1711 	"*(u64 *)(r10 - 16) = r1;"
1712 	"r1 = 2;"
1713 	"r2 = cb_cond_read ll;"
1714 	"r3 = r10;"
1715 	"r3 += -8;"
1716 	"r4 = 0;"
1717 	"call %[bpf_loop];"
1718 	"r0 = 0;"
1719 	"exit;"
1720 	:: __imm(bpf_loop)
1721 	: __clobber_all);
1722 }
1723 
1724 /* Callback conditionally reads *(ctx - 8) = caller fp-16 */
1725 static __used __naked void cb_cond_read(void)
1726 {
1727 	asm volatile (
1728 	"r6 = r2;"
1729 	"call %[bpf_get_prandom_u32];"
1730 	"if r0 == 0 goto 1f;"
1731 	"r0 = *(u64 *)(r6 - 8);"
1732 	"1:"
1733 	"r0 = 0;"
1734 	"exit;"
1735 	:: __imm(bpf_get_prandom_u32)
1736 	: __clobber_all);
1737 }
1738 
1739 SEC("socket")
1740 __log_level(2)
1741 __msg("14: (7b) *(u64 *)(r6 -8) = r7         ; def: fp0-16")
1742 __msg("15: (79) r0 = *(u64 *)(r6 -8)         ; use: fp0-16")
1743 __naked void callback_write_before_read_kills(void)
1744 {
1745 	asm volatile (
1746 	"r1 = 42;"
1747 	"*(u64 *)(r10 - 8) = r1;"
1748 	"*(u64 *)(r10 - 16) = r1;"
1749 	"r1 = 2;"
1750 	"r2 = cb_write_read ll;"
1751 	"r3 = r10;"
1752 	"r3 += -8;"
1753 	"r4 = 0;"
1754 	"call %[bpf_loop];"
1755 	"r0 = 0;"
1756 	"exit;"
1757 	:: __imm(bpf_loop)
1758 	: __clobber_all);
1759 }
1760 
1761 /* Callback unconditionally writes *(ctx-8), then reads it back.
1762  * The write (parent_def) kills liveness before entry.
1763  */
1764 static __used __naked void cb_write_read(void)
1765 {
1766 	asm volatile (
1767 	"r6 = r2;"
1768 	"r7 = 99;"
1769 	"*(u64 *)(r6 - 8) = r7;"
1770 	"r0 = *(u64 *)(r6 - 8);"
1771 	"r0 = 0;"
1772 	"exit;"
1773 	::: __clobber_all);
1774 }
1775 
1776 /*
1777  * bpf_loop callback conditionally writes fp-16 then unconditionally
1778  * reads it. The conditional write does NOT kill liveness
1779  */
1780 SEC("socket")
1781 __log_level(2)
1782 __msg("9: (85) call bpf_loop#181            ; use: fp0-16")
1783 __naked void callback_conditional_write_preserves(void)
1784 {
1785 	asm volatile (
1786 	"r1 = 42;"
1787 	"*(u64 *)(r10 - 8) = r1;"
1788 	"*(u64 *)(r10 - 16) = r1;"
1789 	"r1 = 2;"
1790 	"r2 = cb_cond_write_read ll;"
1791 	"r3 = r10;"
1792 	"r3 += -8;"
1793 	"r4 = 0;"
1794 	"call %[bpf_loop];"
1795 	"r0 = 0;"
1796 	"exit;"
1797 	:: __imm(bpf_loop)
1798 	: __clobber_all);
1799 }
1800 
1801 static __used __naked void cb_cond_write_read(void)
1802 {
1803 	asm volatile (
1804 	"r6 = r2;"
1805 	"call %[bpf_get_prandom_u32];"
1806 	"if r0 == 0 goto 1f;"
1807 	"*(u64 *)(r6 - 8) = r0;"
1808 	"1:"
1809 	"r0 = *(u64 *)(r6 - 8);"
1810 	"r0 = 0;"
1811 	"exit;"
1812 	:: __imm(bpf_get_prandom_u32)
1813 	: __clobber_all);
1814 }
1815 
1816 /*
1817  * Two bpf_loop calls with the same callback but different ctx pointers.
1818  *
1819  * First call: ctx=fp-8, second call: ctx=fp-24.
1820  */
1821 SEC("socket")
1822 __log_level(2)
1823 __msg(" 8: (85) call bpf_loop{{.*}}            ; use: fp0-8")
1824 __msg("15: (85) call bpf_loop{{.*}}            ; use: fp0-24")
1825 __naked void callback_two_calls_different_ctx(void)
1826 {
1827 	asm volatile (
1828 	"*(u64 *)(r10 - 8) = 0;"
1829 	"*(u64 *)(r10 - 24) = 0;"
1830 	"r1 = 1;"
1831 	"r2 = cb_read_ctx ll;"
1832 	"r3 = r10;"
1833 	"r3 += -8;"
1834 	"r4 = 0;"
1835 	"call %[bpf_loop];"
1836 	"r1 = 1;"
1837 	"r2 = cb_read_ctx ll;"
1838 	"r3 = r10;"
1839 	"r3 += -24;"
1840 	"r4 = 0;"
1841 	"call %[bpf_loop];"
1842 	"r0 = 0;"
1843 	"exit;"
1844 	:: __imm(bpf_loop)
1845 	: __clobber_all);
1846 }
1847 
1848 /* Callback reads at ctx+0 unconditionally */
1849 static __used __naked void cb_read_ctx(void)
1850 {
1851 	asm volatile (
1852 	"r0 = *(u64 *)(r2 + 0);"
1853 	"r0 = 0;"
1854 	"exit;"
1855 	::: __clobber_all);
1856 }
1857 
1858 /*
1859  * Reproducer for unsound pruning in refined_caller_live_stack().
1860  *
1861  * Three-level call chain: main → mid_fwd → grandchild_deref.
1862  * Main passes &fp-8 to mid_fwd, which forwards R1 to grandchild_deref.
1863  * grandchild_deref reads main's fp-8 through the forwarded pointer
1864  * and dereferences the result.
1865  *
1866  * refined_caller_live_stack() has a callee_offset++ when mid_fwd
1867  * (frame 1) is mid-call. This drops the transitive parent_live
1868  * contribution at mid_fwd's call instruction — the only place
1869  * where grandchild_deref's read of main's fp-8 is recorded.
1870  * As a result, main's fp-8 is cleaned to STACK_INVALID at the
1871  * pruning point inside grandchild_deref, and path B is
1872  * incorrectly pruned against path A.
1873  *
1874  * Path A: main stores PTR_TO_MAP_VALUE at fp-8
1875  * Path B: main stores scalar 42 at fp-8
1876  *
1877  * Correct behavior: reject (path B dereferences scalar)
1878  * Bug behavior: accept (path B pruned against cleaned path A)
1879  */
1880 SEC("socket")
1881 __flag(BPF_F_TEST_STATE_FREQ)
1882 __failure __msg("R0 invalid mem access 'scalar'")
1883 __naked void transitive_parent_stack_read_unsound(void)
1884 {
1885 	asm volatile (
1886 	/* Map lookup to get PTR_TO_MAP_VALUE */
1887 	"r1 = %[map] ll;"
1888 	"*(u32 *)(r10 - 32) = 0;"
1889 	"r2 = r10;"
1890 	"r2 += -32;"
1891 	"call %[bpf_map_lookup_elem];"
1892 	"if r0 == 0 goto l_exit%=;"
1893 	"r6 = r0;"
1894 	/* Branch: path A (fall-through) explored first */
1895 	"call %[bpf_get_prandom_u32];"
1896 	"if r0 != 0 goto l_scalar%=;"
1897 	/* Path A: fp-8 = PTR_TO_MAP_VALUE */
1898 	"*(u64 *)(r10 - 8) = r6;"
1899 	"goto l_merge%=;"
1900 "l_scalar%=:"
1901 	/* Path B: fp-8 = scalar 42 */
1902 	"r1 = 42;"
1903 	"*(u64 *)(r10 - 8) = r1;"
1904 "l_merge%=:"
1905 	/* Pass &fp-8 to mid_fwd → grandchild_deref */
1906 	"r1 = r10;"
1907 	"r1 += -8;"
1908 	"call mid_fwd;"
1909 	"r0 = 0;"
1910 	"exit;"
1911 "l_exit%=:"
1912 	"r0 = 0;"
1913 	"exit;"
1914 	:
1915 	: __imm(bpf_map_lookup_elem),
1916 	  __imm(bpf_get_prandom_u32),
1917 	  __imm_addr(map)
1918 	: __clobber_all);
1919 }
1920 
1921 /* Forwards R1 (ptr to main's fp-8) to grandchild_deref */
1922 static __used __naked void mid_fwd(void)
1923 {
1924 	asm volatile (
1925 	"call grandchild_deref;"
1926 	"r0 = 0;"
1927 	"exit;"
1928 	::: __clobber_all);
1929 }
1930 
1931 /* Reads main's fp-8 through forwarded pointer, dereferences result */
1932 static __used __naked void grandchild_deref(void)
1933 {
1934 	asm volatile (
1935 	"goto +0;"				/* checkpoint */
1936 	"goto +0;"				/* checkpoint */
1937 	/* read main's fp-8: map_ptr (path A) or scalar (path B) */
1938 	"r0 = *(u64 *)(r1 + 0);"
1939 	/* dereference: safe for map_ptr, unsafe for scalar */
1940 	"r0 = *(u64 *)(r0 + 0);"
1941 	"r0 = 0;"
1942 	"exit;"
1943 	::: __clobber_all);
1944 }
1945 
1946 SEC("socket")
1947 __log_level(2)
1948 __success
1949 __msg("14: (79) r1 = *(u64 *)(r10 -8) // r6=fp0-8 r7=fp1-16 fp-8=fp1-16 fp-16=fp0-8")
1950 __msg("15: (79) r0 = *(u64 *)(r1 +0) // r1=fp1-16 r6=fp0-8 r7=fp1-16 fp-8=fp1-16 fp-16=fp0-8")
1951 __msg("stack use/def subprog#1 mid_two_fp_threshold (d1,cs2):")
1952 __msg("14: (79) r1 = *(u64 *)(r10 -8)        ; use: fp1-8")
1953 __msg("15: (79) r0 = *(u64 *)(r1 +0)         ; use: fp1-16")
1954 __naked void two_fp_clear_stack_threshold(void)
1955 {
1956 	asm volatile (
1957 	"r1 = r10;"
1958 	"r1 += -8;"
1959 	"call mid_two_fp_threshold;"
1960 	"r0 = 0;"
1961 	"exit;"
1962 	::: __clobber_all);
1963 }
1964 
1965 static __used __naked void mid_two_fp_threshold(void)
1966 {
1967 	asm volatile (
1968 	"r6 = r1;"
1969 	"r7 = r10;"
1970 	"r7 += -16;"
1971 	"*(u64 *)(r10 - 8) = r7;"
1972 	"*(u64 *)(r10 - 16) = r6;"
1973 	"r1 = r10;"
1974 	"r1 += -8;"
1975 	"r2 = r6;"
1976 	"call inner_nop_fptest;"
1977 	"r1 = *(u64 *)(r10 - 8);"
1978 	"r0 = *(u64 *)(r1 + 0);"
1979 	"r0 = 0;"
1980 	"exit;"
1981 	::: __clobber_all);
1982 }
1983 
1984 static __used __naked void inner_nop_fptest(void)
1985 {
1986 	asm volatile (
1987 	"r0 = 0;"
1988 	"exit;"
1989 	::: __clobber_all);
1990 }
1991 
1992 SEC("socket")
1993 __log_level(2)
1994 __success
1995 __msg("13: (79) r1 = *(u64 *)(r10 -8) // r6=fp0-8 r7=fp1-16 fp-8=fp1-16 fp-16=fp0-8")
1996 __msg("14: (79) r0 = *(u64 *)(r1 +0) // r1=fp1-16 r6=fp0-8 r7=fp1-16 fp-8=fp1-16 fp-16=fp0-8")
1997 __msg("stack use/def subprog#1 mid_one_fp_threshold (d1,cs2):")
1998 __msg("13: (79) r1 = *(u64 *)(r10 -8)        ; use: fp1-8")
1999 __msg("14: (79) r0 = *(u64 *)(r1 +0)         ; use: fp1-16")
2000 __naked void one_fp_clear_stack_threshold(void)
2001 {
2002 	asm volatile (
2003 	"r1 = r10;"
2004 	"r1 += -8;"
2005 	"call mid_one_fp_threshold;"
2006 	"r0 = 0;"
2007 	"exit;"
2008 	::: __clobber_all);
2009 }
2010 
2011 static __used __naked void mid_one_fp_threshold(void)
2012 {
2013 	asm volatile (
2014 	"r6 = r1;"
2015 	"r7 = r10;"
2016 	"r7 += -16;"
2017 	"*(u64 *)(r10 - 8) = r7;"
2018 	"*(u64 *)(r10 - 16) = r6;"
2019 	"r1 = r10;"
2020 	"r1 += -8;"
2021 	"call inner_nop_fptest;"
2022 	"r1 = *(u64 *)(r10 - 8);"
2023 	"r0 = *(u64 *)(r1 + 0);"
2024 	"r0 = 0;"
2025 	"exit;"
2026 	::: __clobber_all);
2027 }
2028 
2029 /*
2030  * Reproducer for unsound pruning when a subprog forwards a parent
2031  * stack pointer (AT_PARENT) to a helper with a memory argument.
2032  *
2033  * set_call_stack_access_at() previously only tracked AT_CURRENT args,
2034  * skipping AT_PARENT entirely. This meant helper reads through parent
2035  * stack pointers did not set parent_use, letting the slot appear dead
2036  * at pruning checkpoints inside the subprog.
2037  *
2038  * Program shape:
2039  *   main:
2040  *     *(u32)(fp-4) = 0             key = STACK_ZERO (const 0)
2041  *     call bpf_get_prandom_u32
2042  *     if r0 != 0 goto clobber      path A (fall-through) first
2043  *     goto merge
2044  *   clobber:
2045  *     *(u8)(fp-4) = r0             path B: key[0] = STACK_MISC
2046  *   merge:
2047  *     r1 = fp - 4
2048  *     call fwd_parent_key_to_helper
2049  *     r0 = 0
2050  *     exit
2051  *
2052  *   fwd_parent_key_to_helper(r1 = &caller_fp-4):
2053  *     goto +0                      checkpoint
2054  *     r2 = r1                      R2 = AT_PARENT ptr to caller fp-4
2055  *     r1 = array_map_8b ll         R1 = array map
2056  *     call bpf_map_lookup_elem     reads key_size(4) from parent fp-4
2057  *     r0 = *(u64 *)(r0 + 0)        deref without null check
2058  *     r0 = 0
2059  *     exit
2060  *
2061  * Path A: STACK_ZERO key = const 0 -> array lookup -> PTR_TO_MAP_VALUE
2062  *         (non-NULL for in-bounds const key) -> deref OK.
2063  * Path B: STACK_MISC key = unknown -> array lookup ->
2064  *         PTR_TO_MAP_VALUE_OR_NULL -> deref UNSAFE.
2065  *
2066  * Bug: AT_PARENT R2 arg to bpf_map_lookup_elem skipped -> parent_use
2067  *      not set -> fp-4 cleaned at checkpoint -> STACK_ZERO collapses
2068  *      to STACK_INVALID -> path B pruned -> deref never checked.
2069  *
2070  * Correct verifier behavior: reject (path B deref of map_value_or_null).
2071  */
2072 SEC("socket")
2073 __flag(BPF_F_TEST_STATE_FREQ)
2074 __failure __msg("R0 invalid mem access 'map_value_or_null'")
2075 __naked void helper_parent_stack_read_unsound(void)
2076 {
2077 	asm volatile (
2078 	/* key at fp-4: all bytes STACK_ZERO */
2079 	"*(u32 *)(r10 - 4) = 0;"
2080 	"call %[bpf_get_prandom_u32];"
2081 	/* fall-through (path A) explored first */
2082 	"if r0 != 0 goto l_clobber%=;"
2083 	/* path A: key stays constant zero */
2084 	"goto l_merge%=;"
2085 "l_clobber%=:"
2086 	/* path B: key[0] becomes STACK_MISC, key no longer const */
2087 	"*(u8 *)(r10 - 4) = r0;"
2088 "l_merge%=:"
2089 	"r1 = r10;"
2090 	"r1 += -4;"
2091 	"call fwd_parent_key_to_helper;"
2092 	"r0 = 0;"
2093 	"exit;"
2094 	:
2095 	: __imm(bpf_get_prandom_u32)
2096 	: __clobber_all);
2097 }
2098 
2099 /*
2100  * Subprog forwards parent stack pointer to bpf_map_lookup_elem as key
2101  * on an array map, then dereferences the result without a null check.
2102  * R1 = &parent_fp-4 (AT_PARENT in this frame).
2103  *
2104  * The helper reads key_size(4) bytes from parent stack.  The deref of
2105  * R0 reads the map value, NOT parent stack, so record_insn_mem_accesses
2106  * does not set parent_use for it.  The ONLY parent stack access is
2107  * through the helper's R2 arg.
2108  */
2109 static __used __naked void fwd_parent_key_to_helper(void)
2110 {
2111 	asm volatile (
2112 	"goto +0;"				/* checkpoint */
2113 	"r2 = r1;"				/* R2 = parent ptr (AT_PARENT) */
2114 	"r1 = %[array_map_8b] ll;"		/* R1 = array map */
2115 	"call %[bpf_map_lookup_elem];"		/* reads 4 bytes from parent fp-4 */
2116 	/* deref without null check: safe for PTR_TO_MAP_VALUE,
2117 	 * unsafe for PTR_TO_MAP_VALUE_OR_NULL
2118 	 */
2119 	"r0 = *(u64 *)(r0 + 0);"
2120 	"r0 = 0;"
2121 	"exit;"
2122 	:
2123 	: __imm(bpf_map_lookup_elem),
2124 	  __imm_addr(array_map_8b)
2125 	: __clobber_all);
2126 }
2127 
2128 /*
2129  * Regression for keeping later helper args after a whole-stack fallback
2130  * on an earlier local arg.  The bpf_map_update_elem() key is a local
2131  * frame-derived pointer with offset-imprecise tracking (`fp1 ?`), which
2132  * conservatively marks the whole local stack live.  The value arg still
2133  * forwards &parent_fp-8 and must contribute nonlocal_use[0]=0:3.
2134  */
2135 SEC("socket")
2136 __log_level(2)
2137 __success
2138 __msg("call bpf_map_update_elem{{.*}}; use: fp1-8..-{{(512|2048)}} fp0-8")
2139 __naked void helper_arg_fallback_keeps_scanning(void)
2140 {
2141 	asm volatile (
2142 	"r1 = 42;"
2143 	"*(u64 *)(r10 - 8) = r1;"
2144 	"r1 = r10;"
2145 	"r1 += -8;"
2146 	"call helper_update_parent_after_local_fallback;"
2147 	"r0 = 0;"
2148 	"exit;"
2149 	::: __clobber_all);
2150 }
2151 
2152 static __used __naked void helper_update_parent_after_local_fallback(void)
2153 {
2154 	asm volatile (
2155 	"r6 = r1;"				/* save &parent_fp-8 */
2156 	"call %[bpf_get_prandom_u32];"
2157 	"r0 &= 8;"
2158 	"*(u64 *)(r10 - 16) = 0;"
2159 	"*(u64 *)(r10 - 8) = 0;"
2160 	"r2 = r10;"
2161 	"r2 += -16;"
2162 	"r2 += r0;"				/* local fp, offset-imprecise */
2163 	"r1 = %[array_map_8b] ll;"
2164 	"r3 = r6;"				/* later arg: parent fp-8 */
2165 	"r4 = 0;"
2166 	"call %[bpf_map_update_elem];"
2167 	"r0 = 0;"
2168 	"exit;"
2169 	:
2170 	: __imm(bpf_get_prandom_u32),
2171 	  __imm(bpf_map_update_elem),
2172 	  __imm_addr(array_map_8b)
2173 	: __clobber_all);
2174 }
2175 
2176 /*
2177  * Test that propagate_callee_ancestor() correctly chains ancestor
2178  * liveness across sequential calls within a single frame.
2179  *
2180  * main → mid_seq_touch → {nop_callee, deref_ancestor}
2181  *
2182  * mid_seq_touch receives two pointers: R1 = &main_fp-8 (forwarded to
2183  * deref_ancestor) and R2 = &main_fp-16 (read directly by mid_seq_touch).
2184  * The direct read of fp-16 forces ensure_anc_arrays() to allocate
2185  * ancestor_live[0] for mid_seq_touch, so refined_caller_live_stack()
2186  * uses the refined path (not the conservative fallback).
2187  *
2188  * mid_seq_touch calls nop_callee first (no-op, creates a pruning point),
2189  * then calls deref_ancestor which reads main's fp-8 and dereferences it.
2190  *
2191  * propagate_callee_ancestor() propagates deref_ancestor's entry
2192  * ancestor_live[0] into mid_seq_touch's anc_use[0] at the call-to-deref
2193  * instruction.  mid_seq_touch's backward pass flows this backward so
2194  * ancestor_live[0] includes fp-8 at the pruning point between the calls.
2195  *
2196  * Without propagation, mid_seq_touch's ancestor_live[0] only has fp-16
2197  * (from the direct read) — fp-8 is missing.  refined_caller_live_stack()
2198  * Term 1 says fp-8 is dead, the verifier cleans it, and path B
2199  * (scalar 42) is incorrectly pruned against path A (MAP_VALUE).
2200  *
2201  * Path A: main stores PTR_TO_MAP_VALUE at fp-8  → deref succeeds
2202  * Path B: main stores scalar 42 at fp-8         → deref must fail
2203  *
2204  * Correct: reject (path B dereferences scalar)
2205  */
2206 SEC("socket")
2207 __flag(BPF_F_TEST_STATE_FREQ)
2208 __failure __msg("R0 invalid mem access 'scalar'")
2209 __naked void propagate_callee_ancestor_chain(void)
2210 {
2211 	asm volatile (
2212 	/* Map lookup to get PTR_TO_MAP_VALUE */
2213 	"r1 = %[map] ll;"
2214 	"*(u32 *)(r10 - 32) = 0;"
2215 	"r2 = r10;"
2216 	"r2 += -32;"
2217 	"call %[bpf_map_lookup_elem];"
2218 	"if r0 == 0 goto l_exit%=;"
2219 	"r6 = r0;"
2220 	/* Branch: path A (fall-through) explored first */
2221 	"call %[bpf_get_prandom_u32];"
2222 	"if r0 != 0 goto l_scalar%=;"
2223 	/* Path A: fp-8 = PTR_TO_MAP_VALUE */
2224 	"*(u64 *)(r10 - 8) = r6;"
2225 	"goto l_merge%=;"
2226 "l_scalar%=:"
2227 	/* Path B: fp-8 = scalar 42 */
2228 	"r1 = 42;"
2229 	"*(u64 *)(r10 - 8) = r1;"
2230 "l_merge%=:"
2231 	/* fp-16 = dummy value (mid_seq_touch reads it directly) */
2232 	"r1 = 99;"
2233 	"*(u64 *)(r10 - 16) = r1;"
2234 	/* R1 = &fp-8 (for deref_ancestor), R2 = &fp-16 (for mid_seq_touch) */
2235 	"r1 = r10;"
2236 	"r1 += -8;"
2237 	"r2 = r10;"
2238 	"r2 += -16;"
2239 	"call mid_seq_touch;"
2240 	"r0 = 0;"
2241 	"exit;"
2242 "l_exit%=:"
2243 	"r0 = 0;"
2244 	"exit;"
2245 	:
2246 	: __imm(bpf_map_lookup_elem),
2247 	  __imm(bpf_get_prandom_u32),
2248 	  __imm_addr(map)
2249 	: __clobber_all);
2250 }
2251 
2252 /*
2253  * R1 = &main_fp-8 (forwarded to deref_ancestor)
2254  * R2 = &main_fp-16 (read directly here → allocates ancestor_live[0])
2255  *
2256  * Reads main's fp-16 to force ancestor_live[0] allocation, then
2257  * calls nop_callee (pruning point), then deref_ancestor.
2258  */
2259 static __used __naked void mid_seq_touch(void)
2260 {
2261 	asm volatile (
2262 	"r6 = r1;"			/* save &main_fp-8 in callee-saved */
2263 	"r0 = *(u64 *)(r2 + 0);"	/* read main's fp-16: triggers anc_use[0] */
2264 	"call nop_callee;"		/* no-op, creates pruning point after */
2265 	"r1 = r6;"			/* restore ptr to &main_fp-8 */
2266 	"call deref_ancestor;"		/* reads main's fp-8, dereferences */
2267 	"r0 = 0;"
2268 	"exit;"
2269 	::: __clobber_all);
2270 }
2271 
2272 static __used __naked void nop_callee(void)
2273 {
2274 	asm volatile (
2275 	"r0 = 0;"
2276 	"exit;"
2277 	::: __clobber_all);
2278 }
2279 
2280 /* Reads main's fp-8 through forwarded pointer, dereferences result */
2281 static __used __naked void deref_ancestor(void)
2282 {
2283 	asm volatile (
2284 	"r0 = *(u64 *)(r1 + 0);"	/* read main's fp-8 */
2285 	"r0 = *(u64 *)(r0 + 0);"	/* deref: safe for map_ptr, unsafe for scalar */
2286 	"r0 = 0;"
2287 	"exit;"
2288 	::: __clobber_all);
2289 }
2290 
2291 /*
2292  * Test: callee loads an fp-derived pointer from caller's stack, then
2293  * reads through it to access another caller stack slot.
2294  *
2295  * main stores PTR_TO_MAP_VALUE at fp-24, stores &fp-24 (an fp-derived
2296  * pointer) at fp-8, passes &fp-8 through mid_fwd_spilled_ptr to
2297  * load_ptr_deref_grandchild.  The leaf loads the pointer from main's
2298  * fp-8, then reads main's fp-24 through the loaded pointer.
2299  *
2300  * fill_from_stack() in arg_track_xfer() only handles local-frame
2301  * FP-derived loads (src_is_local_fp check requires frame == depth).
2302  * When a callee loads from a parent-frame pointer (frame < depth),
2303  * the loaded value gets ARG_NONE instead of being recognized as
2304  * fp-derived.  Subsequent reads through that loaded pointer are
2305  * invisible to liveness — nonlocal_use is never set for fp-24.
2306  *
2307  * clean_live_states() cleans the current state at every prune point.
2308  * Because liveness misses fp-24, refined_caller_live_stack() tells
2309  * __clean_func_state() that fp-24 is dead, which destroys the
2310  * PTR_TO_MAP_VALUE spill before the grandchild can read it.
2311  * The grandchild then reads STACK_INVALID → scalar, and the deref
2312  * is rejected with "R0 invalid mem access 'scalar'" — even though
2313  * fp-24 is genuinely live and holds a valid map pointer.
2314  *
2315  * This is a false positive: a valid program incorrectly rejected.
2316  */
2317 SEC("socket")
2318 __flag(BPF_F_TEST_STATE_FREQ)
2319 __success
2320 __naked void spilled_fp_cross_frame_deref(void)
2321 {
2322 	asm volatile (
2323 	/* Map lookup to get PTR_TO_MAP_VALUE */
2324 	"r1 = %[map] ll;"
2325 	"*(u32 *)(r10 - 32) = 0;"
2326 	"r2 = r10;"
2327 	"r2 += -32;"
2328 	"call %[bpf_map_lookup_elem];"
2329 	"if r0 == 0 goto l_exit%=;"
2330 	/* fp-24 = PTR_TO_MAP_VALUE */
2331 	"*(u64 *)(r10 - 24) = r0;"
2332 	/* Store pointer to fp-24 at fp-8 */
2333 	"r1 = r10;"
2334 	"r1 += -24;"
2335 	"*(u64 *)(r10 - 8) = r1;"
2336 	/* R1 = &fp-8: pointer to the spilled ptr */
2337 	"r1 = r10;"
2338 	"r1 += -8;"
2339 	"call mid_fwd_spilled_ptr;"
2340 	"r0 = 0;"
2341 	"exit;"
2342 "l_exit%=:"
2343 	"r0 = 0;"
2344 	"exit;"
2345 	:
2346 	: __imm(bpf_map_lookup_elem),
2347 	  __imm_addr(map)
2348 	: __clobber_all);
2349 }
2350 
2351 /* Forwards R1 (ptr to main's fp-8, which holds &main_fp-24) to leaf */
2352 static __used __naked void mid_fwd_spilled_ptr(void)
2353 {
2354 	asm volatile (
2355 	"call load_ptr_deref_grandchild;"
2356 	"r0 = 0;"
2357 	"exit;"
2358 	::: __clobber_all);
2359 }
2360 
2361 /*
2362  * R1 = &main_fp-8 (where main stored ptr to fp-24)
2363  * Loads the ptr from main's fp-8, reads main's fp-24 through it,
2364  * then dereferences the result.
2365  */
2366 static __used __naked void load_ptr_deref_grandchild(void)
2367 {
2368 	asm volatile (
2369 	/* Load ptr from main's fp-8 → r2 = &main_fp-24 */
2370 	"r2 = *(u64 *)(r1 + 0);"
2371 	/* Read main's fp-24 through loaded ptr */
2372 	"r0 = *(u64 *)(r2 + 0);"
2373 	/* Dereference: safe for map_ptr */
2374 	"r0 = *(u64 *)(r0 + 0);"
2375 	"r0 = 0;"
2376 	"exit;"
2377 	::: __clobber_all);
2378 }
2379 
2380 /*
2381  * Exercise merge_nonlocal_live().
2382  *
2383  * merge_shared_mid is analyzed twice (once from each wrapper), so the
2384  * callsite within merge_shared_mid that calls merge_leaf_read gets its
2385  * nonlocal_live info merged twice via merge_nonlocal_live().
2386  */
2387 SEC("socket")
2388 __log_level(2)
2389 __success
2390 __msg("14: (85) call pc+2	r1: fp0-16")
2391 __msg("17: (79) r0 = *(u64 *)(r1 +0) // r1=fp0-16")
2392 __msg("14: (85) call pc+2	r1: fp0-8")
2393 __msg("17: (79) r0 = *(u64 *)(r1 +0) // r1=fp0-8")
2394 __msg("5: (85) call pc+{{.*}}                   ; use: fp0-8 fp0-16")
2395 __naked void test_merge_nonlocal_live(void)
2396 {
2397 	asm volatile (
2398 	"r1 = 0;"
2399 	"*(u64 *)(r10 - 8) = r1;"
2400 	"*(u64 *)(r10 - 16) = r1;"
2401 	"r1 = r10;"
2402 	"r1 += -8;"
2403 	"call merge_wrapper_a;"
2404 	"r1 = r10;"
2405 	"r1 += -16;"
2406 	"call merge_wrapper_b;"
2407 	"r0 = 0;"
2408 	"exit;"
2409 	::: __clobber_all);
2410 }
2411 
2412 static __used __naked void merge_wrapper_a(void)
2413 {
2414 	asm volatile (
2415 	"call merge_shared_mid;"
2416 	"r0 = 0;"
2417 	"exit;"
2418 	::: __clobber_all);
2419 }
2420 
2421 static __used __naked void merge_wrapper_b(void)
2422 {
2423 	asm volatile (
2424 	"call merge_shared_mid;"
2425 	"r0 = 0;"
2426 	"exit;"
2427 	::: __clobber_all);
2428 }
2429 
2430 static __used __naked void merge_shared_mid(void)
2431 {
2432 	asm volatile (
2433 	"call merge_leaf_read;"
2434 	"r0 = 0;"
2435 	"exit;"
2436 	::: __clobber_all);
2437 }
2438 
2439 static __used __naked void merge_leaf_read(void)
2440 {
2441 	asm volatile (
2442 	"r0 = *(u64 *)(r1 + 0);"
2443 	"r0 = 0;"
2444 	"exit;"
2445 	::: __clobber_all);
2446 }
2447 
2448 /* Same bpf_loop instruction calls different callbacks depending on branch. */
2449 SEC("socket")
2450 __log_level(2)
2451 __success
2452 __msg("stack use/def subprog#0 bpf_loop_two_callbacks (d0,cs0):")
2453 __msg("5: (85) call pc+{{.*}};{{.*}} may_def: fp0-8..-{{(512|2048)}}{{$}}")
2454 __msg("8: (85) call pc+{{.*}};{{.*}} may_def: fp0-8..-{{(512|2048)}}{{$}}")
2455 __msg("call bpf_loop#181            ; use: fp2-8..-{{(512|2048)}} fp1-8..-{{(512|2048)}} fp0-8..-{{(512|2048)}}"
2456       " may_def: fp2-8..-{{(512|2048)}} fp1-8..-{{(512|2048)}} fp0-8..-{{(512|2048)}}{{$}}")
2457 __naked void bpf_loop_two_callbacks(void)
2458 {
2459 	asm volatile (
2460 	"r1 = 0;"
2461 	"*(u64 *)(r10 - 8) = r1;"
2462 	"*(u64 *)(r10 - 16) = r1;"
2463 	"r1 = r10;"
2464 	"r1 += -8;"
2465 	"call dyn_wrapper_a;"
2466 	"r1 = r10;"
2467 	"r1 += -16;"
2468 	"call dyn_wrapper_b;"
2469 	"r0 = 0;"
2470 	"exit;"
2471 	::: __clobber_all);
2472 }
2473 
2474 static __used __naked void dyn_wrapper_a(void)
2475 {
2476 	asm volatile (
2477 	"call mid_dynamic_cb;"
2478 	"r0 = 0;"
2479 	"exit;"
2480 	::: __clobber_all);
2481 }
2482 
2483 static __used __naked void dyn_wrapper_b(void)
2484 {
2485 	asm volatile (
2486 	"call mid_dynamic_cb;"
2487 	"r0 = 0;"
2488 	"exit;"
2489 	::: __clobber_all);
2490 }
2491 
2492 static __used __naked void mid_dynamic_cb(void)
2493 {
2494 	asm volatile (
2495 	"r6 = r1;"
2496 	"call %[bpf_get_prandom_u32];"
2497 	"if r0 == 0 goto 1f;"
2498 	"r2 = dyn_cb_a ll;"
2499 	"goto 2f;"
2500 	"1:"
2501 	"r2 = dyn_cb_b ll;"
2502 	"2:"
2503 	"r1 = 1;"
2504 	"r3 = r6;" /* ctx = fp-derived ptr from parent */
2505 	"r4 = 0;"
2506 	"call %[bpf_loop];"
2507 	"r0 = 0;"
2508 	"exit;"
2509 	:: __imm(bpf_get_prandom_u32),
2510 	   __imm(bpf_loop)
2511 	: __clobber_all);
2512 }
2513 
2514 /* Callback A/B: read parent stack through ctx */
2515 static __used __naked void dyn_cb_a(void)
2516 {
2517 	asm volatile (
2518 	"r0 = *(u64 *)(r2 + 0);"
2519 	"r0 = 0;"
2520 	"exit;"
2521 	::: __clobber_all);
2522 }
2523 
2524 static __used __naked void dyn_cb_b(void)
2525 {
2526 	asm volatile (
2527 	"r0 = *(u64 *)(r2 + 0);"
2528 	"r0 = 0;"
2529 	"exit;"
2530 	::: __clobber_all);
2531 }
2532 
2533 /*
2534  * Path A: r0 = map_lookup result (non-FP, ARG_NONE for stack tracking)
2535  * Path B: r0 = fp-8 (FP-derived, frame=0, off=-8)
2536  * At the join: r0 is not guaranteed to be a frame pointer.
2537  */
2538 SEC("socket")
2539 __log_level(2)
2540 __msg("10: (79) r0 = *(u64 *)(r10 -8) // r0=fp0-8|fp0+0")
2541 __naked void stack_or_non_stack_write(void)
2542 {
2543 	asm volatile (
2544 	/* initial write to fp-8 */
2545 	"*(u64 *)(r10 - 8) = 0;"
2546 	/* map lookup to get a non-FP pointer */
2547 	"r2 = r10;"
2548 	"r2 += -4;"
2549 	"r1 = %[map] ll;"
2550 	"call %[bpf_map_lookup_elem];"
2551 	/* r0 = map_value (ARG_NONE) */
2552 	"if r0 != 0 goto 1f;"
2553 	/* path B: r0 = fp-8 */
2554 	"r0 = r10;"
2555 	"r0 += -8;"
2556 "1:"
2557 	/* join: the write is not a def for fp[0]-8 */
2558 	"*(u64 *)(r0 + 0) = 7;"
2559 	/* read fp-8: should be non-poisoned */
2560 	"r0 = *(u64 *)(r10 - 8);"
2561 	"exit;"
2562 	:
2563 	: __imm(bpf_map_lookup_elem),
2564 	  __imm_addr(map)
2565 	: __clobber_all);
2566 }
2567 
2568 SEC("socket")
2569 __log_level(2)
2570 __flag(BPF_F_TEST_STATE_FREQ)
2571 __msg("subprog#2 write_first_read_second:")
2572 __msg("17: (7a) *(u64 *)(r1 +0) = 42{{$}}")
2573 __msg("18: (79) r0 = *(u64 *)(r2 +0) // r1=fp0-8 r2=fp0-16{{$}}")
2574 /*
2575  * The callee's write into the caller frame is summarized as may_def at the
2576  * call sites, transitively across both frames (depth 2 -> 1 -> 0). The two
2577  * call sites differ because the shared write_first_read_second instance
2578  * accumulates the cross-pass union ({fp-8, fp-16}) at different points
2579  * relative to each forwarding_rw's summarization.
2580  */
2581 __msg("stack use/def subprog#0 shared_instance_must_write_overwrite (d0,cs0):")
2582 __msg("7: (85) call pc+7                    ; use: fp0-8 fp0-16 may_def: fp0-8 fp0-16")
2583 __msg("12: (85) call pc+2                    ; use: fp0-8 may_def: fp0-16")
2584 __msg("stack use/def subprog#1 forwarding_rw (d1,cs7):")
2585 __msg("15: (85) call pc+1                    ; use: fp0-8 fp0-16 may_def: fp0-8 fp0-16")
2586 __msg("stack use/def subprog#1 forwarding_rw (d1,cs12):")
2587 __msg("15: (85) call pc+1                    ; use: fp0-8 may_def: fp0-16")
2588 __msg("stack use/def subprog#2 write_first_read_second (d2,cs15):")
2589 /*
2590  * Shared across two callsites with swapped args (r1 is fp-8 on one pass,
2591  * fp-16 on the other): must_write intersects to empty (no def), may_write
2592  * unions to both slots.
2593  */
2594 __msg("17: (7a) *(u64 *)(r1 +0) = 42         ; may_def: fp0-8 fp0-16")
2595 __msg("18: (79) r0 = *(u64 *)(r2 +0)         ; use: fp0-8 fp0-16")
2596 __naked void shared_instance_must_write_overwrite(void)
2597 {
2598 	asm volatile (
2599 	"r1 = 1;"
2600 	"*(u64 *)(r10 - 8) = r1;"
2601 	"*(u64 *)(r10 - 16) = r1;"
2602 	/* Call 1: write_first_read_second(&fp[-8], &fp[-16]) */
2603 	"r1 = r10;"
2604 	"r1 += -8;"
2605 	"r2 = r10;"
2606 	"r2 += -16;"
2607 	"call forwarding_rw;"
2608 	/* Call 2: write_first_read_second(&fp[-16], &fp[-8]) */
2609 	"r1 = r10;"
2610 	"r1 += -16;"
2611 	"r2 = r10;"
2612 	"r2 += -8;"
2613 	"call forwarding_rw;"
2614 	"r0 = 0;"
2615 	"exit;"
2616 	::: __clobber_all);
2617 }
2618 
2619 static __used __naked void forwarding_rw(void)
2620 {
2621 	asm volatile (
2622 	"call write_first_read_second;"
2623 	"exit;"
2624 	::: __clobber_all);
2625 }
2626 
2627 static __used __naked void write_first_read_second(void)
2628 {
2629 	asm volatile (
2630 	"*(u64 *)(r1 + 0) = 42;"
2631 	"r0 = *(u64 *)(r2 + 0);"
2632 	"exit;"
2633 	::: __clobber_all);
2634 }
2635 
2636 /*
2637  * merge_instances() must preserve may_write when one pass doesn't touch a frame.
2638  * mvs_leaf is reached through a single callsite (mvs_mid) from two outer chains,
2639  * so it is one shared (depth,callsite) instance analyzed twice and merged:
2640  * - chain A passes r2 = map value, mvs_leaf's doesn't touch stack;
2641  * - chain B passes r2 = caller stack slot fp-8.
2642  * The merge must keep the stack pass's may_write even though the map pass left
2643  * frame 0 untouched.
2644  */
2645 SEC("socket")
2646 __success
2647 __log_level(2)
2648 __msg("stack use/def subprog#2 mvs_leaf (d2,cs19):")
2649 __msg("21: (7a) *(u64 *)(r2 +0) = 42         ; may_def: fp0-8")
2650 __naked void merge_preserves_may_write(void)
2651 {
2652 	asm volatile (
2653 	"r1 = 0;"
2654 	"*(u64 *)(r10 - 24) = r1;"		/* map key */
2655 	"r1 = %[map] ll;"
2656 	"r2 = r10;"
2657 	"r2 += -24;"
2658 	"call %[bpf_map_lookup_elem];"
2659 	"if r0 == 0 goto 1f;"
2660 	/* chain A: r2 = map value (ARG_NONE, not stack) */
2661 	"r1 = r10;"
2662 	"r1 += -16;"				/* unused fp anchor */
2663 	"r2 = r0;"
2664 	"call mvs_mid;"
2665 	/* chain B: r2 = caller stack slot fp-8 */
2666 	"r1 = r10;"
2667 	"r1 += -16;"
2668 	"r2 = r10;"
2669 	"r2 += -8;"
2670 	"call mvs_mid;"
2671 "1:"
2672 	"r0 = 0;"
2673 	"exit;"
2674 	: : __imm(bpf_map_lookup_elem), __imm_addr(map)
2675 	: __clobber_all);
2676 }
2677 
2678 static __used __naked void mvs_mid(void)
2679 {
2680 	asm volatile (
2681 	"call mvs_leaf;"
2682 	"exit;"
2683 	::: __clobber_all);
2684 }
2685 
2686 static __used __naked void mvs_leaf(void)
2687 {
2688 	asm volatile (
2689 	"*(u64 *)(r2 + 0) = 42;"
2690 	"r0 = 0;"
2691 	"exit;"
2692 	::: __clobber_all);
2693 }
2694 
2695 /*
2696  * Shared must_write when (callsite, depth) instance is reused.
2697  * Main calls fwd_to_stale_wr at two sites. fwd_to_stale_wr calls
2698  * stale_wr_leaf at a single internal callsite. Both calls share
2699  * stale_wr_leaf's (callsite, depth) instance.
2700  *
2701  * Call 1: stale_wr_leaf(map_value, fp-8) writes map, reads fp-8.
2702  * Call 2: stale_wr_leaf(fp-8, fp-8) writes fp-8, reads fp-8.
2703  *
2704  * The analysis can't presume that stale_wr_leaf() always writes fp-8,
2705  * it must conservatively join must_write masks computed for both calls.
2706  */
2707 SEC("socket")
2708 __success
2709 __naked void stale_must_write_cross_callsite(void)
2710 {
2711 	asm volatile (
2712 	"*(u64 *)(r10 - 8) = 0;"
2713 	/* Call 1: map_value write, fp-8 read (processed second in PO) */
2714 	"*(u32 *)(r10 - 16) = 0;"
2715 	"r1 = %[map] ll;"
2716 	"r2 = r10;"
2717 	"r2 += -16;"
2718 	"call %[bpf_map_lookup_elem];"
2719 	"if r0 == 0 goto 1f;"
2720 	"r1 = r0;"
2721 	"r2 = r10;"
2722 	"r2 += -8;"
2723 	"call fwd_to_stale_wr;"
2724 	/* Call 2: fp-8 write, fp-8 read (processed first in PO) */
2725 	"r1 = r10;"
2726 	"r1 += -8;"
2727 	"r2 = r1;"
2728 	"call fwd_to_stale_wr;"
2729 "1:"
2730 	"r0 = 0;"
2731 	"exit;"
2732 	:: __imm_addr(map),
2733 	   __imm(bpf_map_lookup_elem)
2734 	: __clobber_all);
2735 }
2736 
2737 static __used __naked void fwd_to_stale_wr(void)
2738 {
2739 	asm volatile (
2740 	"call stale_wr_leaf;"
2741 	"exit;"
2742 	::: __clobber_all);
2743 }
2744 
2745 static __used __naked void stale_wr_leaf(void)
2746 {
2747 	asm volatile (
2748 	"*(u64 *)(r1 + 0) = 42;"
2749 	"r0 = *(u64 *)(r2 + 0);"
2750 	"exit;"
2751 	::: __clobber_all);
2752 }
2753 
2754 #ifdef CAN_USE_LOAD_ACQ_STORE_REL
2755 
2756 SEC("socket")
2757 __log_level(2)
2758 __success
2759 __msg("*(u64 *)(r0 +0) = 42         ; def: fp0-16")
2760 __naked void load_acquire_dont_clear_dst(void)
2761 {
2762 	asm volatile (
2763 	"r0 = r10;"
2764 	"r0 += -16;"
2765 	"*(u64 *)(r0 + 0) = r0;"	/* fp[-16] == &fp[-16] */
2766 	".8byte %[load_acquire_insn];"	/* load_acquire is a special case for BPF_STX, */
2767 	"r0 = *(u64 *)(r10 - 16);"	/* it shouldn't clear tracking info for */
2768 	"*(u64 *)(r0 + 0) = 42;"	/* dst register, r0 in this case. */
2769 	"r0 = 0;"
2770 	"exit;"
2771 	:
2772 	: __imm_insn(load_acquire_insn,
2773 		     BPF_ATOMIC_OP(BPF_DW, BPF_LOAD_ACQ, BPF_REG_0, BPF_REG_0, 0))
2774 	: __clobber_all);
2775 }
2776 
2777 #endif /* CAN_USE_LOAD_ACQ_STORE_REL */
2778 
2779 SEC("socket")
2780 __log_level(2)
2781 __success
2782 __msg("lock *(u64 *)(r10 -8) += r1{{.*}}use: fp0-8{{.*}}may_def: fp0-8")
2783 __msg("r1 = atomic64_fetch_add((u64 *)(r10 -8), r1){{.*}}use: fp0-8{{.*}}may_def: fp0-8")
2784 __msg("r1 = atomic64_xchg((u64 *)(r10 -8), r1){{.*}}use: fp0-8{{.*}}may_def: fp0-8")
2785 __msg("r0 = atomic64_cmpxchg((u64 *)(r10 -8), r0, r1){{.*}}use: fp0-8{{.*}}may_def: fp0-8")
2786 __naked void atomic_rmw(void)
2787 {
2788 	asm volatile (
2789 	"r1 = 0;"
2790 	"*(u64 *)(r10 - 8) = r1;"
2791 	".8byte %[atomic_add];"
2792 	".8byte %[atomic_fetch_add];"
2793 	".8byte %[atomic_xchg];"
2794 	"r0 = 0;"
2795 	".8byte %[atomic_cmpxchg];"
2796 	"exit;"
2797 	:
2798 	: __imm_insn(atomic_add, BPF_ATOMIC_OP(BPF_DW, BPF_ADD, BPF_REG_10, BPF_REG_1, -8)),
2799 	  __imm_insn(atomic_fetch_add, BPF_ATOMIC_OP(BPF_DW, BPF_ADD | BPF_FETCH, BPF_REG_10, BPF_REG_1, -8)),
2800 	  __imm_insn(atomic_xchg, BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_10, BPF_REG_1, -8)),
2801 	  __imm_insn(atomic_cmpxchg, BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_10, BPF_REG_1, -8))
2802 	: __clobber_all);
2803 }
2804 
2805 #ifdef CAN_USE_LOAD_ACQ_STORE_REL
2806 
2807 /*
2808  * An 8-byte load-acquire from a spill slot restores the spilled register.
2809  * The store through the restored pointer is a definite write to fp-8.
2810  */
2811 SEC("socket")
2812 __log_level(2)
2813 __success
2814 __msg("r2 = load_acquire((u64 *)(r10 -16)){{.*}}use: fp0-16")
2815 __msg("*(u64 *)(r2 +0) = 42{{.*}}def: fp0-8 may_def: fp0-8")
2816 __naked void load_acquire_fills_spilled_ptr(void)
2817 {
2818 	asm volatile (
2819 	"r1 = r10;"
2820 	"r1 += -8;"
2821 	"*(u64 *)(r10 - 16) = r1;"		/* fp-16 = &fp-8 */
2822 	".8byte %[load_acquire_insn];"		/* r2 = load_acquire(fp-16) */
2823 	"*(u64 *)(r2 + 0) = 42;"
2824 	"r0 = 0;"
2825 	"exit;"
2826 	:
2827 	: __imm_insn(load_acquire_insn,
2828 		     BPF_ATOMIC_OP(BPF_DW, BPF_LOAD_ACQ, BPF_REG_2, BPF_REG_10, -16))
2829 	: __clobber_all);
2830 }
2831 
2832 /* An 8-byte store-release spills the register, same as a plain store. */
2833 SEC("socket")
2834 __log_level(2)
2835 __success
2836 __msg("store_release((u64 *)(r10 -16), r1){{.*}}def: fp0-16")
2837 __msg("*(u64 *)(r2 +0) = 42{{.*}}def: fp0-8 may_def: fp0-8")
2838 __naked void store_release_spills_ptr(void)
2839 {
2840 	asm volatile (
2841 	"r1 = r10;"
2842 	"r1 += -8;"
2843 	".8byte %[store_release_insn];"		/* store_release(fp-16, r1): fp-16 = &fp-8 */
2844 	"r2 = *(u64 *)(r10 - 16);"
2845 	"*(u64 *)(r2 + 0) = 42;"
2846 	"r0 = 0;"
2847 	"exit;"
2848 	:
2849 	: __imm_insn(store_release_insn,
2850 		     BPF_ATOMIC_OP(BPF_DW, BPF_STORE_REL, BPF_REG_10, BPF_REG_1, -16))
2851 	: __clobber_all);
2852 }
2853 
2854 #endif /* CAN_USE_LOAD_ACQ_STORE_REL */
2855 
2856 /* Stack fills through xchg, fetch_add and cmpxchg instructions. */
2857 SEC("socket")
2858 __log_level(2)
2859 __success
2860 __msg("r2 = atomic64_xchg((u64 *)(r10 -16), r2){{.*}}use: fp0-16{{.*}}may_def: fp0-16")
2861 __msg("*(u64 *)(r2 +0) = 42{{.*}}def: fp0-8 may_def: fp0-8")
2862 __msg("r3 = atomic64_fetch_add((u64 *)(r10 -16), r3){{.*}}use: fp0-16{{.*}}may_def: fp0-16")
2863 __msg("*(u64 *)(r3 +0) = 42{{.*}}def: fp0-8 may_def: fp0-8")
2864 __msg("r0 = atomic64_cmpxchg((u64 *)(r10 -16), r0, r4){{.*}}use: fp0-16{{.*}}may_def: fp0-16")
2865 __msg("*(u64 *)(r0 +0) = 42{{.*}}def: fp0-8 may_def: fp0-8")
2866 __naked void atomic_rmw_fills_spilled_ptr(void)
2867 {
2868 	asm volatile (
2869 	"r1 = r10;"
2870 	"r1 += -8;"
2871 	"*(u64 *)(r10 - 16) = r1;"		/* fp-16 = &fp-8 */
2872 	"r2 = 0;"
2873 	".8byte %[atomic_xchg];"		/* r2 = xchg(fp-16, r2) */
2874 	"*(u64 *)(r2 + 0) = 42;"
2875 	"*(u64 *)(r10 - 16) = r1;"
2876 	"r3 = 0;"
2877 	".8byte %[atomic_fetch_add];"		/* r3 = fetch_add(fp-16, r3) */
2878 	"*(u64 *)(r3 + 0) = 42;"
2879 	"*(u64 *)(r10 - 16) = r1;"
2880 	"r0 = 0;"
2881 	"r4 = 0;"
2882 	".8byte %[atomic_cmpxchg];"		/* r0 = cmpxchg(fp-16, r0, r4) */
2883 	"*(u64 *)(r0 + 0) = 42;"
2884 	"r0 = 0;"
2885 	"exit;"
2886 	:
2887 	: __imm_insn(atomic_xchg, BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_10, BPF_REG_2, -16)),
2888 	  __imm_insn(atomic_fetch_add, BPF_ATOMIC_OP(BPF_DW, BPF_ADD | BPF_FETCH, BPF_REG_10, BPF_REG_3, -16)),
2889 	  __imm_insn(atomic_cmpxchg, BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_10, BPF_REG_4, -16))
2890 	: __clobber_all);
2891 }
2892 
2893 /* xchg returning caller's &fp-8. */
2894 SEC("socket")
2895 __log_level(2)
2896 __success
2897 __msg("r2 = atomic64_xchg((u64 *)(r1 +0), r2){{.*}}use: fp0-16{{.*}}may_def: fp0-16")
2898 __msg("*(u64 *)(r2 +0) = 42{{.*}}; may_def: fp0-8")
2899 __naked void atomic_rmw_fills_parent_spilled_ptr(void)
2900 {
2901 	asm volatile (
2902 	"r1 = r10;"
2903 	"r1 += -8;"
2904 	"*(u64 *)(r10 - 16) = r1;"		/* fp-16 = &fp-8 */
2905 	"r1 = r10;"
2906 	"r1 += -16;"
2907 	"call atomic_rmw_fills_parent_spilled_ptr_callee;"
2908 	"r0 = 0;"
2909 	"exit;"
2910 	::: __clobber_all);
2911 }
2912 
2913 static __used __naked void atomic_rmw_fills_parent_spilled_ptr_callee(void)
2914 {
2915 	asm volatile (
2916 	"r2 = 0;"
2917 	".8byte %[atomic_xchg];"		/* r2 = xchg(r1, r2) == caller's &fp-8 */
2918 	"*(u64 *)(r2 + 0) = 42;"
2919 	"r0 = 0;"
2920 	"exit;"
2921 	:
2922 	: __imm_insn(atomic_xchg, BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_1, BPF_REG_2, 0))
2923 	: __clobber_all);
2924 }
2925 
2926 SEC("socket")
2927 __success
2928 __naked void imprecise_fill_loses_cross_frame(void)
2929 {
2930 	asm volatile (
2931 	"*(u64 *)(r10 - 8) = 0;"
2932 	"r1 = r10;"
2933 	"r1 += -8;"
2934 	"call imprecise_fill_cross_frame;"
2935 	"exit;"
2936 	::: __clobber_all);
2937 }
2938 
2939 static __used __naked void imprecise_fill_cross_frame(void)
2940 {
2941 	asm volatile (
2942 	/* spill &caller_fp-8 to callee's fp-8 */
2943 	"*(u64 *)(r10 - 8) = r1;"
2944 	/* imprecise FP pointer in r1 */
2945 	"r1 = r10;"
2946 	"r2 = -8;"
2947 	"r1 += r2;"
2948 	/* load from imprecise offset. fill_from_stack returns
2949 	 * ARG_IMPRECISE{mask=BIT(1)}, losing frame 0
2950 	 */
2951 	"r1 = *(u64 *)(r1 + 0);"
2952 	/* read caller's fp-8 through loaded pointer, should mark fp0-8 live */
2953 	"r0 = *(u64 *)(r1 + 0);"
2954 	"r0 = 0;"
2955 	"exit;"
2956 	:: __imm(bpf_get_prandom_u32)
2957 	: __clobber_all);
2958 }
2959 
2960 /* Test that spill_to_stack with multi-offset dst (sz=8) joins instead
2961  * of overwriting. r1 has offsets [-8, -16]. Both slots hold FP-derived
2962  * pointers. Writing through r1 should join *val with existing values,
2963  * not destroy them.
2964  *
2965  *   fp-8  = &fp-24
2966  *   fp-16 = &fp-32
2967  *   r1 = fp-8 or fp-16 (two offsets from branch)
2968  *   *(u64 *)(r1 + 0) = &fp-24   -- writes to one slot, other untouched
2969  *   r0 = *(u64 *)(r10 - 16)     -- fill from fp-16
2970  *   r0 = *(u64 *)(r0 + 0)       -- deref: should produce use
2971  */
2972 SEC("socket")
2973 __log_level(2)
2974 __success
2975 __msg("20: (79) r0 = *(u64 *)(r10 -16)")
2976 __msg("21: (79) r0 = *(u64 *)(r0 +0)         ; use: fp0-24 fp0-32")
2977 __naked void spill_join_with_multi_off(void)
2978 {
2979 	asm volatile (
2980 	/* fp-8 = &fp-24, fp-16 = &fp-32 (different pointers) */
2981 	"*(u64 *)(r10 - 24) = 0;"
2982 	"*(u64 *)(r10 - 32) = 0;"
2983 	"r1 = r10;"
2984 	"r1 += -24;"
2985 	"*(u64 *)(r10 - 8) = r1;"
2986 	"r1 = r10;"
2987 	"r1 += -32;"
2988 	"*(u64 *)(r10 - 16) = r1;"
2989 	/* create r1 with two candidate offsets: fp-8 or fp-16 */
2990 	"call %[bpf_get_prandom_u32];"
2991 	"if r0 == 0 goto 1f;"
2992 	"r1 = r10;"
2993 	"r1 += -8;"
2994 	"goto 2f;"
2995 "1:"
2996 	"r1 = r10;"
2997 	"r1 += -16;"
2998 "2:"
2999 	/* write &fp-24 through multi-offset r1: hits one slot, other untouched */
3000 	"r2 = r10;"
3001 	"r2 += -24;"
3002 	"*(u64 *)(r1 + 0) = r2;"
3003 	/* read back *fp-8 and *fp-16 */
3004 	"r0 = *(u64 *)(r10 - 8);"
3005 	"r0 = *(u64 *)(r0 + 0);"
3006 	"r0 = *(u64 *)(r10 - 16);"
3007 	"r0 = *(u64 *)(r0 + 0);"
3008 	"exit;"
3009 	:: __imm(bpf_get_prandom_u32)
3010 	: __clobber_all);
3011 }
3012 
3013 /* Test that spill_to_stack with imprecise dst (off_cnt == 0, sz=8)
3014  * joins instead of overwriting. Use "r2 = -8; r1 += r2" to make
3015  * arg tracking lose offset precision while the main verifier keeps
3016  * r1 as PTR_TO_STACK with fixed offset. Both slots hold FP-derived
3017  * pointers. Writing through r1 should join *val with existing
3018  * values, not destroy them.
3019  *
3020  *   fp-8  = &fp-24
3021  *   fp-16 = &fp-32
3022  *   r1 = fp-8 (imprecise to arg tracking)
3023  *   *(u64 *)(r1 + 0) = &fp-24   -- since r1 is imprecise, this adds &fp-24
3024  *                                  to the set of possible values for all slots,
3025  *                                  hence the values at fp-16 become [fp-24, fp-32]
3026  *   r0 = *(u64 *)(r10 - 16)
3027  *   r0 = *(u64 *)(r0 + 0)       -- deref: should produce use of fp-24 or fp-32
3028  */
3029 SEC("socket")
3030 __log_level(2)
3031 __success
3032 __msg("15: (79) r0 = *(u64 *)(r0 +0)         ; use: fp0-24 fp0-32")
3033 __naked void spill_join_with_imprecise_off(void)
3034 {
3035 	asm volatile (
3036 	"*(u64 *)(r10 - 24) = 0;"
3037 	"*(u64 *)(r10 - 32) = 0;"
3038 	"r1 = r10;"
3039 	"r1 += -24;"
3040 	"*(u64 *)(r10 - 8) = r1;"
3041 	"r1 = r10;"
3042 	"r1 += -32;"
3043 	"*(u64 *)(r10 - 16) = r1;"
3044 	/* r1 = fp-8 but arg tracking sees off_cnt == 0 */
3045 	"r1 = r10;"
3046 	"r2 = -8;"
3047 	"r1 += r2;"
3048 	/* write through imprecise r1 */
3049 	"r3 = r10;"
3050 	"r3 += -24;"
3051 	"*(u64 *)(r1 + 0) = r3;"
3052 	/* read back fp-16: at_stack should still track &fp-32 */
3053 	"r0 = *(u64 *)(r10 - 16);"
3054 	/* deref: should produce use for fp-32 */
3055 	"r0 = *(u64 *)(r0 + 0);"
3056 	"r0 = 0;"
3057 	"exit;"
3058 	::: __clobber_all);
3059 }
3060 
3061 /*
3062  * Same as spill_join_with_multi_off but the write is BPF_ST (store
3063  * immediate) instead of BPF_STX. BPF_ST goes through
3064  * clear_stack_for_all_offs() rather than spill_to_stack(), and that
3065  * path also needs to join instead of overwriting.
3066  *
3067  *   fp-8  = &fp-24
3068  *   fp-16 = &fp-32
3069  *   r1 = fp-8 or fp-16 (two offsets from branch)
3070  *   *(u64 *)(r1 + 0) = 0        -- BPF_ST with immediate
3071  *   r0 = *(u64 *)(r10 - 16)     -- fill from fp-16
3072  *   r0 = *(u64 *)(r0 + 0)       -- deref: should produce use
3073  */
3074 SEC("socket")
3075 __log_level(2)
3076 __failure
3077 __msg("15: (7a) *(u64 *)(r1 +0) = 0	fp-8: fp0-24 -> fp0-24|fp0+0	fp-16: fp0-32 -> fp0-32|fp0+0")
3078 __msg("17: (79) r0 = *(u64 *)(r0 +0)         ; use: fp0-32")
3079 __naked void st_imm_join_with_multi_off(void)
3080 {
3081 	asm volatile (
3082 	"*(u64 *)(r10 - 24) = 0;"
3083 	"*(u64 *)(r10 - 32) = 0;"
3084 	"r1 = r10;"
3085 	"r1 += -24;"
3086 	"*(u64 *)(r10 - 8) = r1;"
3087 	"r1 = r10;"
3088 	"r1 += -32;"
3089 	"*(u64 *)(r10 - 16) = r1;"
3090 	/* create r1 with two candidate offsets: fp-8 or fp-16 */
3091 	"call %[bpf_get_prandom_u32];"
3092 	"if r0 == 0 goto 1f;"
3093 	"r1 = r10;"
3094 	"r1 += -8;"
3095 	"goto 2f;"
3096 "1:"
3097 	"r1 = r10;"
3098 	"r1 += -16;"
3099 "2:"
3100 	/* BPF_ST: store immediate through multi-offset r1 */
3101 	"*(u64 *)(r1 + 0) = 0;"
3102 	/* read back fp-16 and deref */
3103 	"r0 = *(u64 *)(r10 - 16);"
3104 	"r0 = *(u64 *)(r0 + 0);"
3105 	"r0 = 0;"
3106 	"exit;"
3107 	:: __imm(bpf_get_prandom_u32)
3108 	: __clobber_all);
3109 }
3110 
3111 /*
3112  * Check that BPF_ST with a known offset fully overwrites stack slot
3113  * from the arg tracking point of view.
3114  */
3115 SEC("socket")
3116 __log_level(2)
3117 __success
3118 __msg("5: (7a) *(u64 *)(r1 +0) = 0	fp-8: fp0-16 -> _{{$}}")
3119 __naked void st_imm_join_with_single_off(void)
3120 {
3121 	asm volatile (
3122 	"r2 = r10;"
3123 	"r2 += -16;"
3124 	"*(u64 *)(r10 - 8) = r2;"
3125 	"r1 = r10;"
3126 	"r1 += -8;"
3127 	"*(u64 *)(r1 + 0) = 0;"
3128 	"r0 = 0;"
3129 	"exit;"
3130 	::: __clobber_all);
3131 }
3132 
3133 /*
3134  * Same as spill_join_with_imprecise_off but the write is BPF_ST.
3135  * Use "r2 = -8; r1 += r2" to make arg tracking lose offset
3136  * precision while the main verifier keeps r1 as fixed-offset.
3137  *
3138  *   fp-8  = &fp-24
3139  *   fp-16 = &fp-32
3140  *   r1 = fp-8 (imprecise to arg tracking)
3141  *   *(u64 *)(r1 + 0) = 0        -- BPF_ST with immediate
3142  *   r0 = *(u64 *)(r10 - 16)     -- fill from fp-16
3143  *   r0 = *(u64 *)(r0 + 0)       -- deref: should produce use
3144  */
3145 SEC("socket")
3146 __log_level(2)
3147 __success
3148 __msg("13: (79) r0 = *(u64 *)(r0 +0)         ; use: fp0-32")
3149 __naked void st_imm_join_with_imprecise_off(void)
3150 {
3151 	asm volatile (
3152 	"*(u64 *)(r10 - 24) = 0;"
3153 	"*(u64 *)(r10 - 32) = 0;"
3154 	"r1 = r10;"
3155 	"r1 += -24;"
3156 	"*(u64 *)(r10 - 8) = r1;"
3157 	"r1 = r10;"
3158 	"r1 += -32;"
3159 	"*(u64 *)(r10 - 16) = r1;"
3160 	/* r1 = fp-8 but arg tracking sees off_cnt == 0 */
3161 	"r1 = r10;"
3162 	"r2 = -8;"
3163 	"r1 += r2;"
3164 	/* store immediate through imprecise r1 */
3165 	"*(u64 *)(r1 + 0) = 0;"
3166 	/* read back fp-16 */
3167 	"r0 = *(u64 *)(r10 - 16);"
3168 	/* deref: should produce use */
3169 	"r0 = *(u64 *)(r0 + 0);"
3170 	"r0 = 0;"
3171 	"exit;"
3172 	::: __clobber_all);
3173 }
3174 
3175 /*
3176  * Test that spilling through an ARG_IMPRECISE pointer joins with
3177  * existing at_stack values. Subprog receives r1 = fp0-24 and
3178  * r2 = map_value, creates an ARG_IMPRECISE pointer by joining caller
3179  * and callee FP on two branches.
3180  *
3181  * Setup: callee spills &fp1-16 to fp1-8 (precise, tracked).
3182  * Then writes map_value through ARG_IMPRECISE r1 — on path A
3183  * this hits fp1-8, on path B it hits caller stack.
3184  * Since spill_to_stack is skipped for ARG_IMPRECISE dst,
3185  * fp1-8 tracking isn't joined with none.
3186  *
3187  * Expected after the imprecise write:
3188  * - arg tracking should show fp1-8 = fp1-16|fp1+0 (joined with none)
3189  * - read from fp1-8 and deref should produce use for fp1-16
3190  * - write through it should NOT produce def for fp1-16
3191  */
3192 SEC("socket")
3193 __log_level(2)
3194 __success
3195 __msg("26: (79) r0 = *(u64 *)(r10 -8) // r1=IMP3 r6=fp0-24 r7=fp1-16 fp-8=fp1-16|fp1+0")
3196 __naked void imprecise_dst_spill_join(void)
3197 {
3198 	asm volatile (
3199 	"*(u64 *)(r10 - 24) = 0;"
3200 	/* map lookup for a valid non-FP pointer */
3201 	"*(u32 *)(r10 - 32) = 0;"
3202 	"r1 = %[map] ll;"
3203 	"r2 = r10;"
3204 	"r2 += -32;"
3205 	"call %[bpf_map_lookup_elem];"
3206 	"if r0 == 0 goto 1f;"
3207 	/* r1 = &caller_fp-24, r2 = map_value */
3208 	"r1 = r10;"
3209 	"r1 += -24;"
3210 	"r2 = r0;"
3211 	"call imprecise_dst_spill_join_sub;"
3212 "1:"
3213 	"r0 = 0;"
3214 	"exit;"
3215 	:: __imm_addr(map),
3216 	   __imm(bpf_map_lookup_elem)
3217 	: __clobber_all);
3218 }
3219 
3220 static __used __naked void imprecise_dst_spill_join_sub(void)
3221 {
3222 	asm volatile (
3223 	/* r6 = &caller_fp-24 (frame=0), r8 = map_value */
3224 	"r6 = r1;"
3225 	"r8 = r2;"
3226 	/* spill &fp1-16 to fp1-8: at_stack[0] = fp1-16 */
3227 	"*(u64 *)(r10 - 16) = 0;"
3228 	"r7 = r10;"
3229 	"r7 += -16;"
3230 	"*(u64 *)(r10 - 8) = r7;"
3231 	/* branch to create ARG_IMPRECISE pointer */
3232 	"call %[bpf_get_prandom_u32];"
3233 	/* path B: r1 = caller fp-24 (frame=0) */
3234 	"r1 = r6;"
3235 	"if r0 == 0 goto 1f;"
3236 	/* path A: r1 = callee fp-8 (frame=1) */
3237 	"r1 = r10;"
3238 	"r1 += -8;"
3239 "1:"
3240 	/* r1 = ARG_IMPRECISE{mask=BIT(0)|BIT(1)}.
3241 	 * Write map_value (non-FP) through r1. On path A this overwrites fp1-8.
3242 	 * Should join at_stack[0] with none: fp1-16|fp1+0.
3243 	 */
3244 	"*(u64 *)(r1 + 0) = r8;"
3245 	/* read fp1-8: should be fp1-16|fp1+0 (joined) */
3246 	"r0 = *(u64 *)(r10 - 8);"
3247 	"*(u64 *)(r0 + 0) = 42;"
3248 	"r0 = 0;"
3249 	"exit;"
3250 	:: __imm(bpf_get_prandom_u32)
3251 	: __clobber_all);
3252 }
3253 
3254 /*
3255  * A store that does not fully cover a 4-byte half-slot defines nothing, so a
3256  * narrow store at the top of the frame must not turn any slot into a "def",
3257  * least of all every slot of the frame: the earlier data at fp-8 stays live.
3258  */
3259 SEC("socket")
3260 __log_level(2)
3261 __msg("0: (79) r0 = *(u64 *)(r10 -8)        ; use: fp0-8")
3262 /* narrow stores define nothing, but they may write the half-slot they touch */
3263 __msg("1: (73) *(u8 *)(r10 -1) = r0         ; may_def: fp0-4h")
3264 __msg("2: (6b) *(u16 *)(r10 -4) = r0        ; may_def: fp0-4h")
3265 __msg("3: (79) r0 = *(u64 *)(r10 -8)        ; use: fp0-8")
3266 __naked void narrow_store_defines_nothing(void)
3267 {
3268 	asm volatile (
3269 	"r0 = *(u64 *)(r10 - 8);"
3270 	"*(u8 *)(r10 - 1) = r0;"
3271 	"*(u16 *)(r10 - 4) = r0;"
3272 	"r0 = *(u64 *)(r10 - 8);"
3273 	"exit;"
3274 	::: __clobber_all);
3275 }
3276 
3277 /*
3278  * The same callee instance is analyzed twice: the call sites are visited in
3279  * postorder, so the second one goes first with a precise pointer 248 bytes
3280  * into the main frame, and the first one then passes a pointer of unknown
3281  * offset, which reads the whole frame. The precise pass stays within the
3282  * first word of the masks on 64-bit, so the whole-frame pass is wider under
3283  * every stack budget and merging the second into the first has to widen the
3284  * masks while keeping the whole-frame read.
3285  */
3286 SEC("socket")
3287 __log_level(2)
3288 __msg("stack use/def subprog#{{[0-9]+}} merge_read_all_callee (d2,cs{{[0-9]+}}):")
3289 __msg("(79) r0 = *(u64 *)(r1 +0){{.*}}; use: fp0-8..-{{(512|2048)}}")
3290 __naked void merge_keeps_whole_frame_read(void)
3291 {
3292 	asm volatile (
3293 	"r1 = 0;"
3294 	"*(u64 *)(r10 - 8) = r1;"
3295 	"*(u64 *)(r10 - 16) = r1;"
3296 	"*(u64 *)(r10 - 248) = r1;"
3297 	"call %[bpf_get_prandom_u32];"
3298 	"r0 &= 8;"
3299 	"r1 = r10;"
3300 	"r1 += -16;"
3301 	"r1 += r0;"
3302 	"call merge_read_all_mid;"
3303 	"r1 = r10;"
3304 	"r1 += -248;"
3305 	"call merge_read_all_mid;"
3306 	"r0 = 0;"
3307 	"exit;"
3308 	:: __imm(bpf_get_prandom_u32)
3309 	: __clobber_all);
3310 }
3311 
3312 static __used __naked void merge_read_all_mid(void)
3313 {
3314 	asm volatile (
3315 	"call merge_read_all_callee;"
3316 	"exit;"
3317 	::: __clobber_all);
3318 }
3319 
3320 static __used __naked void merge_read_all_callee(void)
3321 {
3322 	asm volatile (
3323 	"r0 = *(u64 *)(r1 + 0);"
3324 	"exit;"
3325 	::: __clobber_all);
3326 }
3327 
3328 /*
3329  * A frame pointer spilled below fp-512 is a spill like any other: the fill
3330  * restores its identity, and a load through it reads only the slot it names
3331  * instead of the whole frame.
3332  */
3333 SEC("socket")
3334 __log_level(2)
3335 __load_if_large_stack()
3336 __msg("(79) r0 = *(u64 *)(r1 +0){{.*}}; use: fp0-8{{$}}")
3337 __naked void spill_below_512_stays_precise(void)
3338 {
3339 	asm volatile (
3340 	"r1 = 0;"
3341 	"*(u64 *)(r10 - 8) = r1;"
3342 	"r1 = r10;"
3343 	"r1 += -8;"
3344 	"*(u64 *)(r10 - 520) = r1;"
3345 	"r1 = *(u64 *)(r10 - 520);"
3346 	"r0 = *(u64 *)(r1 + 0);"
3347 	"exit;"
3348 	::: __clobber_all);
3349 }
3350 
3351 /* Unknown-size outputs still carry a possible write, without a read or kill. */
3352 SEC("socket")
3353 __success __log_level(2)
3354 __msg("call bpf_probe_read_kernel{{.*}}; may_def: fp0-8{{$}}")
3355 __naked void helper_output_unknown_size(void)
3356 {
3357 	asm volatile (
3358 	"call %[bpf_get_prandom_u32];"
3359 	"r0 &= 7;"
3360 	"r0 += 1;"
3361 	"r2 = r0;"
3362 	"r1 = r10;"
3363 	"r1 += -8;"
3364 	"r3 = 0;"
3365 	"call %[bpf_probe_read_kernel];"
3366 	"r0 = 0;"
3367 	"exit;"
3368 	:: __imm(bpf_get_prandom_u32), __imm(bpf_probe_read_kernel)
3369 	: __clobber_all);
3370 }
3371 
3372 struct {
3373 	__uint(type, BPF_MAP_TYPE_QUEUE);
3374 	__uint(max_entries, 1);
3375 	__type(value, __u64);
3376 } queue_8b SEC(".maps");
3377 
3378 struct {
3379 	__uint(type, BPF_MAP_TYPE_QUEUE);
3380 	__uint(max_entries, 1);
3381 	__type(value, __u64[2]);
3382 } queue_16b SEC(".maps");
3383 
3384 struct {
3385 	__uint(type, BPF_MAP_TYPE_BLOOM_FILTER);
3386 	__uint(max_entries, 16);
3387 	__type(value, __u64);
3388 } bloom_8b SEC(".maps");
3389 
3390 SEC("socket")
3391 __success __log_level(2)
3392 __msg("call bpf_map_pop_elem{{.*}}; def: fp0-16")
3393 __naked void helper_map_output_known_size(void)
3394 {
3395 	asm volatile (
3396 	"r1 = %[queue_8b] ll;"
3397 	"r2 = r10;"
3398 	"r2 += -16;"
3399 	"call %[bpf_map_pop_elem];"
3400 	"r0 = 0;"
3401 	"exit;"
3402 	:: __imm_addr(queue_8b), __imm(bpf_map_pop_elem)
3403 	: __clobber_all);
3404 }
3405 
3406 /* The maximum map value size is not a guaranteed output extent. */
3407 SEC("socket")
3408 __success __log_level(2)
3409 __msg("call bpf_map_pop_elem{{.*}}; use: fp0-16 fp0-24 may_def: fp0-16 fp0-24{{$}}")
3410 __naked void helper_map_output_merged_sizes(void)
3411 {
3412 	asm volatile (
3413 	"*(u64 *)(r10 - 16) = 0;"
3414 	"*(u64 *)(r10 - 24) = 0;"
3415 	"call %[bpf_get_prandom_u32];"
3416 	"r1 = %[queue_8b] ll;"
3417 	"if r0 == 0 goto 1f;"
3418 	"r1 = %[queue_16b] ll;"
3419 "1:"
3420 	"r2 = r10;"
3421 	"r2 += -24;"
3422 	"call %[bpf_map_pop_elem];"
3423 	"r0 = 0;"
3424 	"exit;"
3425 	:: __imm_addr(queue_8b), __imm_addr(queue_16b),
3426 	   __imm(bpf_get_prandom_u32), __imm(bpf_map_pop_elem)
3427 	: __clobber_all);
3428 }
3429 
3430 /* Bloom-filter peek treats buffer as an input despite the helper's output annotation. */
3431 SEC("socket")
3432 __success __log_level(2)
3433 __msg("call bpf_map_peek_elem{{.*}}; use: fp0-8{{$}}")
3434 __naked void helper_bloom_peek_input(void)
3435 {
3436 	asm volatile (
3437 	"*(u64 *)(r10 - 8) = 0;"
3438 	"r1 = %[bloom_8b] ll;"
3439 	"r2 = r10;"
3440 	"r2 += -8;"
3441 	"call %[bpf_map_peek_elem];"
3442 	"r0 = 0;"
3443 	"exit;"
3444 	:: __imm_addr(bloom_8b), __imm(bpf_map_peek_elem)
3445 	: __clobber_all);
3446 }
3447