summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAmery Hung <ameryhung@gmail.com>2026-05-28 18:49:32 -0700
committerAlexei Starovoitov <ast@kernel.org>2026-06-01 18:31:41 -0700
commit2eee6fe8ac2cd21e931c009d475e5a8407d42d76 (patch)
tree59e34ce662d6ff037e3b35d003ff0553ee45b23e
parentbcfcb15fde94ed39068eb1d6e4b9b37d27111965 (diff)
bpf: Fix dynptr ref counting to scan all call frames
When checking whether a referenced dynptr can be overwritten, destroy_if_dynptr_stack_slot only counted sibling dynptrs in the current call frame. If a clone sharing the same virtual ref parent existed in a different frame (e.g., passed to a subprog), it would not be counted, causing the verifier to incorrectly reject the overwrite with "cannot overwrite referenced dynptr". Fix by extracting the counting into dynptr_ref_cnt() which uses bpf_for_each_reg_in_vstate_mask() to scan dynptr stack slots across all call frames. Fixes: 017f5c4ef73c ("bpf: Allow overwriting referenced dynptr when refcnt > 1") Reported-by: Eduard Zingerman <eddyz87@gmail.com> Acked-by: Eduard Zingerman <eddyz87@gmail.com> Signed-off-by: Amery Hung <ameryhung@gmail.com> Link: https://lore.kernel.org/r/20260529014936.2811085-10-ameryhung@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
-rw-r--r--kernel/bpf/verifier.c52
1 files changed, 29 insertions, 23 deletions
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index caa455fad877..5d8f2656dbfd 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -786,10 +786,29 @@ static void mark_reg_invalid(const struct bpf_verifier_env *env, struct bpf_reg_
__mark_reg_unknown(env, reg);
}
+static int dynptr_ref_cnt(struct bpf_verifier_env *env, int v_parent_id)
+{
+ struct bpf_stack_state *stack;
+ struct bpf_func_state *state;
+ struct bpf_reg_state *reg;
+ int ref_cnt = 0;
+
+ bpf_for_each_reg_in_vstate_mask(env->cur_state, state, reg, stack, 1 << STACK_DYNPTR, ({
+ if (!stack || stack->slot_type[0] != STACK_DYNPTR)
+ continue;
+ if (!stack->spilled_ptr.dynptr.first_slot)
+ continue;
+ if (stack->spilled_ptr.parent_id == v_parent_id)
+ ref_cnt++;
+ }));
+
+ return ref_cnt;
+}
+
static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
struct bpf_func_state *state, int spi)
{
- int i, err = 0;
+ int err = 0;
/* We always ensure that STACK_DYNPTR is never set partially,
* hence just checking for slot_type[0] is enough. This is
@@ -803,28 +822,15 @@ static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
if (!state->stack[spi].spilled_ptr.dynptr.first_slot)
spi = spi + 1;
- if (dynptr_type_referenced(state->stack[spi].spilled_ptr.dynptr.type)) {
- int v_parent_id = state->stack[spi].spilled_ptr.parent_id;
- int ref_cnt = 0;
-
- /*
- * A referenced dynptr can be overwritten only if there is at
- * least one other dynptr sharing the same virtual ref parent,
- * ensuring the reference can still be properly released.
- */
- for (i = 0; i < state->allocated_stack / BPF_REG_SIZE; i++) {
- if (state->stack[i].slot_type[0] != STACK_DYNPTR)
- continue;
- if (!state->stack[i].spilled_ptr.dynptr.first_slot)
- continue;
- if (state->stack[i].spilled_ptr.parent_id == v_parent_id)
- ref_cnt++;
- }
-
- if (ref_cnt <= 1) {
- verbose(env, "cannot overwrite referenced dynptr\n");
- return -EINVAL;
- }
+ /*
+ * A referenced dynptr can be overwritten only if there is at
+ * least one other dynptr sharing the same virtual ref parent,
+ * ensuring the reference can still be properly released.
+ */
+ if (dynptr_type_referenced(state->stack[spi].spilled_ptr.dynptr.type) &&
+ dynptr_ref_cnt(env, state->stack[spi].spilled_ptr.parent_id) <= 1) {
+ verbose(env, "cannot overwrite referenced dynptr\n");
+ return -EINVAL;
}
/* Invalidate the dynptr and any derived slices */