[ Upstream commit 308c7a0ae8859b34d9d90a3dff953b2d14242145 ] This is a stable-sized equivalent, not a cherry-pick of the upstream diff. release_reference_state() rejects an attempt from a BPF callback to release a reference acquired by its caller. For referenced dynptrs, unmark_stack_slots_dynptr() instead wraps release_reference() in WARN_ON_ONCE() and then returns success. Consequently, a callback that submits or discards its caller's ringbuf dynptr can turn this normal verifier rejection into a kernel warning. A kernel configured with panic_on_warn=1 panics while verifying the program. Propagate the release_reference() error to check_helper_call(), which already reports the invalid release and rejects the program. Add a regression test using a non-constant bpf_loop() iteration count so the callback is verified in a separate frame. The upstream parent_id refactor returns release_reference() directly from unmark_stack_slots_dynptr(). Extract that behavior here while retaining the stable kernels' ref_obj_id representation. Fixes: 270605317366 ("bpf: Rework process_dynptr_func") Reported-by: Sashiko Closes: https://lore.kernel.org/r/20260829040110.D6A991F000E9@smtp.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Xu Yunxiang --- kernel/bpf/verifier.c | 6 +++-- .../testing/selftests/bpf/progs/dynptr_fail.c | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index dbc9cfcffcd6..65ee9afaa5ff 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -811,7 +811,7 @@ static void invalidate_slices_of_dynptr(struct bpf_verifier_env *env, int dynptr static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_state *reg) { struct bpf_func_state *state = func(env, reg); - int spi, ref_obj_id, i; + int spi, ref_obj_id, i, err; spi = dynptr_get_spi(env, reg); if (spi < 0) @@ -838,7 +838,9 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re * Mirror destroy_if_dynptr_stack_slot(), which already invalidates * slices by dynptr_id. */ - WARN_ON_ONCE(release_reference(env, ref_obj_id)); + err = release_reference(env, ref_obj_id); + if (err) + return err; invalidate_slices_of_dynptr(env, state->stack[spi].spilled_ptr.id); /* Invalidate any dynptr clones */ diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c index 61bd3f7c946f..52bd7f8871c5 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_fail.c +++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c @@ -134,6 +134,28 @@ int ringbuf_missing_release_callback(void *ctx) return 0; } +static int release_caller_dynptr_callback_fn(__u32 index, void *data) +{ + struct bpf_dynptr *ptr = data; + + bpf_ringbuf_submit_dynptr(ptr, 0); + return 0; +} + +/* A callback cannot release a dynptr reference acquired by its caller. */ +SEC("?raw_tp") +__failure __msg("reference has not been acquired before") +int ringbuf_release_caller_dynptr_callback(void *ctx) +{ + struct bpf_dynptr ptr; + + if (bpf_ringbuf_reserve_dynptr(&ringbuf, 8, 0, &ptr)) + return 0; + bpf_loop(val & 1, release_caller_dynptr_callback_fn, &ptr, 0); + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + /* Can't call bpf_ringbuf_submit/discard_dynptr on a non-initialized dynptr */ SEC("?raw_tp") __failure __msg("arg 1 is an unacquired reference") -- 2.43.0