Skip to content

Commit 325d1ba

Browse files
author
Alexei Starovoitov
committed
Merge branch 'bpf-fix-precision-backtracking-bug-with-linked-registers'
Eduard Zingerman says: ==================== bpf: Fix precision backtracking bug with linked registers Emil Tsalapatis reported a verifier bug hit by the scx_lavd sched_ext scheduler. The essential part of the verifier log looks as follows: 436: ... // checkpoint hit for 438: (1d) if r7 == r8 goto ... frame 3: propagating r2,r7,r8 frame 2: propagating r6 mark_precise: frame3: last_idx ... mark_precise: frame3: regs=r2,r7,r8 stack= before 436: ... mark_precise: frame3: regs=r2,r7 stack= before 435: ... mark_precise: frame3: regs=r2,r7 stack= before 434: (85) call bpf_trace_vprintk#177 verifier bug: backtracking call unexpected regs 84 The log complains that registers r2 and r7 are tracked as precise while processing the bpf_trace_vprintk() call in precision backtracking. This can't be right, as r2 is reset by the call and there is nothing to backtrack it to. The precision propagation is triggered when a checkpoint is hit at instruction 438, r2 is dead at that instruction. This happens because of the following sequence of events: - Instruction 438 is first reached with registers r2 and r7 having the same id via a path that does not call bpf_trace_vprintk(): - Checkpoint is created at 438. - The jump at 438 is predicted, hence r7 and registers linked to it (r2) are propagated as precise, marking r2 and r7 precise in the checkpoint. - Instruction 438 is reached a second time with r2 undefined and via a path that calls bpf_trace_vprintk(): - Checkpoint is hit. - propagate_precision() picks registers r2 and r7 and propagates precision marks for those up to the helper call. The root cause is the fact that states_equal() and propagate_precision() assume that the precision flag can't be set for a dead register (as computed by compute_live_registers()). However, this is not the case when linked registers are at play. Fix this by accounting for live register flags in collect_linked_regs(). --- ==================== Link: https://patch.msgid.link/20260306-linked-regs-and-propagate-precision-v1-0-18e859be570d@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2 parents 6895e1d + 223ffb6 commit 325d1ba

5 files changed

Lines changed: 137 additions & 38 deletions

File tree

kernel/bpf/verifier.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17359,17 +17359,24 @@ static void __collect_linked_regs(struct linked_regs *reg_set, struct bpf_reg_st
1735917359
* in verifier state, save R in linked_regs if R->id == id.
1736017360
* If there are too many Rs sharing same id, reset id for leftover Rs.
1736117361
*/
17362-
static void collect_linked_regs(struct bpf_verifier_state *vstate, u32 id,
17362+
static void collect_linked_regs(struct bpf_verifier_env *env,
17363+
struct bpf_verifier_state *vstate,
17364+
u32 id,
1736317365
struct linked_regs *linked_regs)
1736417366
{
17367+
struct bpf_insn_aux_data *aux = env->insn_aux_data;
1736517368
struct bpf_func_state *func;
1736617369
struct bpf_reg_state *reg;
17370+
u16 live_regs;
1736717371
int i, j;
1736817372

1736917373
id = id & ~BPF_ADD_CONST;
1737017374
for (i = vstate->curframe; i >= 0; i--) {
17375+
live_regs = aux[frame_insn_idx(vstate, i)].live_regs_before;
1737117376
func = vstate->frame[i];
1737217377
for (j = 0; j < BPF_REG_FP; j++) {
17378+
if (!(live_regs & BIT(j)))
17379+
continue;
1737317380
reg = &func->regs[j];
1737417381
__collect_linked_regs(linked_regs, reg, id, i, j, true);
1737517382
}
@@ -17584,9 +17591,9 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
1758417591
* if parent state is created.
1758517592
*/
1758617593
if (BPF_SRC(insn->code) == BPF_X && src_reg->type == SCALAR_VALUE && src_reg->id)
17587-
collect_linked_regs(this_branch, src_reg->id, &linked_regs);
17594+
collect_linked_regs(env, this_branch, src_reg->id, &linked_regs);
1758817595
if (dst_reg->type == SCALAR_VALUE && dst_reg->id)
17589-
collect_linked_regs(this_branch, dst_reg->id, &linked_regs);
17596+
collect_linked_regs(env, this_branch, dst_reg->id, &linked_regs);
1759017597
if (linked_regs.cnt > 1) {
1759117598
err = push_jmp_history(env, this_branch, 0, linked_regs_pack(&linked_regs));
1759217599
if (err)

tools/testing/selftests/bpf/progs/exceptions_assert.c

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,43 +18,43 @@
1818
return *(u64 *)num; \
1919
}
2020

21-
__msg(": R0=0xffffffff80000000")
21+
__msg("R{{.}}=0xffffffff80000000")
2222
check_assert(s64, ==, eq_int_min, INT_MIN);
23-
__msg(": R0=0x7fffffff")
23+
__msg("R{{.}}=0x7fffffff")
2424
check_assert(s64, ==, eq_int_max, INT_MAX);
25-
__msg(": R0=0")
25+
__msg("R{{.}}=0")
2626
check_assert(s64, ==, eq_zero, 0);
27-
__msg(": R0=0x8000000000000000 R1=0x8000000000000000")
27+
__msg("R{{.}}=0x8000000000000000")
2828
check_assert(s64, ==, eq_llong_min, LLONG_MIN);
29-
__msg(": R0=0x7fffffffffffffff R1=0x7fffffffffffffff")
29+
__msg("R{{.}}=0x7fffffffffffffff")
3030
check_assert(s64, ==, eq_llong_max, LLONG_MAX);
3131

32-
__msg(": R0=scalar(id=1,smax=0x7ffffffe)")
32+
__msg("R{{.}}=scalar(id=1,smax=0x7ffffffe)")
3333
check_assert(s64, <, lt_pos, INT_MAX);
34-
__msg(": R0=scalar(id=1,smax=-1,umin=0x8000000000000000,var_off=(0x8000000000000000; 0x7fffffffffffffff))")
34+
__msg("R{{.}}=scalar(id=1,smax=-1,umin=0x8000000000000000,var_off=(0x8000000000000000; 0x7fffffffffffffff))")
3535
check_assert(s64, <, lt_zero, 0);
36-
__msg(": R0=scalar(id=1,smax=0xffffffff7fffffff")
36+
__msg("R{{.}}=scalar(id=1,smax=0xffffffff7fffffff")
3737
check_assert(s64, <, lt_neg, INT_MIN);
3838

39-
__msg(": R0=scalar(id=1,smax=0x7fffffff)")
39+
__msg("R{{.}}=scalar(id=1,smax=0x7fffffff)")
4040
check_assert(s64, <=, le_pos, INT_MAX);
41-
__msg(": R0=scalar(id=1,smax=0)")
41+
__msg("R{{.}}=scalar(id=1,smax=0)")
4242
check_assert(s64, <=, le_zero, 0);
43-
__msg(": R0=scalar(id=1,smax=0xffffffff80000000")
43+
__msg("R{{.}}=scalar(id=1,smax=0xffffffff80000000")
4444
check_assert(s64, <=, le_neg, INT_MIN);
4545

46-
__msg(": R0=scalar(id=1,smin=umin=0x80000000,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
46+
__msg("R{{.}}=scalar(id=1,smin=umin=0x80000000,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
4747
check_assert(s64, >, gt_pos, INT_MAX);
48-
__msg(": R0=scalar(id=1,smin=umin=1,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
48+
__msg("R{{.}}=scalar(id=1,smin=umin=1,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
4949
check_assert(s64, >, gt_zero, 0);
50-
__msg(": R0=scalar(id=1,smin=0xffffffff80000001")
50+
__msg("R{{.}}=scalar(id=1,smin=0xffffffff80000001")
5151
check_assert(s64, >, gt_neg, INT_MIN);
5252

53-
__msg(": R0=scalar(id=1,smin=umin=0x7fffffff,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
53+
__msg("R{{.}}=scalar(id=1,smin=umin=0x7fffffff,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
5454
check_assert(s64, >=, ge_pos, INT_MAX);
55-
__msg(": R0=scalar(id=1,smin=0,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
55+
__msg("R{{.}}=scalar(id=1,smin=0,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
5656
check_assert(s64, >=, ge_zero, 0);
57-
__msg(": R0=scalar(id=1,smin=0xffffffff80000000")
57+
__msg("R{{.}}=scalar(id=1,smin=0xffffffff80000000")
5858
check_assert(s64, >=, ge_neg, INT_MIN);
5959

6060
SEC("?tc")

tools/testing/selftests/bpf/progs/verifier_linked_scalars.c

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,4 +363,68 @@ void alu32_negative_offset(void)
363363
__sink(path[0]);
364364
}
365365

366+
void dummy_calls(void)
367+
{
368+
bpf_iter_num_new(0, 0, 0);
369+
bpf_iter_num_next(0);
370+
bpf_iter_num_destroy(0);
371+
}
372+
373+
SEC("socket")
374+
__success
375+
__flag(BPF_F_TEST_STATE_FREQ)
376+
int spurious_precision_marks(void *ctx)
377+
{
378+
struct bpf_iter_num iter;
379+
380+
asm volatile(
381+
"r1 = %[iter];"
382+
"r2 = 0;"
383+
"r3 = 10;"
384+
"call %[bpf_iter_num_new];"
385+
"1:"
386+
"r1 = %[iter];"
387+
"call %[bpf_iter_num_next];"
388+
"if r0 == 0 goto 4f;"
389+
"r7 = *(u32 *)(r0 + 0);"
390+
"r8 = *(u32 *)(r0 + 0);"
391+
/* This jump can't be predicted and does not change r7 or r8 state. */
392+
"if r7 > r8 goto 2f;"
393+
/* Branch explored first ties r2 and r7 as having the same id. */
394+
"r2 = r7;"
395+
"goto 3f;"
396+
"2:"
397+
/* Branch explored second does not tie r2 and r7 but has a function call. */
398+
"call %[bpf_get_prandom_u32];"
399+
"3:"
400+
/*
401+
* A checkpoint.
402+
* When first branch is explored, this would inject linked registers
403+
* r2 and r7 into the jump history.
404+
* When second branch is explored, this would be a cache hit point,
405+
* triggering propagate_precision().
406+
*/
407+
"if r7 <= 42 goto +0;"
408+
/*
409+
* Mark r7 as precise using an if condition that is always true.
410+
* When reached via the second branch, this triggered a bug in the backtrack_insn()
411+
* because r2 (tied to r7) was propagated as precise to a call.
412+
*/
413+
"if r7 <= 0xffffFFFF goto +0;"
414+
"goto 1b;"
415+
"4:"
416+
"r1 = %[iter];"
417+
"call %[bpf_iter_num_destroy];"
418+
:
419+
: __imm_ptr(iter),
420+
__imm(bpf_iter_num_new),
421+
__imm(bpf_iter_num_next),
422+
__imm(bpf_iter_num_destroy),
423+
__imm(bpf_get_prandom_u32)
424+
: __clobber_common, "r7", "r8"
425+
);
426+
427+
return 0;
428+
}
429+
366430
char _license[] SEC("license") = "GPL";

tools/testing/selftests/bpf/progs/verifier_scalar_ids.c

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ __naked void linked_regs_bpf_k(void)
4040
*/
4141
"r3 = r10;"
4242
"r3 += r0;"
43+
/* Mark r1 and r2 as alive. */
44+
"r1 = r1;"
45+
"r2 = r2;"
4346
"r0 = 0;"
4447
"exit;"
4548
:
@@ -73,6 +76,9 @@ __naked void linked_regs_bpf_x_src(void)
7376
*/
7477
"r4 = r10;"
7578
"r4 += r0;"
79+
/* Mark r1 and r2 as alive. */
80+
"r1 = r1;"
81+
"r2 = r2;"
7682
"r0 = 0;"
7783
"exit;"
7884
:
@@ -106,6 +112,10 @@ __naked void linked_regs_bpf_x_dst(void)
106112
*/
107113
"r4 = r10;"
108114
"r4 += r3;"
115+
/* Mark r1 and r2 as alive. */
116+
"r0 = r0;"
117+
"r1 = r1;"
118+
"r2 = r2;"
109119
"r0 = 0;"
110120
"exit;"
111121
:
@@ -143,6 +153,9 @@ __naked void linked_regs_broken_link(void)
143153
*/
144154
"r3 = r10;"
145155
"r3 += r0;"
156+
/* Mark r1 and r2 as alive. */
157+
"r1 = r1;"
158+
"r2 = r2;"
146159
"r0 = 0;"
147160
"exit;"
148161
:
@@ -156,37 +169,37 @@ __naked void linked_regs_broken_link(void)
156169
*/
157170
SEC("socket")
158171
__success __log_level(2)
159-
__msg("12: (0f) r2 += r1")
172+
__msg("17: (0f) r2 += r1")
160173
/* Current state */
161-
__msg("frame2: last_idx 12 first_idx 11 subseq_idx -1 ")
162-
__msg("frame2: regs=r1 stack= before 11: (bf) r2 = r10")
174+
__msg("frame2: last_idx 17 first_idx 14 subseq_idx -1 ")
175+
__msg("frame2: regs=r1 stack= before 16: (bf) r2 = r10")
163176
__msg("frame2: parent state regs=r1 stack=")
164177
__msg("frame1: parent state regs= stack=")
165178
__msg("frame0: parent state regs= stack=")
166179
/* Parent state */
167-
__msg("frame2: last_idx 10 first_idx 10 subseq_idx 11 ")
168-
__msg("frame2: regs=r1 stack= before 10: (25) if r1 > 0x7 goto pc+0")
180+
__msg("frame2: last_idx 13 first_idx 13 subseq_idx 14 ")
181+
__msg("frame2: regs=r1 stack= before 13: (25) if r1 > 0x7 goto pc+0")
169182
__msg("frame2: parent state regs=r1 stack=")
170183
/* frame1.r{6,7} are marked because mark_precise_scalar_ids()
171184
* looks for all registers with frame2.r1.id in the current state
172185
*/
173186
__msg("frame1: parent state regs=r6,r7 stack=")
174187
__msg("frame0: parent state regs=r6 stack=")
175188
/* Parent state */
176-
__msg("frame2: last_idx 8 first_idx 8 subseq_idx 10")
177-
__msg("frame2: regs=r1 stack= before 8: (85) call pc+1")
189+
__msg("frame2: last_idx 9 first_idx 9 subseq_idx 13")
190+
__msg("frame2: regs=r1 stack= before 9: (85) call pc+3")
178191
/* frame1.r1 is marked because of backtracking of call instruction */
179192
__msg("frame1: parent state regs=r1,r6,r7 stack=")
180193
__msg("frame0: parent state regs=r6 stack=")
181194
/* Parent state */
182-
__msg("frame1: last_idx 7 first_idx 6 subseq_idx 8")
183-
__msg("frame1: regs=r1,r6,r7 stack= before 7: (bf) r7 = r1")
184-
__msg("frame1: regs=r1,r6 stack= before 6: (bf) r6 = r1")
195+
__msg("frame1: last_idx 8 first_idx 7 subseq_idx 9")
196+
__msg("frame1: regs=r1,r6,r7 stack= before 8: (bf) r7 = r1")
197+
__msg("frame1: regs=r1,r6 stack= before 7: (bf) r6 = r1")
185198
__msg("frame1: parent state regs=r1 stack=")
186199
__msg("frame0: parent state regs=r6 stack=")
187200
/* Parent state */
188-
__msg("frame1: last_idx 4 first_idx 4 subseq_idx 6")
189-
__msg("frame1: regs=r1 stack= before 4: (85) call pc+1")
201+
__msg("frame1: last_idx 4 first_idx 4 subseq_idx 7")
202+
__msg("frame1: regs=r1 stack= before 4: (85) call pc+2")
190203
__msg("frame0: parent state regs=r1,r6 stack=")
191204
/* Parent state */
192205
__msg("frame0: last_idx 3 first_idx 1 subseq_idx 4")
@@ -204,6 +217,7 @@ __naked void precision_many_frames(void)
204217
"r1 = r0;"
205218
"r6 = r0;"
206219
"call precision_many_frames__foo;"
220+
"r6 = r6;" /* mark r6 as live */
207221
"exit;"
208222
:
209223
: __imm(bpf_ktime_get_ns)
@@ -220,6 +234,8 @@ void precision_many_frames__foo(void)
220234
"r6 = r1;"
221235
"r7 = r1;"
222236
"call precision_many_frames__bar;"
237+
"r6 = r6;" /* mark r6 as live */
238+
"r7 = r7;" /* mark r7 as live */
223239
"exit"
224240
::: __clobber_all);
225241
}
@@ -229,6 +245,8 @@ void precision_many_frames__bar(void)
229245
{
230246
asm volatile (
231247
"if r1 > 7 goto +0;"
248+
"r6 = 0;" /* mark r6 as live */
249+
"r7 = 0;" /* mark r7 as live */
232250
/* force r1 to be precise, this eventually marks:
233251
* - bar frame r1
234252
* - foo frame r{1,6,7}
@@ -340,6 +358,8 @@ __naked void precision_two_ids(void)
340358
"r3 += r7;"
341359
/* force r9 to be precise, this also marks r8 */
342360
"r3 += r9;"
361+
"r6 = r6;" /* mark r6 as live */
362+
"r8 = r8;" /* mark r8 as live */
343363
"exit;"
344364
:
345365
: __imm(bpf_ktime_get_ns)
@@ -353,7 +373,7 @@ __flag(BPF_F_TEST_STATE_FREQ)
353373
* collect_linked_regs() can't tie more than 6 registers for a single insn.
354374
*/
355375
__msg("8: (25) if r0 > 0x7 goto pc+0 ; R0=scalar(id=1")
356-
__msg("9: (bf) r6 = r6 ; R6=scalar(id=2")
376+
__msg("14: (bf) r6 = r6 ; R6=scalar(id=2")
357377
/* check that r{0-5} are marked precise after 'if' */
358378
__msg("frame0: regs=r0 stack= before 8: (25) if r0 > 0x7 goto pc+0")
359379
__msg("frame0: parent state regs=r0,r1,r2,r3,r4,r5 stack=:")
@@ -372,6 +392,12 @@ __naked void linked_regs_too_many_regs(void)
372392
"r6 = r0;"
373393
/* propagate range for r{0-6} */
374394
"if r0 > 7 goto +0;"
395+
/* keep r{1-5} live */
396+
"r1 = r1;"
397+
"r2 = r2;"
398+
"r3 = r3;"
399+
"r4 = r4;"
400+
"r5 = r5;"
375401
/* make r6 appear in the log */
376402
"r6 = r6;"
377403
/* force r0 to be precise,
@@ -517,7 +543,7 @@ __naked void check_ids_in_regsafe_2(void)
517543
"*(u64*)(r10 - 8) = r1;"
518544
/* r9 = pointer to stack */
519545
"r9 = r10;"
520-
"r9 += -8;"
546+
"r9 += -16;"
521547
/* r8 = ktime_get_ns() */
522548
"call %[bpf_ktime_get_ns];"
523549
"r8 = r0;"
@@ -538,6 +564,8 @@ __naked void check_ids_in_regsafe_2(void)
538564
"if r7 > 4 goto l2_%=;"
539565
/* Access memory at r9[r6] */
540566
"r9 += r6;"
567+
"r9 += r7;"
568+
"r9 += r8;"
541569
"r0 = *(u8*)(r9 + 0);"
542570
"l2_%=:"
543571
"r0 = 0;"

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@
4444
mark_precise: frame0: regs=r2 stack= before 23\
4545
mark_precise: frame0: regs=r2 stack= before 22\
4646
mark_precise: frame0: regs=r2 stack= before 20\
47-
mark_precise: frame0: parent state regs=r2,r9 stack=:\
47+
mark_precise: frame0: parent state regs=r2 stack=:\
4848
mark_precise: frame0: last_idx 19 first_idx 10\
49-
mark_precise: frame0: regs=r2,r9 stack= before 19\
49+
mark_precise: frame0: regs=r2 stack= before 19\
5050
mark_precise: frame0: regs=r9 stack= before 18\
5151
mark_precise: frame0: regs=r8,r9 stack= before 17\
5252
mark_precise: frame0: regs=r0,r9 stack= before 15\
@@ -107,9 +107,9 @@
107107
mark_precise: frame0: parent state regs=r2 stack=:\
108108
mark_precise: frame0: last_idx 20 first_idx 20\
109109
mark_precise: frame0: regs=r2 stack= before 20\
110-
mark_precise: frame0: parent state regs=r2,r9 stack=:\
110+
mark_precise: frame0: parent state regs=r2 stack=:\
111111
mark_precise: frame0: last_idx 19 first_idx 17\
112-
mark_precise: frame0: regs=r2,r9 stack= before 19\
112+
mark_precise: frame0: regs=r2 stack= before 19\
113113
mark_precise: frame0: regs=r9 stack= before 18\
114114
mark_precise: frame0: regs=r8,r9 stack= before 17\
115115
mark_precise: frame0: parent state regs= stack=:",

0 commit comments

Comments
 (0)