All of lore.kernel.org
 help / color / mirror / Atom feed
* [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; 3+ 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] 3+ 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
  2026-07-28  3:38   ` [PATCH bpf] bpf: Reject writes through untrusted allocated pointers Ning Ding
  0 siblings, 1 reply; 3+ 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] 3+ messages in thread

* [PATCH bpf] bpf: Reject writes through untrusted allocated pointers
  2026-07-26  2:13 ` sashiko-bot
@ 2026-07-28  3:38   ` Ning Ding
  0 siblings, 0 replies; 3+ messages in thread
From: Ning Ding @ 2026-07-28  3:38 UTC (permalink / raw)
  To: bpf
  Cc: ast, daniel, john.fastabend, andrii, eddyz87, memxor, martin.lau,
	song, yonghong.song, jolsa, emil, shuah, danieltimlee,
	linux-kernel, linux-kselftest, Ning Ding

A kptr loaded under an RCU read lock has MEM_ALLOC set. After
bpf_rcu_read_unlock(), the verifier marks it PTR_UNTRUSTED, but
check_ptr_to_btf_access() still allows writes because
type_is_ptr_alloc_obj() ignores that flag.

Reject writes through MEM_ALLOC pointers that are PTR_UNTRUSTED. Reads
remain allowed and are converted to probe-memory accesses.

Also skip the owning-reference check for these pointers, since they no
longer have a live reference after leaving the RCU critical section.

Add verifier tests for the rejected write and permitted read cases.

Fixes: 503e4def5414 ("bpf: Replace open code with for allocated object check")
Closes: https://lore.kernel.org/all/20260726021304.97ED91F000E9@smtp.kernel.org/
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Ning Ding <dingning04@gmail.com>
---
 kernel/bpf/verifier.c                         |  6 +-
 .../bpf/progs/local_kptr_stash_fail.c         | 58 +++++++++++++++++++
 2 files changed, 62 insertions(+), 2 deletions(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 7aa47342dc65..24c151ad2e62 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5804,13 +5804,15 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
 		 * program allocated objects (which always have id > 0),
 		 * but not for untrusted PTR_TO_BTF_ID | MEM_ALLOC.
 		 */
-		if (atype != BPF_READ && !type_is_ptr_alloc_obj(reg->type)) {
+		if (atype != BPF_READ &&
+		    (!type_is_ptr_alloc_obj(reg->type) || reg->type & PTR_UNTRUSTED)) {
 			verbose(env, "only read is supported\n");
 			return -EACCES;
 		}
 
 		if (type_is_alloc(reg->type) && !type_is_non_owning_ref(reg->type) &&
-		    !(reg->type & MEM_RCU) && !reg_is_referenced(env, reg)) {
+		    !(reg->type & (MEM_RCU | PTR_UNTRUSTED)) &&
+		    !reg_is_referenced(env, reg)) {
 			verifier_bug(env, "allocated object must have a referenced id");
 			return -EFAULT;
 		}
diff --git a/tools/testing/selftests/bpf/progs/local_kptr_stash_fail.c b/tools/testing/selftests/bpf/progs/local_kptr_stash_fail.c
index fcf7a7567da2..d7b8cd6458bb 100644
--- a/tools/testing/selftests/bpf/progs/local_kptr_stash_fail.c
+++ b/tools/testing/selftests/bpf/progs/local_kptr_stash_fail.c
@@ -8,14 +8,22 @@
 #include "../bpf_experimental.h"
 #include "bpf_misc.h"
 
+extern void bpf_rcu_read_lock(void) __ksym;
+extern void bpf_rcu_read_unlock(void) __ksym;
+
 struct node_data {
 	long key;
 	long data;
 	struct bpf_rb_node node;
 };
 
+struct plain_data {
+	long key;
+};
+
 struct map_value {
 	struct node_data __kptr *node;
+	struct plain_data __kptr *plain;
 };
 
 struct node_data2 {
@@ -31,6 +39,7 @@ struct node_data2 {
  * [35] TYPE_TAG 'kptr_ref' type_id=34
  */
 struct node_data *just_here_because_btf_bug;
+struct plain_data *just_here_because_btf_bug2;
 
 struct {
 	__uint(type, BPF_MAP_TYPE_ARRAY);
@@ -82,4 +91,53 @@ long drop_rb_node_off(void *ctx)
 	return 0;
 }
 
+SEC("?tc")
+__failure __msg("only read is supported")
+long write_untrusted_alloc_obj(void *ctx)
+{
+	struct map_value *mapval;
+	struct node_data *res;
+	int idx = 0;
+
+	mapval = bpf_map_lookup_elem(&some_nodes, &idx);
+	if (!mapval)
+		return 1;
+
+	bpf_rcu_read_lock();
+	res = mapval->node;
+	if (!res) {
+		bpf_rcu_read_unlock();
+		return 2;
+	}
+	barrier_var(res);
+	bpf_rcu_read_unlock();
+
+	res->key = 42;
+	return 0;
+}
+
+SEC("?tc")
+__success
+long read_untrusted_alloc_obj(void *ctx)
+{
+	struct map_value *mapval;
+	struct plain_data *res;
+	int idx = 0;
+
+	mapval = bpf_map_lookup_elem(&some_nodes, &idx);
+	if (!mapval)
+		return 1;
+
+	bpf_rcu_read_lock();
+	res = mapval->plain;
+	if (!res) {
+		bpf_rcu_read_unlock();
+		return 2;
+	}
+	barrier_var(res);
+	bpf_rcu_read_unlock();
+
+	return res->key;
+}
+
 char _license[] SEC("license") = "GPL";
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-07-28  3:38 UTC | newest]

Thread overview: 3+ 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
2026-07-28  3:38   ` [PATCH bpf] bpf: Reject writes through untrusted allocated pointers Ning Ding

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.