* [PATCH bpf-next v2 0/2] bpf: Invalidate RCU pointers after final spin unlock
@ 2026-08-03 11:26 Ning Ding
2026-08-03 11:26 ` [PATCH bpf-next v2 1/2] " Ning Ding
` (3 more replies)
0 siblings, 4 replies; 6+ messages in thread
From: Ning Ding @ 2026-08-03 11:26 UTC (permalink / raw)
To: bpf; +Cc: Ning Ding
In a sleepable BPF program, a spin lock can provide the only RCU protection
for a kptr. The final spin unlock ends that protection, but the verifier
leaves the pointer valid. Another CPU can then free the object before the
pointer is used. A capability-limited runtime PoC triggered a
KASAN-confirmed task_struct use-after-free.
Patch 1 invalidates RCU-protected pointers only when an unlock leaves the
final RCU-protected context. Patch 2 adds a negative sleepable test and
positive controls for non-sleepable and explicit-RCU contexts.
Testing used fresh QEMU/KVM guests with KASAN enabled. The patched focused
test passed all three expected outcomes. The full task_kfunc test passed
all 39 subtests, and the selected RCU, refcount, and spin-lock group had no
failures.
---
v2:
- Rebase onto bpf-next commit 60781269e26c.
- Target bpf-next and split the fix from its selftests, as requested.
- Add positive controls for RCU contexts that remain valid after unlock.
v1: https://lore.kernel.org/r/20260802231248.2781334-1-dingning04@gmail.com
Ning Ding (2):
bpf: Invalidate RCU pointers after final spin unlock
selftests/bpf: Test RCU pointer invalidation after spin unlock
kernel/bpf/verifier.c | 5 ++
.../selftests/bpf/prog_tests/task_kfunc.c | 2 +
.../selftests/bpf/progs/task_kfunc_common.h | 12 +++++
.../selftests/bpf/progs/task_kfunc_failure.c | 24 ++++++++++
.../selftests/bpf/progs/task_kfunc_success.c | 48 +++++++++++++++++++
5 files changed, 91 insertions(+)
base-commit: 60781269e26c786de2bb93fb1e697a5c32ccee48
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH bpf-next v2 1/2] bpf: Invalidate RCU pointers after final spin unlock
2026-08-03 11:26 [PATCH bpf-next v2 0/2] bpf: Invalidate RCU pointers after final spin unlock Ning Ding
@ 2026-08-03 11:26 ` Ning Ding
2026-08-04 9:30 ` Kumar Kartikeya Dwivedi
2026-08-03 11:26 ` [PATCH bpf-next v2 2/2] selftests/bpf: Test RCU pointer invalidation after " Ning Ding
` (2 subsequent siblings)
3 siblings, 1 reply; 6+ messages in thread
From: Ning Ding @ 2026-08-03 11:26 UTC (permalink / raw)
To: bpf
Cc: Ning Ding, Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, Dave Marchevsky, linux-kernel
In a sleepable BPF program, a spin lock can provide the only RCU protection
for a kptr. The final bpf_spin_unlock() ends that protection, but the
verifier leaves the pointer valid. Another CPU can then free the object
before the pointer is used. A capability-limited runtime PoC triggered a
task_struct use-after-free in __bpf_get_task_stack().
Record whether the program is in an RCU-protected context before releasing
the lock. Invalidate RCU-protected pointers only when the unlock leaves the
final such context. This preserves valid pointers in non-sleepable programs
and inside an explicit RCU read-side section.
Fixes: 5861d1e8dbc4 ("bpf: Allow bpf_spin_{lock,unlock} in sleepable progs")
Assisted-by: Codex:gpt-5.6-sol
Assisted-by: ChatGPT:GPT-5.6-Pro
Signed-off-by: Ning Ding <dingning04@gmail.com>
---
kernel/bpf/verifier.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index b274004fccfd9..7439afdc851a7 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -206,6 +206,7 @@ static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int par
static int release_reference_nomark(struct bpf_verifier_state *state, int id);
static int release_reference(struct bpf_verifier_env *env, int id);
static void invalidate_non_owning_refs(struct bpf_verifier_env *env);
+static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env);
static bool in_rbtree_lock_required_cb(struct bpf_verifier_env *env);
static bool is_tracing_prog_type(enum bpf_prog_type type);
static int ref_set_non_owning(struct bpf_verifier_env *env,
@@ -7165,6 +7166,7 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
return err;
}
} else {
+ bool was_in_rcu_cs;
void *ptr;
int type;
@@ -7192,10 +7194,13 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
verbose(env, "%s_unlock cannot be out of order\n", lock_str);
return -EINVAL;
}
+ was_in_rcu_cs = in_rcu_cs(env);
if (release_lock_state(cur, type, reg->id, ptr)) {
verbose(env, "%s_unlock of different lock\n", lock_str);
return -EINVAL;
}
+ if (was_in_rcu_cs && !in_rcu_cs(env))
+ invalidate_rcu_protected_refs(env);
invalidate_non_owning_refs(env);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH bpf-next v2 2/2] selftests/bpf: Test RCU pointer invalidation after spin unlock
2026-08-03 11:26 [PATCH bpf-next v2 0/2] bpf: Invalidate RCU pointers after final spin unlock Ning Ding
2026-08-03 11:26 ` [PATCH bpf-next v2 1/2] " Ning Ding
@ 2026-08-03 11:26 ` Ning Ding
2026-08-04 9:40 ` [PATCH bpf-next v2 0/2] bpf: Invalidate RCU pointers after final " patchwork-bot+netdevbpf
2026-08-04 10:50 ` Puranjay Mohan
3 siblings, 0 replies; 6+ messages in thread
From: Ning Ding @ 2026-08-03 11:26 UTC (permalink / raw)
To: bpf
Cc: Ning Ding, Andrii Nakryiko, Eduard Zingerman, Ihor Solodrai,
Alexei Starovoitov, Daniel Borkmann, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Shuah Khan, Justin Suess, Amery Hung,
linux-kselftest, linux-kernel
The verifier previously accepted a task kptr after the final spin unlock
ended its RCU protection in a sleepable program. The pointer could then be
used after the task was freed.
Add a negative test for that case. Add positive controls showing that the
pointer remains valid in a non-sleepable program and while an explicit RCU
read-side section is still active.
Assisted-by: Codex:gpt-5.6-sol
Assisted-by: ChatGPT:GPT-5.6-Pro
Signed-off-by: Ning Ding <dingning04@gmail.com>
---
.../selftests/bpf/prog_tests/task_kfunc.c | 2 +
.../selftests/bpf/progs/task_kfunc_common.h | 12 +++++
.../selftests/bpf/progs/task_kfunc_failure.c | 24 ++++++++++
.../selftests/bpf/progs/task_kfunc_success.c | 48 +++++++++++++++++++
4 files changed, 86 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/task_kfunc.c b/tools/testing/selftests/bpf/prog_tests/task_kfunc.c
index e6e95c1416e65..fbd7855712c1a 100644
--- a/tools/testing/selftests/bpf/prog_tests/task_kfunc.c
+++ b/tools/testing/selftests/bpf/prog_tests/task_kfunc.c
@@ -176,6 +176,8 @@ static const char * const success_tests[] = {
"test_task_from_pid_current",
"test_task_from_pid_invalid",
"task_kfunc_acquire_trusted_walked",
+ "task_kfunc_acquire_after_spin_unlock_non_sleepable",
+ "task_kfunc_acquire_after_spin_unlock_explicit_rcu",
"test_task_kfunc_flavor_relo",
"test_task_kfunc_flavor_relo_not_found",
};
diff --git a/tools/testing/selftests/bpf/progs/task_kfunc_common.h b/tools/testing/selftests/bpf/progs/task_kfunc_common.h
index e9c4fea7a4bba..052c9d0e3e2a8 100644
--- a/tools/testing/selftests/bpf/progs/task_kfunc_common.h
+++ b/tools/testing/selftests/bpf/progs/task_kfunc_common.h
@@ -20,6 +20,18 @@ struct {
__uint(max_entries, 1);
} __tasks_kfunc_map SEC(".maps");
+struct task_kptr_lock_value {
+ struct bpf_spin_lock lock;
+ struct task_struct __kptr * task;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, int);
+ __type(value, struct task_kptr_lock_value);
+ __uint(max_entries, 1);
+} task_kptr_lock_map SEC(".maps");
+
struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym;
void bpf_task_release(struct task_struct *p) __ksym;
struct task_struct *bpf_task_from_pid(s32 pid) __ksym;
diff --git a/tools/testing/selftests/bpf/progs/task_kfunc_failure.c b/tools/testing/selftests/bpf/progs/task_kfunc_failure.c
index 5c99b1e6532bf..c0e7216b34193 100644
--- a/tools/testing/selftests/bpf/progs/task_kfunc_failure.c
+++ b/tools/testing/selftests/bpf/progs/task_kfunc_failure.c
@@ -378,3 +378,27 @@ int BPF_PROG(task_kfunc_release_in_map, struct task_struct *task, u64 clone_flag
return 0;
}
+
+SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
+__failure __msg("R1 must be a rcu pointer")
+int BPF_PROG(task_kfunc_acquire_after_final_spin_unlock)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_spin_lock(&v->lock);
+ task = v->task;
+ bpf_spin_unlock(&v->lock);
+ if (!task)
+ return 0;
+
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/task_kfunc_success.c b/tools/testing/selftests/bpf/progs/task_kfunc_success.c
index d63a79ee33dce..2bab7634c9dfd 100644
--- a/tools/testing/selftests/bpf/progs/task_kfunc_success.c
+++ b/tools/testing/selftests/bpf/progs/task_kfunc_success.c
@@ -6,6 +6,7 @@
#include <bpf/bpf_helpers.h>
#include "../bpf_experimental.h"
+#include "bpf_misc.h"
#include "task_kfunc_common.h"
char _license[] SEC("license") = "GPL";
@@ -366,6 +367,53 @@ int BPF_PROG(task_kfunc_acquire_trusted_walked, struct task_struct *task, u64 cl
return 0;
}
+SEC("fentry/" SYS_PREFIX "sys_getpgid")
+int BPF_PROG(task_kfunc_acquire_after_spin_unlock_non_sleepable)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_spin_lock(&v->lock);
+ task = v->task;
+ bpf_spin_unlock(&v->lock);
+ if (!task)
+ return 0;
+
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ return 0;
+}
+
+SEC("fentry.s/" SYS_PREFIX "sys_getpgid")
+int BPF_PROG(task_kfunc_acquire_after_spin_unlock_explicit_rcu)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_rcu_read_lock();
+ bpf_spin_lock(&v->lock);
+ task = v->task;
+ bpf_spin_unlock(&v->lock);
+ if (task) {
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ }
+ bpf_rcu_read_unlock();
+ return 0;
+}
+
SEC("syscall")
int test_task_from_vpid_current(const void *ctx)
{
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH bpf-next v2 1/2] bpf: Invalidate RCU pointers after final spin unlock
2026-08-03 11:26 ` [PATCH bpf-next v2 1/2] " Ning Ding
@ 2026-08-04 9:30 ` Kumar Kartikeya Dwivedi
0 siblings, 0 replies; 6+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-04 9:30 UTC (permalink / raw)
To: Ning Ding, bpf
Cc: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
Dave Marchevsky, linux-kernel
On Mon Aug 3, 2026 at 1:26 PM CEST, Ning Ding wrote:
> In a sleepable BPF program, a spin lock can provide the only RCU protection
> for a kptr. The final bpf_spin_unlock() ends that protection, but the
> verifier leaves the pointer valid. Another CPU can then free the object
> before the pointer is used. A capability-limited runtime PoC triggered a
> task_struct use-after-free in __bpf_get_task_stack().
>
> Record whether the program is in an RCU-protected context before releasing
> the lock. Invalidate RCU-protected pointers only when the unlock leaves the
> final such context. This preserves valid pointers in non-sleepable programs
> and inside an explicit RCU read-side section.
>
> Fixes: 5861d1e8dbc4 ("bpf: Allow bpf_spin_{lock,unlock} in sleepable progs")
> Assisted-by: Codex:gpt-5.6-sol
> Assisted-by: ChatGPT:GPT-5.6-Pro
> Signed-off-by: Ning Ding <dingning04@gmail.com>
> ---
This patch set makes sense. I think while we are at it, we should improve the
behavior here.
For RCU unlock, we currently unconditionally invalidate refs when it hits zero,
even though we may have other sources of RCU protection. The only reason it
doesn't matter right now is probably because we cannot do a RCU read unlock
within a spin lock critical section.
We also do not currently consider bpf_preempt_disable() as providing RCU
protection, even though we should.
Could you extend the series with these changes? And corresponding tests where
possible? It might not be possible for all combinations, but we can test those
that are permitted.
Unlike the correctness fix in this change, these are mostly usability paper
cuts, so do not need a Fixes: tag.
Thanks!
> [...]
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH bpf-next v2 0/2] bpf: Invalidate RCU pointers after final spin unlock
2026-08-03 11:26 [PATCH bpf-next v2 0/2] bpf: Invalidate RCU pointers after final spin unlock Ning Ding
2026-08-03 11:26 ` [PATCH bpf-next v2 1/2] " Ning Ding
2026-08-03 11:26 ` [PATCH bpf-next v2 2/2] selftests/bpf: Test RCU pointer invalidation after " Ning Ding
@ 2026-08-04 9:40 ` patchwork-bot+netdevbpf
2026-08-04 10:50 ` Puranjay Mohan
3 siblings, 0 replies; 6+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-08-04 9:40 UTC (permalink / raw)
To: Ning Ding; +Cc: bpf
Hello:
This series was applied to bpf/bpf-next.git (master)
by Kumar Kartikeya Dwivedi <memxor@gmail.com>:
On Mon, 3 Aug 2026 04:26:07 -0700 you wrote:
> In a sleepable BPF program, a spin lock can provide the only RCU protection
> for a kptr. The final spin unlock ends that protection, but the verifier
> leaves the pointer valid. Another CPU can then free the object before the
> pointer is used. A capability-limited runtime PoC triggered a
> KASAN-confirmed task_struct use-after-free.
>
> Patch 1 invalidates RCU-protected pointers only when an unlock leaves the
> final RCU-protected context. Patch 2 adds a negative sleepable test and
> positive controls for non-sleepable and explicit-RCU contexts.
>
> [...]
Here is the summary with links:
- [bpf-next,v2,1/2] bpf: Invalidate RCU pointers after final spin unlock
https://git.kernel.org/bpf/bpf-next/c/180c7000712d
- [bpf-next,v2,2/2] selftests/bpf: Test RCU pointer invalidation after spin unlock
https://git.kernel.org/bpf/bpf-next/c/bb2df6fd891d
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH bpf-next v2 0/2] bpf: Invalidate RCU pointers after final spin unlock
2026-08-03 11:26 [PATCH bpf-next v2 0/2] bpf: Invalidate RCU pointers after final spin unlock Ning Ding
` (2 preceding siblings ...)
2026-08-04 9:40 ` [PATCH bpf-next v2 0/2] bpf: Invalidate RCU pointers after final " patchwork-bot+netdevbpf
@ 2026-08-04 10:50 ` Puranjay Mohan
3 siblings, 0 replies; 6+ messages in thread
From: Puranjay Mohan @ 2026-08-04 10:50 UTC (permalink / raw)
To: Ning Ding, bpf, Paul E. McKenney; +Cc: Ning Ding, Puranjay Mohan
Ning Ding <dingning04@gmail.com> writes:
> In a sleepable BPF program, a spin lock can provide the only RCU protection
> for a kptr. The final spin unlock ends that protection, but the verifier
> leaves the pointer valid. Another CPU can then free the object before the
> pointer is used. A capability-limited runtime PoC triggered a
> KASAN-confirmed task_struct use-after-free.
>
> Patch 1 invalidates RCU-protected pointers only when an unlock leaves the
> final RCU-protected context. Patch 2 adds a negative sleepable test and
> positive controls for non-sleepable and explicit-RCU contexts.
I think if we are doing this we should do it for all things that act as
an entry to a RCU read side critical section [1]:
1. Disabling preemption
2. Disabling bottom halves
3. Disabling interrupts
4. Acquiring a spin lock
As you are doing it for 4, can you also do it for 1 and 3? I don't think
BPF can disable bottom halves right now.
Paul, can you ack this? Let us know if there are more things that can
make a BPF program enter a read side critical section.
Thanks,
Puranjay
[1] https://docs.kernel.org/RCU/whatisRCU.html#:~:text=Note%20that%20anything,side%20critical%20sections.
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-08-04 10:50 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-03 11:26 [PATCH bpf-next v2 0/2] bpf: Invalidate RCU pointers after final spin unlock Ning Ding
2026-08-03 11:26 ` [PATCH bpf-next v2 1/2] " Ning Ding
2026-08-04 9:30 ` Kumar Kartikeya Dwivedi
2026-08-03 11:26 ` [PATCH bpf-next v2 2/2] selftests/bpf: Test RCU pointer invalidation after " Ning Ding
2026-08-04 9:40 ` [PATCH bpf-next v2 0/2] bpf: Invalidate RCU pointers after final " patchwork-bot+netdevbpf
2026-08-04 10:50 ` Puranjay Mohan
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox