#include <alloc/common.h>
#include <alloc/asan.h>
#include <alloc/buddy.h>
#include <alloc/bump.h>
#include <alloc/stack.h>
#include "selftest.h"
#ifdef BPF_ARENA_ASAN
#define ST_PAGES 64
#define ASAN_MAP_STATE(addr) \
do { \
bpf_printk("%s:%d ASAN %lx -> (val: %x gran: %x set: [%s])", \
__func__, __LINE__, addr, \
asan_shadow_value((addr)), ASAN_GRANULE(addr), \
asan_shadow_set((addr)) ? "yes" : "no"); \
} while (0)
#define ASAN_VALIDATE_ADDR(cond, addr) \
do { \
asm volatile("" ::: "memory"); \
if ((asan_violated != 0) != (cond)) { \
bpf_printk("%s:%d ASAN asan_violated %lx", __func__, \
__LINE__, (u64)asan_violated); \
ASAN_MAP_STATE((addr)); \
return -EINVAL; \
} \
asan_violated = 0; \
} while (0)
#define ASAN_VALIDATE() \
do { \
if ((asan_violated)) { \
bpf_printk("%s:%d Found ASAN violation at %lx", \
__func__, __LINE__, asan_violated); \
return -EINVAL; \
} \
} while (0)
struct blob {
volatile u8 mem[59];
u8 oob;
};
int asan_test_static_blob_one(void)
{
volatile struct blob __arena *blob;
const size_t alignment = 1;
blob = bump_alloc(sizeof(blob) - 1, alignment);
if (!blob)
return -ENOMEM;
blob->mem[0] = 0xba;
ASAN_VALIDATE_ADDR(false, &blob->mem[0]);
blob->oob = 0;
ASAN_VALIDATE_ADDR(true, &blob->oob);
blob = (volatile struct blob __arena *)&blob->oob;
blob->mem[0] = 0xba;
ASAN_VALIDATE_ADDR(true, &blob->mem[0]);
blob->oob = 4;
ASAN_VALIDATE_ADDR(true, &blob->oob);
blob = (volatile struct blob __arena *)&blob->oob;
blob->oob = 5;
ASAN_VALIDATE_ADDR(true, &blob->oob);
return 0;
}
int asan_test_static_blob(void)
{
const int iters = 20;
int ret, i;
ret = bump_init(ST_PAGES);
if (ret) {
bpf_printk("bump_init failed with %d", ret);
return ret;
}
for (i = 0; i < iters && can_loop; i++) {
ret = asan_test_static_blob_one();
if (ret) {
bpf_printk("%s:%d Failed on iteration %d", __func__,
__LINE__, i);
return ret;
}
}
bump_destroy();
ASAN_VALIDATE();
return 0;
}
int asan_test_static_array_one(void)
{
size_t bytes = 37;
size_t overrun = 13;
size_t alignment = 1;
char __arena *mem;
int i;
mem = bump_alloc(sizeof(*mem) * bytes, alignment);
if (!mem)
return -ENOMEM;
for (i = 0; i < bytes + overrun && can_loop; i++) {
mem[i] = 0xba;
ASAN_VALIDATE_ADDR(i >= bytes, &mem[i]);
}
ASAN_VALIDATE();
return 0;
}
int asan_test_static_array(void)
{
const size_t iters = 20;
int ret, i;
ret = bump_init(ST_PAGES);
if (ret) {
bpf_printk("bump_init failed with %d", ret);
return ret;
}
for (i = 0; i < iters && can_loop; i++) {
ret = asan_test_static_array_one();
if (ret) {
bpf_printk("%s:%d Failed on iteration %d", __func__,
__LINE__, i);
return ret;
}
}
bump_destroy();
return 0;
}
int asan_test_static_all(void)
{
const int iters = 50;
int ret, i;
ret = bump_init(ST_PAGES);
if (ret) {
bpf_printk("bump_init failed with %d", ret);
return ret;
}
for (i = 0; i < iters && can_loop; i++) {
ret = asan_test_static_array_one();
if (ret) {
bpf_printk("%s:%d Failed on iteration %d", __func__,
__LINE__, i);
return ret;
}
ret = asan_test_static_blob_one();
if (ret) {
bpf_printk("%s:%d Failed on iteration %d", __func__,
__LINE__, i);
return ret;
}
}
bump_destroy();
return 0;
}
#define STACK_PAGES_PER_ALLOC (4)
#define STACK_ALLOCS (4)
u64 __arena stk_blks[STACK_ALLOCS];
private(ST_STACK) struct stk st_stack;
u64 __arena st_asan_lock;
static __maybe_unused void stk_blks_dump(void)
{
int i;
for (i = 0; i < STACK_ALLOCS && can_loop; i++)
bpf_printk("[%d] 0x%lx", i, stk_blks[i]);
}
struct qsort_limits {
int lo;
int hi;
};
__always_inline void swap(unsigned int i, unsigned int j)
{
u64 tmp;
tmp = stk_blks[i];
stk_blks[i] = stk_blks[j];
stk_blks[j] = tmp;
}
__weak int qsort_partition(unsigned int lo, unsigned hi)
{
unsigned int i;
u64 pivotval;
int pivot;
if (lo >= STACK_ALLOCS || hi >= STACK_ALLOCS) {
bpf_printk("%s:%d invalid lo/hi indices %d/%d", __func__,
__LINE__, lo, hi);
return 0;
}
pivotval = stk_blks[hi];
pivot = lo;
for (i = lo; i < hi && can_loop; i++) {
if (stk_blks[i] > pivotval)
continue;
swap(i, pivot);
pivot += 1;
}
swap(pivot, hi);
return pivot;
}
__weak int qsort_stack_blocks(void)
{
struct qsort_limits stack[STACK_ALLOCS];
struct qsort_limits limits;
int stackind = 0;
int pivot;
limits = (struct qsort_limits){ 0, STACK_ALLOCS - 1 };
stack[stackind++] = limits;
while (stackind > 0 && can_loop) {
if (stackind <= 0 || stackind > STACK_ALLOCS) {
bpf_printk("%s:%d invalid stack index %d", __func__,
__LINE__, stackind);
return 0;
}
limits = stack[--stackind];
if (limits.lo >= limits.hi)
continue;
pivot = qsort_partition(limits.lo, limits.hi);
stack[stackind++] = (struct qsort_limits){
.lo = limits.lo,
.hi = pivot - 1,
};
if (stackind <= 0 || stackind >= STACK_ALLOCS) {
bpf_printk("%s:%d invalid stack index", __func__,
__LINE__);
return 0;
}
stack[stackind++] = (struct qsort_limits){
.lo = pivot + 1,
.hi = limits.hi,
};
}
return 0;
}
int asan_test_stack_uaf_oob_single(u8 __arena __arg_arena *alloced,
u8 __arena __arg_arena *freed)
{
const size_t overshoot = 5;
int i;
stk_free(&st_stack, freed);
bpf_for(i, 0, PAGE_SIZE) {
freed[i] = 0xba;
ASAN_VALIDATE_ADDR(true, &freed[i]);
}
bpf_for(i, 0, PAGE_SIZE + overshoot) {
alloced[i] = 0xba;
ASAN_VALIDATE_ADDR(i >= PAGE_SIZE, &alloced[i]);
}
return 0;
}
static int asan_sort_stack_blocks()
{
int i;
qsort_stack_blocks();
if (!stk_blks[0]) {
bpf_printk("NULL stack block pointer");
return -EINVAL;
}
for (i = 1; i < STACK_ALLOCS; i++) {
if (!stk_blks[i]) {
bpf_printk("missing block");
return -EINVAL;
}
if (stk_blks[i] != stk_blks[i - 1] + PAGE_SIZE) {
bpf_printk("allocations not consecutive");
return -EINVAL;
}
}
return 0;
}
__weak int asan_test_stack_uaf_oob(void)
{
u64 base = (u64)(-1);
const u64 alloc_size = 4096;
u64 block;
int ret, i;
ret = stk_init(&st_stack, (arena_spinlock_t __arena *)&st_asan_lock,
alloc_size, STACK_PAGES_PER_ALLOC);
if (ret) {
bpf_printk("stk_init failed with %d", ret);
return ret;
}
bpf_for(i, 0, STACK_ALLOCS) {
block = (u64)stk_alloc(&st_stack);
if (!block) {
bpf_printk("allocation %d failed", i);
return -ENOMEM;
}
stk_blks[i] = block;
base = block < base ? block : base;
}
ret = asan_sort_stack_blocks();
if (ret)
return ret;
for (i = 0; i < STACK_ALLOCS && can_loop; i += 2) {
if (i + 1 >= STACK_ALLOCS)
break;
if (stk_blks[i] + alloc_size != stk_blks[i + 1]) {
bpf_printk("Stack allocations not consecutive");
return -EINVAL;
}
ret = asan_test_stack_uaf_oob_single(
(u8 __arena *)stk_blks[i],
(u8 __arena *)stk_blks[i + 1]);
if (ret)
return ret;
}
stk_destroy(&st_stack);
return 0;
}
int asan_test_stack(void)
{
int ret;
ret = asan_test_stack_uaf_oob();
if (ret) {
bpf_printk("%s:%d test failed", __func__, __LINE__);
return ret;
}
return 0;
}
int asan_test_static(void)
{
int ret;
ret = asan_test_static_blob();
if (ret) {
bpf_printk("%s:%d test failed", __func__, __LINE__);
return ret;
}
ret = asan_test_static_array();
if (ret) {
bpf_printk("%s:%d test failed", __func__, __LINE__);
return ret;
}
ret = asan_test_static_all();
if (ret) {
bpf_printk("%s:%d test failed", __func__, __LINE__);
return ret;
}
return 0;
}
private(ST_BUDDY) struct buddy st_buddy_asan;
__weak int asan_test_buddy_oob_single(size_t alloc_size)
{
u8 __arena *mem;
int i;
ASAN_VALIDATE();
mem = buddy_alloc(&st_buddy_asan, alloc_size);
if (!mem) {
bpf_printk("buddy_alloc failed for size %lu", alloc_size);
return -ENOMEM;
}
ASAN_VALIDATE();
bpf_for(i, 0, alloc_size) {
mem[i] = 0xba;
ASAN_VALIDATE_ADDR(false, &mem[i]);
}
mem[alloc_size] = 0xba;
ASAN_VALIDATE_ADDR(true, &mem[alloc_size]);
buddy_free(&st_buddy_asan, mem);
return 0;
}
__weak int asan_test_buddy_uaf_single(size_t alloc_size)
{
u8 __arena *mem;
int i;
mem = buddy_alloc(&st_buddy_asan, alloc_size);
if (!mem) {
bpf_printk("buddy_alloc failed for size %lu", alloc_size);
return -ENOMEM;
}
ASAN_VALIDATE();
bpf_for(i, 0, alloc_size) {
mem[i] = 0xba;
ASAN_VALIDATE_ADDR(false, &mem[i]);
}
ASAN_VALIDATE();
buddy_free(&st_buddy_asan, mem);
bpf_for(i, 0, alloc_size) {
if (BUDDY_HEADER_OFF <= i &&
i < BUDDY_HEADER_OFF + sizeof(struct buddy_header))
continue;
mem[i] = 0xba;
ASAN_VALIDATE_ADDR(true, &mem[i]);
}
return 0;
}
struct buddy_blob {
volatile u8 mem[48];
u8 oob;
};
__weak int asan_test_buddy_blob_single(void)
{
volatile struct buddy_blob __arena *blob;
const size_t alloc_size = sizeof(struct buddy_blob) - 1;
blob = buddy_alloc(&st_buddy_asan, alloc_size);
if (!blob)
return -ENOMEM;
blob->mem[0] = 0xba;
ASAN_VALIDATE_ADDR(false, &blob->mem[0]);
blob->mem[47] = 0xba;
ASAN_VALIDATE_ADDR(false, &blob->mem[47]);
blob->oob = 0;
ASAN_VALIDATE_ADDR(true, &blob->oob);
buddy_free(&st_buddy_asan, (void __arena *)blob);
return 0;
}
__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(&st_buddy_asan,
(arena_spinlock_t __arena *)&st_asan_lock);
if (ret) {
bpf_printk("buddy_init failed with %d", ret);
return ret;
}
bpf_for(i, 0, 7) {
ret = asan_test_buddy_oob_single(sizes[i]);
if (ret) {
bpf_printk("%s:%d Failed for size %lu", __func__,
__LINE__, sizes[i]);
buddy_destroy(&st_buddy_asan);
return ret;
}
}
buddy_destroy(&st_buddy_asan);
ASAN_VALIDATE();
return 0;
}
__weak int asan_test_buddy_uaf(void)
{
size_t sizes[] = { 16, 32, 64, 128, 256, 512, 128, 1024, 16384 };
int ret, i;
ret = buddy_init(&st_buddy_asan,
(arena_spinlock_t __arena *)&st_asan_lock);
if (ret) {
bpf_printk("buddy_init failed with %d", ret);
return ret;
}
bpf_for(i, 0, 7) {
ret = asan_test_buddy_uaf_single(sizes[i]);
if (ret) {
bpf_printk("%s:%d Failed for size %lu", __func__,
__LINE__, sizes[i]);
buddy_destroy(&st_buddy_asan);
return ret;
}
}
buddy_destroy(&st_buddy_asan);
ASAN_VALIDATE();
return 0;
}
__weak int asan_test_buddy_blob(void)
{
const int iters = 10;
int ret, i;
ret = buddy_init(&st_buddy_asan,
(arena_spinlock_t __arena *)&st_asan_lock);
if (ret) {
bpf_printk("buddy_init failed with %d", ret);
return ret;
}
for (i = 0; i < iters && can_loop; i++) {
ret = asan_test_buddy_blob_single();
if (ret) {
bpf_printk("%s:%d Failed on iteration %d", __func__,
__LINE__, i);
buddy_destroy(&st_buddy_asan);
return ret;
}
}
buddy_destroy(&st_buddy_asan);
ASAN_VALIDATE();
return 0;
}
int asan_test_buddy(void)
{
int ret;
ret = asan_test_buddy_oob();
if (ret) {
bpf_printk("%s:%d OOB test failed", __func__, __LINE__);
return ret;
}
ret = asan_test_buddy_uaf();
if (ret) {
bpf_printk("%s:%d UAF test failed", __func__, __LINE__);
return ret;
}
ret = asan_test_buddy_blob();
if (ret) {
bpf_printk("%s:%d blob test failed", __func__, __LINE__);
return ret;
}
return 0;
}
SEC("syscall")
int asan_test(void)
{
int ret;
bpf_printk("ASAN tests starting...");
ret = asan_test_static();
if (ret)
return ret;
bpf_printk("STATIC test passed...");
ret = asan_test_stack();
if (ret)
return ret;
bpf_printk("STACK test passed...");
ret = asan_test_buddy();
if (ret)
return ret;
bpf_printk("BUDDY test passed...");
bpf_printk("ASAN tests successful.");
return 0;
}
#else
SEC("syscall")
int asan_test(void)
{
return -EOPNOTSUPP;
}
#endif