After bpf_rcu_read_unlock(), a refcounted map kptr is marked PTR_UNTRUSTED because it is no longer protected by RCU. The refcounted-kptr argument check ignores PTR_UNTRUSTED and still allows bpf_refcount_acquire() on that pointer. However, if another thread removes the object and drops its last reference, the stale address can later be reused for another refcounted object. A CAP_BPF-only reproducer observed bpf_refcount_acquire() returning non-NULL through such a stale pointer. Reject PTR_UNTRUSTED refcounted-kptr arguments and add a regression test. Fixes: 7c50b1cb76ac ("bpf: Add bpf_refcount_acquire kfunc") Reported-by: sashiko-bot@kernel.org Link: https://lore.kernel.org/r/20260726021304.97ED91F000E9@smtp.kernel.org Assisted-by: Codex:gpt-5 Signed-off-by: Ning Ding --- kernel/bpf/verifier.c | 5 ++++ .../bpf/progs/refcounted_kptr_fail.c | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 1e7343b625de..63b1d997fe80 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -12414,6 +12414,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ meta->subprogno = reg->subprogno; break; case KF_ARG_PTR_TO_REFCOUNTED_KPTR: + if (reg->type & PTR_UNTRUSTED) { + verbose(env, "%s is an untrusted refcounted kptr\n", + reg_arg_name(env, argno)); + return -EACCES; + } if (!type_is_ptr_alloc_obj(reg->type)) { verbose(env, "%s is neither owning or non-owning ref\n", reg_arg_name(env, argno)); diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c index acd3e81a3916..80b92d7ec9ca 100644 --- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c +++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c @@ -127,6 +127,33 @@ long refcount_acquire_rcu_map_kptr_unchecked_drop(void *ctx) return 0; } +SEC("?tc") +__failure __msg("is an untrusted refcounted kptr") +long refcount_acquire_after_rcu_unlock(void *ctx) +{ + struct map_value_refcount_only *mapval; + struct node_refcount_only *n, *m; + int idx = 0; + + mapval = bpf_map_lookup_elem(&stashed_refcount_only, &idx); + if (!mapval) + return 1; + + bpf_rcu_read_lock(); + n = mapval->node; + if (!n) { + bpf_rcu_read_unlock(); + return 2; + } + bpf_rcu_read_unlock(); + + m = bpf_refcount_acquire(n); + if (m) + bpf_obj_drop(m); + + return 0; +} + SEC("?tc") __failure __msg("Unreleased reference id=3 alloc_insn={{[0-9]+}}") long rbtree_refcounted_node_ref_escapes_owning_input(void *ctx) -- 2.43.0