Older Linux kernels rejected any eBPF program containing a backward jump. What does the verifier require of a loop today, and what problem does the bpf_loop() helper solve?
answer
- the kernel cannot preempt your program
- the proof needed is termination
- originally the graph had to be acyclic
- bounded means the count is derivable
- verify the body once, iterate at runtime
basics
~20 sThe verifier must prove every eBPF program terminates. Originally it demanded a loop-free control-flow graph, so loops had to be unrolled; since Linux 5.3 it accepts loops whose iteration count it can bound, and bpf_loop() moves the iteration to runtime so verification cost stops scaling with the trip count.
solid answer
~50 sTermination is one of the two things the verifier must prove, alongside memory safety — a program that never returns would hang the kernel in whatever context the hook fired. The original rule was blunt: the control-flow graph had to be a DAG, so any backward jump was rejected and loops were written with `#pragma unroll` or by hand. Since **Linux 5.3** the verifier supports **bounded loops**: it simulates the loop and accepts it if it can prove the trip count is finite and the loop's exploration fits its budget. That still means the *verifier* walks the iterations, so a 1,000-iteration loop costs 1,000 iterations of analysis and large loops blow the complexity limit. **`bpf_loop()`**, added in **5.17**, fixes that: you hand it a callback and a maximum iteration count, the verifier checks the callback body *once*, and the loop runs in the kernel at runtime. Linux 6.4 added open-coded iterators for the same purpose with more natural syntax.
code
c · 21 lines#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("tracepoint/syscalls/sys_enter_execve")
int sum_bounded(void *ctx)
{
__u32 total = 0;
int i;
/* Bounded: the counter starts known, steps by one, and is
* compared against a constant, so the verifier can prove exit.
* Accepted since Linux 5.3; before that it needed #pragma unroll.
*/
for (i = 0; i < 64; i++)
total += i;
bpf_printk("total=%u", total);
return 0;
}
char LICENSE[] SEC("license") = "GPL";go deeper
Know that eBPF programs must be guaranteed to finish, that the earliest kernels banned loops outright, and that fixed-count loops used to be handled by unrolling them.
Explain the termination requirement, the original acyclic-graph rule, and what bounded-loop support in Linux 5.3 changed. Be able to say why a loop must have a bound the verifier can derive, not merely one you know about.
Distinguish the two rejections in practice — cannot prove termination versus too expensive to prove — and pick the matching fix, including moving large iteration counts to bpf_loop() or open-coded iterators.
Own the version-skew consequence: loop constructs that load fine on a current kernel are rejected on older fleet nodes, so the lowest supported kernel dictates the idiom and belongs in your build and test matrix.
## Why loops are a verifier problem at all eBPF programs run in kernel context, frequently with preemption disabled, sometimes in an interrupt handler, on paths that fire per packet or per syscall. There is no scheduler that will step in if your program takes an hour, and no watchdog to kill it. So one of the verifier's two core obligations — alongside memory safety — is proving that the program **terminates**. Every accepted program has a bounded worst-case instruction count. ## The original rule: a loop-free graph The first verifier took the simplest possible route to that proof: it built the control-flow graph and required it to be acyclic. Any backward jump was a rejection. That makes termination trivial — the longest path is bounded by the program length — at the cost of banning loops entirely. Programmers worked around it by unrolling: ```c #pragma unroll for (int i = 0; i < 8; i++) total += buf[i]; ``` clang expands that into eight straight-line copies, so the emitted bytecode has no backward jump and the graph stays acyclic. It works, and it is still a reasonable choice for small fixed counts, but it has obvious limits: the trip count must be a compile-time constant, and the instruction count grows linearly with it, so an unrolled loop of a few hundred iterations bloats the program. ## Bounded loops (Linux 5.3) Since 5.3 the verifier accepts real backward jumps when it can bound them. The mechanism is that its abstract interpreter tracks the loop variable's value range across iterations, simulating the loop until it can show the exit condition is reached — or until it can prove the state has converged. A loop like: ```c for (int i = 0; i < 64; i++) total += i; ``` is fine because the counter starts known, increases by a known amount, and is compared against a constant. Loops the verifier cannot bound are still rejected: a `while` on a value read from a map, a counter modified inside the body in a way it cannot follow, or a condition it cannot relate to the induction variable. The catch is that verification effort scales with the iterations. The verifier is *simulating* those iterations, and every simulated instruction counts against its complexity budget — so a loop that is perfectly bounded at 100,000 iterations will be rejected not for being unbounded but for being too expensive to prove. ## bpf_loop() (Linux 5.17) `bpf_loop()` decouples the two. You call it with a maximum iteration count, a callback, and a context pointer: ```c static long body(__u32 index, void *ctx) { /* ... */ return 0; } bpf_loop(nr_iterations, body, &my_ctx, 0); ``` The verifier checks the callback body **once**, as a subprogram, and then trusts the helper to run it up to `nr_iterations` times at runtime; the callback returning non-zero breaks out early. Verification cost is now independent of the trip count, which is what makes loops over thousands of elements practical. The tradeoff is a function call per iteration rather than inlined straight-line code, so for very small fixed counts unrolling is still faster. Linux **6.4** added *open-coded iterators*, which give the same runtime-iteration property with ordinary `for`-loop syntax and the ability to break out naturally, and they have become the preferred style on kernels new enough to have them. ## Choosing between them - **Small, fixed count (under a few dozen):** unroll. Cheapest at runtime, negligible verification cost. - **Moderate, bounded count:** a plain bounded loop, provided the verifier's budget absorbs the simulation. - **Large or data-dependent count, up to a known ceiling:** `bpf_loop()` or open-coded iterators. Verification cost stays flat. ## What this means when you are debugging Two different rejections look similar and are not. "Back-edge from insn X to Y" or an unbounded-loop complaint means the verifier could not prove termination — the loop's shape is the problem. A complaint about processing too many instructions means the loop *is* bounded but the simulation was too expensive — the trip count is the problem. The first is fixed by restructuring the condition so the bound is derivable; the second by moving iteration to runtime with `bpf_loop()`. Reaching for the wrong fix is a common waste of an afternoon.
- Why is a bounded loop of 100,000 iterations still likely to be rejected?Because the verifier simulates the iterations to build its proof, and every simulated instruction counts against its complexity budget. The loop terminates, but proving it costs more analysis than the kernel will spend. That is the case for `bpf_loop()` or an open-coded iterator, where the body is verified once and iterated at runtime.
- What kinds of loop can the verifier still not bound on a modern kernel?Any whose exit condition it cannot relate to a value it is tracking — a loop counting down from a number read out of a map, a condition depending on unbounded packet contents, or a body that mutates the induction variable in ways the range tracker loses. The fix is usually to add an explicit ceiling that the verifier can see.
- When is unrolling still the right answer?For small fixed counts — walking a handful of struct fields or a short fixed-size array. Unrolled code is straight-line and fastest at runtime, and a dozen iterations cost the verifier almost nothing. Unrolling stops paying once the count grows enough to bloat the program or push the complexity budget.
saying these in an interview costs you the question
- Says eBPF simply cannot have loops
- Thinks pragma unroll is a runtime feature
- Confuses unbounded-loop rejection with the complexity limit
- Assumes any bounded loop will be accepted regardless of size
- Believes bpf_loop verifies the body once per iteration