From 3c0ca9f9a98e6b8bbaea13c5e39613237d50fe47 Mon Sep 17 00:00:00 2001 From: Alexei Starovoitov Date: Tue, 27 Oct 2026 19:03:05 +0000 Subject: [PATCH] selftests/bpf: Avoid a page fault in rcu_reentry while the handler is armed The test arms a handler at rcu_segcblist_enqueue() for its thread. Then it deletes an element of a task storage map. The delete gets to rcu_segcblist_enqueue(), and the handler does a nested delete there. The handler fires at the first rcu_segcblist_enqueue() in the thread. A page fault can be the first: skel->bss->target_pid = tid the handler is armed the next insn is in a page that is not in memory asm_exc_page_fault filemap_fault v9fs_issue_read p9_req_put irq_work_run rcu_defer_drain rcu_segcblist_enqueue the handler deletes the element bpf_map_delete_elem() -ENOENT test_rcu_reentry:FAIL:boot_delete unexpected error: -2 (errno 2) The result depends on the layout of test_progs. In the binary that failed, test_rcu_reentry() crosses the end of a page immediately after the store. This binary fails each time when it is on 9p. A copy of the same file on ext4 passes. Move the store and the delete to a function. Call it one time without a pid and with a bad fd. This call brings the text and .bss into memory, and it does not arm the handler. Fixes: 0464f587c61a ("selftests/bpf: Add a call_srcu() re-entry reproducer") Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20261027190314.1440199-11-alexei.starovoitov@gmail.com --- .../selftests/bpf/prog_tests/rcu_reentry.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/rcu_reentry.c b/tools/testing/selftests/bpf/prog_tests/rcu_reentry.c index de23a14b3d40..10b4ea913921 100644 --- a/tools/testing/selftests/bpf/prog_tests/rcu_reentry.c +++ b/tools/testing/selftests/bpf/prog_tests/rcu_reentry.c @@ -23,10 +23,17 @@ static bool have_tree_srcu(void) return kallsyms_find("srcu_expedite_current", &addr) == 0; } +/* Arm the handler for this thread, then trigger call_rcu_tasks_trace(). */ +static noinline int arm_and_delete(struct rcu_reentry *skel, int pid, int map_fd, int *pidfd) +{ + skel->bss->target_pid = pid; + return bpf_map_delete_elem(map_fd, pidfd); +} + void test_rcu_reentry(void) { struct rcu_reentry *skel; - int err, pidfd = -1, map_fd; + int err, pidfd = -1, bad_fd = -1, map_fd; cpu_set_t set, old_set; bool affinity_saved; __u64 val = 1; @@ -63,9 +70,13 @@ void test_rcu_reentry(void) if (!ASSERT_OK(err, "boot_create")) goto restore; - /* Arm the handler for this thread, then trigger call_rcu_tasks_trace(). */ - skel->bss->target_pid = syscall(__NR_gettid); - err = bpf_map_delete_elem(map_fd, &pidfd); + /* + * The handler fires at the first rcu_segcblist_enqueue() in this thread, and it deletes + * the element. A page fault can get there too, when the file system reads the page. + * Go through the same code first, without a pid, to bring its text and .bss in. + */ + arm_and_delete(skel, 0, map_fd, &bad_fd); + err = arm_and_delete(skel, syscall(__NR_gettid), map_fd, &pidfd); if (!ASSERT_OK(err, "boot_delete")) goto restore;