* [PATCH bpf] bpf: Keep refcount_acquire nullable for borrowed RCU kptrs
@ 2026-07-26 1:53 Ning Ding
2026-07-26 2:13 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Ning Ding @ 2026-07-26 1:53 UTC (permalink / raw)
To: bpf; +Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, davemarchevsky,
greg
bpf_refcount_acquire() can fail for a refcounted map kptr loaded
within RCU. Another execution can replace the map kptr and drop the
last owning reference after the pointer is loaded but before the
acquire, causing refcount_inc_not_zero() to fail.
check_kfunc_args() currently treats a refcounted pointer as owning
whenever it is not explicitly marked NON_OWN_REF. But an RCU-loaded
map kptr can lack NON_OWN_REF while still being borrowed and absent
from the verifier state's acquired-reference tracking. This sets
arg_owning_ref and causes is_kfunc_ret_null() to suppress KF_RET_NULL,
allowing an unchecked NULL return from bpf_refcount_acquire() to be
passed to bpf_obj_drop().
Require reg_is_referenced() before treating the input as owning. Add
tests for a genuine owning input, a checked borrowed RCU input, and
an unchecked borrowed RCU input.
Fixes: 7793fc3babe9 ("bpf: Make bpf_refcount_acquire fallible for non-owning refs")
Assisted-by: Codex:gpt-5.5
Assisted-by: ChatGPT:GPT-5.6-Thinking
Signed-off-by: Ning Ding <dingning04@gmail.com>
---
kernel/bpf/verifier.c | 2 +-
.../selftests/bpf/progs/refcounted_kptr.c | 61 +++++++++++++++++++
.../bpf/progs/refcounted_kptr_fail.c | 47 ++++++++++++++
3 files changed, 109 insertions(+), 1 deletion(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 7aa47342dc65..1e7343b625de 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -12419,7 +12419,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
reg_arg_name(env, argno));
return -EINVAL;
}
- if (!type_is_non_owning_ref(reg->type))
+ if (!type_is_non_owning_ref(reg->type) && reg_is_referenced(env, reg))
meta->arg_owning_ref = true;
rec = reg_btf_record(reg);
diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr.c b/tools/testing/selftests/bpf/progs/refcounted_kptr.c
index 61906f48025c..fd35093285c0 100644
--- a/tools/testing/selftests/bpf/progs/refcounted_kptr.c
+++ b/tools/testing/selftests/bpf/progs/refcounted_kptr.c
@@ -23,6 +23,15 @@ struct map_value {
struct node_data __kptr *node;
};
+struct node_refcount_only {
+ long key;
+ struct bpf_refcount refcount;
+};
+
+struct map_value_refcount_only {
+ struct node_refcount_only __kptr *node;
+};
+
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, int);
@@ -30,6 +39,13 @@ struct {
__uint(max_entries, 2);
} stashed_nodes SEC(".maps");
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, int);
+ __type(value, struct map_value_refcount_only);
+ __uint(max_entries, 1);
+} stashed_refcount_only SEC(".maps");
+
struct node_acquire {
long key;
long data;
@@ -832,6 +848,51 @@ long rbtree_refcounted_node_ref_escapes_owning_input(void *ctx)
return 0;
}
+SEC("tc")
+__success
+long refcount_acquire_owning_input_no_null_check(void *ctx)
+{
+ struct node_refcount_only *n, *m;
+
+ n = bpf_obj_new(typeof(*n));
+ if (!n)
+ return 1;
+
+ m = bpf_refcount_acquire(n);
+ bpf_obj_drop(m);
+ bpf_obj_drop(n);
+
+ return 0;
+}
+
+SEC("tc")
+__success
+long refcount_acquire_rcu_map_kptr_null_checked(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;
+ }
+ m = bpf_refcount_acquire(n);
+ bpf_rcu_read_unlock();
+
+ if (!m)
+ return 3;
+ bpf_obj_drop(m);
+
+ return 0;
+}
+
static long __stash_map_empty_xchg(struct node_data *n, int idx)
{
struct map_value *mapval = bpf_map_lookup_elem(&stashed_nodes, &idx);
diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
index 024ef2aae200..acd3e81a3916 100644
--- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
@@ -19,6 +19,15 @@ struct node_refcounted {
struct bpf_refcount refcount;
};
+struct node_refcount_only {
+ long key;
+ struct bpf_refcount refcount;
+};
+
+struct map_value_refcount_only {
+ struct node_refcount_only __kptr *node;
+};
+
extern void bpf_rcu_read_lock(void) __ksym;
extern void bpf_rcu_read_unlock(void) __ksym;
@@ -28,6 +37,13 @@ private(A) struct bpf_rb_root groot __contains(node_acquire, node);
private(B) struct bpf_spin_lock lock;
private(B) struct bpf_list_head head __contains(node_refcounted, list);
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, int);
+ __type(value, struct map_value_refcount_only);
+ __uint(max_entries, 1);
+} stashed_refcount_only SEC(".maps");
+
static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b)
{
struct node_acquire *node_a;
@@ -80,6 +96,37 @@ long refcount_acquire_maybe_null(void *ctx)
return 0;
}
+SEC("?tc")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
+long refcount_acquire_rcu_map_kptr_unchecked_drop(void *ctx)
+{
+ struct map_value_refcount_only *mapval;
+ struct node_refcount_only *tmp, *n, *m;
+ int idx = 0;
+
+ tmp = bpf_obj_new(typeof(*tmp));
+ if (!tmp)
+ return 3;
+ bpf_obj_drop(tmp);
+
+ 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;
+ }
+ m = bpf_refcount_acquire(n);
+ bpf_rcu_read_unlock();
+
+ 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)
base-commit: 0ce37745d4bfbc493f718169c3974898ffec8ee7
--
2.43.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH bpf] bpf: Keep refcount_acquire nullable for borrowed RCU kptrs
2026-07-26 1:53 [PATCH bpf] bpf: Keep refcount_acquire nullable for borrowed RCU kptrs Ning Ding
@ 2026-07-26 2:13 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-07-26 2:13 UTC (permalink / raw)
To: Ning Ding; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] bpf_refcount_acquire() permits untrusted pointers due to inadequate modifier checks, leading to Use-After-Free.
--
--- Patch [1]: [PATCH bpf] bpf: Keep refcount_acquire nullable for borrowed RCU kptrs ---
commit 5798e6cddf6ec01af2fa26e9f86dfc1bb3fd1681
Author: Ning Ding <dingning04@gmail.com>
bpf: Keep refcount_acquire nullable for borrowed RCU kptrs
bpf_refcount_acquire() can fail for a refcounted map kptr loaded
within RCU. Another execution can replace the map kptr and drop the
last owning reference after the pointer is loaded but before the
acquire, causing refcount_inc_not_zero() to fail.
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 7aa47342dc659..1e7343b625de5 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -12419,7 +12419,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
> reg_arg_name(env, argno));
> return -EINVAL;
> }
[Severity: Critical]
This is a pre-existing issue, but does bpf_refcount_acquire() permit untrusted
pointers due to inadequate modifier checks, potentially leading to a
use-after-free?
In check_kfunc_args(), KF_ARG_PTR_TO_REFCOUNTED_KPTR skips the is_trusted_reg()
checks that are applied to other pointer types. The argument is subsequently
validated using type_is_ptr_alloc_obj():
include/linux/bpf_verifier.h:type_is_ptr_alloc_obj() {
return base_type(type) == PTR_TO_BTF_ID && type_flag(type) & MEM_ALLOC;
}
Since type_is_ptr_alloc_obj() only requires MEM_ALLOC to be set and does not
restrict unsafe modifiers, it returns true for a pointer with the PTR_UNTRUSTED
flag.
If a BPF program loads a refcounted pointer from a map inside an RCU read-side
critical section, checks it for NULL, and then drops the RCU lock using
bpf_rcu_read_unlock(), the pointer's verifier type is updated to include the
PTR_UNTRUSTED flag.
The program can then pass this untrusted pointer to bpf_refcount_acquire(),
which bypasses trusted register checks and accepts it. At runtime, the
bpf_refcount_acquire_impl kfunc executes refcount_inc_not_zero() on the
provided untrusted address.
Since the address might have already been freed outside the RCU critical
section, could this result in memory corruption or a kernel panic? While this
patch sets meta->arg_owning_ref = false for untrusted inputs, does it fail to
prevent the unsafe input pointer from being dereferenced inside the kfunc
itself?
> - if (!type_is_non_owning_ref(reg->type))
> + if (!type_is_non_owning_ref(reg->type) && reg_is_referenced(env, reg))
> meta->arg_owning_ref = true;
>
> rec = reg_btf_record(reg);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260726015330.705259-1-dingning04@gmail.com?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-26 2:13 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-26 1:53 [PATCH bpf] bpf: Keep refcount_acquire nullable for borrowed RCU kptrs Ning Ding
2026-07-26 2:13 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.