]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
bpf: Fix dynptr ref counting to scan all call frames
authorAmery Hung <ameryhung@gmail.com>
Fri, 29 May 2026 01:49:32 +0000 (18:49 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Tue, 2 Jun 2026 01:31:41 +0000 (18:31 -0700)
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>
kernel/bpf/verifier.c

index caa455fad877ed02f4c2d8531471edb4845c915a..5d8f2656dbfd43a2f573f4239dd6dd860720a3a9 100644 (file)
@@ -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 */