]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
selftests/bpf: Reuse stderr parsing for libarena ASAN tests
authorEmil Tsalapatis <emil@etsalapatis.com>
Sun, 26 Apr 2026 19:03:38 +0000 (15:03 -0400)
committerAlexei Starovoitov <ast@kernel.org>
Mon, 27 Apr 2026 01:12:22 +0000 (18:12 -0700)
Add code to directly test the output of libarena ASAN tests.
The code reuses testing infrastructure originally for BPF streams
to verify that ASAN emits call stacks when the selftests trigger
a memory error.

Since stderr() testing uses logic from test_progs, it is only
available on the test_progs-based selftest runner. The standalone
runner still uses internal ASAN state to verify access errors are
triaged as expected.

Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
Link: https://lore.kernel.org/r/20260426190338.4615-9-emil@etsalapatis.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
tools/testing/selftests/bpf/libarena/selftests/st_asan_buddy.bpf.c
tools/testing/selftests/bpf/libarena/selftests/test_progs_compat.h [new file with mode: 0644]
tools/testing/selftests/bpf/prog_tests/libarena_asan.c
tools/testing/selftests/bpf/test_loader.c
tools/testing/selftests/bpf/test_progs.h

index 9dd2980b5d6cab9703b8ccb036f9e23c804c2ff3..97acd50ffa5c4b489377736ff72128e572e0dbf5 100644 (file)
@@ -5,6 +5,9 @@
 #include <libarena/asan.h>
 #include <libarena/buddy.h>
 
+/* Required for parsing the ASAN call stacks. */
+#include "test_progs_compat.h"
+
 extern buddy_t buddy;
 
 #ifdef BPF_ARENA_ASAN
@@ -141,6 +144,11 @@ static __always_inline int asan_test_buddy_blob_single(void)
 }
 
 SEC("syscall")
+__stderr("Memory violation for address {{.*}} for write of size 1")
+__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
+__stderr("Call trace:\n"
+"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
+"|[ \t]+[^\n]+\n)*}}")
 __weak int asan_test_buddy_oob(void)
 {
        size_t sizes[] = {
@@ -174,6 +182,11 @@ __weak int asan_test_buddy_oob(void)
 }
 
 SEC("syscall")
+__stderr("Memory violation for address {{.*}} for write of size 1")
+__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
+__stderr("Call trace:\n"
+"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
+"|[ \t]+[^\n]+\n)*}}")
 __weak int asan_test_buddy_uaf(void)
 {
        size_t sizes[] = { 16, 32, 64, 128, 256, 512, 1024, 16384 };
@@ -205,6 +218,11 @@ __weak int asan_test_buddy_uaf(void)
 }
 
 SEC("syscall")
+__stderr("Memory violation for address {{.*}} for write of size 1")
+__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
+__stderr("Call trace:\n"
+"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
+"|[ \t]+[^\n]+\n)*}}")
 __weak int asan_test_buddy_blob(void)
 {
        const int iters = 10;
diff --git a/tools/testing/selftests/bpf/libarena/selftests/test_progs_compat.h b/tools/testing/selftests/bpf/libarena/selftests/test_progs_compat.h
new file mode 100644 (file)
index 0000000..9d43137
--- /dev/null
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#pragma once
+
+#ifdef __BPF__
+
+/* Selftests use these tags for compatibility with test_progs. */
+#define __test_tag(tag)                __attribute__((btf_decl_tag("comment:" XSTR(__COUNTER__) ":" tag)))
+#define __stderr(msg)          __test_tag("test_expect_stderr=" msg)
+#define __stderr_unpriv(msg)   __test_tag("test_expect_stderr_unpriv=" msg)
+
+#define XSTR(s) STR(s)
+#define STR(s) #s
+
+#endif
index b4fba10cdfbf6d77a400616a6426675cdaa95d31..d59d9dd12ef2b2a8c2cc3851ed69f37db5843d4b 100644 (file)
@@ -25,6 +25,8 @@ static void run_libarena_asan_test(struct libarena_asan *skel,
 
        ret = libarena_run_prog(bpf_program__fd(prog));
        ASSERT_OK(ret, name);
+
+       verify_test_stderr(skel->obj, prog);
 }
 
 static void run_test(void)
index c4c34cae61027d8e1931e31535a41274c5312e3a..ee637809a1d4edce95fb1693814a095f958ecda4 100644 (file)
@@ -93,7 +93,7 @@ void test_loader_fini(struct test_loader *tester)
        free(tester->log_buf);
 }
 
-static void free_msgs(struct expected_msgs *msgs)
+void free_msgs(struct expected_msgs *msgs)
 {
        int i;
 
@@ -789,6 +789,43 @@ static void emit_stderr(const char *stderr, bool force)
        fprintf(stdout, "STDERR:\n=============\n%s=============\n", stderr);
 }
 
+static void verify_stderr(int prog_fd, struct expected_msgs *msgs)
+{
+       LIBBPF_OPTS(bpf_prog_stream_read_opts, ropts);
+       char *buf;
+       int ret;
+
+       if (!msgs->cnt)
+               return;
+
+       buf = malloc(TEST_LOADER_LOG_BUF_SZ);
+       if (!ASSERT_OK_PTR(buf, "malloc"))
+               return;
+
+       ret = bpf_prog_stream_read(prog_fd, 2, buf, TEST_LOADER_LOG_BUF_SZ - 1,
+                                   &ropts);
+       if (ret > 0) {
+               buf[ret] = '\0';
+               emit_stderr(buf, false);
+               validate_msgs(buf, msgs, emit_stderr);
+       } else {
+               ASSERT_GT(ret, 0, "stderr stream read");
+       }
+
+       free(buf);
+}
+
+void verify_test_stderr(struct bpf_object *obj, struct bpf_program *prog)
+{
+       struct test_spec spec = {};
+
+       if (parse_test_spec(NULL, obj, prog, &spec))
+               return;
+
+       verify_stderr(bpf_program__fd(prog), &spec.priv.stderr);
+       free_test_spec(&spec);
+}
+
 static void emit_stdout(const char *bpf_stdout, bool force)
 {
        if (!force && env.verbosity == VERBOSE_NONE)
@@ -1314,17 +1351,7 @@ void run_subtest(struct test_loader *tester,
                        goto tobj_cleanup;
                }
 
-               if (subspec->stderr.cnt) {
-                       err = get_stream(2, bpf_program__fd(tprog),
-                                        tester->log_buf, tester->log_buf_sz);
-                       if (err <= 0) {
-                               PRINT_FAIL("Unexpected retval from get_stream(): %d, errno = %d\n",
-                                          err, errno);
-                               goto tobj_cleanup;
-                       }
-                       emit_stderr(tester->log_buf, false /*force*/);
-                       validate_msgs(tester->log_buf, &subspec->stderr, emit_stderr);
-               }
+               verify_stderr(bpf_program__fd(tprog), &subspec->stderr);
 
                if (subspec->stdout.cnt) {
                        err = get_stream(1, bpf_program__fd(tprog),
index 1a44467f4310dfdf86685217a682b0e2ddbf46a5..37955a8ad385150c54e8ab51ba773cce41bca8a3 100644 (file)
@@ -563,5 +563,7 @@ struct expected_msgs {
 
 void validate_msgs(const char *log_buf, struct expected_msgs *msgs,
                   void (*emit_fn)(const char *buf, bool force));
+void free_msgs(struct expected_msgs *msgs);
+void verify_test_stderr(struct bpf_object *obj, struct bpf_program *prog);
 
 #endif /* __TEST_PROGS_H */