All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH bpf-next 0/2] bpf: Reject offset refcount acquire arguments
@ 2026-06-19  7:59 Yiyang Chen
  2026-06-19  7:59 ` [PATCH bpf-next 1/2] " Yiyang Chen
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Yiyang Chen @ 2026-06-19  7:59 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: Yiyang Chen, John Fastabend, Martin KaFai Lau, Song Liu,
	Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
	Viktor Malik, Leon Hwang, Dave Marchevsky, bpf, linux-kselftest,
	linux-kernel

bpf_refcount_acquire() is modeled as returning a refcounted allocation
base, but it currently accepts PTR_TO_BTF_ID | MEM_ALLOC arguments whose
offset already points at an embedded graph node returned from a list or
rbtree operation.

At runtime the kfunc starts from the supplied pointer and adds the type's
refcount offset.  With a graph-node pointer, that starts from base +
node_off, while the verifier treats the returned pointer as the allocation
base.  Reject non-zero-offset arguments to keep the runtime operation and
the verifier model aligned.

Programs that pop graph nodes can still acquire a reference after
normalizing the node pointer with container_of().

Patch 1 adds the verifier-side zero-offset check for
KF_ARG_PTR_TO_REFCOUNTED_KPTR.

Patch 2 adds regression coverage for the accepted container_of() case and
the rejected direct list and rbtree node cases.

Validation, rebased on current bpf-next master e771677c937d
("Merge tag 'for-linus-iommufd' of
git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd"):

  git ls-remote https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git \
    refs/heads/master: e771677c937d
  git diff --check e771677c937d..HEAD: OK
  make O=/root/ebpf-verifier-bug-detection/kernel-build/bpf-next-latest-20260618 \
    kernel/bpf/verifier.o: OK
  make -C tools/testing/selftests/bpf \
    O=/root/ebpf-verifier-bug-detection/kernel-build/bpf-next-latest-20260618 \
    OUTPUT=/tmp/c5-027-selftests \
    VMLINUX_BTF=/root/ebpf-verifier-bug-detection/kernel-build/bpf-next-latest-20260618/vmlinux \
    /tmp/c5-027-selftests/refcounted_kptr.bpf.o \
    /tmp/c5-027-selftests/refcounted_kptr_fail.bpf.o: OK
  make -C tools/testing/selftests/bpf ... BPF_STRICT_BUILD=0 test_progs: OK
  ./test_progs --list: listed refcounted_kptr and refcounted_kptr_fail

The BPF object build needed a local-only generated-vmlinux.h fixup for
missing experimental kfunc prototypes in this environment.  No source-tree
files were changed for that workaround.

The explicit runtime run was attempted with:

  ./test_progs -t refcounted_kptr

It failed before verifier checks in this local container because libbpf
could not load a trivial BPF program after failing to raise RLIMIT_MEMLOCK
(-EPERM).  The container's memlock limit is 64 KiB and cannot be raised
here ("Operation not permitted").

Yiyang Chen (2):
  bpf: Reject offset refcount acquire arguments
  selftests/bpf: Cover refcount acquire node offsets

 kernel/bpf/verifier.c                         |  5 ++
 .../selftests/bpf/progs/refcounted_kptr.c     | 33 ++++++++
 .../bpf/progs/refcounted_kptr_fail.c          | 84 +++++++++++++++++++
 3 files changed, 122 insertions(+)


base-commit: e771677c937da5808f7b6c1f0e4a97ec1a84f8a8
-- 
2.34.1


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

* [PATCH bpf-next 1/2] bpf: Reject offset refcount acquire arguments
  2026-06-19  7:59 [PATCH bpf-next 0/2] bpf: Reject offset refcount acquire arguments Yiyang Chen
@ 2026-06-19  7:59 ` Yiyang Chen
  2026-06-19 19:28   ` Eduard Zingerman
  2026-06-19  7:59 ` [PATCH bpf-next 2/2] selftests/bpf: Cover refcount acquire node offsets Yiyang Chen
  2026-06-20 15:04 ` [PATCH bpf-next v2 0/2] bpf: Reject offset refcount acquire arguments Yiyang Chen
  2 siblings, 1 reply; 11+ messages in thread
From: Yiyang Chen @ 2026-06-19  7:59 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: Yiyang Chen, John Fastabend, Martin KaFai Lau, Song Liu,
	Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
	Viktor Malik, Leon Hwang, Dave Marchevsky, bpf, linux-kselftest,
	linux-kernel

bpf_refcount_acquire() increments the refcount at the caller-supplied
pointer plus the refcount field offset, then returns the caller-supplied
pointer unchanged.

The verifier records the return value as a base pointer to the refcounted
object.

bpf_list_pop_front() and bpf_rbtree_remove() can return embedded graph-node
pointers as PTR_TO_BTF_ID | MEM_ALLOC with a fixed offset equal to the node
field offset. Passing such a pointer directly to bpf_refcount_acquire()
currently passes the refcounted-kptr type check.

That makes the runtime operation start from base + node_off while the
verifier models the returned pointer as the object base.

Require refcount-acquire arguments to have zero offset. Programs can still
acquire a refcount from a graph-node-derived pointer after normalizing it
with container_of().

Fixes: 7c50b1cb76aca ("bpf: Add bpf_refcount_acquire kfunc")
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
 kernel/bpf/verifier.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 2abc79dbf..4510fe0b4 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -12392,6 +12392,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 					reg_arg_name(env, argno));
 				return -EINVAL;
 			}
+			if (reg->var_off.value != 0) {
+				verbose(env, "%s must have zero offset when passed to %s\n",
+					reg_arg_name(env, argno), meta->func_name);
+				return -EINVAL;
+			}
 			if (!type_is_non_owning_ref(reg->type))
 				meta->arg_owning_ref = true;
 
-- 
2.34.1


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

* [PATCH bpf-next 2/2] selftests/bpf: Cover refcount acquire node offsets
  2026-06-19  7:59 [PATCH bpf-next 0/2] bpf: Reject offset refcount acquire arguments Yiyang Chen
  2026-06-19  7:59 ` [PATCH bpf-next 1/2] " Yiyang Chen
