bpf_get_spi() computes (-off - 1) / BPF_REG_SIZE using C division, which truncates toward zero. For off == 0, this produces spi 0, the same index used by the valid stack slot at fp-8. stack_slot_obj_get_spi() currently checks alignment and the resulting spi bounds, but does not reject the non-negative offset itself. It can therefore validate a PTR_TO_STACK register holding fp+0 against an iterator stored at fp-8 even though the runtime receives the actual fp+0 pointer. An effectful iterator kfunc can then interpret memory outside the BPF stack as iterator state. Reject non-negative offsets before converting the offset to an spi. All valid stack objects begin at a negative offset from the frame pointer. Add a verifier test that initializes an iterator at fp-8 and attempts to destroy it through fp+0. Fixes: 06accc8779c1 ("bpf: add support for open-coded iterator loops") Assisted-by: LLM Signed-off-by: Xu Yunxiang --- Please queue this fix for stable after it reaches the BPF tree. Tests: - W=1 make O= kernel/bpf/verifier.o (15071f-based candidate) - make O= -j12 bzImage modules (f77d212 baseline and candidate) - strict clang-20 build of iters_state_safety.bpf.o (f77d212 candidate) - test_progs -t iters -v --watchdog-timeout 180 --workers=1 - f77d212 baseline: 96 subtests pass; destroy_fp0_fail unexpectedly loads - f77d212 patched: 97/97 subtests pass - panic_on_warn=1 and panic_on_oops=1; no warning, Oops, or BUG The four commits from f77d212 to this base only change sock-destroy networking code and its selftests; the patch applies without conflict and verifier.c is unchanged across that interval. A separate effectful reproducer is available privately on request. It was not rerun in this validation round. The full unfiltered BPF selftest suite was not run. kernel/bpf/verifier.c | 2 +- .../selftests/bpf/progs/iters_state_safety.c | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 1b0b1fb628786..efbb446998848 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -567,7 +567,7 @@ static int stack_slot_obj_get_spi(struct bpf_verifier_env *env, struct bpf_reg_s } off = reg->var_off.value; - if (off % BPF_REG_SIZE) { + if (off >= 0 || off % BPF_REG_SIZE) { verbose(env, "cannot pass in %s at an offset=%d\n", obj_kind, off); return -EINVAL; } diff --git a/tools/testing/selftests/bpf/progs/iters_state_safety.c b/tools/testing/selftests/bpf/progs/iters_state_safety.c index 646026430e9b5..e5bb9fe6d5e53 100644 --- a/tools/testing/selftests/bpf/progs/iters_state_safety.c +++ b/tools/testing/selftests/bpf/progs/iters_state_safety.c @@ -52,6 +52,28 @@ int create_and_destroy(void *ctx) return 0; } +/* fp+0 is not a stack slot. bpf_get_spi(0) used to alias spi 0 (fp-8). */ +SEC("?raw_tp") +__failure __msg("cannot pass in iter at an offset=0") +int destroy_fp0_fail(void *ctx) +{ + struct bpf_iter_num iter; + + asm volatile ("r1 = %[iter];" + "r2 = 0;" + "r3 = 1000;" + "call %[bpf_iter_num_new];" + /* r10 is fp+0, one byte above the top of the BPF stack */ + "r1 = r10;" + "call %[bpf_iter_num_destroy];" + : + : __imm_ptr(iter), ITER_HELPERS + : __clobber_common + ); + + return 0; +} + SEC("?raw_tp") __failure __msg("Unreleased reference id=1") int create_and_forget_to_destroy_fail(void *ctx) base-commit: 15071f2a1263e82150c77eeb1e94dbfc31950a8e -- 2.43.0