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: 0464f587c6 ("selftests/bpf: Add a call_srcu() re-entry reproducer")
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20261027190314.1440199-11-alexei.starovoitov@gmail.com
This commit is contained in:
Alexei Starovoitov 2026-10-27 19:03:05 +00:00
parent 2ad395a384
commit 3c0ca9f9a9
No known key found for this signature in database

View File

@ -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;