@ 2026-06-19  7:59 ` Yiyang Chen
  2026-06-19  8:30   ` sashiko-bot
                     ` (2 more replies)
  2026-06-20 15:04 ` [PATCH bpf-next v2 0/2] bpf: Reject offset refcount acquire arguments Yiyang Chen
  2 siblings, 3 replies; 11+ messages in thread
From: Yiyang Chen @ 2026-06-19  7:59 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: Yiyang Chen, John Fastabend, Martin KaFai Lau, Song Liu,
	Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
	Viktor Malik, Leon Hwang, Dave Marchevsky, bpf, linux-kselftest,
	linux-kernel

Add regression coverage for bpf_refcount_acquire() on graph-node-derived
pointers.

The accepted case pops a list node and normalizes it with container_of()
before acquiring a refcount. The rejected cases pass popped list and rbtree
node pointers directly to bpf_refcount_acquire(), which must fail because
those pointers carry non-zero fixed offsets.

Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
 .../selftests/bpf/progs/refcounted_kptr.c     | 33 ++++++++
 .../bpf/progs/refcounted_kptr_fail.c          | 84 +++++++++++++++++++
 2 files changed, 117 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr.c b/tools/testing/selftests/bpf/progs/refcounted_kptr.c
index 61906f480..7955a9973 100644
--- a/tools/testing/selftests/bpf/progs/refcounted_kptr.c
+++ b/tools/testing/selftests/bpf/progs/refcounted_kptr.c
@@ -741,6 +741,39 @@ int list_push_back_uninit_head(void *ctx)
 	return ret;
 }
 
+SEC("tc")
+__description("refcount_acquire_list_pop_container: acquire normalized list pop")
+__success __retval(0)
+int refcount_acquire_list_pop_container(void *ctx)
+{
+	struct node_data *node, *base, *ref;
+	struct bpf_list_node *list_node;
+	long err;
+
+	node = bpf_obj_new(typeof(*node));
+	if (!node)
+		return -1;
+
+	bpf_spin_lock(&lock);
+	err = bpf_list_push_front(&head, &node->l);
+	if (err) {
+		bpf_spin_unlock(&lock);
+		bpf_obj_drop(node);
+		return -2;
+	}
+
+	list_node = bpf_list_pop_front(&head);
+	bpf_spin_unlock(&lock);
+	if (!list_node)
+		return -3;
+
+	base = container_of(list_node, struct node_data, l);
+	ref = bpf_refcount_acquire(base);
+	bpf_obj_drop(ref);
+	bpf_obj_drop(base);
+	return 0;
+}
+
 SEC("?tc")
 __failure __msg("bpf_spin_lock at off=32 must be held for bpf_list_head")
 long list_del_without_lock_fail(void *ctx)
diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
index 7247a20c0..f6cb084af 100644
--- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
@@ -13,12 +13,22 @@ struct node_acquire {
 	struct bpf_refcount refcount;
 };
 
+struct node_refcounted {
+	long key;
+	struct bpf_rb_node rb;
+	struct bpf_list_node list;
+	struct bpf_refcount refcount;
+};
+
 extern void bpf_rcu_read_lock(void) __ksym;
 extern void bpf_rcu_read_unlock(void) __ksym;
 
 #define private(name) SEC(".data." #name) __hidden __attribute__((aligned(8)))
 private(A) struct bpf_spin_lock glock;
 private(A) struct bpf_rb_root groot __contains(node_acquire, node);
+private(B) struct bpf_spin_lock lock;
+private(B) struct bpf_rb_root root __contains(node_refcounted, rb);
+private(B) struct bpf_list_head head __contains(node_refcounted, list);
 
 static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b)
 {
@@ -31,6 +41,17 @@ static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b)
 	return node_a->key < node_b->key;
 }
 
