[PATCH bpf-next 2/2] selftests/bpf: Cover nullable RCU pointer use after unlock
From: Yiyang Chen
Date: Sat Jun 20 2026 - 11:22:42 EST
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@xxxxxxxxxxxxxxxxxxxxx>
---
.../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