* [PATCH bpf v3 2/4] selftests/bpf: Test refcount_acquire return nullability [not found] <20260803112218.3361213-1-dingning04@gmail.com> @ 2026-08-03 11:22 ` Ning Ding 2026-08-03 13:59 ` Amery Hung 2026-08-03 11:22 ` [PATCH bpf v3 4/4] selftests/bpf: Test untrusted allocated-object pointers Ning Ding 1 sibling, 1 reply; 4+ messages in thread From: Ning Ding @ 2026-08-03 11:22 UTC (permalink / raw) To: bpf Cc: memxor, greg, Ning Ding, Andrii Nakryiko, Eduard Zingerman, Alexei Starovoitov, Daniel Borkmann, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan, Viktor Malik, Justin Suess, Leon Hwang, Kaitao Cheng, Yiyang Chen, linux-kselftest, linux-kernel The verifier could accept an unchecked bpf_refcount_acquire() result for a borrowed RCU-loaded map kptr. If the call returns NULL, passing the result to bpf_obj_drop() can crash the kernel. Add tests showing that an owned input remains non-NULL, a checked borrowed result is accepted, and an unchecked borrowed result is rejected. Assisted-by: Codex:gpt-5.5 Assisted-by: ChatGPT:GPT-5.6-Thinking Signed-off-by: Ning Ding <dingning04@gmail.com> --- .../selftests/bpf/progs/refcounted_kptr.c | 61 +++++++++++++++++++ .../bpf/progs/refcounted_kptr_fail.c | 47 ++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr.c b/tools/testing/selftests/bpf/progs/refcounted_kptr.c index 61906f48025cc..fd35093285c0d 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 024ef2aae2008..acd3e81a39168 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] 4+ messages in thread
* Re: [PATCH bpf v3 2/4] selftests/bpf: Test refcount_acquire return nullability 2026-08-03 11:22 ` [PATCH bpf v3 2/4] selftests/bpf: Test refcount_acquire return nullability Ning Ding @ 2026-08-03 13:59 ` Amery Hung 2026-08-03 22:48 ` Ning Ding 0 siblings, 1 reply; 4+ messages in thread From: Amery Hung @ 2026-08-03 13:59 UTC (permalink / raw) To: Ning Ding Cc: bpf, memxor, greg, Andrii Nakryiko, Eduard Zingerman, Alexei Starovoitov, Daniel Borkmann, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan, Viktor Malik, Justin Suess, Leon Hwang, Kaitao Cheng, Yiyang Chen, linux-kselftest, linux-kernel On Mon, Aug 3, 2026 at 4:26 AM Ning Ding <dingning04@gmail.com> wrote: > > The verifier could accept an unchecked bpf_refcount_acquire() result for a > borrowed RCU-loaded map kptr. If the call returns NULL, passing the result > to bpf_obj_drop() can crash the kernel. > > Add tests showing that an owned input remains non-NULL, a checked borrowed > result is accepted, and an unchecked borrowed result is rejected. > > Assisted-by: Codex:gpt-5.5 > Assisted-by: ChatGPT:GPT-5.6-Thinking > Signed-off-by: Ning Ding <dingning04@gmail.com> > --- > .../selftests/bpf/progs/refcounted_kptr.c | 61 +++++++++++++++++++ > .../bpf/progs/refcounted_kptr_fail.c | 47 ++++++++++++++ > 2 files changed, 108 insertions(+) > > diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr.c b/tools/testing/selftests/bpf/progs/refcounted_kptr.c > index 61906f48025cc..fd35093285c0d 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 024ef2aae2008..acd3e81a39168 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); Could you explain the purpose of this chunk? Otherwise, it looks good to me. Reviewed-by: Amery Hung <ameryhung@gmail.com> > + > + 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 [flat|nested] 4+ messages in thread
* Re: [PATCH bpf v3 2/4] selftests/bpf: Test refcount_acquire return nullability 2026-08-03 13:59 ` Amery Hung @ 2026-08-03 22:48 ` Ning Ding 0 siblings, 0 replies; 4+ messages in thread From: Ning Ding @ 2026-08-03 22:48 UTC (permalink / raw) To: Amery Hung Cc: bpf, memxor, greg, Andrii Nakryiko, Eduard Zingerman, Alexei Starovoitov, Daniel Borkmann, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan, Viktor Malik, Justin Suess, Leon Hwang, Kaitao Cheng, Yiyang Chen, linux-kselftest, linux-kernel > > + tmp = bpf_obj_new(typeof(*tmp)); > > + if (!tmp) > > + return 3; > > + bpf_obj_drop(tmp); This dummy code creates a temporary node_refcount_only object and immediately releases it, the actual bug test starts afterward. Its purpose is compiler scaffolding for BTF: The dummy bpf_obj_new() forces Clang to emit the complete BTF definition of that type, including its bpf_refcount field. bpf_obj_drop() then releases the temporary object so it won't affect the actual bug checking code. If we remove the dummy block, Clang might not include the complete node_refcount_only description in BTF. The program could then fail during loading (e.g. while creating the map, since it can't validate the kptr's complete type, or during verification, since the verifier is not sure the object contains bpf_refcount field) before reaching the actual test. (whether it still works depends on the clang version) ^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH bpf v3 4/4] selftests/bpf: Test untrusted allocated-object pointers [not found] <20260803112218.3361213-1-dingning04@gmail.com> 2026-08-03 11:22 ` [PATCH bpf v3 2/4] selftests/bpf: Test refcount_acquire return nullability Ning Ding @ 2026-08-03 11:22 ` Ning Ding 1 sibling, 0 replies; 4+ messages in thread From: Ning Ding @ 2026-08-03 11:22 UTC (permalink / raw) To: bpf Cc: memxor, greg, Ning Ding, sashiko-bot, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan, Viktor Malik, Justin Suess, Kaitao Cheng, Leon Hwang, Yiyang Chen, linux-kselftest, linux-kernel The verifier previously allowed pointers used after RCU protection ended to reach bpf_refcount_acquire() and, for one object layout, a direct write. If the object was freed and reused, these operations could access stale memory. Add tests that keep BPF_PROBE_MEM reads accepted but reject reference acquisition and direct writes after RCU protection ends. Cover both tested object layouts. 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> --- .../selftests/bpf/progs/refcounted_kptr.c | 100 ++++++++++++++++++ .../bpf/progs/refcounted_kptr_fail.c | 27 +++++ 2 files changed, 127 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr.c b/tools/testing/selftests/bpf/progs/refcounted_kptr.c index fd35093285c0d..b70be8b52ff80 100644 --- a/tools/testing/selftests/bpf/progs/refcounted_kptr.c +++ b/tools/testing/selftests/bpf/progs/refcounted_kptr.c @@ -893,6 +893,106 @@ long refcount_acquire_rcu_map_kptr_null_checked(void *ctx) return 0; } +SEC("?tc") +__success +long map_kptr_read_after_rcu_unlock(void *ctx) +{ + struct map_value_refcount_only *mapval; + struct node_refcount_only *n; + int idx = 0; + + mapval = bpf_map_lookup_elem(&stashed_refcount_only, &idx); + if (!mapval) + return 0; + + bpf_rcu_read_lock(); + n = mapval->node; + if (!n) { + bpf_rcu_read_unlock(); + return 0; + } + bpf_rcu_read_unlock(); + + return n->key; +} + +SEC("?tc") +__failure __msg("is neither owning or non-owning ref") +long refcount_acquire_graph_after_rcu_unlock(void *ctx) +{ + struct map_value *mapval; + struct node_data *n, *m; + int idx = 0; + + mapval = bpf_map_lookup_elem(&stashed_nodes, &idx); + if (!mapval) + return 0; + + bpf_rcu_read_lock(); + n = mapval->node; + if (!n) { + bpf_rcu_read_unlock(); + return 0; + } + bpf_rcu_read_unlock(); + + m = bpf_refcount_acquire(n); + if (m) + bpf_obj_drop(m); + + return 0; +} + +SEC("?tc") +__failure __msg("only read is supported") +long graph_map_kptr_write_after_rcu_unlock(void *ctx) +{ + struct map_value *mapval; + struct node_data *n; + int idx = 0; + + mapval = bpf_map_lookup_elem(&stashed_nodes, &idx); + if (!mapval) + return 1; + + bpf_rcu_read_lock(); + n = mapval->node; + if (!n) { + bpf_rcu_read_unlock(); + return 2; + } + bpf_rcu_read_unlock(); + + n->key = 1; + return 0; +} + +SEC("?tc") +__success +long graph_map_kptr_read_after_spin_unlock(void *ctx) +{ + struct map_value *mapval; + struct node_data *n; + int idx = 0; + + mapval = bpf_map_lookup_elem(&stashed_nodes, &idx); + if (!mapval) + return 0; + + bpf_rcu_read_lock(); + n = mapval->node; + if (!n) { + bpf_rcu_read_unlock(); + return 0; + } + bpf_rcu_read_unlock(); + + bpf_spin_lock(&lock); + bpf_spin_unlock(&lock); + + return n->key; +} + 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 acd3e81a39168..3408f68ad444d 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 neither owning or non-owning ref") +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] 4+ messages in thread
end of thread, other threads:[~2026-08-03 22:48 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260803112218.3361213-1-dingning04@gmail.com>
2026-08-03 11:22 ` [PATCH bpf v3 2/4] selftests/bpf: Test refcount_acquire return nullability Ning Ding
2026-08-03 13:59 ` Amery Hung
2026-08-03 22:48 ` Ning Ding
2026-08-03 11:22 ` [PATCH bpf v3 4/4] selftests/bpf: Test untrusted allocated-object pointers Ning Ding
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox