* [PATCH bpf v2 0/2] bpf: Fix refcount_acquire handling for borrowed kptrs @ 2026-07-26 23:50 Ning Ding 2026-07-26 23:50 ` [PATCH bpf v2 1/2] bpf: Keep refcount_acquire nullable for borrowed RCU kptrs Ning Ding 2026-07-26 23:50 ` [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire Ning Ding 0 siblings, 2 replies; 16+ messages in thread From: Ning Ding @ 2026-07-26 23:50 UTC (permalink / raw) To: bpf; +Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, davem, greg This series fixes two bugs in bpf_refcount_acquire() verifier handling. Patch 1 fixes return-value tracking. The verifier can treat an RCU-loaded borrowed map kptr as owning and mark the return value non-NULL. At runtime, bpf_refcount_acquire() can still return NULL, allowing an unchecked NULL value to reach bpf_obj_drop() and crash the kernel. Patch 2 fixes argument validation. After bpf_rcu_read_unlock(), the pointer is PTR_UNTRUSTED, but the verifier still allows it to be passed to bpf_refcount_acquire(). A reproducer with only CAP_BPF acquired a reference through this stale pointer after the original object was dropped and its address was reused. The series adds verifier regression tests for both cases. Changes since v1: - Patch 1 is unchanged. - Added Patch 2 to reject PTR_UNTRUSTED refcounted kptr inputs after bpf_rcu_read_unlock(). - Reused the refcount-only selftest fixture introduced by Patch 1. - Added fail-before/pass-after verifier validation and CAP_BPF-only runtime confirmation. Link: https://lore.kernel.org/r/20260726015330.705259-1-dingning04@gmail.com Ning Ding (2): bpf: Keep refcount_acquire nullable for borrowed RCU kptrs bpf: Reject untrusted pointers in refcount_acquire kernel/bpf/verifier.c | 7 +- .../selftests/bpf/progs/refcounted_kptr.c | 61 +++++++++++++++ .../bpf/progs/refcounted_kptr_fail.c | 74 +++++++++++++++++++ 3 files changed, 141 insertions(+), 1 deletion(-) -- 2.43.0 ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH bpf v2 1/2] bpf: Keep refcount_acquire nullable for borrowed RCU kptrs 2026-07-26 23:50 [PATCH bpf v2 0/2] bpf: Fix refcount_acquire handling for borrowed kptrs Ning Ding @ 2026-07-26 23:50 ` Ning Ding 2026-07-27 4:50 ` Greg KH 2026-07-26 23:50 ` [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire Ning Ding 1 sibling, 1 reply; 16+ messages in thread From: Ning Ding @ 2026-07-26 23:50 UTC (permalink / raw) To: bpf; +Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, davem, greg bpf_refcount_acquire() returns NULL if the object's refcount has reached zero. For an RCU-loaded map kptr, another thread can replace the map entry and drop the last owning reference before the acquire, which makes the ref count drop to 0. However, the verifier incorrectly marks the input as owning solely because it lacks NON_OWN_REF, and therefore treats the return value as non-NULL. This allows an unchecked NULL return to be passed to bpf_obj_drop(), which can crash the kernel. Only treat the input as owning when it is present in the verifier's acquired-reference state. Add tests for owning input, checked borrowed RCU input, and 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) -- 2.43.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v2 1/2] bpf: Keep refcount_acquire nullable for borrowed RCU kptrs 2026-07-26 23:50 ` [PATCH bpf v2 1/2] bpf: Keep refcount_acquire nullable for borrowed RCU kptrs Ning Ding @ 2026-07-27 4:50 ` Greg KH 0 siblings, 0 replies; 16+ messages in thread From: Greg KH @ 2026-07-27 4:50 UTC (permalink / raw) To: Ning Ding; +Cc: bpf, ast, daniel, andrii, eddyz87, memxor, martin.lau, davem On Sun, Jul 26, 2026 at 04:50:29PM -0700, Ning Ding wrote: > bpf_refcount_acquire() returns NULL if the object's refcount has > reached zero. For an RCU-loaded map kptr, another thread can replace > the map entry and drop the last owning reference before the acquire, > which makes the ref count drop to 0. > > However, the verifier incorrectly marks the input as owning solely because it > lacks NON_OWN_REF, and therefore treats the return value as non-NULL. > This allows an unchecked NULL return to be passed to bpf_obj_drop(), > which can crash the kernel. > > Only treat the input as owning when it is present in the verifier's > acquired-reference state. Add tests for owning input, checked borrowed > RCU input, and 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(-) > Hi, This is the friendly patch-bot of Greg Kroah-Hartman. You have sent him a patch that has triggered this response. He used to manually respond to these common problems, but in order to save his sanity (he kept writing the same thing over and over, yet to different people), I was created. Hopefully you will not take offence and will fix the problem in your patch and resubmit it so that it can be accepted into the Linux kernel tree. You are receiving this message because of the following common error(s) as indicated below: - You have marked a patch with a "Fixes:" tag for a commit that is in an older released kernel, yet you do not have a cc: stable line in the signed-off-by area at all, which means that the patch will not be applied to any older kernel releases. To properly fix this, please follow the documented rules in the Documentation/process/stable-kernel-rules.rst file for how to resolve this. If you wish to discuss this problem further, or you have questions about how to resolve this issue, please feel free to respond to this email and Greg will reply once he has dug out from the pending patches received from other developers. thanks, greg k-h's patch email bot ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire 2026-07-26 23:50 [PATCH bpf v2 0/2] bpf: Fix refcount_acquire handling for borrowed kptrs Ning Ding 2026-07-26 23:50 ` [PATCH bpf v2 1/2] bpf: Keep refcount_acquire nullable for borrowed RCU kptrs Ning Ding @ 2026-07-26 23:50 ` Ning Ding 2026-07-27 0:13 ` sashiko-bot ` (2 more replies) 1 sibling, 3 replies; 16+ messages in thread From: Ning Ding @ 2026-07-26 23:50 UTC (permalink / raw) To: bpf; +Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, davem, greg 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 <dingning04@gmail.com> --- 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 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire 2026-07-26 23:50 ` [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire Ning Ding @ 2026-07-27 0:13 ` sashiko-bot 2026-07-27 0:40 ` Ning Ding 2026-07-27 4:50 ` Greg KH 2026-08-02 23:29 ` Kumar Kartikeya Dwivedi 2 siblings, 1 reply; 16+ messages in thread From: sashiko-bot @ 2026-07-27 0: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] check_ptr_to_btf_access() incorrectly allows writes to PTR_UNTRUSTED allocated objects or triggers a verifier_bug WARN_ONCE when reading from them. -- --- Patch [2]: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire --- commit 80e6c61503b2eee2d4804b0a7ec8927827ed7d92 Author: Ning Ding <dingning04@gmail.com> bpf: Reject untrusted pointers in refcount_acquire This commit fixes an issue where a refcounted map kptr could still be passed to bpf_refcount_acquire() after being marked PTR_UNTRUSTED due to bpf_rcu_read_unlock(). It updates check_kfunc_args() to reject PTR_UNTRUSTED arguments for KF_ARG_PTR_TO_REFCOUNTED_KPTR. Link: https://lore.kernel.org/r/20260726021304.97ED91F000E9@smtp.kernel.org > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 1e7343b625de5..63b1d997fe804 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; > + } [Severity: Critical] This is a pre-existing issue, but I noticed a similar problem with PTR_UNTRUSTED handling in check_ptr_to_btf_access() for allocated objects: kernel/bpf/verifier.c:check_ptr_to_btf_access() { ... if (atype != BPF_READ && !type_is_ptr_alloc_obj(reg->type)) { 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)) { verifier_bug(env, "allocated object must have a referenced id"); return -EFAULT; } ... } Does this logic incorrectly allow writes to PTR_UNTRUSTED allocated objects or trigger a verifier_bug WARN_ONCE when reading from them? If a program loads a program-allocated kptr containing an RCU-protected field inside bpf_rcu_read_lock(), the verifier marks it MEM_ALLOC | MEM_RCU (and NON_OWN_REF if it contains a bpf_rb_node or bpf_list_node). When the program calls bpf_rcu_read_unlock(), it strips MEM_RCU and adds PTR_UNTRUSTED. If the program subsequently tries to read or write this pointer, type_is_ptr_alloc_obj(reg->type) evaluates to true because it ignores PTR_UNTRUSTED, bypassing the read-only check for writes. Then, if the pointer lacks NON_OWN_REF, it hits the type_is_alloc() condition and triggers a verifier_bug(), leading to a BPF_WARN_ONCE. If it has NON_OWN_REF, it bypasses the verifier_bug() check entirely, and proceeds to allow a memory write to the untrusted pointer. Could a malicious BPF program exploit this to achieve a local denial of service via panic_on_warn or a use-after-free memory write? > if (!type_is_ptr_alloc_obj(reg->type)) { > verbose(env, "%s is neither owning or non-owning ref\n", > reg_arg_name(env, argno)); [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260726235030.1152542-1-dingning04@gmail.com?part=2 ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire 2026-07-27 0:13 ` sashiko-bot @ 2026-07-27 0:40 ` Ning Ding 0 siblings, 0 replies; 16+ messages in thread From: Ning Ding @ 2026-07-27 0:40 UTC (permalink / raw) To: sashiko-reviews; +Cc: bpf This seems to be a separate issue from the refcount_acquire argument check... For a pointer without NON_OWN_REF, code inspection shows that direct access can reach verifier_bug(). If that warning is emitted, a system configured with panic_on_warn may panic. (haven't reproduced that yet) In my runtime experiment, the stale address was reused for another BPF object before physical reclamation. A read through the stale pointer returned a value belonging to the recycled object. And I did not observe a WARN, panic, physical-kfree UAF, or a successful stale write in my experiment configuration. So the current experimental evidence shows only a stale read from a recycled object, while the panic only remains possibilities in the source code, but haven't done any experiments to prove that it is feasible. Regards, Ning Ding On Sun, Jul 26, 2026 at 5:13 PM <sashiko-bot@kernel.org> wrote: > > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: > > Pre-existing issues: > - [Critical] check_ptr_to_btf_access() incorrectly allows writes to PTR_UNTRUSTED allocated objects or triggers a verifier_bug WARN_ONCE when reading from them. > -- > > --- Patch [2]: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire --- > commit 80e6c61503b2eee2d4804b0a7ec8927827ed7d92 > Author: Ning Ding <dingning04@gmail.com> > > bpf: Reject untrusted pointers in refcount_acquire > > This commit fixes an issue where a refcounted map kptr could still be passed > to bpf_refcount_acquire() after being marked PTR_UNTRUSTED due to > bpf_rcu_read_unlock(). It updates check_kfunc_args() to reject PTR_UNTRUSTED > arguments for KF_ARG_PTR_TO_REFCOUNTED_KPTR. > > Link: https://lore.kernel.org/r/20260726021304.97ED91F000E9@smtp.kernel.org > > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > > index 1e7343b625de5..63b1d997fe804 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; > > + } > > [Severity: Critical] > This is a pre-existing issue, but I noticed a similar problem with > PTR_UNTRUSTED handling in check_ptr_to_btf_access() for allocated objects: > > kernel/bpf/verifier.c:check_ptr_to_btf_access() { > ... > if (atype != BPF_READ && !type_is_ptr_alloc_obj(reg->type)) { > 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)) { > verifier_bug(env, "allocated object must have a referenced id"); > return -EFAULT; > } > ... > } > > Does this logic incorrectly allow writes to PTR_UNTRUSTED allocated objects > or trigger a verifier_bug WARN_ONCE when reading from them? > > If a program loads a program-allocated kptr containing an RCU-protected field > inside bpf_rcu_read_lock(), the verifier marks it MEM_ALLOC | MEM_RCU (and > NON_OWN_REF if it contains a bpf_rb_node or bpf_list_node). When the program > calls bpf_rcu_read_unlock(), it strips MEM_RCU and adds PTR_UNTRUSTED. > > If the program subsequently tries to read or write this pointer, > type_is_ptr_alloc_obj(reg->type) evaluates to true because it ignores > PTR_UNTRUSTED, bypassing the read-only check for writes. > > Then, if the pointer lacks NON_OWN_REF, it hits the type_is_alloc() condition > and triggers a verifier_bug(), leading to a BPF_WARN_ONCE. If it has > NON_OWN_REF, it bypasses the verifier_bug() check entirely, and proceeds to > allow a memory write to the untrusted pointer. > > Could a malicious BPF program exploit this to achieve a local denial of > service via panic_on_warn or a use-after-free memory write? > > > if (!type_is_ptr_alloc_obj(reg->type)) { > > verbose(env, "%s is neither owning or non-owning ref\n", > > reg_arg_name(env, argno)); > > [ ... ] > > -- > Sashiko AI review · https://sashiko.dev/#/patchset/20260726235030.1152542-1-dingning04@gmail.com?part=2 ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire 2026-07-26 23:50 ` [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire Ning Ding 2026-07-27 0:13 ` sashiko-bot @ 2026-07-27 4:50 ` Greg KH 2026-08-02 23:29 ` Kumar Kartikeya Dwivedi 2 siblings, 0 replies; 16+ messages in thread From: Greg KH @ 2026-07-27 4:50 UTC (permalink / raw) To: Ning Ding; +Cc: bpf, ast, daniel, andrii, eddyz87, memxor, martin.lau, davem On Sun, Jul 26, 2026 at 04:50:30PM -0700, Ning Ding wrote: > 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 <dingning04@gmail.com> > --- > kernel/bpf/verifier.c | 5 ++++ > .../bpf/progs/refcounted_kptr_fail.c | 27 +++++++++++++++++++ > 2 files changed, 32 insertions(+) > Hi, This is the friendly patch-bot of Greg Kroah-Hartman. You have sent him a patch that has triggered this response. He used to manually respond to these common problems, but in order to save his sanity (he kept writing the same thing over and over, yet to different people), I was created. Hopefully you will not take offence and will fix the problem in your patch and resubmit it so that it can be accepted into the Linux kernel tree. You are receiving this message because of the following common error(s) as indicated below: - You have marked a patch with a "Fixes:" tag for a commit that is in an older released kernel, yet you do not have a cc: stable line in the signed-off-by area at all, which means that the patch will not be applied to any older kernel releases. To properly fix this, please follow the documented rules in the Documentation/process/stable-kernel-rules.rst file for how to resolve this. If you wish to discuss this problem further, or you have questions about how to resolve this issue, please feel free to respond to this email and Greg will reply once he has dug out from the pending patches received from other developers. thanks, greg k-h's patch email bot ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire 2026-07-26 23:50 ` [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire Ning Ding 2026-07-27 0:13 ` sashiko-bot 2026-07-27 4:50 ` Greg KH @ 2026-08-02 23:29 ` Kumar Kartikeya Dwivedi 2026-08-03 5:57 ` Ning Ding 2 siblings, 1 reply; 16+ messages in thread From: Kumar Kartikeya Dwivedi @ 2026-08-02 23:29 UTC (permalink / raw) To: Ning Ding, bpf; +Cc: ast, daniel, andrii, eddyz87, martin.lau, davem, greg On Mon Jul 27, 2026 at 1:50 AM CEST, Ning Ding wrote: > 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 <dingning04@gmail.com> > --- Same comment as the new set; split kernel commits and selftest commits into separate ones. As for the fix, I think it would make more sense if type_is_ptr_alloc_obj() was fixed to PTR_UNTRUSTED by definition, instead of having to add extra checks on top. pw-bot: cr > 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) ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire 2026-08-02 23:29 ` Kumar Kartikeya Dwivedi @ 2026-08-03 5:57 ` Ning Ding 2026-08-03 6:08 ` Kumar Kartikeya Dwivedi 0 siblings, 1 reply; 16+ messages in thread From: Ning Ding @ 2026-08-03 5:57 UTC (permalink / raw) To: Kumar Kartikeya Dwivedi Cc: bpf, ast, daniel, andrii, eddyz87, martin.lau, davem, greg > Same comment as the new set; split kernel commits and selftest commits into > separate ones. As for the fix, I think it would make more sense if > type_is_ptr_alloc_obj() was fixed to PTR_UNTRUSTED by definition, instead of > having to add extra checks on top. I tried this change on bpf-next 60781269e26c. It fixes the refcount_acquire case; the focused tests pass 43/43, and the list/rbtree tests pass 176/176. But here is one concern: type_is_ptr_alloc_obj() is also used by type_is_non_owning_ref() and reg_btf_record(). In particular, type_is_non_owning_ref() is defined in terms of it. After the final RCU unlock, a graph-object pointer has MEM_ALLOC | NON_OWN_REF | PTR_UNTRUSTED. Excluding PTR_UNTRUSTED from the shared helper makes type_is_non_owning_ref() return false. This also seems inconsistent with d8939cb0a03c, which allowed extra flags, including PTR_UNTRUSTED, for metadata lookup. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire 2026-08-03 5:57 ` Ning Ding @ 2026-08-03 6:08 ` Kumar Kartikeya Dwivedi 2026-08-03 6:48 ` Ning Ding 0 siblings, 1 reply; 16+ messages in thread From: Kumar Kartikeya Dwivedi @ 2026-08-03 6:08 UTC (permalink / raw) To: Ning Ding; +Cc: bpf, ast, daniel, andrii, eddyz87, martin.lau, davem, greg On Mon Aug 3, 2026 at 7:57 AM CEST, Ning Ding wrote: >> Same comment as the new set; split kernel commits and selftest commits into >> separate ones. As for the fix, I think it would make more sense if >> type_is_ptr_alloc_obj() was fixed to PTR_UNTRUSTED by definition, instead of >> having to add extra checks on top. > > I tried this change on bpf-next 60781269e26c. It fixes the > refcount_acquire case; the focused tests pass 43/43, and the > list/rbtree tests pass 176/176. > > But here is one concern: type_is_ptr_alloc_obj() is also used by > type_is_non_owning_ref() and reg_btf_record(). In particular, > type_is_non_owning_ref() is defined in terms of it. After the final > RCU unlock, a graph-object pointer has MEM_ALLOC | NON_OWN_REF | > PTR_UNTRUSTED. Excluding PTR_UNTRUSTED from the shared helper makes > type_is_non_owning_ref() return false. > > This also seems inconsistent with d8939cb0a03c, which allowed extra > flags, including PTR_UNTRUSTED, for metadata lookup. I would just disallow it. I don't even know how things would be correct if a untrusted owning or non-owning ref is passed around. I think the case in patch 2 is demonstrating that it's a bogus type state for being passed around into the kernel. The only meaningful correct use seems to be reading from such a pointer, for which PTR_UNTRUSTED downgrade instead of invalidating it completely should be good enough. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire 2026-08-03 6:08 ` Kumar Kartikeya Dwivedi @ 2026-08-03 6:48 ` Ning Ding 2026-08-03 6:50 ` Kumar Kartikeya Dwivedi 0 siblings, 1 reply; 16+ messages in thread From: Ning Ding @ 2026-08-03 6:48 UTC (permalink / raw) To: Kumar Kartikeya Dwivedi Cc: bpf, ast, daniel, andrii, eddyz87, martin.lau, davem, greg > I would just disallow it. I don't even know how things would be correct if a > untrusted owning or non-owning ref is passed around. I think the case in patch 2 > is demonstrating that it's a bogus type state for being passed around into the > kernel. > > The only meaningful correct use seems to be reading from such a pointer, for > which PTR_UNTRUSTED downgrade instead of invalidating it completely should be > good enough. Just to confirm, semantically should this case be rejected by the verifier? bpf_rcu_read_lock(); res = mapval->node; bpf_rcu_read_unlock(); return res->key; At the final read, res is MEM_ALLOC | NON_OWN_REF | PTR_UNTRUSTED. Should this be accepted for scalar reads, or should this pointer state be rejected since it could possibly cause read-after-free. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire 2026-08-03 6:48 ` Ning Ding @ 2026-08-03 6:50 ` Kumar Kartikeya Dwivedi 2026-08-03 7:10 ` Ning Ding 0 siblings, 1 reply; 16+ messages in thread From: Kumar Kartikeya Dwivedi @ 2026-08-03 6:50 UTC (permalink / raw) To: Ning Ding; +Cc: bpf, ast, daniel, andrii, eddyz87, martin.lau, davem, greg On Mon Aug 3, 2026 at 8:48 AM CEST, Ning Ding wrote: >> I would just disallow it. I don't even know how things would be correct if a >> untrusted owning or non-owning ref is passed around. I think the case in patch 2 >> is demonstrating that it's a bogus type state for being passed around into the >> kernel. >> >> The only meaningful correct use seems to be reading from such a pointer, for >> which PTR_UNTRUSTED downgrade instead of invalidating it completely should be >> good enough. > > Just to confirm, semantically should this case be rejected by the verifier? > > bpf_rcu_read_lock(); > res = mapval->node; > bpf_rcu_read_unlock(); > return res->key; > > At the final read, res is MEM_ALLOC | NON_OWN_REF | PTR_UNTRUSTED. > Should this be accepted for scalar reads, or should this pointer state > be rejected since it could possibly cause read-after-free. It will work, you can test it yourself. Reads on PTR_UNTRUSTED is fine, it gets handled by the BPF_PROBE_MEM loads, which handle use-after-free safely. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire 2026-08-03 6:50 ` Kumar Kartikeya Dwivedi @ 2026-08-03 7:10 ` Ning Ding 2026-08-03 7:21 ` Kumar Kartikeya Dwivedi 0 siblings, 1 reply; 16+ messages in thread From: Ning Ding @ 2026-08-03 7:10 UTC (permalink / raw) To: Kumar Kartikeya Dwivedi Cc: bpf, ast, daniel, andrii, eddyz87, martin.lau, davem, greg > It will work, you can test it yourself. Reads on PTR_UNTRUSTED is fine, it gets > handled by the BPF_PROBE_MEM loads, which handle use-after-free safely. But if we apply the one-line change to type_is_ptr_alloc_obj(), the read-only test I added will be rejected. SInce the read will hit that code block and trigger the allocated object must have a referenced id". if (type_is_alloc(reg->type) && !type_is_non_owning_ref(reg->type) && !(reg->type & MEM_RCU) && !reg_is_referenced(env, reg)) { verifier_bug(env, "allocated object must have a referenced id"); return -EFAULT; } There are 2 possible solutions: 1. Split the type_is_ptr_alloc_obj into 2 helpers, one is only checking whether the pointer has PTR_TO_BTF_ID | MEM_ALLOC, the other one checks if it is also trusted: static inline bool type_is_any_ptr_alloc_obj(u32 type) { return base_type(type) == PTR_TO_BTF_ID && type_flag(type) & MEM_ALLOC; } static inline bool type_is_ptr_alloc_obj(u32 type) { return type_is_any_ptr_alloc_obj(type) && !(type_flag(type) & PTR_UNTRUSTED); } reg_btf_record() will use type_is_any_ptr_alloc_obj since it only looks up metadata, while bpf_refcount_acquire should use type_is_ptr_alloc_obj. 3. Keep the untrusted pointer check on top like the current patch. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire 2026-08-03 7:10 ` Ning Ding @ 2026-08-03 7:21 ` Kumar Kartikeya Dwivedi 2026-08-03 7:44 ` Ning Ding 0 siblings, 1 reply; 16+ messages in thread From: Kumar Kartikeya Dwivedi @ 2026-08-03 7:21 UTC (permalink / raw) To: Ning Ding; +Cc: bpf, ast, daniel, andrii, eddyz87, martin.lau, davem, greg On Mon Aug 3, 2026 at 9:10 AM CEST, Ning Ding wrote: >> It will work, you can test it yourself. Reads on PTR_UNTRUSTED is fine, it gets >> handled by the BPF_PROBE_MEM loads, which handle use-after-free safely. > > But if we apply the one-line change to type_is_ptr_alloc_obj(), the > read-only test I added will be rejected. SInce the read will hit that > code block and trigger the allocated object must have a referenced > id". > if (type_is_alloc(reg->type) && > !type_is_non_owning_ref(reg->type) && > !(reg->type & MEM_RCU) && > !reg_is_referenced(env, reg)) { > verifier_bug(env, "allocated object must have a referenced id"); > return -EFAULT; > } We can make a specific exception in this case IMO. That is easier to reason about than allowing it by default everywhere. We can even add a comment about it. > > There are 2 possible solutions: > 1. Split the type_is_ptr_alloc_obj into 2 helpers, one is only > checking whether the pointer has PTR_TO_BTF_ID | MEM_ALLOC, the other > one checks if it is also trusted: > static inline bool type_is_any_ptr_alloc_obj(u32 type) > { > return base_type(type) == PTR_TO_BTF_ID && > type_flag(type) & MEM_ALLOC; > } > static inline bool type_is_ptr_alloc_obj(u32 type) > { > return type_is_any_ptr_alloc_obj(type) && > !(type_flag(type) & PTR_UNTRUSTED); > } I would just create another more specific type_is_untrusted_ptr_alloc_obj(). Tighten existing type_is_ptr_alloc_obj() to exclude untrusted case. Then just add "&& !type_is_untrusted_ptr_alloc_obj(...)" to the list of existing checks before the error you pointed out above. In addition, I would add a comment saying that the BPF_WRITE case for the type_is_untrusted_ptr_alloc_obj() types was already rejected earlier, so this check only permits reads on them. Then the test you mentioned should continue passing. > reg_btf_record() will use type_is_any_ptr_alloc_obj since it only > looks up metadata, while bpf_refcount_acquire should use > type_is_ptr_alloc_obj. Is reg_btf_record() going to ever get such a pointer as input? From my cursory look that doesn't seem so. It was happening before since such pointers could be passed around to helpers or kfuncs. That should not be the case anymore. > > 3. Keep the untrusted pointer check on top like the current patch. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire 2026-08-03 7:21 ` Kumar Kartikeya Dwivedi @ 2026-08-03 7:44 ` Ning Ding 2026-08-03 7:48 ` Kumar Kartikeya Dwivedi 0 siblings, 1 reply; 16+ messages in thread From: Ning Ding @ 2026-08-03 7:44 UTC (permalink / raw) To: Kumar Kartikeya Dwivedi Cc: bpf, ast, daniel, andrii, eddyz87, martin.lau, davem, greg > In addition, I would add a comment saying that the BPF_WRITE case for the > type_is_untrusted_ptr_alloc_obj() types was already rejected earlier, so this > check only permits reads on them. Thanks, I’m preparing the respin and will check every type_is_ptr_alloc_obj() caller to decide whether it should use the new stricter one or stay the same. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire 2026-08-03 7:44 ` Ning Ding @ 2026-08-03 7:48 ` Kumar Kartikeya Dwivedi 0 siblings, 0 replies; 16+ messages in thread From: Kumar Kartikeya Dwivedi @ 2026-08-03 7:48 UTC (permalink / raw) To: Ning Ding; +Cc: bpf, ast, daniel, andrii, eddyz87, martin.lau, davem, greg On Mon Aug 3, 2026 at 9:44 AM CEST, Ning Ding wrote: >> In addition, I would add a comment saying that the BPF_WRITE case for the >> type_is_untrusted_ptr_alloc_obj() types was already rejected earlier, so this >> check only permits reads on them. > > Thanks, I’m preparing the respin and will check every > type_is_ptr_alloc_obj() caller to decide whether it should use the new > stricter one or stay the same. Sounds good. I'd still keep type_is_ptr_alloc_obj() as the naming for stricter one, but auditing all users sounds good. On quick skim I don't think we need to relax anything except this case. ^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-08-03 7:48 UTC | newest] Thread overview: 16+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-26 23:50 [PATCH bpf v2 0/2] bpf: Fix refcount_acquire handling for borrowed kptrs Ning Ding 2026-07-26 23:50 ` [PATCH bpf v2 1/2] bpf: Keep refcount_acquire nullable for borrowed RCU kptrs Ning Ding 2026-07-27 4:50 ` Greg KH 2026-07-26 23:50 ` [PATCH bpf v2 2/2] bpf: Reject untrusted pointers in refcount_acquire Ning Ding 2026-07-27 0:13 ` sashiko-bot 2026-07-27 0:40 ` Ning Ding 2026-07-27 4:50 ` Greg KH 2026-08-02 23:29 ` Kumar Kartikeya Dwivedi 2026-08-03 5:57 ` Ning Ding 2026-08-03 6:08 ` Kumar Kartikeya Dwivedi 2026-08-03 6:48 ` Ning Ding 2026-08-03 6:50 ` Kumar Kartikeya Dwivedi 2026-08-03 7:10 ` Ning Ding 2026-08-03 7:21 ` Kumar Kartikeya Dwivedi 2026-08-03 7:44 ` Ning Ding 2026-08-03 7:48 ` Kumar Kartikeya Dwivedi
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).