+static bool less_refcounted(struct bpf_rb_node *a, const struct bpf_rb_node *b)
+{
+	struct node_refcounted *node_a;
+	struct node_refcounted *node_b;
+
+	node_a = container_of(a, struct node_refcounted, rb);
+	node_b = container_of(b, struct node_refcounted, rb);
+
+	return node_a->key < node_b->key;
+}
+
 SEC("?tc")
 __failure __msg("Unreleased reference id=4 alloc_insn={{[0-9]+}}")
 long rbtree_refcounted_node_ref_escapes(void *ctx)
@@ -93,6 +114,69 @@ long rbtree_refcounted_node_ref_escapes_owning_input(void *ctx)
 	return 0;
 }
 
+SEC("?tc")
+__failure __msg("R1 must have zero offset when passed to bpf_refcount_acquire")
+long refcount_acquire_list_node_offset(void *ctx)
+{
+	struct node_refcounted *node, *base, *ref;
+	struct bpf_list_node *list_node;
+	long err;
+
+	node = bpf_obj_new(typeof(*node));
+	if (!node)
+		return 1;
+
+	bpf_spin_lock(&lock);
+	err = bpf_list_push_front(&head, &node->list);
+	if (err) {
+		bpf_spin_unlock(&lock);
+		bpf_obj_drop(node);
+		return 2;
+	}
+
+	list_node = bpf_list_pop_front(&head);
+	bpf_spin_unlock(&lock);
+	if (!list_node)
+		return 3;
+
+	base = container_of(list_node, struct node_refcounted, list);
+	ref = bpf_refcount_acquire(list_node);
+	if (ref)
+		bpf_obj_drop(ref);
+	bpf_obj_drop(base);
+	return 0;
+}
+
+SEC("?tc")
+__failure __msg("R1 must have zero offset when passed to bpf_refcount_acquire")
+long refcount_acquire_rbtree_node_offset(void *ctx)
+{
+	struct node_refcounted *node, *base, *ref;
+	struct bpf_rb_node *rb_node;
+
+	node = bpf_obj_new(typeof(*node));
+	if (!node)
+		return 1;
+
+	node->key = 1;
+
+	bpf_spin_lock(&lock);
+	bpf_rbtree_add(&root, &node->rb, less_refcounted);
+	rb_node = bpf_rbtree_first(&root);
+	if (rb_node)
+		rb_node = bpf_rbtree_remove(&root, rb_node);
+	bpf_spin_unlock(&lock);
+	if (!rb_node)
+		return 2;
+
+	base = container_of(rb_node, struct node_refcounted, rb);
+	ref = bpf_refcount_acquire(rb_node);
+	if (ref)
+		bpf_obj_drop(ref);
+	bpf_obj_drop(base);
+	return 0;
+}
+
 SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
 __failure __msg("function calls are not allowed while holding a lock")
 int BPF_PROG(rbtree_fail_sleepable_lock_across_rcu,
-- 
2.34.1


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

* Re: [PATCH bpf-next 2/2] selftests/bpf: Cover refcount acquire node offsets
  2026-06-19  7:59 ` [PATCH bpf-next 2/2] selftests/bpf: Cover refcount acquire node offsets Yiyang Chen
@ 2026-06-19  8:30   ` sashiko-bot
  2026-06-19  8:47   ` bot+bpf-ci
  2026-06-19 19:30   ` Eduard Zingerman
  2 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-06-19  8:30 UTC (permalink / raw)
  To: Yiyang Chen; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Logical double-free and verifier rejection in bpf_list_push_front error paths.
--

commit 822cb053457c04aa9becab9c713d49977dc74582
Author: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>

selftests/bpf: Cover refcount acquire node offsets

This commit adds regression tests for bpf_refcount_acquire() when used
with graph-node-derived pointers. It includes both accepted cases with
normalized pointers and rejected cases with non-zero fixed offsets.

> diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr.c b/tools/testing/selftests/bpf/progs/refcounted_kptr.c
> index 61906f48025cc..7955a997316e0 100644
> --- a/tools/testing/selftests/bpf/progs/refcounted_kptr.c
> +++ b/tools/testing/selftests/bpf/progs/refcounted_kptr.c
[ ... ]
> +SEC("tc")
> +__description("refcount_acquire_list_pop_container: acquire normalized list pop")
> +__success __retval(0)
> +int refcount_acquire_list_pop_container(void *ctx)
> +{
> +	struct node_data *node, *base, *ref;
> +	struct bpf_list_node *list_node;
> +	long err;
> +
> +	node = bpf_obj_new(typeof(*node));
> +	if (!node)
> +		return -1;
> +
> +	bpf_spin_lock(&lock);
> +	err = bpf_list_push_front(&head, &node->l);
> +	if (err) {
> +		bpf_spin_unlock(&lock);
> +		bpf_obj_drop(node);

[Severity: Medium]
Does this cause a double-free and verifier rejection? 

Since bpf_list_push_front() consumes the referenced object unconditionally,
its reference ID is cleared upon return. The verifier treats the return value
as an unknown scalar and explores both branches of the error check.

Because calling bpf_obj_drop() requires an owning reference, dropping the
already-consumed node here might cause the __success test to fail loading
with a 'release kfunc expects referenced PTR_TO_BTF_ID' error.

> +		return -2;
> +	}
> +
> +	list_node = bpf_list_pop_front(&head);
> +	bpf_spin_unlock(&lock);
[ ... ]
> diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
> index 7247a20c0a3bf..f6cb084af16aa 100644
> --- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
> +++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
[ ... ]
> +SEC("?tc")
> +__failure __msg("R1 must have zero offset when passed to bpf_refcount_acquire")
> +long refcount_acquire_list_node_offset(void *ctx)
> +{
> +	struct node_refcounted *node, *base, *ref;
> +	struct bpf_list_node *list_node;
> +	long err;
> +
> +	node = bpf_obj_new(typeof(*node));
> +	if (!node)
> +		return 1;
> +
> +	bpf_spin_lock(&lock);
> +	err = bpf_list_push_front(&head, &node->list);
> +	if (err) {
> +		bpf_spin_unlock(&lock);
> +		bpf_obj_drop(node);

[Severity: Medium]
Similarly here in refcount_acquire_list_node_offset(), does dropping the
node after bpf_list_push_front() cause the same issue?

Depending on the verifier's branch evaluation order, this double-free logic
might cause the test to fail verification for the wrong reason, which would
break the expected __msg check.

> +		return 2;
> +	}
> +
> +	list_node = bpf_list_pop_front(&head);
> +	bpf_spin_unlock(&lock);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781852308.git.chenyy23@mails.tsinghua.edu.cn?part=2

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

* Re: [PATCH bpf-next 2/2] selftests/bpf: Cover refcount acquire node offsets
  2026-06-19  7:59 ` [PATCH bpf-next 2/2] selftests/bpf: Cover refcount acquire node offsets Yiyang Chen
  2026-06-19  8:30   ` sashiko-bot
@ 2026-06-19  8:47   ` bot+bpf-ci
  2026-06-19 19:30   ` Eduard Zingerman
  2 siblings, 0 replies; 11+ messages in thread
From: bot+bpf-ci @ 2026-06-19  8:47 UTC (permalink / raw)
  To: chenyy23, ast, daniel, andrii, eddyz87, memxor
  Cc: chenyy23, john.fastabend, martin.lau, song, yonghong.song, jolsa,
	emil, shuah, vmalik, leon.hwang, davemarchevsky, bpf,
	linux-kselftest, linux-kernel, ast, andrii, daniel, martin.lau,
	eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 1232 bytes --]

>     selftests/bpf: Cover refcount acquire node offsets
>
>     Add regression coverage for bpf_refcount_acquire() on graph-node-derived
>     pointers.
>
>     The accepted case pops a list node and normalizes it with container_of()
>     before acquiring a refcount. The rejected cases pass popped list and rbtree
>     node pointers directly to bpf_refcount_acquire(), which must fail because
>     those pointers carry non-zero fixed offsets.
>
>     Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>

This adds coverage for a verifier bug where bpf_refcount_acquire() did not
require its argument to have a zero offset, so graph-node-derived pointers
from bpf_list_pop_front()/bpf_rbtree_remove() carrying a fixed node-field
offset could pass the type check. The companion kernel fix that rejects
those offset arguments tags the commit that introduced the kfunc. Should
this selftest commit carry the same tag?

  Fixes: 7c50b1cb76aca ("bpf: Add bpf_refcount_acquire kfunc")


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/27814304568

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

* Re: [PATCH bpf-next 1/2] bpf: Reject offset refcount acquire arguments
  2026-06-19  7:59 ` [PATCH bpf-next 1/2] " Yiyang Chen
@ 2026-06-19 19:28   ` Eduard Zingerman
  0 siblings, 0 replies; 11+ messages in thread
From: Eduard Zingerman @ 2026-06-19 19:28 UTC (permalink / raw)
  To: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Kumar Kartikeya Dwivedi
  Cc: John Fastabend, Martin KaFai Lau, Song Liu, Yonghong Song,
	Jiri Olsa, Emil Tsalapatis, Shuah Khan, Viktor Malik, Leon Hwang,
	Dave Marchevsky, bpf, linux-kselftest, linux-kernel

On Fri, 2026-06-19 at 07:59 +0000, Yiyang Chen wrote:
> bpf_refcount_acquire() increments the refcount at the caller-supplied
> pointer plus the refcount field offset, then returns the caller-supplied
> pointer unchanged.
> 
> The verifier records the return value as a base pointer to the refcounted
> object.
> 
> bpf_list_pop_front() and bpf_rbtree_remove() can return embedded graph-node
> pointers as PTR_TO_BTF_ID | MEM_ALLOC with a fixed offset equal to the node
> field offset. Passing such a pointer directly to bpf_refcount_acquire()
> currently passes the refcounted-kptr type check.
> 
> That makes the runtime operation start from base + node_off while the
> verifier models the returned pointer as the object base.
> 
> Require refcount-acquire arguments to have zero offset. Programs can still
> acquire a refcount from a graph-node-derived pointer after normalizing it
> with container_of().
> 
> Fixes: 7c50b1cb76aca ("bpf: Add bpf_refcount_acquire kfunc")
> Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
> ---

Hi Yiyang,

thank you for the bug report.

>  kernel/bpf/verifier.c | 5 +++++
>  1 file changed, 5 insertions(+)
> 
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 2abc79dbf..4510fe0b4 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -12392,6 +12392,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
>  					reg_arg_name(env, argno));
>  				return -EINVAL;
>  			}
> +			if (reg->var_off.value != 0) {

I think this check should be handled by check_func_arg_reg_off() /
__check_ptr_off_reg(), could you please reorganize the code such that
it is handled there?

> +				verbose(env, "%s must have zero offset when passed to %s\n",
> +					reg_arg_name(env, argno), meta->func_name);
> +				return -EINVAL;
> +			}
>  			if (!type_is_non_owning_ref(reg->type))
>  				meta->arg_owning_ref = true;
>  

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

* Re: [PATCH bpf-next 2/2] selftests/bpf: Cover refcount acquire node offsets
  2026-06-19  7:59 ` [PATCH bpf-next 2/2] selftests/bpf: Cover refcount acquire node offsets Yiyang Chen
  2026-06-19  8:30   ` sashiko-bot
  2026-06-19  8:47   ` bot+bpf-ci
@ 2026-06-19 19:30   ` Eduard Zingerman
  2 siblings, 0 replies; 11+ messages in thread
From: Eduard Zingerman @ 2026-06-19 19:30 UTC (permalink / raw)
  To: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Kumar Kartikeya Dwivedi
  Cc: John Fastabend, Martin KaFai Lau, Song Liu, Yonghong Song,
	Jiri Olsa, Emil Tsalapatis, Shuah Khan, Viktor Malik, Leon Hwang,
	Dave Marchevsky, bpf, linux-kselftest, linux-kernel

On Fri, 2026-06-19 at 07:59 +0000, Yiyang Chen wrote:

[...]

> diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr.c b/tools/testing/selftests/bpf/progs/refcounted_kptr.c
> index 61906f480..7955a9973 100644
> --- a/tools/testing/selftests/bpf/progs/refcounted_kptr.c
> +++ b/tools/testing/selftests/bpf/progs/refcounted_kptr.c
> @@ -741,6 +741,39 @@ int list_push_back_uninit_head(void *ctx)
>  	return ret;
>  }
>  
> +SEC("tc")
> +__description("refcount_acquire_list_pop_container: acquire normalized list pop")
> +__success __retval(0)
> +int refcount_acquire_list_pop_container(void *ctx)
> +{
> +	struct node_data *node, *base, *ref;
> +	struct bpf_list_node *list_node;
> +	long err;
> +
> +	node = bpf_obj_new(typeof(*node));
> +	if (!node)
> +		return -1;
> +
> +	bpf_spin_lock(&lock);
> +	err = bpf_list_push_front(&head, &node->l);
> +	if (err) {
> +		bpf_spin_unlock(&lock);
> +		bpf_obj_drop(node);
> +		return -2;
> +	}
> +
> +	list_node = bpf_list_pop_front(&head);
> +	bpf_spin_unlock(&lock);
> +	if (!list_node)
> +		return -3;
> +
> +	base = container_of(list_node, struct node_data, l);
> +	ref = bpf_refcount_acquire(base);
> +	bpf_obj_drop(ref);
> +	bpf_obj_drop(base);
> +	return 0;
> +}
> +

Why do you think the positive test case is necessary?

[...]

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

* [PATCH bpf-next v2 0/2] bpf: Reject offset refcount acquire arguments
  2026-06-19  7:59 [PATCH bpf-next 0/2] bpf: Reject offset refcount acquire arguments Yiyang Chen
  2026-06-19  7:59 ` [PATCH bpf-next 1/2] " Yiyang Chen
  2026-06-19  7:59 ` [PATCH bpf-next 2/2] selftests/bpf: Cover refcount acquire node offsets Yiyang Chen
@ 2026-06-20 15:04 ` Yiyang Chen
  2026-06-20 15:04   ` [PATCH bpf-next v2 1/2] " Yiyang Chen
  2026-06-20 15:04   ` [PATCH bpf-next v2 2/2] selftests/bpf: Cover refcount acquire node offsets Yiyang Chen
  2 siblings, 2 replies; 11+ messages in thread
From: Yiyang Chen @ 2026-06-20 15:04 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: Yiyang Chen, John Fastabend, Martin KaFai Lau, Song Liu,
	Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
	Viktor Malik, Leon Hwang, Dave Marchevsky, bpf, linux-kselftest,
	linux-kernel

bpf_refcount_acquire() is modeled as returning a refcounted allocation
base, but it currently accepts PTR_TO_BTF_ID | MEM_ALLOC arguments whose
offset already points at an embedded graph node returned from a list or
rbtree operation.

At runtime the kfunc starts from the supplied pointer and adds the type's
refcount offset. With a graph-node pointer, that starts from base +
node_off, while the verifier treats the returned pointer as the allocation
base. Reject non-zero fixed-offset arguments to keep the runtime operation
and the verifier model aligned.

Programs that pop graph nodes can still acquire a reference after
normalizing the node pointer with container_of().

Patch 1 adds a PTR_ZERO_OFF argument flag and handles the zero fixed-offset
requirement through check_func_arg_reg_off() / __check_ptr_off_reg().

Patch 2 adds rejected direct list and rbtree node cases.

Changes from v1:
  - Move zero fixed-offset enforcement into check_func_arg_reg_off() /
    __check_ptr_off_reg(), as suggested by Eduard.
  - Drop the positive container_of() selftest case.
  - Remove the stale bpf_obj_drop() after bpf_list_push_front(), since the
    pushed reference is consumed even when the verifier explores the error
    branch.
  - Add a Fixes tag to the selftest patch.
  - Rebase to bpf-next master a975094bf98c.

Yiyang Chen (2):
  bpf: Reject offset refcount acquire arguments
  selftests/bpf: Cover refcount acquire node offsets

 include/linux/bpf.h                           |  3 +
 kernel/bpf/verifier.c                         | 18 +++--
 .../bpf/progs/refcounted_kptr_fail.c          | 77 +++++++++++++++++++
 3 files changed, 91 insertions(+), 7 deletions(-)


base-commit: a975094bf98ca97be9146f9d3b5681a6f9cf5ce3
-- 
2.34.1


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

* [PATCH bpf-next v2 1/2] bpf: Reject offset refcount acquire arguments
  2026-06-20 15:04 ` [PATCH bpf-next v2 0/2] bpf: Reject offset refcount acquire arguments Yiyang Chen
@ 2026-06-20 15:04   ` Yiyang Chen
  2026-06-20 17:48     ` Alexei Starovoitov
  2026-06-20 15:04   ` [PATCH bpf-next v2 2/2] selftests/bpf: Cover refcount acquire node offsets Yiyang Chen
  1 sibling, 1 reply; 11+ messages in thread
From: Yiyang Chen @ 2026-06-20 15:04 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: Yiyang Chen, John Fastabend, Martin KaFai Lau, Song Liu,
	Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
	Viktor Malik, Leon Hwang, Dave Marchevsky, bpf, linux-kselftest,
	linux-kernel

bpf_refcount_acquire() increments the refcount at the caller-supplied
pointer plus the refcount field offset, then returns the caller-supplied
pointer unchanged.

The verifier records the return value as a base pointer to the refcounted
object.

bpf_list_pop_front() and bpf_rbtree_remove() can return embedded
graph-node pointers as PTR_TO_BTF_ID | MEM_ALLOC with a fixed offset equal
to the node field offset. Passing such a pointer directly to
bpf_refcount_acquire() currently passes the refcounted-kptr type check.

That makes the runtime operation start from base + node_off while the
verifier models the returned pointer as the object base.

Require refcount-acquire arguments to have zero fixed offset by carrying
the requirement through check_func_arg_reg_off() to __check_ptr_off_reg().
Programs can still acquire a refcount from a graph-node-derived pointer
after normalizing it with container_of().

Fixes: 7c50b1cb76aca ("bpf: Add bpf_refcount_acquire kfunc")
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
 include/linux/bpf.h   |  3 +++
 kernel/bpf/verifier.c | 18 +++++++++++-------
 2 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 7719f6528..b9b7d19cb 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -859,6 +859,9 @@ enum bpf_type_flag {
 	/* DYNPTR points to file */
 	DYNPTR_TYPE_FILE	= BIT(20 + BPF_BASE_TYPE_BITS),
 
+	/* PTR argument cannot have a fixed offset. */
+	PTR_ZERO_OFF		= BIT(21 + BPF_BASE_TYPE_BITS),
+
 	__BPF_TYPE_FLAG_MAX,
 	__BPF_TYPE_LAST_FLAG	= __BPF_TYPE_FLAG_MAX - 1,
 };
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 2abc79dbf..b41aee8c6 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -7994,8 +7994,11 @@ static int check_func_arg_reg_off(struct bpf_verifier_env *env,
 				  const struct bpf_reg_state *reg, argno_t argno,
 				  enum bpf_arg_type arg_type)
 {
+	bool fixed_off_ok = !(arg_type & PTR_ZERO_OFF);
 	u32 type = reg->type;
 
+	arg_type &= ~PTR_ZERO_OFF;
+
 	/* When referenced register is passed to release function, its fixed
 	 * offset must be 0.
 	 *
@@ -8048,13 +8051,12 @@ static int check_func_arg_reg_off(struct bpf_verifier_env *env,
 	case PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF:
 	case PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF | MEM_RCU:
 		/* When referenced PTR_TO_BTF_ID is passed to release function,
-		 * its fixed offset must be 0. In the other cases, fixed offset
-		 * can be non-zero. This was already checked above. So pass
-		 * fixed_off_ok as true to allow fixed offset for all other
-		 * cases. var_off always must be 0 for PTR_TO_BTF_ID, hence we
-		 * still need to do checks instead of returning.
+		 * or when the argument type requires zero fixed offset, its
+		 * fixed offset must be 0. In the other cases, fixed offset can
+		 * be non-zero. var_off always must be 0 for PTR_TO_BTF_ID,
+		 * hence we still need to do checks instead of returning.
 		 */
-		return __check_ptr_off_reg(env, reg, argno, true);
+		return __check_ptr_off_reg(env, reg, argno, fixed_off_ok);
 	case PTR_TO_CTX:
 		/*
 		 * Allow fixed and variable offsets for syscall context, but
@@ -12114,7 +12116,6 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 		case KF_ARG_PTR_TO_MEM:
 		case KF_ARG_PTR_TO_MEM_SIZE:
 		case KF_ARG_PTR_TO_CALLBACK:
-		case KF_ARG_PTR_TO_REFCOUNTED_KPTR:
 		case KF_ARG_PTR_TO_CONST_STR:
 		case KF_ARG_PTR_TO_WORKQUEUE:
 		case KF_ARG_PTR_TO_TIMER:
@@ -12128,6 +12129,9 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 		case KF_ARG_PTR_TO_CTX:
 			arg_type = ARG_PTR_TO_CTX;
 			break;
+		case KF_ARG_PTR_TO_REFCOUNTED_KPTR:
+			arg_type = ARG_PTR_TO_BTF_ID | PTR_ZERO_OFF;
+			break;
 		default:
 			verifier_bug(env, "unknown kfunc arg type %d", kf_arg_type);
 			return -EFAULT;
-- 
2.34.1


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

* [PATCH bpf-next v2 2/2] selftests/bpf: Cover refcount acquire node offsets
  2026-06-20 15:04 ` [PATCH bpf-next v2 0/2] bpf: Reject offset refcount acquire arguments Yiyang Chen
  2026-06-20 15:04   ` [PATCH bpf-next v2 1/2] " Yiyang Chen
@ 2026-06-20 15:04   ` Yiyang Chen
  1 sibling, 0 replies; 11+ messages in thread
From: Yiyang Chen @ 2026-06-20 15:04 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: Yiyang Chen, John Fastabend, Martin KaFai Lau, Song Liu,
	Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
	Viktor Malik, Leon Hwang, Dave Marchevsky, bpf, linux-kselftest,
	linux-kernel

Add regression coverage for bpf_refcount_acquire() on graph-node-derived
pointers.

The rejected cases pass popped list and rbtree node pointers directly to
bpf_refcount_acquire(), which must fail because those pointers carry
non-zero fixed offsets.

Do not add a positive container_of() case here. Existing refcounted_kptr
coverage already exercises valid base-pointer acquisitions, and this patch
only checks the rejected offset forms.

Fixes: 7c50b1cb76aca ("bpf: Add bpf_refcount_acquire kfunc")
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
 .../bpf/progs/refcounted_kptr_fail.c          | 77 +++++++++++++++++++
 1 file changed, 77 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
index 7247a20c0..5ec392e72 100644
--- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
@@ -13,12 +13,22 @@ struct node_acquire {
 	struct bpf_refcount refcount;
 };
 
+struct node_refcounted {
+	long key;
+	struct bpf_rb_node rb;
+	struct bpf_list_node list;
+	struct bpf_refcount refcount;
+};
+
 extern void bpf_rcu_read_lock(void) __ksym;
 extern void bpf_rcu_read_unlock(void) __ksym;
 
 #define private(name) SEC(".data." #name) __hidden __attribute__((aligned(8)))
 private(A) struct bpf_spin_lock glock;
 private(A) struct bpf_rb_root groot __contains(node_acquire, node);
+private(B) struct bpf_spin_lock lock;
+private(B) struct bpf_rb_root root __contains(node_refcounted, rb);
+private(B) struct bpf_list_head head __contains(node_refcounted, list);
 
 static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b)
 {
@@ -31,6 +41,17 @@ static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b)
 	return node_a->key < node_b->key;
 }
 
+static bool less_refcounted(struct bpf_rb_node *a, const struct bpf_rb_node *b)
+{
+	struct node_refcounted *node_a;
+	struct node_refcounted *node_b;
+
+	node_a = container_of(a, struct node_refcounted, rb);
+	node_b = container_of(b, struct node_refcounted, rb);
+
+	return node_a->key < node_b->key;
+}
+
 SEC("?tc")
 __failure __msg("Unreleased reference id=4 alloc_insn={{[0-9]+}}")
 long rbtree_refcounted_node_ref_escapes(void *ctx)
@@ -93,6 +114,62 @@ long rbtree_refcounted_node_ref_escapes_owning_input(void *ctx)
 	return 0;
 }
 
+SEC("?tc")
+__failure __msg("dereference of modified ptr_node_refcounted ptr R1")
+long refcount_acquire_list_node_offset(void *ctx)
+{
+	struct node_refcounted *node, *base, *ref;
+	struct bpf_list_node *list_node;
+
+	node = bpf_obj_new(typeof(*node));
+	if (!node)
+		return 1;
+
+	bpf_spin_lock(&lock);
+	bpf_list_push_front(&head, &node->list);
+	list_node = bpf_list_pop_front(&head);
+	bpf_spin_unlock(&lock);
+	if (!list_node)
+		return 2;
+
+	base = container_of(list_node, struct node_refcounted, list);
+	ref = bpf_refcount_acquire(list_node);
+	if (ref)
+		bpf_obj_drop(ref);
+	bpf_obj_drop(base);
+	return 0;
+}
+
+SEC("?tc")
+__failure __msg("dereference of modified ptr_node_refcounted ptr R1")
+long refcount_acquire_rbtree_node_offset(void *ctx)
+{
+	struct node_refcounted *node, *base, *ref;
+	struct bpf_rb_node *rb_node;
+
+	node = bpf_obj_new(typeof(*node));
+	if (!node)
+		return 1;
+
+	node->key = 1;
+
+	bpf_spin_lock(&lock);
+	bpf_rbtree_add(&root, &node->rb, less_refcounted);
+	rb_node = bpf_rbtree_first(&root);
+	if (rb_node)
+		rb_node = bpf_rbtree_remove(&root, rb_node);
+	bpf_spin_unlock(&lock);
+	if (!rb_node)
+		return 2;
+
+	base = container_of(rb_node, struct node_refcounted, rb);
+	ref = bpf_refcount_acquire(rb_node);
+	if (ref)
+		bpf_obj_drop(ref);
+	bpf_obj_drop(base);
+	return 0;
+}
+
 SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
 __failure __msg("function calls are not allowed while holding a lock")
 int BPF_PROG(rbtree_fail_sleepable_lock_across_rcu,
-- 
2.34.1


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

* Re: [PATCH bpf-next v2 1/2] bpf: Reject offset refcount acquire arguments
  2026-06-20 15:04   ` [PATCH bpf-next v2 1/2] " Yiyang Chen
@ 2026-06-20 17:48     ` Alexei Starovoitov
  0 siblings, 0 replies; 11+ messages in thread
From: Alexei Starovoitov @ 2026-06-20 17:48 UTC (permalink / raw)
  To: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi
  Cc: John Fastabend, Martin KaFai Lau, Song Liu, Yonghong Song,
	Jiri Olsa, Emil Tsalapatis, Shuah Khan, Viktor Malik, Leon Hwang,
	Dave Marchevsky, bpf, linux-kselftest, linux-kernel

On Sat Jun 20, 2026 at 8:04 AM PDT, Yiyang Chen wrote:
> bpf_refcount_acquire() increments the refcount at the caller-supplied
> pointer plus the refcount field offset, then returns the caller-supplied
> pointer unchanged.
>
> The verifier records the return value as a base pointer to the refcounted
> object.
>
> bpf_list_pop_front() and bpf_rbtree_remove() can return embedded
> graph-node pointers as PTR_TO_BTF_ID | MEM_ALLOC with a fixed offset equal
> to the node field offset. Passing such a pointer directly to
> bpf_refcount_acquire() currently passes the refcounted-kptr type check.
>
> That makes the runtime operation start from base + node_off while the
> verifier models the returned pointer as the object base.
>
> Require refcount-acquire arguments to have zero fixed offset by carrying
> the requirement through check_func_arg_reg_off() to __check_ptr_off_reg().
> Programs can still acquire a refcount from a graph-node-derived pointer
> after normalizing it with container_of().
>
> Fixes: 7c50b1cb76aca ("bpf: Add bpf_refcount_acquire kfunc")
> Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
> ---
>  include/linux/bpf.h   |  3 +++
>  kernel/bpf/verifier.c | 18 +++++++++++-------
>  2 files changed, 14 insertions(+), 7 deletions(-)
>
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 7719f6528..b9b7d19cb 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -859,6 +859,9 @@ enum bpf_type_flag {
>  	/* DYNPTR points to file */
>  	DYNPTR_TYPE_FILE	= BIT(20 + BPF_BASE_TYPE_BITS),
>  
> +	/* PTR argument cannot have a fixed offset. */
> +	PTR_ZERO_OFF		= BIT(21 + BPF_BASE_TYPE_BITS),

No. We're not going to burn the bit.

pw-bot: cr

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

end of thread, other threads:[~2026-06-20 17:48 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-19  7:59 [PATCH bpf-next 0/2] bpf: Reject offset refcount acquire arguments Yiyang Chen
2026-06-19  7:59 ` [PATCH bpf-next 1/2] " Yiyang Chen
2026-06-19 19:28   ` Eduard Zingerman
2026-06-19  7:59 ` [PATCH bpf-next 2/2] selftests/bpf: Cover refcount acquire node offsets Yiyang Chen
2026-06-19  8:30   ` sashiko-bot
2026-06-19  8:47   ` bot+bpf-ci
2026-06-19 19:30   ` Eduard Zingerman
2026-06-20 15:04 ` [PATCH bpf-next v2 0/2] bpf: Reject offset refcount acquire arguments Yiyang Chen
2026-06-20 15:04   ` [PATCH bpf-next v2 1/2] " Yiyang Chen
2026-06-20 17:48     ` Alexei Starovoitov
2026-06-20 15:04   ` [PATCH bpf-next v2 2/2] selftests/bpf: Cover refcount acquire node offsets Yiyang Chen

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.