Skip to content

Commit fdcecdf

Browse files
eddyz87Alexei Starovoitov
authored andcommitted
selftests/bpf: test cases for callchain sensitive live stack tracking
- simple propagation of read/write marks; - joining read/write marks from conditional branches; - avoid must_write marks in when same instruction accesses different stack offsets on different execution paths; - avoid must_write marks in case same instruction accesses stack and non-stack pointers on different execution paths; - read/write marks propagation to outer stack frame; - independent read marks for different callchains ending with the same function; - bpf_calls_callback() dependent logic in liveness.c:bpf_stack_slot_alive(). Signed-off-by: Eduard Zingerman <[email protected]> Link: https://lore.kernel.org/r/20250918-callchain-sensitive-liveness-v3-12-c3cd27bacc60@gmail.com Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent 34c513b commit fdcecdf

File tree

2 files changed

+296
-0
lines changed

2 files changed

+296
-0
lines changed

tools/testing/selftests/bpf/prog_tests/verifier.c

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

0 commit comments

Comments
 (0)