* [PATCH bpf-next 1/2] bpf: Preserve nullable RCU pointer state on unlock
2026-06-20 15:17 [PATCH bpf-next 0/2] bpf: Preserve RCU pointer nullness after unlock Yiyang Chen
@ 2026-06-20 15:17 ` Yiyang Chen
2026-06-20 17:44 ` Alexei Starovoitov
2026-06-20 15:17 ` [PATCH bpf-next 2/2] selftests/bpf: Cover nullable RCU pointer use after unlock Yiyang Chen
1 sibling, 1 reply; 4+ messages in thread
From: Yiyang Chen @ 2026-06-20 15:17 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, bpf,
linux-kselftest, linux-kernel
bpf_rcu_read_unlock() converts RCU-protected verifier registers to
untrusted pointers so that programs cannot keep using RCU-trusted
references after the read-side critical section ends.
That conversion also clears PTR_MAYBE_NULL. For fields from the
BTF_TYPE_SAFE_RCU_OR_NULL allowlist, such as skb->sk, the verifier records
MEM_RCU | PTR_MAYBE_NULL while inside the RCU read-side critical section.
Clearing both flags on unlock drops the nullable state and allows a direct
post-unlock BTF member load without an explicit NULL check.
Only clear MEM_RCU during RCU unlock invalidation. Preserve PTR_MAYBE_NULL
so normal nullable-pointer checks reject direct access, while an explicit
NULL check can still refine the pointer before use.
Fixes: 30ee9821f943 ("bpf: Allowlist few fields similar to __rcu tag.")
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
kernel/bpf/verifier.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 2abc79dbf..e53c4bfe4 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -9001,7 +9001,7 @@ static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env)
bpf_for_each_reg_in_vstate_mask(env->cur_state, state, reg, stack, clear_mask, ({
if (reg->type & MEM_RCU) {
- reg->type &= ~(MEM_RCU | PTR_MAYBE_NULL);
+ reg->type &= ~MEM_RCU;
reg->type |= PTR_UNTRUSTED;
}
}));
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH bpf-next 2/2] selftests/bpf: Cover nullable RCU pointer use after unlock
2026-06-20 15:17 [PATCH bpf-next 0/2] bpf: Preserve RCU pointer nullness after unlock Yiyang Chen
2026-06-20 15:17 ` [PATCH bpf-next 1/2] bpf: Preserve nullable RCU pointer state on unlock Yiyang Chen
@ 2026-06-20 15:17 ` Yiyang Chen
1 sibling, 0 replies; 4+ messages in thread
From: Yiyang Chen @ 2026-06-20 15:17 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, bpf,
linux-kselftest, linux-kernel
Add coverage for nullable BTF pointers that are read under
bpf_rcu_read_lock() and then used after bpf_rcu_read_unlock().
The unchecked skb->sk dereference should be rejected because the pointer
can still be NULL after it loses MEM_RCU trust. The matched control
performs an explicit NULL check after unlock and should keep loading
successfully.
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
.../selftests/bpf/prog_tests/rcu_read_lock.c | 17 ++++++++++++++++
.../selftests/bpf/progs/rcu_read_lock.c | 20 +++++++++++++++++++
2 files changed, 37 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/rcu_read_lock.c b/tools/testing/selftests/bpf/prog_tests/rcu_read_lock.c
index 246eb259c..be0317a47 100644
--- a/tools/testing/selftests/bpf/prog_tests/rcu_read_lock.c
+++ b/tools/testing/selftests/bpf/prog_tests/rcu_read_lock.c
@@ -72,6 +72,20 @@ static void test_rcuptr_acquire(void)
rcu_read_lock__destroy(skel);
}
+static void test_rcuptr_null_check(void)
+{
+ struct rcu_read_lock *skel;
+
+ skel = rcu_read_lock__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+
+ bpf_program__set_autoload(skel->progs.rcu_null_check_after_unlock, true);
+ ASSERT_OK(rcu_read_lock__load(skel), "skel_load");
+
+ rcu_read_lock__destroy(skel);
+}
+
static const char * const inproper_region_tests[] = {
"miss_lock",
"no_lock",
@@ -113,6 +127,7 @@ static void test_inproper_region(void)
static const char * const rcuptr_misuse_tests[] = {
"task_untrusted_rcuptr",
"cross_rcu_region",
+ "rcu_null_deref_after_unlock",
};
static void test_rcuptr_misuse(void)
@@ -150,6 +165,8 @@ void test_rcu_read_lock(void)
test_success();
if (test__start_subtest("rcuptr_acquire"))
test_rcuptr_acquire();
+ if (test__start_subtest("rcuptr_null_check"))
+ test_rcuptr_null_check();
if (test__start_subtest("negative_tests_inproper_region"))
test_inproper_region();
if (test__start_subtest("negative_tests_rcuptr_misuse"))
diff --git a/tools/testing/selftests/bpf/progs/rcu_read_lock.c b/tools/testing/selftests/bpf/progs/rcu_read_lock.c
index b4e073168..b78542706 100644
--- a/tools/testing/selftests/bpf/progs/rcu_read_lock.c
+++ b/tools/testing/selftests/bpf/progs/rcu_read_lock.c
@@ -372,6 +372,26 @@ int cross_rcu_region(void *ctx)
return 0;
}
+SEC("?tp_btf/net_dev_queue")
+int BPF_PROG(rcu_null_check_after_unlock, struct sk_buff *skb)
+{
+ bpf_rcu_read_lock();
+ bpf_rcu_read_unlock();
+
+ if (!skb->sk)
+ return 0;
+ return skb->sk->__sk_common.skc_daddr;
+}
+
+SEC("?tp_btf/net_dev_queue")
+int BPF_PROG(rcu_null_deref_after_unlock, struct sk_buff *skb)
+{
+ bpf_rcu_read_lock();
+ bpf_rcu_read_unlock();
+
+ return skb->sk->__sk_common.skc_daddr;
+}
+
__noinline
static int static_subprog(void *ctx)
{
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread