Expand the arena library selftest infrastructure to support address sanitization. Add the compiler flags necessary to compile the library under ASAN when supported. Signed-off-by: Emil Tsalapatis (Meta) --- tools/testing/selftests/bpf/Makefile | 14 +++- tools/testing/selftests/bpf/libarena/Makefile | 26 ++++++- .../selftests/bpf/libarena/include/userapi.h | 1 + .../bpf/libarena/selftests/selftest.c | 69 ++++++++++++++++++- .../bpf/libarena/selftests/st_asan_common.h | 49 +++++++++++++ 5 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 tools/testing/selftests/bpf/libarena/selftests/st_asan_common.h diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile index 931153ae7a22..8774c31cbf21 100644 --- a/tools/testing/selftests/bpf/Makefile +++ b/tools/testing/selftests/bpf/Makefile @@ -917,7 +917,19 @@ override define INSTALL_RULE endef test_libarena: $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) - +$(MAKE) -C libarena all \ + +$(MAKE) -C libarena $@ \ + BPFTOOL="$(BPFTOOL)" \ + INCLUDE_DIR="$(HOST_INCLUDE_DIR)" \ + LIBBPF_INCLUDE="$(HOST_INCLUDE_DIR)" \ + BPFOBJ="$(BPFOBJ)" \ + LDLIBS="$(LDLIBS)" \ + CLANG="$(CLANG)" \ + BPF_CFLAGS="$(BPF_CFLAGS) $(CLANG_CFLAGS)" \ + BPF_TARGET_ENDIAN="$(BPF_TARGET_ENDIAN)" \ + Q="$(Q)" + +test_libarena_asan: $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) + +$(MAKE) -C libarena $@ \ BPFTOOL="$(BPFTOOL)" \ INCLUDE_DIR="$(HOST_INCLUDE_DIR)" \ LIBBPF_INCLUDE="$(HOST_INCLUDE_DIR)" \ diff --git a/tools/testing/selftests/bpf/libarena/Makefile b/tools/testing/selftests/bpf/libarena/Makefile index 70901bb5237f..1ec1a83c0c04 100644 --- a/tools/testing/selftests/bpf/libarena/Makefile +++ b/tools/testing/selftests/bpf/libarena/Makefile @@ -5,6 +5,7 @@ LIBARENA=$(abspath .) LIBARENA_SOURCES = $(wildcard $(LIBARENA)/src/*.bpf.c) $(wildcard $(LIBARENA)/selftests/*.bpf.c) LIBARENA_OBJECTS = $(notdir $(LIBARENA_SOURCES:.bpf.c=.bpf.o)) +LIBARENA_OBJECTS_ASAN = $(notdir $(LIBARENA_SOURCES:.bpf.c=_asan.bpf.o)) INCLUDES = -I$(LIBARENA)/include -I$(LIBARENA)/.. ifneq ($(INCLUDE_DIR),) @@ -14,6 +15,13 @@ ifneq ($(LIBBPF_INCLUDE),) INCLUDES += -I$(LIBBPF_INCLUDE) endif +ASAN_FLAGS = -fsanitize=kernel-address -fno-stack-protector -fno-builtin +ASAN_FLAGS += -mllvm -asan-instrument-address-spaces=1 -mllvm -asan-shadow-addr-space=1 +ASAN_FLAGS += -mllvm -asan-use-stack-safety=0 -mllvm -asan-stack=0 +ASAN_FLAGS += -mllvm -asan-kernel=1 +ASAN_FLAGS += -mllvm -asan-constructor-kind=none +ASAN_FLAGS += -mllvm -asan-destructor-kind=none + # ENABLE_ATOMICS_TESTS required because we use arena spinlocks override BPF_CFLAGS += -DENABLE_ATOMICS_TESTS override BPF_CFLAGS += -O2 -Wno-incompatible-pointer-types-discards-qualifiers @@ -25,23 +33,37 @@ CFLAGS += $(INCLUDES) vpath %.bpf.c $(LIBARENA)/src $(LIBARENA)/selftests vpath %.c $(LIBARENA)/src $(LIBARENA)/selftests -all: test_libarena +test_libarena_asan: selftest.c $(BPFOBJ) selftest_asan.skel.h + $(call msg,BINARY,libarena,$@) + $(Q)$(CLANG) $(LDLIBS) $(CFLAGS) -DBPF_ARENA_ASAN $< $(BPFOBJ) -o $@ test_libarena: selftest.c $(BPFOBJ) selftest.skel.h $(call msg,BINARY,libarena,$@) $(Q)$(CLANG) $(LDLIBS) $(CFLAGS) $< $(BPFOBJ) -o $@ +selftest_asan.skel.h: main_asan.bpf.o + $(call msg,GEN-SKEL,libarena,$@) + $(Q)$(BPFTOOL) gen skeleton $< name "selftest_asan" > $@ + selftest.skel.h: main.bpf.o $(call msg,GEN-SKEL,libarena,$@) $(Q)$(BPFTOOL) gen skeleton $< name "selftest" > $@ +main_asan.bpf.o: $(LIBARENA_OBJECTS_ASAN) + $(call msg,GEN-OBJ,libarena,$@) + $(Q)$(BPFTOOL) gen object $@ $^ + main.bpf.o: $(LIBARENA_OBJECTS) $(call msg,GEN-OBJ,libarena,$@) $(Q)$(BPFTOOL) gen object $@ $^ +%_asan.bpf.o: %.bpf.c + $(call msg,CLNG-BPF,libarena,$@) + $(Q)$(CLANG) $(BPF_CFLAGS) $(ASAN_FLAGS) -DBPF_ARENA_ASAN $(BPF_TARGET_ENDIAN) -c $< -mcpu=v3 -o $@ + %.bpf.o: %.bpf.c $(call msg,CLNG-BPF,libarena,$@) $(Q)$(CLANG) $(BPF_CFLAGS) $(BPF_TARGET_ENDIAN) -c $< -o $@ clean: - $(Q)rm -f *.skel.h *.bpf.o test_libarena + $(Q)rm -f *.skel.h *.bpf.o test_libarena test_libarena_asan diff --git a/tools/testing/selftests/bpf/libarena/include/userapi.h b/tools/testing/selftests/bpf/libarena/include/userapi.h index bd5e7042a44f..c294b6818bed 100644 --- a/tools/testing/selftests/bpf/libarena/include/userapi.h +++ b/tools/testing/selftests/bpf/libarena/include/userapi.h @@ -20,3 +20,4 @@ typedef int64_t s64; #define arena_spinlock_t u64 #include "common.h" +#include "asan.h" diff --git a/tools/testing/selftests/bpf/libarena/selftests/selftest.c b/tools/testing/selftests/bpf/libarena/selftests/selftest.c index 2e4c0830fed9..4d7db66e75ed 100644 --- a/tools/testing/selftests/bpf/libarena/selftests/selftest.c +++ b/tools/testing/selftests/bpf/libarena/selftests/selftest.c @@ -22,8 +22,20 @@ #include "selftest.h" +#include + +#ifdef BPF_ARENA_ASAN +#include "../selftest_asan.skel.h" +typedef struct selftest_asan selftest; +#define selftest__open selftest_asan__open +#define selftest__open_and_load selftest_asan__open_and_load +#define selftest__load selftest_asan__load +#define selftest__attach selftest_asan__attach +#define selftest__destroy selftest_asan__destroy +#else #include "../selftest.skel.h" typedef struct selftest selftest; +#endif static bool verbose = false; static int testno = 1; @@ -155,6 +167,48 @@ selftest_globals_pages(selftest *skel, size_t arena_all_pages, u64 *globals_page return 0; } +#if BPF_ARENA_ASAN +static int +selftest_asan_init(selftest *skel) +{ + struct bpf_test_run_opts opts; + size_t arena_all_pages = 1ULL << 20; + struct asan_init_args args; + u64 globals_pages; + int prog_fd; + int ret; + + ret = selftest_globals_pages(skel, arena_all_pages, &globals_pages); + if (ret) + return ret; + + /* Taken from the arena map header. */ + args = (struct asan_init_args) { + .arena_all_pages = arena_all_pages, + .arena_globals_pages = globals_pages, + }; + + opts = (struct bpf_test_run_opts) { + .sz = sizeof(opts), + .ctx_in = &args, + .ctx_size_in = sizeof(args), + }; + + prog_fd = bpf_program__fd(skel->progs.asan_init); + assert(prog_fd >= 0 && "no program found"); + return selftest_fd(prog_fd, &opts); +} + +#else /* BPF_ARENA_ASAN */ + +static int +selftest_asan_init(selftest *skel) +{ + return 0; +} + +#endif /* BPF_ARENA_ASAN */ + static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) { @@ -172,6 +226,10 @@ int run_test(selftest *skel, const struct bpf_program *prog) if (ret) return ret; + ret = selftest_asan_init(skel); + if (ret) + return ret; + prog_fd = bpf_program__fd(prog); if (!prog_fd) return -ENOENT; @@ -211,8 +269,17 @@ static void banner(const char *progpath) { char *name = basename(progpath); + bool is_asan; + + /* + * Check if our BPF programs are ASAN-capable by inspecting the prog name. + * Command line arguments are guaranteed to be NULL-terminated, use strlen. + * Calculate the hardcoded name's length at compile time. + */ + printf("%s\n", name); + is_asan = strlen(name) > (sizeof("selftest") - 1); - printf("=== %s ===\n", "libarena selftests"); + printf("=== %s %s===\n", "libarena selftests", is_asan ? "(asan) " : ""); } int main(int argc, char *argv[]) diff --git a/tools/testing/selftests/bpf/libarena/selftests/st_asan_common.h b/tools/testing/selftests/bpf/libarena/selftests/st_asan_common.h new file mode 100644 index 000000000000..d8a6316bba47 --- /dev/null +++ b/tools/testing/selftests/bpf/libarena/selftests/st_asan_common.h @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: GPL-2.0 + * Copyright (c) 2025 Meta Platforms, Inc. and affiliates. + * Copyright (c) 2025 Emil Tsalapatis + */ + +#pragma once + +#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) + +/* + * Emit an error and force the current function to exit if the ASAN + * violation state is unexpected. Reset the violation state after. + */ +#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; +}; + + -- 2.47.3