Commit efecc9e8 authored by Puranjay Mohan's avatar Puranjay Mohan Committed by Alexei Starovoitov
Browse files

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: default avatarPuranjay Mohan <puranjay@kernel.org>
Link: https://lore.kernel.org/r/20251222195022.431211-5-puranjay@kernel.org


Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parent b8467290
Loading
Loading
Loading
Loading
+15 −5
Original line number Diff line number Diff line
@@ -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);
}
+11 −0
Original line number Diff line number Diff line
@@ -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
+185 −0
Original line number Diff line number Diff line
@@ -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)
+29 −0
Original line number Diff line number Diff line
@@ -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";