[ Upstream commit 308c7a0ae8859b34d9d90a3dff953b2d14242145 ] This is a 6.12.y-sized equivalent, not a cherry-pick of the upstream diff. unmark_stack_slots_dynptr() comments say it invalidates slices of the released dynptr, but it only calls release_reference(ref_obj_id). bpf_dynptr_slice() and bpf_dynptr_slice_rdwr() stamp dynptr_id and leave ref_obj_id at 0, so LDX/STX through those slices remains allowed after bpf_ringbuf_submit_dynptr() / bpf_ringbuf_discard_dynptr(). bpf_dynptr_data() slices already carry ref_obj_id. Walk registers with a matching dynptr_id, the same way destroy_if_dynptr_stack_slot() already does when a dynptr stack slot is overwritten. Do this for the released dynptr, for each clone, and for the non-refcounted path. Check the register base type before reading dynptr_id because the field shares a union with other register metadata. Use base_type() rather than an exact type match so slices carrying DYNPTR_TYPE_* flags remain covered. The upstream commit is Amery Hung's parent_id refactor (Fixes: 870c28588afa, qdisc kfuncs). It cannot be applied here: - It is an 11-file bpf-next change, well over the 100-line stable limit, and it removes dynptr_id / ref_obj_id. - 6.12.y still uses dynptr_id and does not have the qdisc kfuncs in that Fixes: tag, so AUTOSEL never pulled it. Deviations from 308c7a0ae8859: keep dynptr_id and ref_obj_id; do not introduce parent_id; only add slice invalidation on unmark_stack_slots_dynptr(). Fixes: 66e3a13e7c2c ("bpf: Add bpf_dynptr_slice and bpf_dynptr_slice_rdwr") Reported-by: Xu Yunxiang Closes: https://lore.kernel.org/r/20260829033725.2365697-2-xyx2021@mail.ustc.edu.cn Assisted-by: Pi:GLM-5.3 Signed-off-by: Xu Yunxiang --- kernel/bpf/verifier.c | 27 +++++++++++++++++-- .../testing/selftests/bpf/progs/dynptr_fail.c | 20 ++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 99b3f539e431..dbc9cfcffcd6 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -805,6 +805,9 @@ static void invalidate_dynptr(struct bpf_verifier_env *env, struct bpf_func_stat state->stack[spi - 1].spilled_ptr.live |= REG_LIVE_WRITTEN; } +static void mark_reg_invalid(const struct bpf_verifier_env *env, struct bpf_reg_state *reg); +static void invalidate_slices_of_dynptr(struct bpf_verifier_env *env, int dynptr_id); + static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_state *reg) { struct bpf_func_state *state = func(env, reg); @@ -815,6 +818,7 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re return spi; if (!dynptr_type_refcounted(state->stack[spi].spilled_ptr.dynptr.type)) { + invalidate_slices_of_dynptr(env, state->stack[spi].spilled_ptr.id); invalidate_dynptr(env, state, spi); return 0; } @@ -828,8 +832,14 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re * 2) Any slices derived from this dynptr. */ - /* Invalidate any slices associated with this dynptr */ + /* Invalidate any slices associated with this dynptr. + * release_reference() only walks registers that carry ref_obj_id; + * slices are PTR_TO_MEM with dynptr_id set and ref_obj_id left 0. + * Mirror destroy_if_dynptr_stack_slot(), which already invalidates + * slices by dynptr_id. + */ 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++) { @@ -844,8 +854,10 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re verbose(env, "verifier internal error: misconfigured ref_obj_id\n"); return -EFAULT; } - if (state->stack[i].spilled_ptr.dynptr.first_slot) + 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); + } } return 0; @@ -862,6 +874,17 @@ static void mark_reg_invalid(const struct bpf_verifier_env *env, struct bpf_reg_ __mark_reg_unknown(env, reg); } +static void invalidate_slices_of_dynptr(struct bpf_verifier_env *env, int dynptr_id) +{ + struct bpf_func_state *fstate; + struct bpf_reg_state *dreg; + + bpf_for_each_reg_in_vstate(env->cur_state, fstate, dreg, ({ + if (base_type(dreg->type) == PTR_TO_MEM && dreg->dynptr_id == dynptr_id) + mark_reg_invalid(env, dreg); + })); +} + static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env, struct bpf_func_state *state, int spi) { diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c index dfd817d0348c..61bd3f7c946f 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_fail.c +++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c @@ -316,6 +316,26 @@ int data_slice_use_after_release1(void *ctx) return 0; } +/* Releasing a dynptr must invalidate slices that only carry dynptr_id. */ +SEC("?raw_tp") +__failure __msg("invalid mem access 'scalar'") +int slice_kfunc_use_after_submit(void *ctx) +{ + struct bpf_dynptr ptr; + struct sample *sample; + + bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(*sample), 0, &ptr); + sample = bpf_dynptr_slice(&ptr, 0, NULL, sizeof(*sample)); + if (!sample) { + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; + } + bpf_ringbuf_submit_dynptr(&ptr, 0); + /* this should fail */ + val = sample->pid; + return 0; +} + /* A data slice can't be used after it has been released. * * This tests the case where the data slice tracks a dynptr (ptr2) -- 2.43.0