selftests: bpf: test non-sleepable arena allocations

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>
This commit is contained in:
Puranjay Mohan 2025-12-22 11:50:19 -08:00 committed by Alexei Starovoitov
parent b8467290ed
commit efecc9e825
4 changed files with 240 additions and 5 deletions

View 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 @@ static void test_arena_list_add_del(int cnt)
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);
}

View 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

View 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)

View 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";