Merge branch 'bpf-calls-to-bpf_loop-should-have-an-scc-and-accumulate-backedges'

Eduard Zingerman says:

====================
bpf: calls to bpf_loop() should have an SCC and accumulate backedges

This is a correctness fix for the verification of BPF programs that
work with callback-calling functions. The problem is the same as the
issue fixed by series [1] for iterator-based loops: some of the states
created while processing the callback function body might have
incomplete read or precision marks.

An example of an unsafe program that is accepted without this fix can
be found in patch #2.

There is some impact on verification performance:

File                             Program               Insns (A)  Insns (B)  Insns      (DIFF)
-------------------------------  --------------------  ---------  ---------  -----------------
pyperf600_bpf_loop.bpf.o         on_event                   4247       9985   +5738 (+135.11%)
setget_sockopt.bpf.o             skops_sockopt              5719       7446    +1727 (+30.20%)
setget_sockopt.bpf.o             socket_post_create         1253       1603     +350 (+27.93%)
strobemeta_bpf_loop.bpf.o        on_event                   3424       7224   +3800 (+110.98%)
test_tcp_custom_syncookie.bpf.o  tcp_custom_syncookie      11929      38307  +26378 (+221.12%)
xdp_synproxy_kern.bpf.o          syncookie_tc              13986      23035    +9049 (+64.70%)
xdp_synproxy_kern.bpf.o          syncookie_xdp             13881      21022    +7141 (+51.44%)

Total progs: 4172
Old success: 2520
New success: 2520
total_insns diff min:    0.00%
total_insns diff max:  221.12%
0 -> value: 0
value -> 0: 0
total_insns abs max old: 837,487
total_insns abs max new: 837,487
   0 .. 5    %: 4163
   5 .. 15   %: 2
  25 .. 35   %: 2
  50 .. 60   %: 1
  60 .. 70   %: 1
 110 .. 120  %: 1
 135 .. 145  %: 1
 220 .. 225  %: 1

[1] https://lore.kernel.org/bpf/174968344350.3524559.14906547029551737094.git-patchwork-notify@kernel.org/
---
====================

Link: https://patch.msgid.link/20251229-scc-for-callbacks-v1-0-ceadfe679900@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2025-12-30 15:42:42 -08:00
commit ccaa6d2c96
2 changed files with 84 additions and 4 deletions

View File

@ -19830,8 +19830,10 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
}
}
if (bpf_calls_callback(env, insn_idx)) {
if (states_equal(env, &sl->state, cur, RANGE_WITHIN))
if (states_equal(env, &sl->state, cur, RANGE_WITHIN)) {
loop = true;
goto hit;
}
goto skip_inf_loop_check;
}
/* attempt to detect infinite loop to avoid unnecessary doomed work */
@ -25071,15 +25073,18 @@ static int compute_scc(struct bpf_verifier_env *env)
}
/*
* Assign SCC number only if component has two or more elements,
* or if component has a self reference.
* or if component has a self reference, or if instruction is a
* callback calling function (implicit loop).
*/
assign_scc = stack[stack_sz - 1] != w;
for (j = 0; j < succ->cnt; ++j) {
assign_scc = stack[stack_sz - 1] != w; /* two or more elements? */
for (j = 0; j < succ->cnt; ++j) { /* self reference? */
if (succ->items[j] == w) {
assign_scc = true;
break;
}
}
if (bpf_calls_callback(env, w)) /* implicit loop? */
assign_scc = true;
/* Pop component elements from stack */
do {
t = stack[--stack_sz];

View File

@ -1926,4 +1926,79 @@ static int loop1_wrapper(void)
);
}
/*
* This is similar to a test case absent_mark_in_the_middle_state(),
* but adapted for use with bpf_loop().
*/
SEC("raw_tp")
__flag(BPF_F_TEST_STATE_FREQ)
__failure __msg("math between fp pointer and register with unbounded min value is not allowed")
__naked void absent_mark_in_the_middle_state4(void)
{
/*
* Equivalent to a C program below:
*
* int main(void) {
* fp[-8] = bpf_get_prandom_u32();
* fp[-16] = -32; // used in a memory access below
* bpf_loop(7, loop_cb4, fp, 0);
* return 0;
* }
*
* int loop_cb4(int i, void *ctx) {
* if (unlikely(ctx[-8] > bpf_get_prandom_u32()))
* *(u64 *)(fp + ctx[-16]) = 42; // aligned access expected
* if (unlikely(fp[-8] > bpf_get_prandom_u32()))
* ctx[-16] = -31; // makes said access unaligned
* return 0;
* }
*/
asm volatile (
"call %[bpf_get_prandom_u32];"
"r8 = r0;"
"*(u64 *)(r10 - 8) = r0;"
"*(u64 *)(r10 - 16) = -32;"
"r1 = 7;"
"r2 = loop_cb4 ll;"
"r3 = r10;"
"r4 = 0;"
"call %[bpf_loop];"
"r0 = 0;"
"exit;"
:
: __imm(bpf_loop),
__imm(bpf_get_prandom_u32)
: __clobber_all
);
}
__used __naked
static void loop_cb4(void)
{
asm volatile (
"r9 = r2;"
"r8 = *(u64 *)(r9 - 8);"
"r6 = *(u64 *)(r9 - 16);"
"call %[bpf_get_prandom_u32];"
"if r0 > r8 goto use_fp16_%=;"
"1:"
"call %[bpf_get_prandom_u32];"
"if r0 > r8 goto update_fp16_%=;"
"2:"
"r0 = 0;"
"exit;"
"use_fp16_%=:"
"r1 = r10;"
"r1 += r6;"
"*(u64 *)(r1 + 0) = 42;"
"goto 1b;"
"update_fp16_%=:"
"*(u64 *)(r9 - 16) = -31;"
"goto 2b;"
:
: __imm(bpf_get_prandom_u32)
: __clobber_all
);
}
char _license[] SEC("license") = "GPL";