mirror of
https://github.com/torvalds/linux.git
synced 2026-07-28 01:55:51 +02:00
selftests/bpf: Add selftests for libarena buddy allocator
Introduce selftests for the buddy allocator with and without ASAN. Add the libarena selftests both to the libarena test runner and to test_progs, so that they are a) available when libarena is pulled as a standalone library, and b) exercised along with all other test programs in this directory. ASAN for libarena requires LLVM 22. Add logic in the top-level selftests Makefile to only compile the ASAN variant if the compiler supports it, otherwise skip the test. Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com> Link: https://lore.kernel.org/r/20260426190338.4615-8-emil@etsalapatis.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
parent
86426a28c5
commit
b1487dc1b1
|
|
@ -0,0 +1,240 @@
|
|||
// SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <libarena/common.h>
|
||||
#include <libarena/asan.h>
|
||||
#include <libarena/buddy.h>
|
||||
|
||||
extern buddy_t buddy;
|
||||
|
||||
#ifdef BPF_ARENA_ASAN
|
||||
|
||||
#include "st_asan_common.h"
|
||||
|
||||
static __always_inline int asan_test_buddy_oob_single(size_t alloc_size)
|
||||
{
|
||||
u8 __arena *mem;
|
||||
int ret, i;
|
||||
|
||||
ret = asan_validate();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
mem = buddy_alloc(&buddy, alloc_size);
|
||||
if (!mem) {
|
||||
arena_stdout("buddy_alloc failed for size %lu", alloc_size);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = asan_validate();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = zero; i < alloc_size && can_loop; i++) {
|
||||
mem[i] = 0xba;
|
||||
ret = asan_validate_addr(false, &mem[i]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
mem[alloc_size] = 0xba;
|
||||
ret = asan_validate_addr(true, &mem[alloc_size]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
buddy_free(&buddy, mem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Factored out because asan_validate_addr is complex enough to cause
|
||||
* verification failures if verified with the rest of asan_test_buddy_uaf_single.
|
||||
*/
|
||||
__weak int asan_test_buddy_byte(u8 __arena __arg_arena *mem, int i, bool freed)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* The header in freed blocks doesn't get poisoned. */
|
||||
if (freed && BUDDY_HEADER_OFF <= i &&
|
||||
i < BUDDY_HEADER_OFF + sizeof(struct buddy_header))
|
||||
return 0;
|
||||
|
||||
mem[i] = 0xba;
|
||||
ret = asan_validate_addr(freed, &mem[i]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
__weak int asan_test_buddy_uaf_single(size_t alloc_size)
|
||||
{
|
||||
u8 __arena *mem;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
mem = buddy_alloc(&buddy, alloc_size);
|
||||
if (!mem) {
|
||||
arena_stdout("buddy_alloc failed for size %lu", alloc_size);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = asan_validate();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = zero; i < alloc_size && can_loop; i++) {
|
||||
ret = asan_test_buddy_byte(mem, i, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = asan_validate();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
buddy_free(&buddy, mem);
|
||||
|
||||
for (i = zero; i < alloc_size && can_loop; i++) {
|
||||
ret = asan_test_buddy_byte(mem, i, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct buddy_blob {
|
||||
volatile u8 mem[48];
|
||||
u8 oob;
|
||||
};
|
||||
|
||||
static __always_inline int asan_test_buddy_blob_single(void)
|
||||
{
|
||||
volatile struct buddy_blob __arena *blob;
|
||||
const size_t alloc_size = sizeof(struct buddy_blob) - 1;
|
||||
int ret;
|
||||
|
||||
blob = buddy_alloc(&buddy, alloc_size);
|
||||
if (!blob)
|
||||
return -ENOMEM;
|
||||
|
||||
blob->mem[0] = 0xba;
|
||||
ret = asan_validate_addr(false, &blob->mem[0]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
blob->mem[47] = 0xba;
|
||||
ret = asan_validate_addr(false, &blob->mem[47]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
blob->oob = 0;
|
||||
ret = asan_validate_addr(true, &blob->oob);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
buddy_free(&buddy, (void __arena *)blob);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
__weak int asan_test_buddy_oob(void)
|
||||
{
|
||||
size_t sizes[] = {
|
||||
7, 8, 17, 18, 64, 256, 317, 512, 1024,
|
||||
};
|
||||
int ret, i;
|
||||
|
||||
ret = buddy_init(&buddy);
|
||||
if (ret) {
|
||||
arena_stdout("buddy_init failed with %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = zero; i < sizeof(sizes) / sizeof(sizes[0]) && can_loop; i++) {
|
||||
ret = asan_test_buddy_oob_single(sizes[i]);
|
||||
if (ret) {
|
||||
arena_stdout("%s:%d Failed for size %lu", __func__,
|
||||
__LINE__, sizes[i]);
|
||||
buddy_destroy(&buddy);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
buddy_destroy(&buddy);
|
||||
|
||||
ret = asan_validate();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
__weak int asan_test_buddy_uaf(void)
|
||||
{
|
||||
size_t sizes[] = { 16, 32, 64, 128, 256, 512, 1024, 16384 };
|
||||
int ret, i;
|
||||
|
||||
ret = buddy_init(&buddy);
|
||||
if (ret) {
|
||||
arena_stdout("buddy_init failed with %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = zero; i < sizeof(sizes) / sizeof(sizes[0]) && can_loop; i++) {
|
||||
ret = asan_test_buddy_uaf_single(sizes[i]);
|
||||
if (ret) {
|
||||
arena_stdout("%s:%d Failed for size %lu", __func__,
|
||||
__LINE__, sizes[i]);
|
||||
buddy_destroy(&buddy);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
buddy_destroy(&buddy);
|
||||
|
||||
ret = asan_validate();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
__weak int asan_test_buddy_blob(void)
|
||||
{
|
||||
const int iters = 10;
|
||||
int ret, i;
|
||||
|
||||
ret = buddy_init(&buddy);
|
||||
if (ret) {
|
||||
arena_stdout("buddy_init failed with %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = zero; i < iters && can_loop; i++) {
|
||||
ret = asan_test_buddy_blob_single();
|
||||
if (ret) {
|
||||
arena_stdout("%s:%d Failed on iteration %d", __func__,
|
||||
__LINE__, i);
|
||||
buddy_destroy(&buddy);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
buddy_destroy(&buddy);
|
||||
|
||||
ret = asan_validate();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
__weak char _license[] SEC("license") = "GPL";
|
||||
209
tools/testing/selftests/bpf/libarena/selftests/st_buddy.bpf.c
Normal file
209
tools/testing/selftests/bpf/libarena/selftests/st_buddy.bpf.c
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
// SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <libarena/common.h>
|
||||
|
||||
#include <libarena/asan.h>
|
||||
#include <libarena/buddy.h>
|
||||
|
||||
extern buddy_t buddy;
|
||||
|
||||
struct segarr_entry {
|
||||
u8 __arena *block;
|
||||
size_t sz;
|
||||
u8 poison;
|
||||
};
|
||||
|
||||
#define SEGARRLEN (512)
|
||||
static struct segarr_entry __arena segarr[SEGARRLEN];
|
||||
static void __arena *ptrs[17];
|
||||
size_t __arena alloc_sizes[] = { 3, 17, 1025, 129, 16350, 333, 9, 517 };
|
||||
size_t __arena alloc_multiple_sizes[] = { 3, 17, 1025, 129, 16350, 333, 9, 517, 2099 };
|
||||
size_t __arena alloc_free_sizes[] = { 3, 17, 64, 129, 256, 333, 512, 517 };
|
||||
size_t __arena alignment_sizes[] = { 1, 3, 7, 8, 9, 15, 16, 17, 31,
|
||||
32, 64, 100, 128, 255, 256, 512, 1000 };
|
||||
|
||||
SEC("syscall")
|
||||
__weak int test_buddy_create(void)
|
||||
{
|
||||
const int iters = 10;
|
||||
int ret, i;
|
||||
|
||||
for (i = zero; i < iters && can_loop; i++) {
|
||||
ret = buddy_init(&buddy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = buddy_destroy(&buddy);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
__weak int test_buddy_alloc(void)
|
||||
{
|
||||
void __arena *mem;
|
||||
int ret, i;
|
||||
|
||||
for (i = zero; i < 8 && can_loop; i++) {
|
||||
ret = buddy_init(&buddy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mem = buddy_alloc(&buddy, alloc_sizes[i]);
|
||||
if (!mem) {
|
||||
buddy_destroy(&buddy);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
buddy_destroy(&buddy);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
__weak int test_buddy_alloc_free(void)
|
||||
{
|
||||
const int iters = 800;
|
||||
void __arena *mem;
|
||||
int ret, i;
|
||||
|
||||
ret = buddy_init(&buddy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = zero; i < iters && can_loop; i++) {
|
||||
mem = buddy_alloc(&buddy, alloc_free_sizes[(i * 5) % 8]);
|
||||
if (!mem) {
|
||||
buddy_destroy(&buddy);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
buddy_free(&buddy, mem);
|
||||
}
|
||||
|
||||
buddy_destroy(&buddy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
__weak int test_buddy_alloc_multiple(void)
|
||||
{
|
||||
int ret, j;
|
||||
u32 i, idx;
|
||||
u8 __arena *mem;
|
||||
size_t sz;
|
||||
u8 poison;
|
||||
|
||||
ret = buddy_init(&buddy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Cycle through each size, allocating an entry in the
|
||||
* segarr. Continue for SEGARRLEN iterations. For every
|
||||
* allocation write down the size, use the current index
|
||||
* as a poison value, and log it with the pointer in the
|
||||
* segarr entry. Use the poison value to poison the entire
|
||||
* allocated memory according to the size given.
|
||||
*/
|
||||
for (i = zero; i < SEGARRLEN && can_loop; i++) {
|
||||
sz = alloc_multiple_sizes[i % 9];
|
||||
poison = (u8)i;
|
||||
|
||||
mem = buddy_alloc(&buddy, sz);
|
||||
if (!mem) {
|
||||
buddy_destroy(&buddy);
|
||||
arena_stdout("%s:%d", __func__, __LINE__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
segarr[i].block = mem;
|
||||
segarr[i].sz = sz;
|
||||
segarr[i].poison = poison;
|
||||
|
||||
for (j = zero; j < sz && can_loop; j++) {
|
||||
mem[j] = poison;
|
||||
if (mem[j] != poison) {
|
||||
buddy_destroy(&buddy);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Go to (i * 17) % SEGARRLEN, and free the block pointed to.
|
||||
* Before freeing, check all bytes have the poisoned value
|
||||
* corresponding to the element. If any values are unexpected,
|
||||
* return an error. Skip some elements to test destroying the
|
||||
* buddy allocator while data is still allocated.
|
||||
*/
|
||||
for (i = 10; i < SEGARRLEN && can_loop; i++) {
|
||||
idx = (i * 17) % SEGARRLEN;
|
||||
|
||||
mem = segarr[idx].block;
|
||||
sz = segarr[idx].sz;
|
||||
poison = segarr[idx].poison;
|
||||
|
||||
for (j = zero; j < sz && can_loop; j++) {
|
||||
if (mem[j] != poison) {
|
||||
buddy_destroy(&buddy);
|
||||
arena_stdout("%s:%d %lx %u vs %u", __func__,
|
||||
__LINE__, (uintptr_t)&mem[j],
|
||||
mem[j], poison);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
buddy_free(&buddy, mem);
|
||||
}
|
||||
|
||||
buddy_destroy(&buddy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
__weak int test_buddy_alignment(void)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
ret = buddy_init(&buddy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Allocate various sizes and check alignment */
|
||||
for (i = zero; i < 17 && can_loop; i++) {
|
||||
ptrs[i] = buddy_alloc(&buddy, alignment_sizes[i]);
|
||||
if (!ptrs[i]) {
|
||||
arena_stdout("alignment test: alloc failed for size %lu",
|
||||
alignment_sizes[i]);
|
||||
buddy_destroy(&buddy);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Check 8-byte alignment */
|
||||
if ((u64)ptrs[i] & 0x7) {
|
||||
arena_stdout(
|
||||
"alignment test: ptr %llx not 8-byte aligned (size %lu)",
|
||||
(u64)ptrs[i], alignment_sizes[i]);
|
||||
buddy_destroy(&buddy);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Free all allocations */
|
||||
for (i = zero; i < 17 && can_loop; i++)
|
||||
buddy_free(&buddy, ptrs[i]);
|
||||
|
||||
buddy_destroy(&buddy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
__weak char _license[] SEC("license") = "GPL";
|
||||
66
tools/testing/selftests/bpf/prog_tests/libarena.c
Normal file
66
tools/testing/selftests/bpf/prog_tests/libarena.c
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
#include <test_progs.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <libarena/common.h>
|
||||
#include <libarena/asan.h>
|
||||
#include <libarena/buddy.h>
|
||||
#include <libarena/userspace.h>
|
||||
|
||||
#include "libarena/libarena.skel.h"
|
||||
|
||||
static void run_libarena_test(struct libarena *skel, struct bpf_program *prog,
|
||||
const char *name)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!strstr(name, "test_buddy")) {
|
||||
ret = libarena_run_prog(bpf_program__fd(skel->progs.arena_buddy_reset));
|
||||
if (!ASSERT_OK(ret, "arena_buddy_reset"))
|
||||
return;
|
||||
}
|
||||
|
||||
ret = libarena_run_prog(bpf_program__fd(prog));
|
||||
|
||||
ASSERT_OK(ret, name);
|
||||
|
||||
}
|
||||
|
||||
void test_libarena(void)
|
||||
{
|
||||
struct arena_alloc_reserve_args args;
|
||||
struct libarena *skel;
|
||||
struct bpf_program *prog;
|
||||
int ret;
|
||||
|
||||
skel = libarena__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "open_and_load"))
|
||||
return;
|
||||
|
||||
ret = libarena__attach(skel);
|
||||
if (!ASSERT_OK(ret, "attach"))
|
||||
goto out;
|
||||
|
||||
args.nr_pages = ARENA_RESERVE_PAGES_DFL;
|
||||
|
||||
ret = libarena_run_prog_args(bpf_program__fd(skel->progs.arena_alloc_reserve),
|
||||
&args, sizeof(args));
|
||||
if (!ASSERT_OK(ret, "arena_alloc_reserve"))
|
||||
goto out;
|
||||
|
||||
bpf_object__for_each_program(prog, skel->obj) {
|
||||
const char *name = bpf_program__name(prog);
|
||||
|
||||
if (!libarena_is_test_prog(name))
|
||||
continue;
|
||||
|
||||
if (!test__start_subtest(name))
|
||||
continue;
|
||||
|
||||
run_libarena_test(skel, prog, name);
|
||||
}
|
||||
|
||||
out:
|
||||
libarena__destroy(skel);
|
||||
}
|
||||
91
tools/testing/selftests/bpf/prog_tests/libarena_asan.c
Normal file
91
tools/testing/selftests/bpf/prog_tests/libarena_asan.c
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause
|
||||
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
||||
#include <test_progs.h>
|
||||
|
||||
#ifdef HAS_BPF_ARENA_ASAN
|
||||
#include <unistd.h>
|
||||
|
||||
#include <libarena/common.h>
|
||||
#include <libarena/asan.h>
|
||||
#include <libarena/buddy.h>
|
||||
#include <libarena/userspace.h>
|
||||
|
||||
#include "libarena/libarena_asan.skel.h"
|
||||
|
||||
static void run_libarena_asan_test(struct libarena_asan *skel,
|
||||
struct bpf_program *prog, const char *name)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!strstr(name, "test_buddy")) {
|
||||
ret = libarena_run_prog(bpf_program__fd(skel->progs.arena_buddy_reset));
|
||||
if (!ASSERT_OK(ret, "arena_buddy_reset"))
|
||||
return;
|
||||
}
|
||||
|
||||
ret = libarena_run_prog(bpf_program__fd(prog));
|
||||
ASSERT_OK(ret, name);
|
||||
}
|
||||
|
||||
static void run_test(void)
|
||||
{
|
||||
struct arena_alloc_reserve_args args;
|
||||
struct libarena_asan *skel;
|
||||
struct bpf_program *prog;
|
||||
int ret;
|
||||
|
||||
skel = libarena_asan__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "open_and_load"))
|
||||
return;
|
||||
|
||||
ret = libarena_asan__attach(skel);
|
||||
if (!ASSERT_OK(ret, "attach"))
|
||||
goto out;
|
||||
|
||||
args.nr_pages = ARENA_RESERVE_PAGES_DFL;
|
||||
|
||||
ret = libarena_run_prog_args(bpf_program__fd(skel->progs.arena_alloc_reserve),
|
||||
&args, sizeof(args));
|
||||
if (!ASSERT_OK(ret, "arena_alloc_reserve"))
|
||||
goto out;
|
||||
|
||||
ret = libarena_asan_init(
|
||||
bpf_program__fd(skel->progs.arena_get_info),
|
||||
bpf_program__fd(skel->progs.asan_init),
|
||||
(1ULL << 32) / sysconf(_SC_PAGESIZE));
|
||||
if (!ASSERT_OK(ret, "libarena_asan_init"))
|
||||
goto out;
|
||||
|
||||
bpf_object__for_each_program(prog, skel->obj) {
|
||||
const char *name = bpf_program__name(prog);
|
||||
|
||||
if (!libarena_is_asan_test_prog(name))
|
||||
continue;
|
||||
|
||||
if (!test__start_subtest(name))
|
||||
continue;
|
||||
|
||||
run_libarena_asan_test(skel, prog, name);
|
||||
}
|
||||
|
||||
out:
|
||||
libarena_asan__destroy(skel);
|
||||
}
|
||||
|
||||
#endif /* HAS_BPF_ARENA_ASAN */
|
||||
|
||||
/*
|
||||
* Run the test depending on whether LLVM can compile arena ASAN
|
||||
* programs.
|
||||
*/
|
||||
void test_libarena_asan(void)
|
||||
{
|
||||
#ifdef HAS_BPF_ARENA_ASAN
|
||||
run_test();
|
||||
#else
|
||||
test__skip();
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user