]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests: bpf: test non-sleepable arena allocations
authorPuranjay Mohan <puranjay@kernel.org>
Mon, 22 Dec 2025 19:50:19 +0000 (11:50 -0800)
committerAlexei Starovoitov <ast@kernel.org>
Tue, 23 Dec 2025 19:30:00 +0000 (11:30 -0800)
As arena kfuncs can now be called from non-sleepable contexts, test this
by adding non-sleepable copies of tests in verifier_arena, this is done
by using a socket program instead of syscall.

Add a new test case in verifier_arena_large to check that the
bpf_arena_alloc_pages() works for more than 1024 pages.
1024 * sizeof(struct page *) is the upper limit of kmalloc_nolock() but
bpf_arena_alloc_pages() should still succeed because it re-uses this
array in a loop.

Augment the arena_list selftest to also run in non-sleepable context by
taking rcu_read_lock.

Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Link: https://lore.kernel.org/r/20251222195022.431211-5-puranjay@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
tools/testing/selftests/bpf/prog_tests/arena_list.c
tools/testing/selftests/bpf/progs/arena_list.c
tools/testing/selftests/bpf/progs/verifier_arena.c
tools/testing/selftests/bpf/progs/verifier_arena_large.c

index d15867cddde06ab5c49bfdca8df1c1f69d0931d9..4f2866a615ce520a8cd56201bd74a366a711b56b 100644 (file)
@@ -27,17 +27,23 @@ static int list_sum(struct arena_list_head *head)
        return sum;
 }
 
-static void test_arena_list_add_del(int cnt)
+static void test_arena_list_add_del(int cnt, bool nonsleepable)
 {
        LIBBPF_OPTS(bpf_test_run_opts, opts);
        struct arena_list *skel;
        int expected_sum = (u64)cnt * (cnt - 1) / 2;
        int ret, sum;
 
-       skel = arena_list__open_and_load();
-       if (!ASSERT_OK_PTR(skel, "arena_list__open_and_load"))
+       skel = arena_list__open();
+       if (!ASSERT_OK_PTR(skel, "arena_list__open"))
                return;
 
+       skel->rodata->nonsleepable = nonsleepable;
+
+       ret = arena_list__load(skel);
+       if (!ASSERT_OK(ret, "arena_list__load"))
+               goto out;
+
        skel->bss->cnt = cnt;
        ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.arena_list_add), &opts);
        ASSERT_OK(ret, "ret_add");
@@ -65,7 +71,11 @@ out:
 void test_arena_list(void)
 {
        if (test__start_subtest("arena_list_1"))
-               test_arena_list_add_del(1);
+               test_arena_list_add_del(1, false);
        if (test__start_subtest("arena_list_1000"))
-               test_arena_list_add_del(1000);
+               test_arena_list_add_del(1000, false);
+       if (test__start_subtest("arena_list_1_nonsleepable"))
+               test_arena_list_add_del(1, true);
+       if (test__start_subtest("arena_list_1000_nonsleepable"))
+               test_arena_list_add_del(1000, true);
 }
index 3a2ddcacbea6aee4b84253253fd1a85ee76e5b82..235d8cc95bdda1b65a78205c1450538ab598fd12 100644 (file)
@@ -30,6 +30,7 @@ struct arena_list_head __arena *list_head;
 int list_sum;
 int cnt;
 bool skip = false;
+const volatile bool nonsleepable = false;
 
 #ifdef __BPF_FEATURE_ADDR_SPACE_CAST
 long __arena arena_sum;
@@ -42,6 +43,9 @@ int test_val SEC(".addr_space.1");
 
 int zero;
 
+void bpf_rcu_read_lock(void) __ksym;
+void bpf_rcu_read_unlock(void) __ksym;
+
 SEC("syscall")
 int arena_list_add(void *ctx)
 {
@@ -71,6 +75,10 @@ int arena_list_del(void *ctx)
        struct elem __arena *n;
        int sum = 0;
 
+       /* Take rcu_read_lock to test non-sleepable context */
+       if (nonsleepable)
+               bpf_rcu_read_lock();
+
        arena_sum = 0;
        list_for_each_entry(n, list_head, node) {
                sum += n->value;
@@ -79,6 +87,9 @@ int arena_list_del(void *ctx)
                bpf_free(n);
        }
        list_sum = sum;
+
+       if (nonsleepable)
+               bpf_rcu_read_unlock();
 #else
        skip = true;
 #endif
index 7f4827eede3c513e766eb581f0a1d942887c5583..4a9d96344813711a2009cfbb374570e440458be2 100644 (file)
@@ -21,6 +21,37 @@ struct {
 #endif
 } arena SEC(".maps");
 
+SEC("socket")
+__success __retval(0)
+int basic_alloc1_nosleep(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+       volatile int __arena *page1, *page2, *no_page;
+
+       page1 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+       if (!page1)
+               return 1;
+       *page1 = 1;
+       page2 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+       if (!page2)
+               return 2;
+       *page2 = 2;
+       no_page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+       if (no_page)
+               return 3;
+       if (*page1 != 1)
+               return 4;
+       if (*page2 != 2)
+               return 5;
+       bpf_arena_free_pages(&arena, (void __arena *)page2, 1);
+       if (*page1 != 1)
+               return 6;
+       if (*page2 != 0 && *page2 != 2) /* use-after-free should return 0 or the stored value */
+               return 7;
+#endif
+       return 0;
+}
+
 SEC("syscall")
 __success __retval(0)
 int basic_alloc1(void *ctx)
@@ -60,6 +91,44 @@ int basic_alloc1(void *ctx)
        return 0;
 }
 
+SEC("socket")
+__success __retval(0)
+int basic_alloc2_nosleep(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+       volatile char __arena *page1, *page2, *page3, *page4;
+
+       page1 = bpf_arena_alloc_pages(&arena, NULL, 2, NUMA_NO_NODE, 0);
+       if (!page1)
+               return 1;
+       page2 = page1 + __PAGE_SIZE;
+       page3 = page1 + __PAGE_SIZE * 2;
+       page4 = page1 - __PAGE_SIZE;
+       *page1 = 1;
+       *page2 = 2;
+       *page3 = 3;
+       *page4 = 4;
+       if (*page1 != 1)
+               return 1;
+       if (*page2 != 2)
+               return 2;
+       if (*page3 != 0)
+               return 3;
+       if (*page4 != 0)
+               return 4;
+       bpf_arena_free_pages(&arena, (void __arena *)page1, 2);
+       if (*page1 != 0 && *page1 != 1)
+               return 5;
+       if (*page2 != 0 && *page2 != 2)
+               return 6;
+       if (*page3 != 0)
+               return 7;
+       if (*page4 != 0)
+               return 8;
+#endif
+       return 0;
+}
+
 SEC("syscall")
 __success __retval(0)
 int basic_alloc2(void *ctx)
@@ -102,6 +171,19 @@ struct bpf_arena___l {
         struct bpf_map map;
 } __attribute__((preserve_access_index));
 
+SEC("socket")
+__success __retval(0) __log_level(2)
+int basic_alloc3_nosleep(void *ctx)
+{
+       struct bpf_arena___l *ar = (struct bpf_arena___l *)&arena;
+       volatile char __arena *pages;
+
+       pages = bpf_arena_alloc_pages(&ar->map, NULL, ar->map.max_entries, NUMA_NO_NODE, 0);
+       if (!pages)
+               return 1;
+       return 0;
+}
+
 SEC("syscall")
 __success __retval(0) __log_level(2)
 int basic_alloc3(void *ctx)
@@ -115,6 +197,38 @@ int basic_alloc3(void *ctx)
        return 0;
 }
 
+SEC("socket")
+__success __retval(0)
+int basic_reserve1_nosleep(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+       char __arena *page;
+       int ret;
+
+       page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+       if (!page)
+               return 1;
+
+       page += __PAGE_SIZE;
+
+       /* Reserve the second page */
+       ret = bpf_arena_reserve_pages(&arena, page, 1);
+       if (ret)
+               return 2;
+
+       /* Try to explicitly allocate the reserved page. */
+       page = bpf_arena_alloc_pages(&arena, page, 1, NUMA_NO_NODE, 0);
+       if (page)
+               return 3;
+
+       /* Try to implicitly allocate the page (since there's only 2 of them). */
+       page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+       if (page)
+               return 4;
+#endif
+       return 0;
+}
+
 SEC("syscall")
 __success __retval(0)
 int basic_reserve1(void *ctx)
