From: Amery Hung [ Upstream commit 308c7a0ae8859b34d9d90a3dff953b2d14242145 ] This is a 6.6.y-sized equivalent, not a cherry-pick of the upstream diff. unmark_stack_slots_dynptr() walked clones only in the submitting pointer's frame. A program that clones a ringbuf dynptr in a subprog, submits the clone, then submits the original in the caller is accepted: the caller's STACK_DYNPTR slot is never cleared. The second bpf_ringbuf_commit() flips BUSY on an already-committed header and a userspace consumer stalls. Walk every frame and invalidate matching STACK_DYNPTR slots. Leave WARN_ON_ONCE(release_reference()) as a swallow: a hard -EINVAL would reject legitimate programs that release once in a different frame than they acquired. Depends on "bpf: Invalidate dynptr slices by dynptr_id on release" only for the slice helper call inside the new loop. The upstream commit is Amery Hung's parent_id refactor. It cannot be applied here (see the previous patch). Deviations from 308c7a0ae8859: keep dynptr_id / ref_obj_id; walk frames with the 6.6 STACK_DYNPTR slot loop instead of the parent_id DFS. Fixes: 870c28588afa ("bpf: net_sched: Add basic bpf qdisc kfuncs") Signed-off-by: Amery Hung Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260529014936.2811085-6-ameryhung@gmail.com Signed-off-by: Alexei Starovoitov [ Xu Yunxiang: Split the upstream parent_id refactor into two stable-sized equivalent fixes and rewrite the subject and changelog to describe each one. This second patch keeps dynptr_id/ref_obj_id and scans every call frame instead of using the upstream parent_id DFS. Add a cross-frame clone regression test. ] Fixes: 361f129f3cc1 ("bpf: Add bpf_dynptr_clone") Reported-by: Xu Yunxiang Assisted-by: Pi:GLM-5.3 Signed-off-by: Xu Yunxiang --- kernel/bpf/verifier.c | 39 ++++++++++++------- .../testing/selftests/bpf/progs/dynptr_fail.c | 22 +++++++++++ 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 62fa2895a92050..fa88e7a35f9014 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -996,7 +996,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, frm; spi = dynptr_get_spi(env, reg); if (spi < 0) @@ -1026,22 +1026,31 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re WARN_ON_ONCE(release_reference(env, ref_obj_id)); invalidate_slices_of_dynptr(env, state->stack[spi].spilled_ptr.id); - /* Invalidate any dynptr clones */ - for (i = 1; i < state->allocated_stack / BPF_REG_SIZE; i++) { - if (state->stack[i].spilled_ptr.ref_obj_id != ref_obj_id) + /* Invalidate any dynptr clones, including those in other frames. + * Do not turn a failing release_reference() into -EINVAL: that + * rejects legitimate cross-frame single releases on 6.6/6.12/6.13. + */ + for (frm = 0; frm <= env->cur_state->curframe; frm++) { + struct bpf_func_state *f = env->cur_state->frame[frm]; + + if (!f) continue; + for (i = 1; i < f->allocated_stack / BPF_REG_SIZE; i++) { + if (f->stack[i].spilled_ptr.ref_obj_id != ref_obj_id) + continue; - /* it should always be the case that if the ref obj id - * matches then the stack slot also belongs to a - * dynptr - */ - if (state->stack[i].slot_type[0] != STACK_DYNPTR) { - verbose(env, "verifier internal error: misconfigured ref_obj_id\n"); - return -EFAULT; - } - if (state->stack[i].spilled_ptr.dynptr.first_slot) { - invalidate_slices_of_dynptr(env, state->stack[i].spilled_ptr.id); - invalidate_dynptr(env, state, i); + /* it should always be the case that if the ref obj id + * matches then the stack slot also belongs to a + * dynptr + */ + if (f->stack[i].slot_type[0] != STACK_DYNPTR) { + verbose(env, "verifier internal error: misconfigured ref_obj_id\n"); + return -EFAULT; + } + if (f->stack[i].spilled_ptr.dynptr.first_slot) { + invalidate_slices_of_dynptr(env, f->stack[i].spilled_ptr.id); + invalidate_dynptr(env, f, i); + } } } diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c index 131f55fbd6ed31..91146cf18f4f8b 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_fail.c +++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c @@ -1487,6 +1487,28 @@ int clone_invalid2(struct xdp_md *xdp) } /* Invalidating a dynptr should invalidate its clones */ +static __noinline void submit_dynptr_clone(struct bpf_dynptr *ptr) +{ + struct bpf_dynptr clone; + + bpf_dynptr_clone(ptr, &clone); + bpf_ringbuf_submit_dynptr(&clone, 0); +} + +SEC("?raw_tp") +__failure __msg("is an unacquired reference") +int clone_invalidate_cross_frame(void *ctx) +{ + struct bpf_dynptr ptr; + + bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(struct sample), 0, &ptr); + submit_dynptr_clone(&ptr); + + /* The clone release in the callee must invalidate the caller's ptr. */ + bpf_ringbuf_submit_dynptr(&ptr, 0); + return 0; +} + SEC("?raw_tp") __failure __msg("Expected an initialized dynptr as arg #3") int clone_invalidate1(void *ctx) -- 2.43.0