On Wed, 2026-05-06 at 07:27 -0700, Amery Hung wrote: [...] > +/* Release id and objects referencing the id iteratively in a DFS manner */ > +static int release_reference(struct bpf_verifier_env *env, int id) > +{ > + u32 mask = (1 << STACK_SPILL) | (1 << STACK_DYNPTR); > struct bpf_verifier_state *vstate = env->cur_state; > + struct bpf_idmap *idstack = &env->idmap_scratch; > + struct bpf_stack_state *stack; > struct bpf_func_state *state; > struct bpf_reg_state *reg; > - int err; > + int root_id = id, err; > > - err = release_reference_nomark(vstate, ref_obj_id); > - if (err) > - return err; > + idstack->cnt = 0; > + idstack_push(idstack, id); > > - bpf_for_each_reg_in_vstate(vstate, state, reg, ({ > - if (reg->ref_obj_id == ref_obj_id) > - mark_reg_invalid(env, reg); > - })); > + if (find_reference_state(vstate, id)) > + WARN_ON_ONCE(release_reference_nomark(vstate, id)); > + > + while ((id = idstack_pop(idstack))) { > + bpf_for_each_reg_in_vstate_mask(vstate, state, reg, stack, mask, ({ > + int ref_obj_cnt = 1; > + > + if (reg->id != id && reg->parent_id != id && reg->ref_obj_id != id) > + continue; > + > + /* > + * A referenced dynptr can be overwritten only if there is at > + * least one other dynptr sharing the same ref_obj_id, > + * ensuring the reference can still be properly released. > + */ > + if (stack && stack->slot_type[BPF_REG_SIZE - 1] == STACK_DYNPTR && > + dynptr_type_referenced(reg->dynptr.type)) > + ref_obj_cnt = dynptr_get_refcnt(state, reg->ref_obj_id); Note that dynptr_get_refcnt() only looks for objects in the state's frame, dynptrs in other frames are ignored. This can lead to false rejections, as in the attached test cases, which verifier refuses to load with the following error message: ; *(volatile __u8 *)&clone = 0; @ dynptr_fail.c:2160 19: (73) *(u8 *)(r10 -16) = r1 Leaking reference id=2 alloc_insn=7. Release it first. processed 14 insns (limit 1000000) max_states_per_insn 1 total_states 1 peak_states 1 mark_read 0 > + > + if (reg->ref_obj_id && reg->ref_obj_id != root_id && ref_obj_cnt <= 1) { > + struct bpf_reference_state *ref_state; > + > + ref_state = find_reference_state(env->cur_state, reg->ref_obj_id); > + verbose(env, "Leaking reference id=%d alloc_insn=%d. Release it first.\n", > + ref_state->id, ref_state->insn_idx); > + return -EINVAL; > + } > + > + /* Free objects derived from the current object */ > + if (reg->id != id) { > + err = idstack_push(idstack, reg->id); > + if (err) > + return err; > + } > + > + if (!stack || stack->slot_type[BPF_REG_SIZE - 1] == STACK_SPILL) > + mark_reg_invalid(env, reg); > + else if (stack->slot_type[BPF_REG_SIZE - 1] == STACK_DYNPTR) > + invalidate_dynptr(env, stack); > + })); > + } > > return 0; > } [...]