@@ -147,6 +261,26 @@ int basic_reserve1(void *ctx)
        return 0;
 }
 
+SEC("socket")
+__success __retval(0)
+int basic_reserve2_nosleep(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+       char __arena *page;
+       int ret;
+
+       page = arena_base(&arena);
+       ret = bpf_arena_reserve_pages(&arena, page, 1);
+       if (ret)
+               return 1;
+
+       page = bpf_arena_alloc_pages(&arena, page, 1, NUMA_NO_NODE, 0);
+       if ((u64)page)
+               return 2;
+#endif
+       return 0;
+}
+
 SEC("syscall")
 __success __retval(0)
 int basic_reserve2(void *ctx)
@@ -168,6 +302,27 @@ int basic_reserve2(void *ctx)
 }
 
 /* Reserve the same page twice, should return -EBUSY. */
+SEC("socket")
+__success __retval(0)
+int reserve_twice_nosleep(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+       char __arena *page;
+       int ret;
+
+       page = arena_base(&arena);
+
+       ret = bpf_arena_reserve_pages(&arena, page, 1);
+       if (ret)
+               return 1;
+
+       ret = bpf_arena_reserve_pages(&arena, page, 1);
+       if (ret != -EBUSY)
+               return 2;
+#endif
+       return 0;
+}
+
 SEC("syscall")
 __success __retval(0)
 int reserve_twice(void *ctx)
@@ -190,6 +345,36 @@ int reserve_twice(void *ctx)
 }
 
 /* Try to reserve past the end of the arena. */
+SEC("socket")
+__success __retval(0)
+int reserve_invalid_region_nosleep(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+       char __arena *page;
+       int ret;
+
+       /* Try a NULL pointer. */
+       ret = bpf_arena_reserve_pages(&arena, NULL, 3);
+       if (ret != -EINVAL)
+               return 1;
+
+       page = arena_base(&arena);
+
+       ret = bpf_arena_reserve_pages(&arena, page, 3);
+       if (ret != -EINVAL)
+               return 2;
+
+       ret = bpf_arena_reserve_pages(&arena, page, 4096);
+       if (ret != -EINVAL)
+               return 3;
+
+       ret = bpf_arena_reserve_pages(&arena, page, (1ULL << 32) - 1);
+       if (ret != -EINVAL)
+               return 4;
+#endif
+       return 0;
+}
+
 SEC("syscall")
 __success __retval(0)
 int reserve_invalid_region(void *ctx)
index 2b8cf2a4d8808586d09c4e6cc32e60cc977ff354..4ca491cbe8d15dbce029b04760d2f99b3a77b4cf 100644 (file)
@@ -283,5 +283,34 @@ int big_alloc2(void *ctx)
                return 9;
        return 0;
 }
+
+SEC("socket")
+__success __retval(0)
+int big_alloc3(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+       char __arena *pages;
+       u64 i;
+
+       /*
+        * Allocate 2051 pages in one go to check how kmalloc_nolock() handles large requests.
+        * Since kmalloc_nolock() can allocate up to 1024 struct page * at a time, this call should
+        * result in three batches: two batches of 1024 pages each, followed by a final batch of 3
+        * pages.
+        */
+       pages = bpf_arena_alloc_pages(&arena, NULL, 2051, NUMA_NO_NODE, 0);
+       if (!pages)
+               return -1;
+
+       bpf_for(i, 0, 2051)
+                       pages[i * PAGE_SIZE] = 123;
+       bpf_for(i, 0, 2051)
+                       if (pages[i * PAGE_SIZE] != 123)
+                               return i;
+
+       bpf_arena_free_pages(&arena, pages, 2051);
+#endif
+       return 0;
+}
 #endif
 char _license[] SEC("license") = "GPL";