summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/bpf/prog_tests
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-06-17 09:18:14 +0100
committerLinus Torvalds <torvalds@linux-foundation.org>2026-06-17 09:18:14 +0100
commit9c87e61e3c5797277407ba5eae4eac8a52be3fa3 (patch)
treee3f902cb5363b5b90ab74a4b7e26fafbc15aaeaf /tools/testing/selftests/bpf/prog_tests
parentb85966adbf5de0668a815c6e3527f87e0c387fb4 (diff)
parente4287bf34f97a88c7d9322f5bde828724c073a6b (diff)
downloadlinux-next-9c87e61e3c5797277407ba5eae4eac8a52be3fa3.tar.gz
linux-next-9c87e61e3c5797277407ba5eae4eac8a52be3fa3.zip
Merge tag 'bpf-next-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next
Pull bpf updates from Alexei Starovoitov: "Major changes: - Recover from BPF arena page faults using a scratch page and add ptep_try_set() for lockless empty-slot installs on x86 and arm64. This allows BPF kfuncs to access arena pointers directly. The 'arena_direct_access' stable branch was created for this work and was pulled into sched-ext and bpf-next trees (Tejun Heo, Kumar Kartikeya Dwivedi) - Lift old restriction and support 6+ arguments in BPF programs and kfuncs on x86 and arm64 (Yonghong Song, Puranjay Mohan) Other features and fixes: - Add 24-bit BTF vlen and reclaim unused bits in the BTF UAPI to ease addition of new BTF kinds (Alan Maguire) - Raise the maximum BPF call chain depth from 8 to 16 frames (Alexei Starovoitov) - Refactor object relationship tracking in the verifier and fix a dynptr use-after-free bug (Amery Hung) - Harden the signed program loader and reject exclusive maps as inner maps (Daniel Borkmann) - Replace the verifier min/max bounds fields with a circular number (cnum) representation and improve 32->64 bit range refinements (Eduard Zingerman) - Introduce the arena library and runtime (libarena) with a buddy allocator, rbtree and SPMC queue data structures, ASAN support and a parallel test harness. Allow subprograms to return arena pointers and switch to a BTF type-tag based __arena annotation (Emil Tsalapatis) - Cache build IDs in the sleepable stackmap path and avoid faultable build ID reads under mm locks (Ihor Solodrai) - Introduce the tracing_multi link to attach a single BPF program to many kernel functions at once. Allow specifying the uprobe_multi target via FD (Jiri Olsa) - Extend the bpf_list family of kfuncs with bpf_list_add/del(), and bpf_list_is_first/is_last/empty() (Kaitao Cheng) - Extend the BPF syscall with common attributes support for prog_load, btf_load and map_create (Leon Hwang) - Wrap rhashtable as BPF map (Mykyta Yatsenko, Herbert Xu) - Add sleepable support for tracepoint programs and fix deadlocks in LRU map due to NMI reentry (Mykyta Yatsenko) - Fix OOB access in bpf_flow_keys, fix nullness analysis of inner arrays, enforce write checks for global subprograms (Nuoqi Gui) - Report the maximum combined stack depth and print a breakdown of instructions processed per subprogram (Paul Chaignon) - Add an XDP load-balancer benchmark and arm64 JIT support for stack arguments (Puranjay Mohan) - Add kfuncs to traverse over wakeup_sources (Samuel Wu) - Allow sleepable BPF programs to use LPM trie maps directly (Vlad Poenaru) - Many more fixes and cleanups across the verifier, BTF, sockmap, devmap, bpffs, security hooks, s390/riscv/loongarch JITs, rqspinlock, libbpf, bpftool, selftests" * tag 'bpf-next-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next: (336 commits) selftests/bpf: Work around llvm stack overflow in crypto progs selftests/bpf: add test for bpf_msg_pop_data() overflow bpf, sockmap: fix integer overflow in bpf_msg_pop_data() bounds check sockmap: Fix use-after-free in udp_bpf_recvmsg() bpf, sockmap: keep sk_msg copy state in sync bpf, sockmap: Fix wrong rsge offset in bpf_msg_push_data() bpf, sockmap: reject overflowing copy + len in bpf_msg_push_data() selftsets/bpf: Retry map update on helper_fill_hashmap() selftests/bpf: Add test for sleepable lsm_cgroup rejection selftests/bpf: Add test to verify the fix for bpf_setsockopt() helper bpf: Fix bpf_get/setsockopt to tos for ipv4-mapped ipv6 socket selftests/bpf: Avoid static LLVM linking for cross builds selftests/bpf: Use common CFLAGS for urandom_read selftests/bpf: Initialize operation name before use tools/bpf: build: Append extra cflags libbpf: Initialize CFLAGS before including Makefile.include bpftool: Append extra host flags bpftool: Avoid adding EXTRA_CFLAGS to HOST_CFLAGS bpftool: Pass host flags to bootstrap libbpf selftests/bpf: correct CONFIG_PPC64 macro name in comment ...
Diffstat (limited to 'tools/testing/selftests/bpf/prog_tests')
-rw-r--r--tools/testing/selftests/bpf/prog_tests/arena_direct_value.c73
-rw-r--r--tools/testing/selftests/bpf/prog_tests/arena_spin_lock.c7
-rw-r--r--tools/testing/selftests/bpf/prog_tests/bpf_attr_size.c124
-rw-r--r--tools/testing/selftests/bpf/prog_tests/bpf_cookie.c17
-rw-r--r--tools/testing/selftests/bpf/prog_tests/bpf_nf.c22
-rw-r--r--tools/testing/selftests/bpf/prog_tests/bpf_qdisc.c8
-rw-r--r--tools/testing/selftests/bpf/prog_tests/btf.c45
-rw-r--r--tools/testing/selftests/bpf/prog_tests/btf_dedup_split.c51
-rw-r--r--tools/testing/selftests/bpf/prog_tests/btf_dump.c4
-rw-r--r--tools/testing/selftests/bpf/prog_tests/cb_refs.c4
-rw-r--r--tools/testing/selftests/bpf/prog_tests/cgrp_local_storage.c15
-rw-r--r--tools/testing/selftests/bpf/prog_tests/ctx_rewrite.c17
-rw-r--r--tools/testing/selftests/bpf/prog_tests/exceptions.c7
-rw-r--r--tools/testing/selftests/bpf/prog_tests/file_reader.c22
-rw-r--r--tools/testing/selftests/bpf/prog_tests/fill_link_info.c2
-rw-r--r--tools/testing/selftests/bpf/prog_tests/htab_update.c4
-rw-r--r--tools/testing/selftests/bpf/prog_tests/iters.c2
-rw-r--r--tools/testing/selftests/bpf/prog_tests/kfunc_call.c2
-rw-r--r--tools/testing/selftests/bpf/prog_tests/libarena.c253
-rw-r--r--tools/testing/selftests/bpf/prog_tests/libarena_asan.c93
-rw-r--r--tools/testing/selftests/bpf/prog_tests/linked_list.c37
-rw-r--r--tools/testing/selftests/bpf/prog_tests/lru_lock_nmi.c243
-rw-r--r--tools/testing/selftests/bpf/prog_tests/lsm_cgroup.c79
-rw-r--r--tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c145
-rw-r--r--tools/testing/selftests/bpf/prog_tests/map_excl.c122
-rw-r--r--tools/testing/selftests/bpf/prog_tests/map_init.c192
-rw-r--r--tools/testing/selftests/bpf/prog_tests/map_kptr.c66
-rw-r--r--tools/testing/selftests/bpf/prog_tests/refcounted_kptr.c8
-rw-r--r--tools/testing/selftests/bpf/prog_tests/reg_bounds.c90
-rw-r--r--tools/testing/selftests/bpf/prog_tests/rhash.c183
-rw-r--r--tools/testing/selftests/bpf/prog_tests/setget_sockopt.c78
-rw-r--r--tools/testing/selftests/bpf/prog_tests/signed_loader.c1135
-rw-r--r--tools/testing/selftests/bpf/prog_tests/sleepable_tracepoints.c142
-rw-r--r--tools/testing/selftests/bpf/prog_tests/sockmap_basic.c48
-rw-r--r--tools/testing/selftests/bpf/prog_tests/spin_lock.c4
-rw-r--r--tools/testing/selftests/bpf/prog_tests/stack_arg.c139
-rw-r--r--tools/testing/selftests/bpf/prog_tests/stack_arg_fail.c10
-rw-r--r--tools/testing/selftests/bpf/prog_tests/stack_arg_precision.c10
-rw-r--r--tools/testing/selftests/bpf/prog_tests/tailcalls.c186
-rw-r--r--tools/testing/selftests/bpf/prog_tests/task_kfunc.c42
-rw-r--r--tools/testing/selftests/bpf/prog_tests/task_local_storage.c1
-rw-r--r--tools/testing/selftests/bpf/prog_tests/test_lsm.c22
-rw-r--r--tools/testing/selftests/bpf/prog_tests/test_xdp_veth.c166
-rw-r--r--tools/testing/selftests/bpf/prog_tests/tracing_multi.c960
-rw-r--r--tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c94
-rw-r--r--tools/testing/selftests/bpf/prog_tests/verifier.c8
-rw-r--r--tools/testing/selftests/bpf/prog_tests/verifier_log.c1
-rw-r--r--tools/testing/selftests/bpf/prog_tests/vmlinux.c45
-rw-r--r--tools/testing/selftests/bpf/prog_tests/wakeup_source.c118
49 files changed, 4992 insertions, 154 deletions
diff --git a/tools/testing/selftests/bpf/prog_tests/arena_direct_value.c b/tools/testing/selftests/bpf/prog_tests/arena_direct_value.c
new file mode 100644
index 000000000000..4b4adb3f4b71
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/arena_direct_value.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <bpf/bpf.h>
+#include <errno.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#define ARENA_PAGES 32
+
+static char log_buf[16384];
+
+static void test_arena_direct_value_one_past_end(void)
+{
+ char expected[128];
+ __u32 arena_sz = ARENA_PAGES * getpagesize();
+ struct bpf_insn insns[] = {
+ BPF_LD_IMM64_RAW(BPF_REG_1, BPF_PSEUDO_MAP_VALUE, 0),
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ };
+ LIBBPF_OPTS(bpf_map_create_opts, map_opts);
+ LIBBPF_OPTS(bpf_prog_load_opts, prog_opts);
+ void *arena;
+ int map_fd, prog_fd;
+
+ map_opts.map_flags = BPF_F_MMAPABLE;
+ prog_opts.log_buf = log_buf;
+ prog_opts.log_size = sizeof(log_buf);
+ prog_opts.log_level = 1;
+
+ map_fd = bpf_map_create(BPF_MAP_TYPE_ARENA, "arena_direct_value",
+ 0, 0, ARENA_PAGES, &map_opts);
+ if (map_fd < 0) {
+ if (errno == EOPNOTSUPP) {
+ test__skip();
+ return;
+ }
+ ASSERT_GE(map_fd, 0, "bpf_map_create");
+ return;
+ }
+
+ arena = mmap(NULL, arena_sz, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+ if (!ASSERT_NEQ(arena, MAP_FAILED, "arena_mmap"))
+ goto cleanup;
+
+ insns[0].imm = map_fd;
+ insns[1].imm = arena_sz;
+
+ prog_fd = bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT,
+ "arena_direct_value", "GPL", insns,
+ ARRAY_SIZE(insns), &prog_opts);
+ if (!ASSERT_LT(prog_fd, 0, "prog_load")) {
+ close(prog_fd);
+ goto cleanup;
+ }
+
+ snprintf(expected, sizeof(expected),
+ "invalid access to map value pointer, value_size=0 off=%u",
+ arena_sz);
+ ASSERT_HAS_SUBSTR(log_buf, expected, "verifier_log");
+
+cleanup:
+ if (arena != MAP_FAILED)
+ munmap(arena, arena_sz);
+ close(map_fd);
+}
+
+void test_arena_direct_value(void)
+{
+ if (test__start_subtest("one_past_end"))
+ test_arena_direct_value_one_past_end();
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/arena_spin_lock.c b/tools/testing/selftests/bpf/prog_tests/arena_spin_lock.c
index 693fd86fbde6..acb9d53b5973 100644
--- a/tools/testing/selftests/bpf/prog_tests/arena_spin_lock.c
+++ b/tools/testing/selftests/bpf/prog_tests/arena_spin_lock.c
@@ -5,13 +5,6 @@
#include <sys/sysinfo.h>
struct __qspinlock { int val; };
-typedef struct __qspinlock arena_spinlock_t;
-
-struct arena_qnode {
- unsigned long next;
- int count;
- int locked;
-};
#include "arena_spin_lock.skel.h"
diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_attr_size.c b/tools/testing/selftests/bpf/prog_tests/bpf_attr_size.c
new file mode 100644
index 000000000000..87842c4347a6
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_attr_size.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Google LLC */
+#include <linux/bpf.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <test_progs.h>
+#include <cgroup_helpers.h>
+#include "cgroup_skb_direct_packet_access.skel.h"
+
+#define OLD_QUERY_SIZE offsetofend(union bpf_attr, query.prog_cnt)
+#define FULL_QUERY_SIZE offsetofend(union bpf_attr, query.revision)
+
+static void test_query_size_boundaries(void)
+{
+ struct cgroup_skb_direct_packet_access *skel;
+ struct bpf_link *link = NULL;
+ union bpf_attr attr;
+ int cg_fd = -1;
+ int err;
+
+ skel = cgroup_skb_direct_packet_access__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_load"))
+ return;
+
+ cg_fd = test__join_cgroup("/attr_size_cg");
+ if (!ASSERT_GE(cg_fd, 0, "join_cgroup"))
+ goto cleanup;
+
+ link = bpf_program__attach_cgroup(skel->progs.direct_packet_access,
+ cg_fd);
+ if (!ASSERT_OK_PTR(link, "cg_attach"))
+ goto cleanup;
+
+ memset(&attr, 0, sizeof(attr));
+ attr.query.target_fd = cg_fd;
+ attr.query.attach_type = BPF_CGROUP_INET_INGRESS;
+ attr.query.revision = 0xdeadbeefdeadbeefULL;
+
+ err = syscall(__NR_bpf, BPF_PROG_QUERY, &attr, OLD_QUERY_SIZE);
+ if (ASSERT_OK(err, "query_old_size")) {
+ ASSERT_EQ(attr.query.prog_cnt, 1, "prog_cnt_written_old");
+ ASSERT_EQ(attr.query.revision, 0xdeadbeefdeadbeefULL,
+ "revision_not_written_old");
+ }
+
+ memset(&attr, 0, sizeof(attr));
+ attr.query.target_fd = cg_fd;
+ attr.query.attach_type = BPF_CGROUP_INET_INGRESS;
+
+ err = syscall(__NR_bpf, BPF_PROG_QUERY, &attr, FULL_QUERY_SIZE);
+ if (!ASSERT_OK(err, "query_full_size"))
+ goto cleanup;
+
+ ASSERT_EQ(attr.query.prog_cnt, 1, "prog_cnt_written");
+ ASSERT_GT(attr.query.revision, 0, "revision_written");
+
+cleanup:
+ if (link)
+ bpf_link__destroy(link);
+ if (cg_fd >= 0)
+ close(cg_fd);
+ cgroup_skb_direct_packet_access__destroy(skel);
+}
+
+static void test_map_info_tail_zero(void)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, map_opts);
+ struct bpf_map_info_fake {
+ __u8 info[offsetofend(struct bpf_map_info, hash_size)];
+ __u32 pad;
+ } info = {
+ .pad = 1,
+ };
+ int map_fd, err;
+ __u32 info_len;
+
+ map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "arr", sizeof(int), 1, 1, &map_opts);
+ if (!ASSERT_GE(map_fd, 0, "bpf_map_create"))
+ return;
+
+ info_len = sizeof(info);
+ err = bpf_obj_get_info_by_fd(map_fd, &info, &info_len);
+ ASSERT_EQ(err, -E2BIG, "bpf_obj_get_info_by_fd");
+
+ close(map_fd);
+}
+
+static void test_prog_info_tail_zero(void)
+{
+ LIBBPF_OPTS(bpf_prog_load_opts, prog_opts);
+ struct bpf_insn insns[] = {
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ };
+ struct bpf_prog_info_fake {
+ __u8 info[offsetofend(struct bpf_prog_info, attach_btf_id)];
+ __u32 pad;
+ } info = {
+ .pad = 1,
+ };
+ int prog_fd, err;
+ __u32 info_len;
+
+ prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, "test_prog", "GPL", insns,
+ ARRAY_SIZE(insns), &prog_opts);
+ if (!ASSERT_GE(prog_fd, 0, "bpf_prog_load"))
+ return;
+
+ info_len = sizeof(info);
+ err = bpf_obj_get_info_by_fd(prog_fd, &info, &info_len);
+ ASSERT_EQ(err, -E2BIG, "bpf_obj_get_info_by_fd");
+
+ close(prog_fd);
+}
+
+void test_bpf_attr_size(void)
+{
+ if (test__start_subtest("query_size_boundaries"))
+ test_query_size_boundaries();
+ if (test__start_subtest("map_info_tail_zero"))
+ test_map_info_tail_zero();
+ if (test__start_subtest("prog_info_tail_zero"))
+ test_prog_info_tail_zero();
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_cookie.c b/tools/testing/selftests/bpf/prog_tests/bpf_cookie.c
index 35adc3f6d443..fa484d00a7a5 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_cookie.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_cookie.c
@@ -252,10 +252,17 @@ cleanup:
kprobe_multi__destroy(skel);
}
-/* defined in prog_tests/uprobe_multi_test.c */
-void uprobe_multi_func_1(void);
-void uprobe_multi_func_2(void);
-void uprobe_multi_func_3(void);
+/*
+ * Weak uprobe target stubs. noinline is required because
+ * uprobe_multi_test_run() takes their addresses to configure the BPF
+ * program's attachment points; an inlined function has no stable
+ * address in the binary to probe. The strong definitions in
+ * uprobe_multi_test.c take precedence when that translation unit is
+ * linked.
+ */
+noinline __weak void uprobe_multi_func_1(void) { asm volatile (""); }
+noinline __weak void uprobe_multi_func_2(void) { asm volatile (""); }
+noinline __weak void uprobe_multi_func_3(void) { asm volatile (""); }
static void uprobe_multi_test_run(struct uprobe_multi *skel)
{
@@ -574,8 +581,6 @@ cleanup:
close(fmod_ret_fd);
}
-int stack_mprotect(void);
-
static void lsm_subtest(struct test_bpf_cookie *skel)
{
__u64 cookie;
diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_nf.c b/tools/testing/selftests/bpf/prog_tests/bpf_nf.c
index 215878ea04de..b33dba4b126e 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_nf.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_nf.c
@@ -11,18 +11,18 @@ struct {
const char *prog_name;
const char *err_msg;
} test_bpf_nf_fail_tests[] = {
- { "alloc_release", "kernel function bpf_ct_release args#0 expected pointer to STRUCT nf_conn but" },
- { "insert_insert", "kernel function bpf_ct_insert_entry args#0 expected pointer to STRUCT nf_conn___init but" },
- { "lookup_insert", "kernel function bpf_ct_insert_entry args#0 expected pointer to STRUCT nf_conn___init but" },
- { "set_timeout_after_insert", "kernel function bpf_ct_set_timeout args#0 expected pointer to STRUCT nf_conn___init but" },
- { "set_status_after_insert", "kernel function bpf_ct_set_status args#0 expected pointer to STRUCT nf_conn___init but" },
- { "change_timeout_after_alloc", "kernel function bpf_ct_change_timeout args#0 expected pointer to STRUCT nf_conn but" },
- { "change_status_after_alloc", "kernel function bpf_ct_change_status args#0 expected pointer to STRUCT nf_conn but" },
+ { "alloc_release", "kernel function bpf_ct_release R1 expected pointer to STRUCT nf_conn but" },
+ { "insert_insert", "kernel function bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but" },
+ { "lookup_insert", "kernel function bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but" },
+ { "set_timeout_after_insert", "kernel function bpf_ct_set_timeout R1 expected pointer to STRUCT nf_conn___init but" },
+ { "set_status_after_insert", "kernel function bpf_ct_set_status R1 expected pointer to STRUCT nf_conn___init but" },
+ { "change_timeout_after_alloc", "kernel function bpf_ct_change_timeout R1 expected pointer to STRUCT nf_conn but" },
+ { "change_status_after_alloc", "kernel function bpf_ct_change_status R1 expected pointer to STRUCT nf_conn but" },
{ "write_not_allowlisted_field", "no write support to nf_conn at off" },
- { "lookup_null_bpf_tuple", "Possibly NULL pointer passed to trusted arg1" },
- { "lookup_null_bpf_opts", "Possibly NULL pointer passed to trusted arg3" },
- { "xdp_lookup_null_bpf_tuple", "Possibly NULL pointer passed to trusted arg1" },
- { "xdp_lookup_null_bpf_opts", "Possibly NULL pointer passed to trusted arg3" },
+ { "lookup_null_bpf_tuple", "Possibly NULL pointer passed to trusted R2" },
+ { "lookup_null_bpf_opts", "Possibly NULL pointer passed to trusted R4" },
+ { "xdp_lookup_null_bpf_tuple", "Possibly NULL pointer passed to trusted R2" },
+ { "xdp_lookup_null_bpf_opts", "Possibly NULL pointer passed to trusted R4" },
};
enum {
diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_qdisc.c b/tools/testing/selftests/bpf/prog_tests/bpf_qdisc.c
index 730357cd0c9a..77f1c0550c9b 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_qdisc.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_qdisc.c
@@ -8,6 +8,10 @@
#include "bpf_qdisc_fifo.skel.h"
#include "bpf_qdisc_fq.skel.h"
#include "bpf_qdisc_fail__incompl_ops.skel.h"
+#include "bpf_qdisc_fail__invalid_dynptr.skel.h"
+#include "bpf_qdisc_fail__invalid_dynptr_slice.skel.h"
+#include "bpf_qdisc_fail__invalid_dynptr_cross_frame.skel.h"
+#include "bpf_qdisc_dynptr_use_after_invalidate_clone.skel.h"
#define LO_IFINDEX 1
@@ -223,6 +227,10 @@ void test_ns_bpf_qdisc(void)
test_qdisc_attach_to_non_root();
if (test__start_subtest("incompl_ops"))
test_incompl_ops();
+ RUN_TESTS(bpf_qdisc_fail__invalid_dynptr);
+ RUN_TESTS(bpf_qdisc_fail__invalid_dynptr_cross_frame);
+ RUN_TESTS(bpf_qdisc_fail__invalid_dynptr_slice);
+ RUN_TESTS(bpf_qdisc_dynptr_use_after_invalidate_clone);
}
void serial_test_bpf_qdisc_default(void)
diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
index 054ecb6b1e9f..96f719a0cec9 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf.c
@@ -1924,11 +1924,11 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "invalid BTF_INFO",
+ .descr = "invalid BTF kind",
.raw_types = {
/* int */ /* [1] */
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4),
- BTF_TYPE_ENC(0, 0x20000000, 4),
+ BTF_TYPE_ENC(0, 0x7f000000, 4),
BTF_END_RAW,
},
.str_sec = "",
@@ -1941,7 +1941,7 @@ static struct btf_raw_test raw_tests[] = {
.value_type_id = 1,
.max_entries = 4,
.btf_load_err = true,
- .err_str = "Invalid btf_info",
+ .err_str = "Invalid kind",
},
{
@@ -4258,6 +4258,43 @@ static struct btf_raw_test raw_tests[] = {
.max_entries = 1,
},
+{
+ .descr = "struct test repeated fields count overflow",
+ .raw_types = {
+ BTF_TYPE_INT_ENC(NAME_TBD, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
+ BTF_STRUCT_ENC(NAME_TBD, 0, 0), /* [2] */
+ BTF_TYPE_TAG_ENC(NAME_TBD, 2), /* [3] */
+ BTF_PTR_ENC(3), /* [4] */
+ BTF_TYPE_ARRAY_ENC(4, 1, 1), /* [5] */
+ BTF_STRUCT_ENC(NAME_TBD, 10, 8), /* [6] */
+ BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+ BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+ BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+ BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+ BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+ BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+ BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+ BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+ BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+ BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+ BTF_TYPE_ARRAY_ENC(6, 1, 0x1999999aU), /* [7] */
+ BTF_STRUCT_ENC(NAME_TBD, 2, 8 + 8 * 0x1999999aU), /* [8] */
+ BTF_MEMBER_ENC(NAME_TBD, 4, 0),
+ BTF_MEMBER_ENC(NAME_TBD, 7, 64),
+ BTF_END_RAW,
+ },
+ BTF_STR_SEC("\0int\0prog_test_ref_kfunc\0kptr_untrusted\0elem"
+ "\0p0\0p1\0p2\0p3\0p4\0p5\0p6\0p7\0p8\0p9"
+ "\0outer\0trigger\0elems"),
+ .map_type = BPF_MAP_TYPE_ARRAY,
+ .map_name = "repeat_fields",
+ .key_size = sizeof(int),
+ .value_size = 8 + 8 * 0x1999999aU,
+ .key_type_id = 1,
+ .value_type_id = 8,
+ .max_entries = 1,
+ .btf_load_err = true,
+},
}; /* struct btf_raw_test raw_tests[] */
static const char *get_next_str(const char *start, const char *end)
@@ -8092,7 +8129,7 @@ static struct btf_dedup_test dedup_tests[] = {
static int btf_type_size(const struct btf_type *t)
{
int base_size = sizeof(struct btf_type);
- __u16 vlen = BTF_INFO_VLEN(t->info);
+ __u32 vlen = BTF_INFO_VLEN(t->info);
__u16 kind = BTF_INFO_KIND(t->info);
switch (kind) {
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dedup_split.c b/tools/testing/selftests/bpf/prog_tests/btf_dedup_split.c
index 5bc15bb6b7ce..9d6161151593 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_dedup_split.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_dedup_split.c
@@ -20,18 +20,22 @@ static void test_split_simple() {
btf__add_struct(btf1, "s1", 4); /* [3] struct s1 { */
btf__add_field(btf1, "f1", 1, 0, 0); /* int f1; */
/* } */
+ btf__add_typedef(btf1, "t1", 1); /* [4] typedef int */
VALIDATE_RAW_BTF(
btf1,
"[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
"[2] PTR '(anon)' type_id=1",
"[3] STRUCT 's1' size=4 vlen=1\n"
- "\t'f1' type_id=1 bits_offset=0");
+ "\t'f1' type_id=1 bits_offset=0",
+ "[4] TYPEDEF 't1' type_id=1");
ASSERT_STREQ(btf_type_c_dump(btf1), "\
struct s1 {\n\
int f1;\n\
-};\n\n", "c_dump");
+};\n\
+\n\
+typedef int t1;\n\n", "c_dump");
btf2 = btf__new_empty_split(btf1);
if (!ASSERT_OK_PTR(btf2, "empty_split_btf"))
@@ -49,39 +53,46 @@ struct s1 {\n\
ASSERT_EQ(btf_is_int(t), true, "int_kind");
ASSERT_STREQ(btf__str_by_offset(btf2, t->name_off), "int", "int_name");
- btf__add_struct(btf2, "s2", 16); /* [4] struct s2 { */
- btf__add_field(btf2, "f1", 6, 0, 0); /* struct s1 f1; */
- btf__add_field(btf2, "f2", 5, 32, 0); /* int f2; */
+ btf__add_struct(btf2, "s2", 16); /* [5] struct s2 { */
+ btf__add_field(btf2, "f1", 7, 0, 0); /* struct s1 f1; */
+ btf__add_field(btf2, "f2", 6, 32, 0); /* int f2; */
btf__add_field(btf2, "f3", 2, 64, 0); /* int *f3; */
/* } */
/* duplicated int */
- btf__add_int(btf2, "int", 4, BTF_INT_SIGNED); /* [5] int */
+ btf__add_int(btf2, "int", 4, BTF_INT_SIGNED); /* [6] int */
/* duplicated struct s1 */
- btf__add_struct(btf2, "s1", 4); /* [6] struct s1 { */
- btf__add_field(btf2, "f1", 5, 0, 0); /* int f1; */
+ btf__add_struct(btf2, "s1", 4); /* [7] struct s1 { */
+ btf__add_field(btf2, "f1", 6, 0, 0); /* int f1; */
/* } */
+ /* duplicated typedef t1 */
+ btf__add_typedef(btf2, "t1", 6); /* [8] typedef int */
+
VALIDATE_RAW_BTF(
btf2,
"[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
"[2] PTR '(anon)' type_id=1",
"[3] STRUCT 's1' size=4 vlen=1\n"
"\t'f1' type_id=1 bits_offset=0",
- "[4] STRUCT 's2' size=16 vlen=3\n"
- "\t'f1' type_id=6 bits_offset=0\n"
- "\t'f2' type_id=5 bits_offset=32\n"
+ "[4] TYPEDEF 't1' type_id=1",
+ "[5] STRUCT 's2' size=16 vlen=3\n"
+ "\t'f1' type_id=7 bits_offset=0\n"
+ "\t'f2' type_id=6 bits_offset=32\n"
"\t'f3' type_id=2 bits_offset=64",
- "[5] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
- "[6] STRUCT 's1' size=4 vlen=1\n"
- "\t'f1' type_id=5 bits_offset=0");
+ "[6] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[7] STRUCT 's1' size=4 vlen=1\n"
+ "\t'f1' type_id=6 bits_offset=0",
+ "[8] TYPEDEF 't1' type_id=6");
ASSERT_STREQ(btf_type_c_dump(btf2), "\
struct s1 {\n\
int f1;\n\
};\n\
\n\
+typedef int t1;\n\
+\n\
struct s1___2 {\n\
int f1;\n\
};\n\
@@ -90,7 +101,9 @@ struct s2 {\n\
struct s1___2 f1;\n\
int f2;\n\
int *f3;\n\
-};\n\n", "c_dump");
+};\n\
+\n\
+typedef int t1___2;\n\n", "c_dump");
err = btf__dedup(btf2, NULL);
if (!ASSERT_OK(err, "btf_dedup"))
@@ -102,7 +115,8 @@ struct s2 {\n\
"[2] PTR '(anon)' type_id=1",
"[3] STRUCT 's1' size=4 vlen=1\n"
"\t'f1' type_id=1 bits_offset=0",
- "[4] STRUCT 's2' size=16 vlen=3\n"
+ "[4] TYPEDEF 't1' type_id=1",
+ "[5] STRUCT 's2' size=16 vlen=3\n"
"\t'f1' type_id=3 bits_offset=0\n"
"\t'f2' type_id=1 bits_offset=32\n"
"\t'f3' type_id=2 bits_offset=64");
@@ -112,6 +126,8 @@ struct s1 {\n\
int f1;\n\
};\n\
\n\
+typedef int t1;\n\
+\n\
struct s2 {\n\
struct s1 f1;\n\
int f2;\n\
@@ -487,9 +503,8 @@ static void test_split_module(void)
for (i = 0; i < ARRAY_SIZE(mod_funcs); i++) {
const struct btf_param *p;
const struct btf_type *t;
- __u16 vlen;
+ __u32 vlen, j;
__u32 id;
- int j;
id = btf__find_by_name_kind(btf1, mod_funcs[i], BTF_KIND_FUNC);
if (!ASSERT_GE(id, nr_base_types, "func_id"))
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dump.c b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
index f1642794f70e..9f1b50e07a29 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
@@ -1027,8 +1027,8 @@ static void test_btf_dump_datasec_data(char *str)
char license[4] = "GPL";
struct btf_dump *d;
- btf = btf__parse("xdping_kern.bpf.o", NULL);
- if (!ASSERT_OK_PTR(btf, "xdping_kern.bpf.o BTF not found"))
+ btf = btf__parse("xdp_dummy.bpf.o", NULL);
+ if (!ASSERT_OK_PTR(btf, "xdp_dummy.bpf.o BTF not found"))
return;
d = btf_dump__new(btf, btf_dump_snprintf, str, NULL);
diff --git a/tools/testing/selftests/bpf/prog_tests/cb_refs.c b/tools/testing/selftests/bpf/prog_tests/cb_refs.c
index c40df623a8f7..78566b817fd7 100644
--- a/tools/testing/selftests/bpf/prog_tests/cb_refs.c
+++ b/tools/testing/selftests/bpf/prog_tests/cb_refs.c
@@ -11,8 +11,8 @@ struct {
const char *prog_name;
const char *err_msg;
} cb_refs_tests[] = {
- { "underflow_prog", "must point to scalar, or struct with scalar" },
- { "leak_prog", "Possibly NULL pointer passed to helper arg2" },
+ { "underflow_prog", "release kfunc bpf_kfunc_call_test_release expects referenced PTR_TO_BTF_ID passed to R1" },
+ { "leak_prog", "Possibly NULL pointer passed to helper R2" },
{ "nested_cb", "Unreleased reference id=4 alloc_insn=2" }, /* alloc_insn=2{4,5} */
{ "non_cb_transfer_ref", "Unreleased reference id=4 alloc_insn=1" }, /* alloc_insn=1{1,2} */
};
diff --git a/tools/testing/selftests/bpf/prog_tests/cgrp_local_storage.c b/tools/testing/selftests/bpf/prog_tests/cgrp_local_storage.c
index 478a77cb67e6..c4398ccf3493 100644
--- a/tools/testing/selftests/bpf/prog_tests/cgrp_local_storage.c
+++ b/tools/testing/selftests/bpf/prog_tests/cgrp_local_storage.c
@@ -176,7 +176,7 @@ static void test_cgroup_iter_sleepable(int cgroup_fd, __u64 cgroup_id)
DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
union bpf_iter_link_info linfo;
struct cgrp_ls_sleepable *skel;
- struct bpf_link *link;
+ struct bpf_link *link, *fexit_link;
int err, iter_fd;
char buf[16];
@@ -200,16 +200,27 @@ static void test_cgroup_iter_sleepable(int cgroup_fd, __u64 cgroup_id)
if (!ASSERT_OK_PTR(link, "attach_iter"))
goto out;
+ fexit_link = bpf_program__attach(skel->progs.fexit_update);
+ if (!ASSERT_OK_PTR(fexit_link, "attach_fexit"))
+ goto out_link;
+
iter_fd = bpf_iter_create(bpf_link__fd(link));
if (!ASSERT_GE(iter_fd, 0, "iter_create"))
- goto out_link;
+ goto out_fexit_link;
+
+ skel->bss->target_pid = sys_gettid();
/* trigger the program run */
(void)read(iter_fd, buf, sizeof(buf));
+ skel->bss->target_pid = 0;
+
+ ASSERT_EQ(skel->bss->update_err, 0, "update_err");
ASSERT_EQ(skel->bss->cgroup_id, cgroup_id, "cgroup_id");
close(iter_fd);
+out_fexit_link:
+ bpf_link__destroy(fexit_link);
out_link:
bpf_link__destroy(link);
out:
diff --git a/tools/testing/selftests/bpf/prog_tests/ctx_rewrite.c b/tools/testing/selftests/bpf/prog_tests/ctx_rewrite.c
index 469e92869523..2c3124092b73 100644
--- a/tools/testing/selftests/bpf/prog_tests/ctx_rewrite.c
+++ b/tools/testing/selftests/bpf/prog_tests/ctx_rewrite.c
@@ -69,19 +69,19 @@ static struct test_case test_cases[] = {
#if defined(__x86_64__) || defined(__aarch64__)
{
N(SCHED_CLS, struct __sk_buff, tstamp),
- .read = "r11 = *(u8 *)($ctx + sk_buff::__mono_tc_offset);"
- "if w11 & 0x4 goto pc+1;"
+ .read = "r12 = *(u8 *)($ctx + sk_buff::__mono_tc_offset);"
+ "if w12 & 0x4 goto pc+1;"
"goto pc+4;"
- "if w11 & 0x3 goto pc+1;"
+ "if w12 & 0x3 goto pc+1;"
"goto pc+2;"
"$dst = 0;"
"goto pc+1;"
"$dst = *(u64 *)($ctx + sk_buff::tstamp);",
- .write = "r11 = *(u8 *)($ctx + sk_buff::__mono_tc_offset);"
- "if w11 & 0x4 goto pc+1;"
+ .write = "r12 = *(u8 *)($ctx + sk_buff::__mono_tc_offset);"
+ "if w12 & 0x4 goto pc+1;"
"goto pc+2;"
- "w11 &= -4;"
- "*(u8 *)($ctx + sk_buff::__mono_tc_offset) = r11;"
+ "w12 &= -4;"
+ "*(u8 *)($ctx + sk_buff::__mono_tc_offset) = r12;"
"*(u64 *)($ctx + sk_buff::tstamp) = $src;",
},
#endif
@@ -253,8 +253,7 @@ static int find_field_offset_aux(struct btf *btf, int btf_id, char *field_name,
{
const struct btf_type *type = btf__type_by_id(btf, btf_id);
const struct btf_member *m;
- __u16 mnum;
- int i;
+ __u32 mnum, i;
if (!type) {
PRINT_FAIL("Can't find btf_type for id %d\n", btf_id);
diff --git a/tools/testing/selftests/bpf/prog_tests/exceptions.c b/tools/testing/selftests/bpf/prog_tests/exceptions.c
index e8cbaf2a3e82..3588d6f97fd4 100644
--- a/tools/testing/selftests/bpf/prog_tests/exceptions.c
+++ b/tools/testing/selftests/bpf/prog_tests/exceptions.c
@@ -85,6 +85,13 @@ static void test_exceptions_success(void)
RUN_SUCCESS(exception_bad_assert_range_with, 10);
RUN_SUCCESS(exception_throw_from_void_global, 11);
+ if (skel->rodata->has_stack_arg) {
+ RUN_SUCCESS(exception_throw_stack_arg, 56);
+ RUN_SUCCESS(exception_throw_after_stack_arg, 56);
+ RUN_SUCCESS(exception_throw_subprog_stack_arg, 56);
+ RUN_SUCCESS(exception_throw_subprog_after_stack_arg, 56);
+ }
+
#define RUN_EXT(load_ret, attach_err, expr, msg, after_link) \
{ \
LIBBPF_OPTS(bpf_object_open_opts, o, .kernel_log_buf = log_buf, \
diff --git a/tools/testing/selftests/bpf/prog_tests/file_reader.c b/tools/testing/selftests/bpf/prog_tests/file_reader.c
index 5cde32b35da4..48aae7ea0e4b 100644
--- a/tools/testing/selftests/bpf/prog_tests/file_reader.c
+++ b/tools/testing/selftests/bpf/prog_tests/file_reader.c
@@ -10,6 +10,7 @@
const char *user_ptr = "hello world";
char file_contents[256000];
+void *addr;
void *get_executable_base_addr(void)
{
@@ -26,8 +27,7 @@ void *get_executable_base_addr(void)
static int initialize_file_contents(void)
{
int fd, page_sz = sysconf(_SC_PAGESIZE);
- ssize_t n = 0, cur, off;
- void *addr;
+ ssize_t n = 0, cur;
fd = open("/proc/self/exe", O_RDONLY);
if (!ASSERT_OK_FD(fd, "Open /proc/self/exe\n"))
@@ -52,16 +52,6 @@ static int initialize_file_contents(void)
/* page-align base file address */
addr = (void *)((unsigned long)addr & ~(page_sz - 1));
- /*
- * Page out range 0..512K, use 0..256K for positive tests and
- * 256K..512K for negative tests expecting page faults
- */
- for (off = 0; off < sizeof(file_contents) * 2; off += page_sz) {
- if (!ASSERT_OK(madvise(addr + off, page_sz, MADV_PAGEOUT),
- "madvise pageout"))
- return errno;
- }
-
return 0;
}
@@ -90,6 +80,14 @@ static void run_test(const char *prog_name)
if (!ASSERT_OK(err, "file_reader__load"))
goto cleanup;
+ /*
+ * Page out range 0..512K, use 0..256K for positive tests and
+ * 256K..512K for negative tests expecting page faults
+ */
+ if (!ASSERT_OK(madvise(addr, sizeof(file_contents) * 2, MADV_PAGEOUT),
+ "madvise pageout"))
+ goto cleanup;
+
err = file_reader__attach(skel);
if (!ASSERT_OK(err, "file_reader__attach"))
goto cleanup;
diff --git a/tools/testing/selftests/bpf/prog_tests/fill_link_info.c b/tools/testing/selftests/bpf/prog_tests/fill_link_info.c
index e40114620751..f589eefbf9fb 100644
--- a/tools/testing/selftests/bpf/prog_tests/fill_link_info.c
+++ b/tools/testing/selftests/bpf/prog_tests/fill_link_info.c
@@ -469,7 +469,7 @@ verify_umulti_link_info(int fd, bool retprobe, __u64 *offsets,
ASSERT_EQ(info.uprobe_multi.pid, getpid(), "info.uprobe_multi.pid");
ASSERT_EQ(info.uprobe_multi.count, 3, "info.uprobe_multi.count");
- ASSERT_EQ(info.uprobe_multi.flags & BPF_F_KPROBE_MULTI_RETURN,
+ ASSERT_EQ(info.uprobe_multi.flags & BPF_F_UPROBE_MULTI_RETURN,
retprobe, "info.uprobe_multi.flags.retprobe");
ASSERT_EQ(info.uprobe_multi.path_size, strlen(path) + 1, "info.uprobe_multi.path_size");
ASSERT_STREQ(path_buf, path, "info.uprobe_multi.path");
diff --git a/tools/testing/selftests/bpf/prog_tests/htab_update.c b/tools/testing/selftests/bpf/prog_tests/htab_update.c
index ea1a6766fbe9..0a28d4346924 100644
--- a/tools/testing/selftests/bpf/prog_tests/htab_update.c
+++ b/tools/testing/selftests/bpf/prog_tests/htab_update.c
@@ -23,7 +23,7 @@ static void test_reenter_update(void)
if (!ASSERT_OK_PTR(skel, "htab_update__open"))
return;
- bpf_program__set_autoload(skel->progs.bpf_obj_free_fields, true);
+ bpf_program__set_autoload(skel->progs.bpf_obj_cancel_fields, true);
err = htab_update__load(skel);
if (!ASSERT_TRUE(!err, "htab_update__load") || err)
goto out;
@@ -50,7 +50,7 @@ static void test_reenter_update(void)
/*
* Second update: replace existing element with same key and trigger
* the reentrancy of bpf_map_update_elem().
- * check_and_free_fields() calls bpf_obj_free_fields() on the old
+ * check_and_cancel_fields() calls bpf_obj_cancel_fields() on the old
* value, which is where fentry program runs and performs a nested
* bpf_map_update_elem(), triggering -EDEADLK.
*/
diff --git a/tools/testing/selftests/bpf/prog_tests/iters.c b/tools/testing/selftests/bpf/prog_tests/iters.c
index a539980a2fbe..c0b6082f345a 100644
--- a/tools/testing/selftests/bpf/prog_tests/iters.c
+++ b/tools/testing/selftests/bpf/prog_tests/iters.c
@@ -202,8 +202,6 @@ cleanup:
iters_task__destroy(skel);
}
-extern int stack_mprotect(void);
-
static void subtest_css_task_iters(void)
{
struct iters_css_task *skel = NULL;
diff --git a/tools/testing/selftests/bpf/prog_tests/kfunc_call.c b/tools/testing/selftests/bpf/prog_tests/kfunc_call.c
index 62f3fb79f5d1..3df07680f9e0 100644
--- a/tools/testing/selftests/bpf/prog_tests/kfunc_call.c
+++ b/tools/testing/selftests/bpf/prog_tests/kfunc_call.c
@@ -68,7 +68,7 @@ static struct kfunc_test_params kfunc_tests[] = {
TC_FAIL(kfunc_call_test_get_mem_fail_oob, 0, "min value is outside of the allowed memory range"),
TC_FAIL(kfunc_call_test_get_mem_fail_not_const, 0, "is not a const"),
TC_FAIL(kfunc_call_test_mem_acquire_fail, 0, "acquire kernel function does not return PTR_TO_BTF_ID"),
- TC_FAIL(kfunc_call_test_pointer_arg_type_mismatch, 0, "arg#0 expected pointer to ctx, but got scalar"),
+ TC_FAIL(kfunc_call_test_pointer_arg_type_mismatch, 0, "R1 expected pointer to ctx, but got scalar"),
/* success cases */
TC_TEST(kfunc_call_test1, 12),
diff --git a/tools/testing/selftests/bpf/prog_tests/libarena.c b/tools/testing/selftests/bpf/prog_tests/libarena.c
new file mode 100644
index 000000000000..61ea68dce410
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/libarena.c
@@ -0,0 +1,253 @@
+// 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);
+
+}
+
+static void *run_libarena_parallel_prog(void *arg)
+{
+ struct bpf_program *prog = arg;
+
+ return (void *)(long)libarena_run_prog(bpf_program__fd(prog));
+}
+
+/* Max suffix is ceil((lg 2^32) / (lg 10)) + sizeof("__") = 10 + 2 = 12. */
+#define MAX_PARTEST_SUFFIX (12)
+#define MAX_PARTEST_NAME (1024)
+#define MAX_PARTEST_PREFIX (MAX_PARTEST_NAME - MAX_PARTEST_SUFFIX)
+
+static int run_libarena_parallel_fini(struct libarena *skel, const char *name,
+ size_t prefixlen)
+{
+ char tdname[MAX_PARTEST_NAME];
+ struct bpf_program *fini_prog;
+ int ret;
+
+ ret = snprintf(tdname, sizeof(tdname), "%.*s__fini", (int)prefixlen, name);
+ if (!ASSERT_LT(ret, sizeof(tdname), "partest fini name"))
+ return -ENAMETOOLONG;
+
+ fini_prog = bpf_object__find_program_by_name(skel->obj, tdname);
+ if (!ASSERT_TRUE(fini_prog, "partest fini prog"))
+ return -ENOENT;
+
+ ret = libarena_run_prog(bpf_program__fd(fini_prog));
+ ASSERT_OK(ret, tdname);
+
+ return ret;
+}
+
+static int run_libarena_parallel_test_workers(struct libarena *skel,
+ const char *name, size_t prefixlen)
+{
+ pthread_t *threads = NULL, *tmp_threads;
+ char tdname[MAX_PARTEST_NAME];
+ struct bpf_program *tdprog;
+ uint32_t nthreads;
+ void *thread_ret;
+ int ret, err = 0;
+ int i;
+
+ for (nthreads = 0; nthreads < UINT_MAX; nthreads++) {
+ ret = snprintf(tdname, sizeof(tdname), "%.*s__%u", (int)prefixlen,
+ name, nthreads);
+ if (!ASSERT_LT(ret, sizeof(tdname), "test worker name")) {
+ err = -ENAMETOOLONG;
+ break;
+ }
+
+ /*
+ * We enumerate the worker threads for a given test with __0, __1,
+ * and so on. The suffixes always start from 0 and are contiguous,
+ * so if we don't find a program with the requested name we have
+ * discovered all available worker programs.
+ */
+ tdprog = bpf_object__find_program_by_name(skel->obj, tdname);
+ if (!tdprog)
+ break;
+
+ /* Bump the alloc array to accommodate the new thread. */
+ tmp_threads = realloc(threads, (nthreads + 1) * sizeof(*threads));
+ if (!ASSERT_TRUE(tmp_threads, "realloc")) {
+ err = -ENOMEM;
+ break;
+ }
+ threads = tmp_threads;
+
+ ret = pthread_create(&threads[nthreads], NULL,
+ run_libarena_parallel_prog,
+ tdprog);
+ if (!ASSERT_OK(ret, "pthread_create")) {
+ err = ret;
+ break;
+ }
+ }
+
+
+ for (i = 0; i < nthreads; i++) {
+ ret = pthread_join(threads[i], &thread_ret);
+ if (!ASSERT_OK(ret, "pthread_join")) {
+ err = err ?: ret;
+ continue;
+ }
+
+ err = err ?: (long)thread_ret;
+ }
+
+ free(threads);
+
+ return err;
+}
+
+static bool libarena_parallel_test_enabled(struct libarena *skel,
+ const char *prefix,
+ size_t prefixlen)
+{
+ struct bpf_program *prog;
+ char progname[MAX_PARTEST_NAME];
+ int ret;
+
+ ret = snprintf(progname, sizeof(progname), "%.*s__enabled", (int)prefixlen,
+ prefix);
+ if (!ASSERT_LT(ret, sizeof(progname), "partest enabled name"))
+ return false;
+
+ prog = bpf_object__find_program_by_name(skel->obj, progname);
+ if (!prog)
+ return true;
+
+ ret = libarena_run_prog(bpf_program__fd(prog));
+ if (ret == -EOPNOTSUPP)
+ return false;
+ if (!ASSERT_OK(ret, progname))
+ return false;
+ return true;
+}
+
+static void run_libarena_parallel_test(struct libarena *skel, struct bpf_program *prog,
+ const char *name)
+{
+ char testname[MAX_PARTEST_NAME];
+ size_t prefixlen;
+ const char *pos;
+ int ret;
+
+ /*
+ * We annotate the initialization prog with __init. If the current prog does
+ * not match, it is one of the parallel threads instead and is ignored.
+ *
+ * We assume the test writer knows what they are doing and do not add __init
+ * randomly in the middle of a test name.
+ */
+ pos = strstr(name, "__init");
+ if (!pos)
+ return;
+
+ prefixlen = pos - name;
+ if (!ASSERT_LT(prefixlen, MAX_PARTEST_PREFIX, "partest prefix too long"))
+ return;
+
+ /* The name of the test without the __init suffix. Looks nicer in the test log. */
+ ret = snprintf(testname, sizeof(testname), "%.*s", (int)prefixlen, name);
+ if (!ASSERT_LT(ret, sizeof(testname), "partest test name"))
+ return;
+
+ if (!test__start_subtest(testname))
+ return;
+
+ if (!libarena_parallel_test_enabled(skel, testname, prefixlen)) {
+ test__skip();
+ return;
+ }
+
+ 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));
+ if (!ASSERT_OK(ret, testname))
+ return;
+
+ ret = run_libarena_parallel_test_workers(skel, name, prefixlen);
+
+ ASSERT_OK(ret, testname);
+
+ run_libarena_parallel_fini(skel, name, prefixlen);
+}
+
+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);
+
+ /*
+ * Handle parallel test progs separately. For those
+ * progs it's not a matter of test/skip, because each
+ * parallel test prog includes an initialization prog
+ * and a set of progs to be run in parallel. For the
+ * latter we do not record them as skipped or run,
+ * because we run them all at once when we come across
+ * the initialization prog. For more details on how we
+ * discover the progs see the comment on
+ * run_libarena_parallel_test.
+ */
+ if (libarena_is_parallel_test_prog(name)) {
+ run_libarena_parallel_test(skel, prog, name);
+ continue;
+ }
+
+ if (!libarena_is_test_prog(name))
+ continue;
+
+ if (!test__start_subtest(name))
+ continue;
+
+ run_libarena_test(skel, prog, name);
+ }
+
+out:
+ libarena__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/libarena_asan.c b/tools/testing/selftests/bpf/prog_tests/libarena_asan.c
new file mode 100644
index 000000000000..d59d9dd12ef2
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/libarena_asan.c
@@ -0,0 +1,93 @@
+// 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);
+
+ verify_test_stderr(skel->obj, prog);
+}
+
+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;
+}
+
diff --git a/tools/testing/selftests/bpf/prog_tests/linked_list.c b/tools/testing/selftests/bpf/prog_tests/linked_list.c
index 6f25b5f39a79..8defea0253ed 100644
--- a/tools/testing/selftests/bpf/prog_tests/linked_list.c
+++ b/tools/testing/selftests/bpf/prog_tests/linked_list.c
@@ -81,8 +81,8 @@ static struct {
{ "direct_write_node", "direct access to bpf_list_node is disallowed" },
{ "use_after_unlock_push_front", "invalid mem access 'scalar'" },
{ "use_after_unlock_push_back", "invalid mem access 'scalar'" },
- { "double_push_front", "arg#1 expected pointer to allocated object" },
- { "double_push_back", "arg#1 expected pointer to allocated object" },
+ { "double_push_front", "R2 expected pointer to allocated object" },
+ { "double_push_back", "R2 expected pointer to allocated object" },
{ "no_node_value_type", "bpf_list_node not found at offset=0" },
{ "incorrect_value_type",
"operation on bpf_list_head expects arg#1 bpf_list_node at offset=48 in struct foo, "
@@ -131,13 +131,14 @@ end:
linked_list_fail__destroy(skel);
}
-static void clear_fields(struct bpf_map *map)
+static void clear_fields(struct bpf_program *prog)
{
- char buf[24];
- int key = 0;
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ int ret;
- memset(buf, 0xff, sizeof(buf));
- ASSERT_OK(bpf_map__update_elem(map, &key, sizeof(key), buf, sizeof(buf), 0), "check_and_free_fields");
+ ret = bpf_prog_test_run_opts(bpf_program__fd(prog), &opts);
+ ASSERT_OK(ret, "clear_fields");
+ ASSERT_OK(opts.retval, "clear_fields retval");
}
enum {
@@ -170,31 +171,31 @@ static void test_linked_list_success(int mode, bool leave_in_map)
ASSERT_OK(ret, "map_list_push_pop");
ASSERT_OK(opts.retval, "map_list_push_pop retval");
if (!leave_in_map)
- clear_fields(skel->maps.array_map);
+ clear_fields(skel->progs.clear_map_list);
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.inner_map_list_push_pop), &opts);
ASSERT_OK(ret, "inner_map_list_push_pop");
ASSERT_OK(opts.retval, "inner_map_list_push_pop retval");
if (!leave_in_map)
- clear_fields(skel->maps.inner_map);
+ clear_fields(skel->progs.clear_inner_map_list);
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.global_list_push_pop), &opts);
ASSERT_OK(ret, "global_list_push_pop");
ASSERT_OK(opts.retval, "global_list_push_pop retval");
if (!leave_in_map)
- clear_fields(skel->maps.bss_A);
+ clear_fields(skel->progs.clear_global_list);
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.global_list_push_pop_nested), &opts);
ASSERT_OK(ret, "global_list_push_pop_nested");
ASSERT_OK(opts.retval, "global_list_push_pop_nested retval");
if (!leave_in_map)
- clear_fields(skel->maps.bss_A);
+ clear_fields(skel->progs.clear_global_nested_list);
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.global_list_array_push_pop), &opts);
ASSERT_OK(ret, "global_list_array_push_pop");
ASSERT_OK(opts.retval, "global_list_array_push_pop retval");
if (!leave_in_map)
- clear_fields(skel->maps.bss_A);
+ clear_fields(skel->progs.clear_global_array_list);
if (mode == PUSH_POP)
goto end;
@@ -204,19 +205,19 @@ ppm:
ASSERT_OK(ret, "map_list_push_pop_multiple");
ASSERT_OK(opts.retval, "map_list_push_pop_multiple retval");
if (!leave_in_map)
- clear_fields(skel->maps.array_map);
+ clear_fields(skel->progs.clear_map_list);
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.inner_map_list_push_pop_multiple), &opts);
ASSERT_OK(ret, "inner_map_list_push_pop_multiple");
ASSERT_OK(opts.retval, "inner_map_list_push_pop_multiple retval");
if (!leave_in_map)
- clear_fields(skel->maps.inner_map);
+ clear_fields(skel->progs.clear_inner_map_list);
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.global_list_push_pop_multiple), &opts);
ASSERT_OK(ret, "global_list_push_pop_multiple");
ASSERT_OK(opts.retval, "global_list_push_pop_multiple retval");
if (!leave_in_map)
- clear_fields(skel->maps.bss_A);
+ clear_fields(skel->progs.clear_global_list);
if (mode == PUSH_POP_MULT)
goto end;
@@ -226,19 +227,19 @@ lil:
ASSERT_OK(ret, "map_list_in_list");
ASSERT_OK(opts.retval, "map_list_in_list retval");
if (!leave_in_map)
- clear_fields(skel->maps.array_map);
+ clear_fields(skel->progs.clear_map_list);
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.inner_map_list_in_list), &opts);
ASSERT_OK(ret, "inner_map_list_in_list");
ASSERT_OK(opts.retval, "inner_map_list_in_list retval");
if (!leave_in_map)
- clear_fields(skel->maps.inner_map);
+ clear_fields(skel->progs.clear_inner_map_list);
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.global_list_in_list), &opts);
ASSERT_OK(ret, "global_list_in_list");
ASSERT_OK(opts.retval, "global_list_in_list retval");
if (!leave_in_map)
- clear_fields(skel->maps.bss_A);
+ clear_fields(skel->progs.clear_global_list);
end:
linked_list__destroy(skel);
}
diff --git a/tools/testing/selftests/bpf/prog_tests/lru_lock_nmi.c b/tools/testing/selftests/bpf/prog_tests/lru_lock_nmi.c
new file mode 100644
index 000000000000..60666a9ba41f
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/lru_lock_nmi.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Stress every LRU lock-failure and orphan-recovery.
+ * perf_event NMI BPF on every online CPU does
+ * update+delete on a small LRU map; userspace threads on every CPU do
+ * the same from syscall context.
+ */
+#define _GNU_SOURCE
+#include <pthread.h>
+#include <sched.h>
+#include <sys/syscall.h>
+#include <linux/perf_event.h>
+#include <test_progs.h>
+#include "testing_helpers.h"
+#include "lru_lock_nmi.skel.h"
+
+#define MAP_ENTRIES 64
+#define KEY_RANGE (MAP_ENTRIES * 2)
+#define STRESS_NS (500 * 1000 * 1000ULL)
+
+struct hammer_arg {
+ int map_fd;
+ int cpu;
+ __u64 deadline_ns;
+};
+
+struct refill_arg {
+ int map_fd;
+ int cpu;
+ int per_cpu_quota;
+ int update_errors;
+};
+
+/*
+ * Pin the calling thread to @cpu. Uses dynamically-allocated CPU sets so
+ * we stay correct on hosts with @cpu >= CPU_SETSIZE (default 1024).
+ */
+static int pin_to_cpu(int cpu)
+{
+ cpu_set_t *cs;
+ size_t cs_size;
+ int err;
+
+ cs = CPU_ALLOC(cpu + 1);
+ if (!cs)
+ return -ENOMEM;
+ cs_size = CPU_ALLOC_SIZE(cpu + 1);
+
+ CPU_ZERO_S(cs_size, cs);
+ CPU_SET_S(cpu, cs_size, cs);
+ err = pthread_setaffinity_np(pthread_self(), cs_size, cs);
+ CPU_FREE(cs);
+ return err;
+}
+
+static void *hammer_thread(void *p)
+{
+ struct hammer_arg *a = p;
+ int nr_possible_cpus = libbpf_num_possible_cpus();
+ __u64 val[nr_possible_cpus];
+ unsigned int seed;
+ __u32 key;
+
+ memset(val, 0, sizeof(val));
+ pin_to_cpu(a->cpu);
+
+ seed = (unsigned int)a->cpu ^ (unsigned int)(uintptr_t)pthread_self();
+
+ while (get_time_ns() < a->deadline_ns) {
+ bool do_update = rand_r(&seed) & 1;
+
+ key = rand_r(&seed) % KEY_RANGE;
+ if (do_update)
+ bpf_map_update_elem(a->map_fd, &key, val, BPF_ANY);
+ else
+ bpf_map_delete_elem(a->map_fd, &key);
+ }
+ return NULL;
+}
+
+static void *refill_thread(void *p)
+{
+ struct refill_arg *a = p;
+ int nr_possible_cpus = libbpf_num_possible_cpus();
+ __u64 val[nr_possible_cpus];
+ __u32 start, end, key;
+
+ memset(val, 0, sizeof(val));
+ pin_to_cpu(a->cpu);
+
+ start = (__u32)a->cpu * (__u32)a->per_cpu_quota;
+ end = start + (__u32)a->per_cpu_quota;
+ for (key = start; key < end; key++)
+ if (bpf_map_update_elem(a->map_fd, &key, val, BPF_ANY))
+ a->update_errors++;
+ return NULL;
+}
+
+/*
+ * Drain the map, then refill it with each CPU inserting only its own
+ * quota of keys.
+ * After refill, lookup every key we inserted - a stranded node on any
+ * CPU's pool would have forced eviction.
+ */
+static int drain_then_verify_capacity(int map_fd, int nr_cpus)
+{
+ int per_cpu_quota = MAP_ENTRIES / nr_cpus;
+ int total = per_cpu_quota * nr_cpus;
+ int nr_possible_cpus = libbpf_num_possible_cpus();
+ pthread_t threads[nr_cpus];
+ struct refill_arg args[nr_cpus];
+ __u64 val[nr_possible_cpus];
+ int i, hits = 0, nthreads = 0;
+ __u32 key;
+
+ memset(val, 0, sizeof(val));
+
+ for (key = 0; key < KEY_RANGE; key++)
+ bpf_map_delete_elem(map_fd, &key);
+
+ for (i = 0; i < nr_cpus; i++) {
+ args[i] = (struct refill_arg){
+ .map_fd = map_fd,
+ .cpu = i,
+ .per_cpu_quota = per_cpu_quota,
+ };
+ if (pthread_create(&threads[nthreads], NULL, refill_thread, &args[i]) == 0)
+ nthreads++;
+ }
+ for (i = 0; i < nthreads; i++)
+ pthread_join(threads[i], NULL);
+
+ for (i = 0; i < nr_cpus; i++)
+ if (args[i].update_errors)
+ return -ENOMEM;
+
+ for (key = 0; key < (__u32)total; key++)
+ if (bpf_map_lookup_elem(map_fd, &key, val) == 0)
+ hits++;
+
+ return hits == total ? 0 : -EIO;
+}
+
+static void run_variant(enum bpf_map_type type, __u32 map_flags, const char *name)
+{
+ struct perf_event_attr attr = {
+ .size = sizeof(attr),
+ .type = PERF_TYPE_HARDWARE,
+ .config = PERF_COUNT_HW_CPU_CYCLES,
+ .freq = 1,
+ };
+ int nr_cpus, max_cpus = 64;
+ struct bpf_link *links[max_cpus];
+ pthread_t threads[max_cpus];
+ struct hammer_arg args[max_cpus];
+ struct lru_lock_nmi *skel = NULL;
+ int map_fd, i, err, nr_threads = 0, pmu_fd = -1;
+ __u64 deadline;
+
+ nr_cpus = libbpf_num_possible_cpus();
+ if (!ASSERT_GT(nr_cpus, 0, "num_cpus"))
+ return;
+
+ if (nr_cpus > max_cpus)
+ nr_cpus = max_cpus;
+
+ if (!test__start_subtest(name))
+ return;
+
+ memset(links, 0, sizeof(links));
+ skel = lru_lock_nmi__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ goto cleanup;
+
+ err = bpf_map__set_type(skel->maps.lru_map, type);
+ if (!ASSERT_OK(err, "set_type"))
+ goto cleanup;
+ err = bpf_map__set_map_flags(skel->maps.lru_map, map_flags);
+ if (!ASSERT_OK(err, "set_flags"))
+ goto cleanup;
+ err = bpf_map__set_max_entries(skel->maps.lru_map, MAP_ENTRIES);
+ if (!ASSERT_OK(err, "set_max_entries"))
+ goto cleanup;
+
+ err = lru_lock_nmi__load(skel);
+ if (!ASSERT_OK(err, "skel_load"))
+ goto cleanup;
+
+ skel->bss->hits = 0;
+ map_fd = bpf_map__fd(skel->maps.lru_map);
+ attr.sample_freq = read_perf_max_sample_freq();
+
+ for (i = 0; i < nr_cpus; i++) {
+ pmu_fd = syscall(__NR_perf_event_open, &attr, -1, i, -1, 0);
+ if (pmu_fd < 0) {
+ if (i == 0 &&
+ (errno == ENOENT || errno == EOPNOTSUPP)) {
+ test__skip();
+ goto cleanup;
+ }
+ continue;
+ }
+ /* libbpf takes ownership of pfd on success */
+ links[i] = bpf_program__attach_perf_event(skel->progs.oncpu, pmu_fd);
+ if (!links[i])
+ close(pmu_fd);
+ }
+
+ deadline = get_time_ns() + STRESS_NS;
+ for (i = 0; i < nr_cpus; i++) {
+ args[i].map_fd = map_fd;
+ args[i].cpu = i;
+ args[i].deadline_ns = deadline;
+ if (pthread_create(&threads[nr_threads], NULL, hammer_thread, &args[i]) == 0)
+ nr_threads++;
+ }
+ for (i = 0; i < nr_threads; i++)
+ pthread_join(threads[i], NULL);
+
+ for (i = 0; i < nr_cpus; i++) {
+ if (links[i]) {
+ bpf_link__destroy(links[i]);
+ links[i] = NULL;
+ }
+ }
+
+ ASSERT_GT(skel->bss->hits, 0, "nmi_bpf_ran");
+ ASSERT_OK(drain_then_verify_capacity(map_fd, nr_cpus), "drain_then_verify_capacity");
+
+cleanup:
+ for (i = 0; i < nr_cpus; i++) {
+ if (links[i])
+ bpf_link__destroy(links[i]);
+ }
+ lru_lock_nmi__destroy(skel);
+}
+
+void serial_test_lru_lock_nmi(void)
+{
+ run_variant(BPF_MAP_TYPE_LRU_HASH, 0, "common_lru");
+ run_variant(BPF_MAP_TYPE_LRU_HASH, BPF_F_NO_COMMON_LRU, "no_common_lru");
+ run_variant(BPF_MAP_TYPE_LRU_PERCPU_HASH, 0, "percpu_lru");
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/lsm_cgroup.c b/tools/testing/selftests/bpf/prog_tests/lsm_cgroup.c
index 6df25de8f080..41e867467f6c 100644
--- a/tools/testing/selftests/bpf/prog_tests/lsm_cgroup.c
+++ b/tools/testing/selftests/bpf/prog_tests/lsm_cgroup.c
@@ -2,6 +2,7 @@
#include <sys/types.h>
#include <sys/socket.h>
+#include <sys/xattr.h>
#include <test_progs.h>
#include <bpf/btf.h>
@@ -309,11 +310,89 @@ static void test_lsm_cgroup_nonvoid(void)
lsm_cgroup_nonvoid__destroy(skel);
}
+static void test_lsm_cgroup_retval(void)
+{
+ struct lsm_cgroup *skel = NULL;
+ int skipcap_prog_fd1, skipcap_prog_fd2, socket_prog_fd1, socket_prog_fd2;
+ int cgroup_fd = -1;
+ int err, fd;
+ char tmpfile[] = "/tmp/test_lsm_cgroup_retval.XXXXXX";
+
+ fd = mkstemp(tmpfile);
+ if (!ASSERT_OK_FD(fd, "mkstemp"))
+ return;
+ close(fd);
+
+ cgroup_fd = test__join_cgroup("/default_retval");
+ if (!ASSERT_OK_FD(cgroup_fd, "join_cgroup"))
+ goto cleanup_tmpfile;
+
+ skel = lsm_cgroup__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ goto cleanup_cgroup;
+
+ skipcap_prog_fd1 = bpf_program__fd(skel->progs.skipcap_first);
+ skipcap_prog_fd2 = bpf_program__fd(skel->progs.skipcap_second);
+ socket_prog_fd1 = bpf_program__fd(skel->progs.socket_first);
+ socket_prog_fd2 = bpf_program__fd(skel->progs.socket_second);
+
+ err = bpf_prog_attach(skipcap_prog_fd1, cgroup_fd, BPF_LSM_CGROUP, BPF_F_ALLOW_MULTI);
+ if (err == -ENOTSUPP) {
+ test__skip();
+ goto cleanup_skeleton;
+ }
+ if (!ASSERT_OK(err, "attach first skipcap prog"))
+ goto cleanup_skeleton;
+
+ err = bpf_prog_attach(skipcap_prog_fd2, cgroup_fd, BPF_LSM_CGROUP, BPF_F_ALLOW_MULTI);
+ if (!ASSERT_OK(err, "attach second skipcap prog"))
+ goto cleanup_skipcap1;
+
+ err = bpf_prog_attach(socket_prog_fd1, cgroup_fd, BPF_LSM_CGROUP, BPF_F_ALLOW_MULTI);
+ if (!ASSERT_OK(err, "attach first sock_create prog"))
+ goto cleanup_skipcap2;
+
+ err = bpf_prog_attach(socket_prog_fd2, cgroup_fd, BPF_LSM_CGROUP, BPF_F_ALLOW_MULTI);
+ if (!ASSERT_OK(err, "attach second sock_create prog"))
+ goto cleanup_sock_create1;
+
+ /* trigger the bool hook by setxattr */
+ err = setxattr(tmpfile, "user.test", "value", 5, 0);
+ if (!ASSERT_OK(err, "setxattr"))
+ goto cleanup_sock_create2;
+
+ /* trigger the errno hook by creating a socket */
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (!ASSERT_OK_FD(fd, "socket"))
+ goto cleanup_sock_create2;
+ close(fd);
+
+ ASSERT_EQ(skel->data->skipcap_retval, 0, "bool_hook_retval_should_be_0");
+ ASSERT_EQ(skel->data->socket_retval, -EPERM, "errno_hook_retval_should_be_EPERM");
+
+cleanup_sock_create2:
+ bpf_prog_detach2(socket_prog_fd2, cgroup_fd, BPF_LSM_CGROUP);
+cleanup_sock_create1:
+ bpf_prog_detach2(socket_prog_fd1, cgroup_fd, BPF_LSM_CGROUP);
+cleanup_skipcap2:
+ bpf_prog_detach2(skipcap_prog_fd2, cgroup_fd, BPF_LSM_CGROUP);
+cleanup_skipcap1:
+ bpf_prog_detach2(skipcap_prog_fd1, cgroup_fd, BPF_LSM_CGROUP);
+cleanup_skeleton:
+ lsm_cgroup__destroy(skel);
+cleanup_cgroup:
+ close(cgroup_fd);
+cleanup_tmpfile:
+ unlink(tmpfile);
+}
+
void test_lsm_cgroup(void)
{
if (test__start_subtest("functional"))
test_lsm_cgroup_functional();
if (test__start_subtest("nonvoid"))
test_lsm_cgroup_nonvoid();
+ if (test__start_subtest("retval"))
+ test_lsm_cgroup_retval();
btf__free(btf);
}
diff --git a/tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c b/tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c
index b6391af5f6f9..6606f0ed9a9a 100644
--- a/tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c
+++ b/tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c
@@ -3,6 +3,7 @@
#include "network_helpers.h"
#include "test_progs.h"
+#include "test_lwt_ip_encap.skel.h"
#define BPF_FILE "test_lwt_ip_encap.bpf.o"
@@ -32,6 +33,9 @@
#define IP6_ADDR_8 "fb08::1"
#define IP6_ADDR_GRE "fb10::1"
+#define IP4_ADDR_VXLAN "172.16.17.100"
+#define IP6_ADDR_VXLAN "fb11::1"
+
#define IP6_ADDR_SRC IP6_ADDR_1
#define IP6_ADDR_DST IP6_ADDR_4
@@ -538,3 +542,144 @@ void test_lwt_ip_encap_ipv4(void)
if (test__start_subtest("ingress"))
lwt_ip_encap(IPV4_ENCAP, INGRESS, "");
}
+
+/*
+ * VxLAN Setup/topology:
+ *
+ * NS1 (IP*_ADDR_1) NS2 NS3 (IP*_ADDR_4)
+ * [ping src]
+ * | top route
+ * veth1 (LWT encap) <<-- veth2 veth3 <<-- veth4 (ping dst)
+ * | ^
+ * (bottom route) | (inner pkt)
+ * v bottom route |
+ * veth5 -->> veth6 veth7 -->> veth8 (vxlan decap)
+ * (IP*_ADDR_VXLAN)
+ *
+ * Add the VxLAN endpoint addresses to NS3's veth8, create standard
+ * VxLAN decap devices bound to those addresses, and install routes so
+ * NS1/NS2 can reach the endpoints via the bottom route. NS2 here is to
+ * make sure the LWT-encap VxLAN packets are routed to NS3 correctly.
+ */
+static int setup_vxlan_routes(const char *ns3, const char *ns1, const char *ns2)
+{
+ struct nstoken *nstoken;
+
+ nstoken = open_netns(ns3);
+ if (!ASSERT_OK_PTR(nstoken, "open ns3 for vxlan"))
+ return -1;
+
+ SYS(fail_close, "ip a add %s/32 dev veth8", IP4_ADDR_VXLAN);
+ SYS(fail_close, "ip -6 a add %s/128 dev veth8", IP6_ADDR_VXLAN);
+ /*
+ * Standard VxLAN devices to decap the encapsulated packets. The inner
+ * Ethernet frame uses a broadcast dst MAC so the IP stack accepts it
+ * without ARP or FDB configuration.
+ */
+ SYS(fail_close, "ip link add vxlan4 type vxlan id 1 dstport 4789 local %s dev veth8 nolearning noudpcsum",
+ IP4_ADDR_VXLAN);
+ SYS(fail_close, "ip link set vxlan4 up");
+ SYS(fail_close, "ip link add vxlan6 type vxlan id 1 dstport 4789 local %s dev veth8 nolearning udp6zerocsumrx",
+ IP6_ADDR_VXLAN);
+ SYS(fail_close, "ip link set vxlan6 up");
+ close_netns(nstoken);
+
+ SYS(fail, "ip -n %s route add %s/32 dev veth5 via %s",
+ ns1, IP4_ADDR_VXLAN, IP4_ADDR_6);
+ SYS(fail, "ip -n %s route add %s/32 dev veth7 via %s",
+ ns2, IP4_ADDR_VXLAN, IP4_ADDR_8);
+ SYS(fail, "ip -n %s -6 route add %s/128 dev veth5 via %s",
+ ns1, IP6_ADDR_VXLAN, IP6_ADDR_6);
+ SYS(fail, "ip -n %s -6 route add %s/128 dev veth7 via %s",
+ ns2, IP6_ADDR_VXLAN, IP6_ADDR_8);
+ return 0;
+
+fail_close:
+ close_netns(nstoken);
+fail:
+ return -1;
+}
+
+static void lwt_ip_encap_vxlan(bool ipv4_encap)
+{
+ char ns1[NETNS_NAME_SIZE] = NETNS_BASE "-1-";
+ char ns2[NETNS_NAME_SIZE] = NETNS_BASE "-2-";
+ char ns3[NETNS_NAME_SIZE] = NETNS_BASE "-3-";
+ const char *sec = ipv4_encap ? "encap_vxlan" : "encap_vxlan6";
+ int expected_offset = ipv4_encap ? (int)sizeof(struct iphdr)
+ : (int)sizeof(struct ipv6hdr);
+ struct test_lwt_ip_encap *skel = NULL;
+ int thdr_offset, err;
+
+ if (!ASSERT_OK(create_ns(ns1, NETNS_NAME_SIZE), "create ns1"))
+ goto out;
+ if (!ASSERT_OK(create_ns(ns2, NETNS_NAME_SIZE), "create ns2"))
+ goto out;
+ if (!ASSERT_OK(create_ns(ns3, NETNS_NAME_SIZE), "create ns3"))
+ goto out;
+
+ if (!ASSERT_OK(setup_network(ns1, ns2, ns3, ""), "setup network"))
+ goto out;
+
+ if (!ASSERT_OK(setup_vxlan_routes(ns3, ns1, ns2), "setup vxlan routes"))
+ goto out;
+
+ skel = test_lwt_ip_encap__open();
+ if (!ASSERT_OK_PTR(skel, "test_lwt_ip_encap__open"))
+ goto out;
+
+ bpf_program__set_autoload(skel->progs.bpf_lwt_encap_gre, false);
+ bpf_program__set_autoload(skel->progs.bpf_lwt_encap_gre6, false);
+ bpf_program__set_autoload(skel->progs.bpf_lwt_encap_vxlan, false);
+ bpf_program__set_autoload(skel->progs.bpf_lwt_encap_vxlan6, false);
+ bpf_program__set_autoload(skel->progs.fexit_lwt_push_ip_encap, true);
+ skel->rodata->tgt_ip_version = ipv4_encap ? 4 : 6;
+
+ err = test_lwt_ip_encap__load(skel);
+ if (!ASSERT_OK(err, "test_lwt_ip_encap__load"))
+ goto out;
+
+ err = test_lwt_ip_encap__attach(skel);
+ if (!ASSERT_OK(err, "test_lwt_ip_encap__attach"))
+ goto out;
+
+ /* Remove the direct NS2->DST route so packets must go via LWT encap. */
+ SYS(out, "ip -n %s route del %s/32 dev veth3", ns2, IP4_ADDR_DST);
+ SYS(out, "ip -n %s -6 route del %s/128 dev veth3", ns2, IP6_ADDR_DST);
+
+ if (ipv4_encap)
+ SYS(out, "ip -n %s route add %s encap bpf xmit obj %s sec %s dev veth1",
+ ns1, IP4_ADDR_DST, BPF_FILE, sec);
+ else
+ SYS(out, "ip -n %s -6 route add %s encap bpf xmit obj %s sec %s dev veth1",
+ ns1, IP6_ADDR_DST, BPF_FILE, sec);
+
+ skel->bss->fexit_triggered = false;
+
+ if (ipv4_encap)
+ SYS(out, "ip netns exec %s ping -c 1 -W1 %s", ns1, IP4_ADDR_DST);
+ else
+ SYS(out, "ip netns exec %s ping6 -c 1 -W1 %s", ns1, IP6_ADDR_DST);
+
+ if (!ASSERT_TRUE(skel->bss->fexit_triggered, "fexit_triggered"))
+ goto out;
+
+ thdr_offset = (int)skel->bss->transport_hdr - (int)skel->bss->network_hdr;
+ ASSERT_EQ(thdr_offset, expected_offset, "transport_hdr offset");
+
+out:
+ test_lwt_ip_encap__destroy(skel);
+ SYS_NOFAIL("ip netns del %s", ns1);
+ SYS_NOFAIL("ip netns del %s", ns2);
+ SYS_NOFAIL("ip netns del %s", ns3);
+}
+
+void test_lwt_ip_encap_vxlan_ipv4(void)
+{
+ lwt_ip_encap_vxlan(IPV4_ENCAP);
+}
+
+void test_lwt_ip_encap_vxlan_ipv6(void)
+{
+ lwt_ip_encap_vxlan(IPV6_ENCAP);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/map_excl.c b/tools/testing/selftests/bpf/prog_tests/map_excl.c
index 6bdc6d6de0da..3f4422b9ffa6 100644
--- a/tools/testing/selftests/bpf/prog_tests/map_excl.c
+++ b/tools/testing/selftests/bpf/prog_tests/map_excl.c
@@ -7,6 +7,11 @@
#include <bpf/btf.h>
#include "map_excl.skel.h"
+#include "bpf_iter_bpf_array_map.skel.h"
+
+#ifndef SHA256_DIGEST_SIZE
+#define SHA256_DIGEST_SIZE 32
+#endif
static void test_map_excl_allowed(void)
{
@@ -45,10 +50,127 @@ out:
}
+static void test_map_excl_no_map_in_map(void)
+{
+ __u8 hash[SHA256_DIGEST_SIZE] = {};
+ LIBBPF_OPTS(bpf_map_create_opts, excl_opts,
+ .excl_prog_hash = hash,
+ .excl_prog_hash_size = sizeof(hash));
+ LIBBPF_OPTS(bpf_map_create_opts, outer_opts);
+ int excl_fd, tmpl_fd = -1, outer_fd = -1, err;
+ __u32 key = 0;
+
+ excl_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "excl_inner", 4, 4, 1, &excl_opts);
+ if (!ASSERT_OK_FD(excl_fd, "create exclusive map"))
+ return;
+
+ outer_opts.inner_map_fd = excl_fd;
+ err = bpf_map_create(BPF_MAP_TYPE_ARRAY_OF_MAPS, "outer_from_excl",
+ 4, 4, 1, &outer_opts);
+ if (err >= 0)
+ close(err);
+ ASSERT_EQ(err, -ENOTSUPP, "reject exclusive map as map-in-map template");
+
+ tmpl_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "tmpl", 4, 4, 1, NULL);
+ if (!ASSERT_OK_FD(tmpl_fd, "create inner template"))
+ goto out;
+
+ outer_opts.inner_map_fd = tmpl_fd;
+ outer_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY_OF_MAPS, "outer", 4, 4, 1, &outer_opts);
+ if (!ASSERT_OK_FD(outer_fd, "create map-of-maps"))
+ goto out;
+
+ err = bpf_map_update_elem(outer_fd, &key, &excl_fd, 0);
+ ASSERT_EQ(err, -ENOTSUPP, "reject exclusive map as map-in-map element");
+out:
+ if (outer_fd >= 0)
+ close(outer_fd);
+ if (tmpl_fd >= 0)
+ close(tmpl_fd);
+ close(excl_fd);
+}
+
+static void test_map_excl_no_map_iter(void)
+{
+ __u8 hash[SHA256_DIGEST_SIZE] = {};
+ LIBBPF_OPTS(bpf_map_create_opts, excl_opts,
+ .excl_prog_hash = hash,
+ .excl_prog_hash_size = sizeof(hash));
+ DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
+ struct bpf_iter_bpf_array_map *skel = NULL;
+ union bpf_iter_link_info linfo;
+ struct bpf_link *link;
+ int excl_fd;
+
+ excl_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "excl_iter", 4, 8, 3, &excl_opts);
+ if (!ASSERT_OK_FD(excl_fd, "create exclusive map"))
+ return;
+
+ skel = bpf_iter_bpf_array_map__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "bpf_iter_bpf_array_map__open_and_load"))
+ goto out;
+
+ memset(&linfo, 0, sizeof(linfo));
+ linfo.map.map_fd = excl_fd;
+ opts.link_info = &linfo;
+ opts.link_info_len = sizeof(linfo);
+
+ link = bpf_program__attach_iter(skel->progs.dump_bpf_array_map, &opts);
+ if (!ASSERT_ERR_PTR(link, "reject exclusive map as iter target")) {
+ bpf_link__destroy(link);
+ goto out;
+ }
+ ASSERT_EQ(libbpf_get_error(link), -EPERM, "iter attach errno");
+out:
+ bpf_iter_bpf_array_map__destroy(skel);
+ close(excl_fd);
+}
+
+static void test_map_excl_create_validation(void)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, o);
+ __u8 hash[SHA256_DIGEST_SIZE] = {};
+ int fd;
+
+ o.excl_prog_hash = hash;
+ o.excl_prog_hash_size = SHA256_DIGEST_SIZE / 2;
+ fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "excl", 4, 4, 1, &o);
+ if (fd >= 0)
+ close(fd);
+ ASSERT_EQ(fd, -EINVAL, "reject short excl_prog_hash_size");
+
+ o.excl_prog_hash = hash;
+ o.excl_prog_hash_size = SHA256_DIGEST_SIZE * 2;
+ fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "excl", 4, 4, 1, &o);
+ if (fd >= 0)
+ close(fd);
+ ASSERT_EQ(fd, -EINVAL, "reject long excl_prog_hash_size");
+
+ o.excl_prog_hash = hash;
+ o.excl_prog_hash_size = 0;
+ fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "excl", 4, 4, 1, &o);
+ if (fd >= 0)
+ close(fd);
+ ASSERT_EQ(fd, -EINVAL, "reject hash pointer with zero size");
+
+ o.excl_prog_hash = NULL;
+ o.excl_prog_hash_size = SHA256_DIGEST_SIZE;
+ fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "excl", 4, 4, 1, &o);
+ if (fd >= 0)
+ close(fd);
+ ASSERT_EQ(fd, -EINVAL, "reject size with NULL hash pointer");
+}
+
void test_map_excl(void)
{
if (test__start_subtest("map_excl_allowed"))
test_map_excl_allowed();
if (test__start_subtest("map_excl_denied"))
test_map_excl_denied();
+ if (test__start_subtest("map_excl_no_map_in_map"))
+ test_map_excl_no_map_in_map();
+ if (test__start_subtest("map_excl_no_map_iter"))
+ test_map_excl_no_map_iter();
+ if (test__start_subtest("map_excl_create_validation"))
+ test_map_excl_create_validation();
}
diff --git a/tools/testing/selftests/bpf/prog_tests/map_init.c b/tools/testing/selftests/bpf/prog_tests/map_init.c
index 14a31109dd0e..c804c3ce9be9 100644
--- a/tools/testing/selftests/bpf/prog_tests/map_init.c
+++ b/tools/testing/selftests/bpf/prog_tests/map_init.c
@@ -212,3 +212,195 @@ void test_map_init(void)
if (test__start_subtest("pcpu_lru_map_init"))
test_pcpu_lru_map_init();
}
+
+static void test_map_create(enum bpf_map_type map_type, const char *map_name,
+ struct bpf_map_create_opts *opts, const char *exp_msg)
+{
+ const int key_size = 4, value_size = 4, max_entries = 1;
+ char log_buf[128];
+ int fd;
+ LIBBPF_OPTS(bpf_log_opts, log_opts);
+
+ log_buf[0] = '\0';
+ log_opts.buf = log_buf;
+ log_opts.size = sizeof(log_buf);
+ log_opts.level = 1;
+ opts->log_opts = &log_opts;
+ fd = bpf_map_create(map_type, map_name, key_size, value_size, max_entries, opts);
+ if (!ASSERT_LT(fd, 0, "bpf_map_create")) {
+ close(fd);
+ return;
+ }
+
+ ASSERT_STREQ(log_buf, exp_msg, "log_buf");
+ ASSERT_EQ(log_opts.true_size, strlen(exp_msg) + 1, "true_size");
+}
+
+static void test_map_create_array(struct bpf_map_create_opts *opts, const char *exp_msg)
+{
+ test_map_create(BPF_MAP_TYPE_ARRAY, "test_map_create", opts, exp_msg);
+}
+
+static void test_invalid_vmlinux_value_type_id_struct_ops(void)
+{
+ const char *msg = "btf_vmlinux_value_type_id can only be used with struct_ops maps.\n";
+ LIBBPF_OPTS(bpf_map_create_opts, opts,
+ .btf_vmlinux_value_type_id = 1,
+ );
+
+ test_map_create_array(&opts, msg);
+}
+
+static void test_invalid_vmlinux_value_type_id_kv_type_id(void)
+{
+ const char *msg = "btf_vmlinux_value_type_id is mutually exclusive with btf_key_type_id and btf_value_type_id.\n";
+ LIBBPF_OPTS(bpf_map_create_opts, opts,
+ .btf_vmlinux_value_type_id = 1,
+ .btf_key_type_id = 1,
+ );
+
+ test_map_create(BPF_MAP_TYPE_STRUCT_OPS, "test_map_create", &opts, msg);
+}
+
+static void test_invalid_value_type_id(void)
+{
+ const char *msg = "Invalid btf_value_type_id.\n";
+ LIBBPF_OPTS(bpf_map_create_opts, opts,
+ .btf_key_type_id = 1,
+ );
+
+ test_map_create_array(&opts, msg);
+}
+
+static void test_invalid_map_extra(void)
+{
+ const char *msg = "Invalid map_extra.\n";
+ LIBBPF_OPTS(bpf_map_create_opts, opts,
+ .map_extra = 1,
+ );
+
+ test_map_create_array(&opts, msg);
+}
+
+static void test_invalid_numa_node(void)
+{
+ const char *msg = "Invalid numa_node.\n";
+ LIBBPF_OPTS(bpf_map_create_opts, opts,
+ .map_flags = BPF_F_NUMA_NODE,
+ .numa_node = 0xFF,
+ );
+
+ test_map_create_array(&opts, msg);
+}
+
+static void test_invalid_map_type(void)
+{
+ const char *msg = "Invalid map_type.\n";
+ LIBBPF_OPTS(bpf_map_create_opts, opts);
+
+ test_map_create(__MAX_BPF_MAP_TYPE, "test_map_create", &opts, msg);
+}
+
+static void test_invalid_token_fd(void)
+{
+ const char *msg = "Invalid map_token_fd.\n";
+ LIBBPF_OPTS(bpf_map_create_opts, opts,
+ .map_flags = BPF_F_TOKEN_FD,
+ .token_fd = -1,
+ );
+
+ test_map_create_array(&opts, msg);
+}
+
+static void test_invalid_map_name(void)
+{
+ const char *msg = "Invalid map_name.\n";
+ LIBBPF_OPTS(bpf_map_create_opts, opts);
+
+ test_map_create(BPF_MAP_TYPE_ARRAY, "test-!@#", &opts, msg);
+}
+
+static void test_invalid_btf_fd(void)
+{
+ const char *msg = "Invalid btf_fd.\n";
+ LIBBPF_OPTS(bpf_map_create_opts, opts,
+ .btf_fd = -1,
+ .btf_key_type_id = 1,
+ .btf_value_type_id = 1,
+ );
+
+ test_map_create_array(&opts, msg);
+}
+
+static void test_excl_prog_hash_size_1(void)
+{
+ const char *msg = "Invalid excl_prog_hash_size.\n";
+ const char *hash = "DEADCODE";
+ LIBBPF_OPTS(bpf_map_create_opts, opts,
+ .excl_prog_hash = hash,
+ );
+
+ test_map_create_array(&opts, msg);
+}
+
+static void test_excl_prog_hash_size_2(void)
+{
+ const char *msg = "Invalid excl_prog_hash_size.\n";
+ LIBBPF_OPTS(bpf_map_create_opts, opts,
+ .excl_prog_hash_size = 1,
+ );
+
+ test_map_create_array(&opts, msg);
+}
+
+static void test_common_attr_padding(void)
+{
+ struct bpf_common_attr_fake {
+ __u8 attrs[offsetofend(struct bpf_common_attr, log_true_size)];
+ __u32 pad;
+ } attr_common = {
+ .pad = 1,
+ };
+ union bpf_attr attr = {
+ .map_type = BPF_MAP_TYPE_ARRAY,
+ .key_size = 4,
+ .value_size = 4,
+ .max_entries = 1,
+ };
+ int fd;
+
+ fd = syscall(__NR_bpf, BPF_MAP_CREATE | BPF_COMMON_ATTRS, &attr, sizeof(attr), &attr_common,
+ sizeof(attr_common));
+ if (!ASSERT_LT(fd, 0, "syscall"))
+ close(fd);
+ else
+ ASSERT_EQ(errno, E2BIG, "errno");
+}
+
+void test_map_create_failure(void)
+{
+ if (test__start_subtest("invalid_vmlinux_value_type_id_struct_ops"))
+ test_invalid_vmlinux_value_type_id_struct_ops();
+ if (test__start_subtest("invalid_vmlinux_value_type_id_kv_type_id"))
+ test_invalid_vmlinux_value_type_id_kv_type_id();
+ if (test__start_subtest("invalid_value_type_id"))
+ test_invalid_value_type_id();
+ if (test__start_subtest("invalid_map_extra"))
+ test_invalid_map_extra();
+ if (test__start_subtest("invalid_numa_node"))
+ test_invalid_numa_node();
+ if (test__start_subtest("invalid_map_type"))
+ test_invalid_map_type();
+ if (test__start_subtest("invalid_token_fd"))
+ test_invalid_token_fd();
+ if (test__start_subtest("invalid_map_name"))
+ test_invalid_map_name();
+ if (test__start_subtest("invalid_btf_fd"))
+ test_invalid_btf_fd();
+ if (test__start_subtest("invalid_excl_prog_hash_size_1"))
+ test_excl_prog_hash_size_1();
+ if (test__start_subtest("invalid_excl_prog_hash_size_2"))
+ test_excl_prog_hash_size_2();
+ if (test__start_subtest("common_attr_padding"))
+ test_common_attr_padding();
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/map_kptr.c b/tools/testing/selftests/bpf/prog_tests/map_kptr.c
index 03b46f17cf53..17e707dddda8 100644
--- a/tools/testing/selftests/bpf/prog_tests/map_kptr.c
+++ b/tools/testing/selftests/bpf/prog_tests/map_kptr.c
@@ -51,7 +51,6 @@ static void test_map_kptr_success(bool test_run)
ret = bpf_map__update_elem(skel->maps.array_map,
&key, sizeof(key), buf, sizeof(buf), 0);
ASSERT_OK(ret, "array_map update");
- skel->data->ref--;
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.test_map_kptr_ref3), &opts);
ASSERT_OK(ret, "test_map_kptr_ref3 refcount");
ASSERT_OK(opts.retval, "test_map_kptr_ref3 retval");
@@ -59,49 +58,42 @@ static void test_map_kptr_success(bool test_run)
ret = bpf_map__update_elem(skel->maps.pcpu_array_map,
&key, sizeof(key), pbuf, cpu * sizeof(buf), 0);
ASSERT_OK(ret, "pcpu_array_map update");
- skel->data->ref--;
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.test_map_kptr_ref3), &opts);
ASSERT_OK(ret, "test_map_kptr_ref3 refcount");
ASSERT_OK(opts.retval, "test_map_kptr_ref3 retval");
ret = bpf_map__delete_elem(skel->maps.hash_map, &key, sizeof(key), 0);
ASSERT_OK(ret, "hash_map delete");
- skel->data->ref--;
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.test_map_kptr_ref3), &opts);
ASSERT_OK(ret, "test_map_kptr_ref3 refcount");
ASSERT_OK(opts.retval, "test_map_kptr_ref3 retval");
ret = bpf_map__delete_elem(skel->maps.pcpu_hash_map, &key, sizeof(key), 0);
ASSERT_OK(ret, "pcpu_hash_map delete");
- skel->data->ref--;
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.test_map_kptr_ref3), &opts);
ASSERT_OK(ret, "test_map_kptr_ref3 refcount");
ASSERT_OK(opts.retval, "test_map_kptr_ref3 retval");
ret = bpf_map__delete_elem(skel->maps.hash_malloc_map, &key, sizeof(key), 0);
ASSERT_OK(ret, "hash_malloc_map delete");
- skel->data->ref--;
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.test_map_kptr_ref3), &opts);
ASSERT_OK(ret, "test_map_kptr_ref3 refcount");
ASSERT_OK(opts.retval, "test_map_kptr_ref3 retval");
ret = bpf_map__delete_elem(skel->maps.pcpu_hash_malloc_map, &key, sizeof(key), 0);
ASSERT_OK(ret, "pcpu_hash_malloc_map delete");
- skel->data->ref--;
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.test_map_kptr_ref3), &opts);
ASSERT_OK(ret, "test_map_kptr_ref3 refcount");
ASSERT_OK(opts.retval, "test_map_kptr_ref3 retval");
ret = bpf_map__delete_elem(skel->maps.lru_hash_map, &key, sizeof(key), 0);
ASSERT_OK(ret, "lru_hash_map delete");
- skel->data->ref--;
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.test_map_kptr_ref3), &opts);
ASSERT_OK(ret, "test_map_kptr_ref3 refcount");
ASSERT_OK(opts.retval, "test_map_kptr_ref3 retval");
ret = bpf_map__delete_elem(skel->maps.lru_pcpu_hash_map, &key, sizeof(key), 0);
ASSERT_OK(ret, "lru_pcpu_hash_map delete");
- skel->data->ref--;
ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.test_map_kptr_ref3), &opts);
ASSERT_OK(ret, "test_map_kptr_ref3 refcount");
ASSERT_OK(opts.retval, "test_map_kptr_ref3 retval");
@@ -151,12 +143,68 @@ static void wait_for_map_release(void)
map_kptr__destroy(skel);
}
+enum map_update_kptr_case {
+ MAP_UPDATE_KPTR_ARRAY,
+ MAP_UPDATE_KPTR_HASH,
+ MAP_UPDATE_KPTR_HASH_MALLOC,
+};
+
+static struct bpf_program *map_update_kptr_prog(struct map_kptr *skel,
+ enum map_update_kptr_case test)
+{
+ switch (test) {
+ case MAP_UPDATE_KPTR_ARRAY:
+ return skel->progs.test_array_map_update_kptr;
+ case MAP_UPDATE_KPTR_HASH:
+ return skel->progs.test_hash_map_update_kptr;
+ case MAP_UPDATE_KPTR_HASH_MALLOC:
+ return skel->progs.test_hash_malloc_map_update_kptr;
+ }
+
+ return NULL;
+}
+
+static void test_map_update_kptr(enum map_update_kptr_case test)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ struct map_kptr *skel;
+ struct bpf_program *prog;
+ int ret;
+
+ skel = map_kptr__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "map_kptr__open_and_load"))
+ return;
+
+ prog = map_update_kptr_prog(skel, test);
+ if (!ASSERT_OK_PTR(prog, "map_update_kptr_prog"))
+ goto out;
+
+ ret = bpf_prog_test_run_opts(bpf_program__fd(prog), &opts);
+ if (!ASSERT_OK(ret, "map_update_kptr"))
+ goto out;
+ if (!ASSERT_OK(opts.retval, "map_update_kptr retval"))
+ goto out;
+
+ ASSERT_EQ(skel->bss->num_of_refs, 3, "refs_after_update");
+
+out:
+ map_kptr__destroy(skel);
+ wait_for_map_release();
+}
+
void serial_test_map_kptr(void)
{
struct rcu_tasks_trace_gp *skel;
RUN_TESTS(map_kptr_fail);
+ if (test__start_subtest("update_array_map_kptr"))
+ test_map_update_kptr(MAP_UPDATE_KPTR_ARRAY);
+ if (test__start_subtest("update_hash_map_kptr"))
+ test_map_update_kptr(MAP_UPDATE_KPTR_HASH);
+ if (test__start_subtest("update_hash_malloc_map_kptr"))
+ test_map_update_kptr(MAP_UPDATE_KPTR_HASH_MALLOC);
+
skel = rcu_tasks_trace_gp__open_and_load();
if (!ASSERT_OK_PTR(skel, "rcu_tasks_trace_gp__open_and_load"))
return;
@@ -175,7 +223,7 @@ void serial_test_map_kptr(void)
ASSERT_OK(kern_sync_rcu(), "sync rcu");
wait_for_map_release();
- /* Observe refcount dropping to 1 on synchronous delete elem */
+ /* Observe refcount dropping to 1 on map release. */
test_map_kptr_success(true);
}
diff --git a/tools/testing/selftests/bpf/prog_tests/refcounted_kptr.c b/tools/testing/selftests/bpf/prog_tests/refcounted_kptr.c
index d2c0542716a8..1737eba34323 100644
--- a/tools/testing/selftests/bpf/prog_tests/refcounted_kptr.c
+++ b/tools/testing/selftests/bpf/prog_tests/refcounted_kptr.c
@@ -57,6 +57,7 @@ void test_percpu_hash_refcounted_kptr_refcount_leak(void)
.data_size_in = sizeof(pkt_v4),
.repeat = 1,
);
+ LIBBPF_OPTS(bpf_test_run_opts, syscall_opts);
cpu_nr = libbpf_num_possible_cpus();
if (!ASSERT_GT(cpu_nr, 0, "libbpf_num_possible_cpus"))
@@ -87,8 +88,11 @@ void test_percpu_hash_refcounted_kptr_refcount_leak(void)
if (!ASSERT_EQ(opts.retval, 2, "opts.retval"))
goto out;
- err = bpf_map__update_elem(map, &key, sizeof(key), values, values_sz, 0);
- if (!ASSERT_OK(err, "bpf_map__update_elem"))
+ fd = bpf_program__fd(skel->progs.clear_percpu_hash_kptr);
+ err = bpf_prog_test_run_opts(fd, &syscall_opts);
+ if (!ASSERT_OK(err, "bpf_prog_test_run_opts"))
+ goto out;
+ if (!ASSERT_EQ(syscall_opts.retval, 1, "syscall_opts.retval"))
goto out;
fd = bpf_program__fd(skel->progs.check_percpu_hash_refcount);
diff --git a/tools/testing/selftests/bpf/prog_tests/reg_bounds.c b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c
index 71f5240cc5b7..7f170a69d1d8 100644
--- a/tools/testing/selftests/bpf/prog_tests/reg_bounds.c
+++ b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c
@@ -478,6 +478,52 @@ static struct range range_refine_in_halves(enum num_t x_t, struct range x,
}
+static __always_inline u64 next_u32_block(u64 x) { return x + (1ULL << 32); }
+static __always_inline u64 prev_u32_block(u64 x) { return x - (1ULL << 32); }
+
+/* Is v within the circular u64 range [base, base + len]? */
+static __always_inline bool u64_range_contains(u64 v, u64 base, u64 len)
+{
+ return v - base <= len;
+}
+
+/* Is v within the circular u32 range [base, base + len]? */
+static __always_inline bool u32_range_contains(u32 v, u32 base, u32 len)
+{
+ return v - base <= len;
+}
+
+static bool range64_range32_intersect(enum num_t a_t,
+ struct range a /* 64 */,
+ struct range b /* 32 */,
+ struct range *out /* 64 */)
+{
+ u64 b_len = (u32)(b.b - b.a);
+ u64 a_len = a.b - a.a;
+ u64 lo, hi;
+
+ if (u32_range_contains((u32)a.a, (u32)b.a, b_len)) {
+ lo = a.a;
+ } else {
+ lo = swap_low32(a.a, (u32)b.a);
+ if (!u64_range_contains(lo, a.a, a_len))
+ lo = next_u32_block(lo);
+ if (!u64_range_contains(lo, a.a, a_len))
+ return false;
+ }
+ if (u32_range_contains(a.b, (u32)b.a, b_len)) {
+ hi = a.b;
+ } else {
+ hi = swap_low32(a.b, (u32)b.b);
+ if (!u64_range_contains(hi, a.a, a_len))
+ hi = prev_u32_block(hi);
+ if (!u64_range_contains(hi, a.a, a_len))
+ return false;
+ }
+ *out = range(a_t, lo, hi);
+ return true;
+}
+
static struct range range_refine(enum num_t x_t, struct range x, enum num_t y_t, struct range y)
{
struct range y_cast;
@@ -533,23 +579,12 @@ static struct range range_refine(enum num_t x_t, struct range x, enum num_t y_t,
}
}
- /* the case when new range knowledge, *y*, is a 32-bit subregister
- * range, while previous range knowledge, *x*, is a full register
- * 64-bit range, needs special treatment to take into account upper 32
- * bits of full register range
- */
if (t_is_32(y_t) && !t_is_32(x_t)) {
- struct range x_swap;
+ struct range x1;
- /* some combinations of upper 32 bits and sign bit can lead to
- * invalid ranges, in such cases it's easier to detect them
- * after cast/swap than try to enumerate all the conditions
- * under which transformation and knowledge transfer is valid
- */
- x_swap = range(x_t, swap_low32(x.a, y_cast.a), swap_low32(x.b, y_cast.b));
- if (!is_valid_range(x_t, x_swap))
- return x;
- return range_intersection(x_t, x, x_swap);
+ if (range64_range32_intersect(x_t, x, y, &x1))
+ return x1;
+ return x;
}
/* otherwise, plain range cast and intersection works */
@@ -1300,6 +1335,26 @@ static bool assert_range_eq(enum num_t t, struct range x, struct range y,
return false;
}
+/* For a pair of signed/unsigned t1/t2 checks if r1/r2 intersect in two intervals. */
+static bool needs_two_arcs(enum num_t t1, struct range r1,
+ enum num_t t2, struct range r2)
+{
+ u64 lo = cast_t(t1, r2.a);
+ u64 hi = cast_t(t1, r2.b);
+
+ /* does r2 wrap in t1's domain: [0, hi] ∪ [lo, MAX]? */
+ return lo > hi && r1.a <= hi && r1.b >= lo;
+}
+
+static bool reg_state_needs_two_arcs(struct reg_state *s)
+{
+ if (!s->valid)
+ return false;
+
+ return needs_two_arcs(U64, s->r[U64], S64, s->r[S64]) ||
+ needs_two_arcs(U32, s->r[U32], S32, s->r[S32]);
+}
+
/* Validate that register states match, and print details if they don't */
static bool assert_reg_state_eq(struct reg_state *r, struct reg_state *e, const char *ctx)
{
@@ -1524,6 +1579,11 @@ static int verify_case_op(enum num_t init_t, enum num_t cond_t,
!assert_reg_state_eq(&fr2, &fe2, "false_reg2") ||
!assert_reg_state_eq(&tr1, &te1, "true_reg1") ||
!assert_reg_state_eq(&tr2, &te2, "true_reg2")) {
+ if (reg_state_needs_two_arcs(&fe1) || reg_state_needs_two_arcs(&fe2) ||
+ reg_state_needs_two_arcs(&te1) || reg_state_needs_two_arcs(&te2)) {
+ test__skip();
+ return 0;
+ }
failed = true;
}
diff --git a/tools/testing/selftests/bpf/prog_tests/rhash.c b/tools/testing/selftests/bpf/prog_tests/rhash.c
new file mode 100644
index 000000000000..98bb66907b7f
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/rhash.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <test_progs.h>
+#include <string.h>
+#include <stdio.h>
+#include "rhash.skel.h"
+#include "bpf_iter_bpf_rhash_map.skel.h"
+#include <linux/bpf.h>
+#include <linux/perf_event.h>
+#include <sys/syscall.h>
+
+static void rhash_run(const char *prog_name)
+{
+ struct rhash *skel;
+ struct bpf_program *prog;
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ int err;
+
+ skel = rhash__open();
+ if (!ASSERT_OK_PTR(skel, "rhash__open"))
+ return;
+
+ prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+ if (!ASSERT_OK_PTR(prog, "bpf_object__find_program_by_name"))
+ goto cleanup;
+ bpf_program__set_autoload(prog, true);
+
+ err = rhash__load(skel);
+ if (!ASSERT_OK(err, "skel_load"))
+ goto cleanup;
+
+ err = bpf_prog_test_run_opts(bpf_program__fd(prog), &opts);
+ if (!ASSERT_OK(err, "prog run"))
+ goto cleanup;
+
+ if (!ASSERT_OK(opts.retval, "prog retval"))
+ goto cleanup;
+
+ if (!ASSERT_OK(skel->bss->err, "bss->err"))
+ goto cleanup;
+
+cleanup:
+ rhash__destroy(skel);
+}
+
+static int rhash_map_create(__u32 max_entries, __u64 map_extra)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, opts,
+ .map_flags = BPF_F_NO_PREALLOC,
+ .map_extra = map_extra);
+
+ return bpf_map_create(BPF_MAP_TYPE_RHASH, "rhash_extra",
+ sizeof(__u32), sizeof(__u64), max_entries, &opts);
+}
+
+static void rhash_map_extra_presize(void)
+{
+ const __u32 max_entries = 1024;
+ const __u32 nelem_hint = 256;
+ struct bpf_map_info info = {};
+ __u32 info_len = sizeof(info);
+ __u64 val = 0;
+ __u32 key;
+ int fd, i;
+
+ fd = rhash_map_create(max_entries, nelem_hint);
+ if (!ASSERT_GE(fd, 0, "rhash_map_create presize"))
+ return;
+
+ if (!ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &info_len), "info"))
+ goto close;
+ ASSERT_EQ(info.map_extra, nelem_hint, "info.map_extra");
+
+ for (i = 0; i < (int)nelem_hint; i++) {
+ key = i;
+ if (!ASSERT_OK(bpf_map_update_elem(fd, &key, &val, BPF_NOEXIST),
+ "update"))
+ goto close;
+ }
+close:
+ close(fd);
+}
+
+static void rhash_map_extra_too_big(void)
+{
+ int fd;
+
+ fd = rhash_map_create(1U << 20, 0x10000);
+ if (!ASSERT_LT(fd, 0, "rhash_map_create hint > U16_MAX"))
+ close(fd);
+}
+
+static void rhash_iter_test(void)
+{
+ DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
+ struct bpf_iter_bpf_rhash_map *skel;
+ int err, i, len, map_fd, iter_fd;
+ union bpf_iter_link_info linfo;
+ u32 expected_key_sum = 0, key;
+ struct bpf_link *link;
+ u64 val = 0;
+ char buf[64];
+
+ skel = bpf_iter_bpf_rhash_map__open();
+ if (!ASSERT_OK_PTR(skel, "bpf_iter_bpf_rhash_map__open"))
+ return;
+
+ err = bpf_iter_bpf_rhash_map__load(skel);
+ if (!ASSERT_OK(err, "bpf_iter_bpf_rhash_map__load"))
+ goto out;
+
+ map_fd = bpf_map__fd(skel->maps.rhashmap);
+
+ /* Populate map with test data */
+ for (i = 0; i < 64; i++) {
+ key = i + 1;
+ expected_key_sum += key;
+
+ err = bpf_map_update_elem(map_fd, &key, &val, BPF_NOEXIST);
+ if (!ASSERT_OK(err, "map_update"))
+ goto out;
+ }
+
+ memset(&linfo, 0, sizeof(linfo));
+ linfo.map.map_fd = map_fd;
+ opts.link_info = &linfo;
+ opts.link_info_len = sizeof(linfo);
+
+ link = bpf_program__attach_iter(skel->progs.dump_bpf_rhash_map, &opts);
+ if (!ASSERT_OK_PTR(link, "attach_iter"))
+ goto out;
+
+ iter_fd = bpf_iter_create(bpf_link__fd(link));
+ if (!ASSERT_GE(iter_fd, 0, "create_iter"))
+ goto free_link;
+
+ do {
+ len = read(iter_fd, buf, sizeof(buf));
+ } while (len > 0);
+
+ ASSERT_EQ(skel->bss->key_sum, expected_key_sum, "key_sum");
+ ASSERT_EQ(skel->bss->elem_count, 64, "elem_count");
+
+ close(iter_fd);
+
+free_link:
+ bpf_link__destroy(link);
+out:
+ bpf_iter_bpf_rhash_map__destroy(skel);
+}
+
+void test_rhash(void)
+{
+ if (test__start_subtest("test_rhash_lookup_update"))
+ rhash_run("test_rhash_lookup_update");
+
+ if (test__start_subtest("test_rhash_update_delete"))
+ rhash_run("test_rhash_update_delete");
+
+ if (test__start_subtest("test_rhash_update_elements"))
+ rhash_run("test_rhash_update_elements");
+
+ if (test__start_subtest("test_rhash_update_exist"))
+ rhash_run("test_rhash_update_exist");
+
+ if (test__start_subtest("test_rhash_update_any"))
+ rhash_run("test_rhash_update_any");
+
+ if (test__start_subtest("test_rhash_noexist_duplicate"))
+ rhash_run("test_rhash_noexist_duplicate");
+
+ if (test__start_subtest("test_rhash_delete_nonexistent"))
+ rhash_run("test_rhash_delete_nonexistent");
+
+ if (test__start_subtest("test_rhash_map_extra_presize"))
+ rhash_map_extra_presize();
+
+ if (test__start_subtest("test_rhash_map_extra_too_big"))
+ rhash_map_extra_too_big();
+
+ if (test__start_subtest("test_rhash_iter"))
+ rhash_iter_test();
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/setget_sockopt.c b/tools/testing/selftests/bpf/prog_tests/setget_sockopt.c
index 77fe1bfb7504..4e91d9b615ce 100644
--- a/tools/testing/selftests/bpf/prog_tests/setget_sockopt.c
+++ b/tools/testing/selftests/bpf/prog_tests/setget_sockopt.c
@@ -199,6 +199,83 @@ err_out:
bpf_link__destroy(getsockopt_link);
}
+static int connect_to_v4mapped_v6_fd(int server_fd)
+{
+ struct sockaddr_storage addr;
+ struct sockaddr_in *addr4 = (void *)&addr;
+ socklen_t addrlen = sizeof(addr);
+ struct sockaddr_in6 addr6 = {};
+ int fd = -1, v6only = 0, err;
+
+ err = getsockname(server_fd, (struct sockaddr *)&addr, &addrlen);
+ if (!ASSERT_OK(err, "getsockname"))
+ return -1;
+
+ fd = socket(AF_INET6, SOCK_STREAM, 0);
+ if (!ASSERT_GE(fd, 0, "socket"))
+ return -1;
+
+ err = settimeo(fd, 0);
+ if (!ASSERT_OK(err, "settimeo"))
+ goto err_out;
+
+ err = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only));
+ if (!ASSERT_OK(err, "clear_v6only"))
+ goto err_out;
+
+ addr6.sin6_family = AF_INET6;
+ addr6.sin6_port = addr4->sin_port;
+ addr6.sin6_addr.s6_addr[10] = 0xff;
+ addr6.sin6_addr.s6_addr[11] = 0xff;
+ memcpy(&addr6.sin6_addr.s6_addr[12], &addr4->sin_addr, sizeof(addr4->sin_addr));
+
+ err = connect(fd, (struct sockaddr *)&addr6, sizeof(addr6));
+ if (!ASSERT_OK(err, "connect"))
+ goto err_out;
+
+ return fd;
+
+err_out:
+ close(fd);
+ return -1;
+}
+
+static void test_v4mapped_v6_ip_tos(void)
+{
+ struct setget_sockopt__bss *bss = skel->bss;
+ int sfd = -1, fd = -1, got = 0, exp = 0x1c;
+ socklen_t optlen;
+
+ memset(bss, 0, sizeof(*bss));
+ bss->v4mapped_v6_ip_tos_enable = 1;
+ bss->v4mapped_v6_ip_tos_ret = -1;
+ bss->v4mapped_v6_ip_tos_val = exp;
+
+ sfd = start_server(AF_INET, SOCK_STREAM, addr4_str, 0, 0);
+ if (!ASSERT_GE(sfd, 0, "start_server"))
+ goto err_out;
+
+ fd = connect_to_v4mapped_v6_fd(sfd);
+ if (!ASSERT_GE(fd, 0, "connect_to_v4mapped_v6_fd"))
+ goto err_out;
+
+ ASSERT_GT(bss->v4mapped_v6_ip_tos_cnt, 0, "v4mapped_v6_ip_tos_cnt");
+ ASSERT_EQ(bss->v4mapped_v6_ip_tos_ret, 0, "v4mapped_v6_ip_tos_ret");
+
+ optlen = sizeof(got);
+ if (!ASSERT_OK(getsockopt(fd, SOL_IP, IP_TOS, &got, &optlen), "getsockopt_ip_tos"))
+ goto err_out;
+
+ ASSERT_EQ(got, exp, "ip_tos");
+
+err_out:
+ bss->v4mapped_v6_ip_tos_enable = 0;
+ if (fd >= 0)
+ close(fd);
+ if (sfd >= 0)
+ close(sfd);
+}
+
void test_setget_sockopt(void)
{
cg_fd = test__join_cgroup(CG_NAME);
@@ -238,6 +315,7 @@ void test_setget_sockopt(void)
test_ktls(AF_INET);
test_nonstandard_opt(AF_INET);
test_nonstandard_opt(AF_INET6);
+ test_v4mapped_v6_ip_tos();
done:
setget_sockopt__destroy(skel);
diff --git a/tools/testing/selftests/bpf/prog_tests/signed_loader.c b/tools/testing/selftests/bpf/prog_tests/signed_loader.c
new file mode 100644
index 000000000000..5fc417e31fc6
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/signed_loader.c
@@ -0,0 +1,1135 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include <test_progs.h>
+#include <sys/syscall.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/keyctl.h>
+#include <linux/bpf.h>
+
+#include "bpf/libbpf_internal.h" /* for libbpf_sha256() */
+#include "bpf/skel_internal.h" /* for loader ctx layout (bpf_loader_ctx etc) */
+
+#include "test_signed_loader.skel.h"
+#include "test_signed_loader_map.skel.h"
+#include "test_signed_loader_data.skel.h"
+#include "test_signed_loader_lsm.skel.h"
+
+#define SIG_MATCH_INSNS 33 /* excl (5) + 4 * sha-dword (7) */
+
+enum {
+ BPF_SIG_UNSIGNED = 0,
+ BPF_SIG_VERIFIED,
+};
+
+enum {
+ BPF_SIG_KEYRING_NONE = 0,
+ BPF_SIG_KEYRING_BUILTIN,
+ BPF_SIG_KEYRING_SECONDARY,
+ BPF_SIG_KEYRING_PLATFORM,
+ BPF_SIG_KEYRING_USER,
+};
+
+static int load_loader(const void *insns, __u32 insns_sz, int map_fd,
+ const void *sig, __u32 sig_sz, __s32 keyring_id)
+{
+ union bpf_attr attr;
+ int fd;
+
+ memset(&attr, 0, sizeof(attr));
+ attr.prog_type = BPF_PROG_TYPE_SYSCALL;
+ attr.insns = ptr_to_u64(insns);
+ attr.insn_cnt = insns_sz / sizeof(struct bpf_insn);
+ attr.license = ptr_to_u64("Dual BSD/GPL");
+ attr.prog_flags = BPF_F_SLEEPABLE;
+ attr.fd_array = ptr_to_u64(&map_fd);
+ if (sig) {
+ attr.signature = ptr_to_u64(sig);
+ attr.signature_size = sig_sz;
+ attr.keyring_id = keyring_id;
+ }
+ memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
+ fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
+ offsetofend(union bpf_attr, keyring_id));
+ return fd < 0 ? -errno : fd;
+}
+
+static int run_gen_loader(const void *insns, __u32 insns_sz,
+ const void *data, __u32 data_sz,
+ const void *excl, __u32 excl_sz,
+ const void *sig, __u32 sig_sz,
+ bool get_hash, void *ctx, __u32 ctx_sz, bool *loader_ran)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, mopts,
+ .excl_prog_hash = excl,
+ .excl_prog_hash_size = excl_sz);
+ __u8 hbuf[SHA256_DIGEST_LENGTH];
+ struct bpf_map_info info;
+ __u32 ilen = sizeof(info), key = 0;
+ union bpf_attr attr;
+ int map_fd, prog_fd, ret;
+
+ *loader_ran = false;
+
+ map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map",
+ 4, data_sz, 1, &mopts);
+ if (map_fd < 0)
+ return -errno;
+ if (bpf_map_update_elem(map_fd, &key, data, 0)) {
+ ret = -errno;
+ goto out_map;
+ }
+ if (bpf_map_freeze(map_fd)) {
+ ret = -errno;
+ goto out_map;
+ }
+ if (get_hash) {
+ memset(&info, 0, sizeof(info));
+ info.hash = ptr_to_u64(hbuf);
+ info.hash_size = sizeof(hbuf);
+ if (bpf_map_get_info_by_fd(map_fd, &info, &ilen)) {
+ ret = -errno;
+ goto out_map;
+ }
+ }
+
+ memset(&attr, 0, sizeof(attr));
+ attr.prog_type = BPF_PROG_TYPE_SYSCALL;
+ attr.insns = ptr_to_u64(insns);
+ attr.insn_cnt = insns_sz / sizeof(struct bpf_insn);
+ attr.license = ptr_to_u64("Dual BSD/GPL");
+ attr.prog_flags = BPF_F_SLEEPABLE;
+ attr.fd_array = ptr_to_u64(&map_fd);
+ if (sig) {
+ attr.signature = ptr_to_u64(sig);
+ attr.signature_size = sig_sz;
+ attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
+ }
+ memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
+ prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
+ offsetofend(union bpf_attr, keyring_id));
+ if (prog_fd < 0) {
+ ret = -errno;
+ goto out_map;
+ }
+
+ memset(&attr, 0, sizeof(attr));
+ attr.test.prog_fd = prog_fd;
+ attr.test.ctx_in = ptr_to_u64(ctx);
+ attr.test.ctx_size_in = ctx_sz;
+ if (syscall(__NR_bpf, BPF_PROG_RUN, &attr,
+ offsetofend(union bpf_attr, test)) < 0) {
+ ret = -errno;
+ goto out_prog;
+ }
+ *loader_ran = true;
+ ret = (int)attr.test.retval;
+out_prog:
+ close(prog_fd);
+out_map:
+ close(map_fd);
+ return ret;
+}
+
+static void close_loader_ctx_fds(void *ctx, int nr_maps, int nr_progs)
+{
+ struct bpf_map_desc *md = (struct bpf_map_desc *)((char *)ctx +
+ sizeof(struct bpf_loader_ctx));
+ struct bpf_prog_desc *pd = (struct bpf_prog_desc *)(md + nr_maps);
+ int i;
+
+ for (i = 0; i < nr_maps; i++)
+ if (md[i].map_fd > 0)
+ close(md[i].map_fd);
+ for (i = 0; i < nr_progs; i++)
+ if (pd[i].prog_fd > 0)
+ close(pd[i].prog_fd);
+}
+
+static int run_setup(const char *cmd, const char *dir)
+{
+ int pid, status;
+
+ pid = fork();
+ if (pid < 0)
+ return -errno;
+ if (pid == 0) {
+ execlp("./verify_sig_setup.sh", "./verify_sig_setup.sh",
+ cmd, dir, NULL);
+ exit(1);
+ }
+ if (waitpid(pid, &status, 0) < 0)
+ return -errno;
+ return (WIFEXITED(status) &&
+ WEXITSTATUS(status) == 0) ? 0 : -EINVAL;
+}
+
+static int sign_buf(const char *dir, const void *buf, __u32 len,
+ void *sig, __u32 *sig_sz)
+{
+ char data_tmpl[PATH_MAX], key[PATH_MAX];
+ char sigpath[PATH_MAX + sizeof(".p7s")];
+ int fd, pid, status, ret;
+ struct stat st;
+
+ ret = snprintf(data_tmpl, sizeof(data_tmpl), "%s/dataXXXXXX", dir);
+ if (ret < 0 || ret >= (int)sizeof(data_tmpl))
+ return -ENAMETOOLONG;
+ ret = 0;
+
+ fd = mkstemp(data_tmpl);
+ if (fd < 0)
+ return -errno;
+ if (write(fd, buf, len) != (ssize_t)len) {
+ close(fd);
+ ret = -EIO;
+ goto out;
+ }
+ close(fd);
+
+ pid = fork();
+ if (pid < 0) {
+ ret = -errno;
+ goto out;
+ }
+ if (pid == 0) {
+ snprintf(key, sizeof(key), "%s/signing_key.pem", dir);
+ execlp("./sign-file", "./sign-file", "-d", "sha256",
+ key, key, data_tmpl, NULL);
+ exit(1);
+ }
+ if (waitpid(pid, &status, 0) < 0 ||
+ !WIFEXITED(status) || WEXITSTATUS(status)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ snprintf(sigpath, sizeof(sigpath), "%s.p7s", data_tmpl);
+ if (stat(sigpath, &st) < 0) {
+ ret = -errno;
+ goto out;
+ }
+ if (st.st_size > (off_t)*sig_sz) {
+ ret = -E2BIG;
+ goto out_sig;
+ }
+ fd = open(sigpath, O_RDONLY);
+ if (fd < 0) {
+ ret = -errno;
+ goto out_sig;
+ }
+ if (read(fd, sig, st.st_size) != st.st_size) {
+ close(fd);
+ ret = -EIO;
+ goto out_sig;
+ }
+ close(fd);
+ *sig_sz = st.st_size;
+out_sig:
+ unlink(sigpath);
+out:
+ unlink(data_tmpl);
+ return ret;
+}
+
+static void check_sig_match_shape(const struct bpf_insn *in, int n)
+{
+ int a = -1, cleanup = -1, i, base, t, br[5], nb = 0;
+
+ /* BPF_PSEUDO_MAP_IDX (the struct bpf_map * form) is used only here. */
+ for (i = 0; i + 1 < n; i++) {
+ if (in[i].code == (BPF_LD | BPF_IMM | BPF_DW) &&
+ in[i].src_reg == BPF_PSEUDO_MAP_IDX) {
+ a = i;
+ break;
+ }
+ }
+ if (!ASSERT_GE(a, 0, "emit_signature_match present"))
+ return;
+ if (!ASSERT_LE(a + SIG_MATCH_INSNS, n, "block fits in program"))
+ return;
+
+ /* excl check: r2 = *(u32 *)(map + 32); if r2 != 1 goto cleanup */
+ ASSERT_EQ(in[a + 2].code, (BPF_LDX | BPF_MEM | BPF_W), "excl load width");
+ ASSERT_EQ(in[a + 2].off, SHA256_DIGEST_LENGTH, "excl field offset");
+ ASSERT_EQ(in[a + 4].code, (BPF_JMP | BPF_JNE | BPF_K), "excl branch op");
+ ASSERT_EQ(in[a + 4].imm, 1, "excl compared to 1");
+ br[nb++] = a + 4;
+
+ /* 4 sha-dword checks: r2 = *(u64 *)(map + i*8); if r2 != r3 goto cleanup */
+ for (i = 0; i < 4; i++) {
+ base = a + 5 + i * 7;
+ ASSERT_EQ(in[base + 2].code, (BPF_LDX | BPF_MEM | BPF_DW), "sha load width");
+ ASSERT_EQ(in[base + 2].off, i * 8, "sha dword offset");
+ ASSERT_EQ(in[base + 3].code, (BPF_LD | BPF_IMM | BPF_DW), "sha imm64 (H_meta)");
+ ASSERT_EQ(in[base + 6].code, (BPF_JMP | BPF_JNE | BPF_X), "sha branch op");
+ br[nb++] = base + 6;
+ }
+
+ /*
+ * Locate the real cleanup label so we can pin the exact jump target,
+ * not just "some backward label". bpf_gen__init() emits the cleanup
+ * block as a prog-fd close loop whose first instruction is the label
+ * every error branch jumps to.
+ */
+ for (i = 0; i + 2 < a; i++) {
+ if (in[i].code == (BPF_LDX | BPF_MEM | BPF_W) &&
+ in[i].dst_reg == BPF_REG_1 && in[i].src_reg == BPF_REG_10 &&
+ in[i + 1].code == (BPF_JMP | BPF_JSLE | BPF_K) &&
+ in[i + 1].dst_reg == BPF_REG_1 && in[i + 1].imm == 0 &&
+ in[i + 1].off == 1 &&
+ in[i + 2].code == (BPF_JMP | BPF_CALL) &&
+ in[i + 2].imm == BPF_FUNC_sys_close) {
+ cleanup = i;
+ break;
+ }
+ }
+ if (!ASSERT_GE(cleanup, 0, "cleanup label located"))
+ return;
+ for (i = 0; i < nb; i++) {
+ t = br[i] + 1 + in[br[i]].off;
+ ASSERT_EQ(t, cleanup, "sig-match lands on cleanup");
+ }
+ /*
+ * Same invariant for every other cleanup-bound jump in the program:
+ * emit_check_err() is the only source of "if (r7 < 0) goto cleanup",
+ * so each of those must also resolve exactly to cleanup.
+ */
+ for (i = 0, t = 0; i < n; i++) {
+ if (in[i].code != (BPF_JMP | BPF_JSLT | BPF_K) ||
+ in[i].dst_reg != BPF_REG_7 || in[i].imm != 0 || in[i].off >= 0)
+ continue;
+ ASSERT_EQ(i + 1 + in[i].off, cleanup, "err-check lands on cleanup");
+ t++;
+ }
+ ASSERT_GT(t, 0, "found emit_check_err jumps");
+}
+
+struct gen_loader_fixture {
+ struct test_signed_loader *skel;
+ struct gen_loader_opts gopts;
+ unsigned char *blob;
+ void *ctx;
+ __u32 data_sz;
+ __u32 ctx_sz;
+ int nr_maps;
+ int nr_progs;
+ __u8 excl[SHA256_DIGEST_LENGTH];
+};
+
+static int gen_loader_fixture_init(struct gen_loader_fixture *f)
+{
+ LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
+ int nr_maps = 0, nr_progs = 0;
+ struct bpf_program *p;
+ struct bpf_map *m;
+
+ memset(f, 0, sizeof(*f));
+ f->skel = test_signed_loader__open();
+ if (!ASSERT_OK_PTR(f->skel, "skel_open"))
+ return -1;
+ if (!ASSERT_OK(bpf_object__gen_loader(f->skel->obj, &gopts), "gen_loader"))
+ return -1;
+ if (!ASSERT_OK(bpf_object__load(f->skel->obj), "gen_load"))
+ return -1;
+ f->gopts = gopts;
+
+ bpf_object__for_each_program(p, f->skel->obj)
+ nr_progs++;
+ bpf_object__for_each_map(m, f->skel->obj)
+ nr_maps++;
+ f->nr_maps = nr_maps;
+ f->nr_progs = nr_progs;
+ f->ctx_sz = sizeof(struct bpf_loader_ctx) +
+ nr_maps * sizeof(struct bpf_map_desc) +
+ nr_progs * sizeof(struct bpf_prog_desc);
+ f->ctx = calloc(1, f->ctx_sz);
+ if (!ASSERT_OK_PTR(f->ctx, "ctx_alloc"))
+ return -1;
+ ((struct bpf_loader_ctx *)f->ctx)->sz = f->ctx_sz;
+
+ f->data_sz = gopts.data_sz;
+ f->blob = malloc(f->data_sz);
+ if (!ASSERT_OK_PTR(f->blob, "blob_alloc"))
+ return -1;
+ memcpy(f->blob, gopts.data, f->data_sz);
+
+ /* excl_prog_hash = SHA256(loader insns) == the loader's prog->digest. */
+ libbpf_sha256(gopts.insns, gopts.insns_sz, f->excl);
+ return 0;
+}
+
+static void gen_loader_fixture_fini(struct gen_loader_fixture *f)
+{
+ if (f->ctx)
+ close_loader_ctx_fds(f->ctx, f->nr_maps, f->nr_progs);
+ free(f->blob);
+ free(f->ctx);
+ test_signed_loader__destroy(f->skel);
+}
+
+static void metadata_check_shape(void)
+{
+ struct gen_loader_fixture f;
+
+ if (gen_loader_fixture_init(&f) == 0)
+ check_sig_match_shape((const struct bpf_insn *)f.gopts.insns,
+ f.gopts.insns_sz / sizeof(struct bpf_insn));
+ gen_loader_fixture_fini(&f);
+}
+
+static void metadata_match(void)
+{
+ struct gen_loader_fixture f;
+ bool ran;
+ int r;
+
+ if (gen_loader_fixture_init(&f) == 0) {
+ r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,
+ f.data_sz, f.excl, sizeof(f.excl), NULL, 0,
+ true, f.ctx, f.ctx_sz, &ran);
+ ASSERT_TRUE(ran, "loader ran");
+ ASSERT_EQ(r, 0, "honest loader retval");
+ }
+ gen_loader_fixture_fini(&f);
+}
+
+static void metadata_sha_mismatch(void)
+{
+ struct gen_loader_fixture f;
+ bool ran;
+ int r;
+
+ if (gen_loader_fixture_init(&f) == 0) {
+ /*
+ * blob[0] lives in the loader's fd_array scratch (first add_data in
+ * bpf_gen__init); a 0-map program never reads it, so flipping it
+ * changes only map->sha. The metadata check is the only thing that
+ * can notice -> isolates emit_signature_match.
+ */
+ f.blob[0] ^= 0xff;
+ r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,
+ f.data_sz, f.excl, sizeof(f.excl), NULL, 0,
+ true, f.ctx, f.ctx_sz, &ran);
+ ASSERT_TRUE(ran, "loader ran");
+ ASSERT_EQ(r, -EINVAL, "tampered blob rejected by emit_signature_match");
+ }
+ gen_loader_fixture_fini(&f);
+}
+
+static void metadata_not_exclusive(void)
+{
+ struct gen_loader_fixture f;
+ bool ran;
+ int r;
+
+ if (gen_loader_fixture_init(&f) == 0) {
+ /*
+ * Correct blob but a non-exclusive metadata map: the verifier does
+ * not reject (excl_prog_sha unset), so the runtime map->excl == 1
+ * check in the loader must.
+ */
+ r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,
+ f.data_sz, NULL, 0, NULL, 0, true, f.ctx,
+ f.ctx_sz, &ran);
+ ASSERT_TRUE(ran, "loader ran");
+ ASSERT_EQ(r, -EINVAL, "non-exclusive metadata map rejected");
+ }
+ gen_loader_fixture_fini(&f);
+}
+
+static void metadata_hash_not_computed(void)
+{
+ struct gen_loader_fixture f;
+ bool ran;
+ int r;
+
+ if (gen_loader_fixture_init(&f) == 0) {
+ /*
+ * Correct, exclusive, frozen map, but its hash was never computed
+ * (no OBJ_GET_INFO_BY_FD), so map->sha stays zero. The loader must
+ * fail closed rather than treat an unset hash as a match.
+ */
+ r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,
+ f.data_sz, f.excl, sizeof(f.excl), NULL, 0,
+ false, f.ctx, f.ctx_sz, &ran);
+ ASSERT_TRUE(ran, "loader ran");
+ ASSERT_EQ(r, -EINVAL, "uncomputed metadata hash rejected");
+ }
+ gen_loader_fixture_fini(&f);
+}
+
+static void signature_enforced(void)
+{
+ static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
+ struct gen_loader_fixture f;
+ int fd;
+
+ if (gen_loader_fixture_init(&f) == 0) {
+ /*
+ * A present-but-invalid signature (the cert bytes are not a
+ * PKCS#7 signature) must be rejected at load: the signature
+ * path is honored, not ignored. (The valid path is covered by
+ * the signed lskels.)
+ */
+ fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
+ sizeof(junk), KEY_SPEC_SESSION_KEYRING);
+ ASSERT_LT(fd, 0, "invalid signature rejected at load");
+ }
+ gen_loader_fixture_fini(&f);
+}
+
+static void signature_too_large(void)
+{
+ static const __u8 junk[64] = {};
+ struct gen_loader_fixture f;
+ int fd;
+
+ if (gen_loader_fixture_init(&f) == 0) {
+ /*
+ * signature_size beyond the kernel's bound (KMALLOC_MAX_CACHE_SIZE)
+ * is rejected before the buffer is read.
+ */
+ fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
+ 64 << 20, KEY_SPEC_SESSION_KEYRING);
+ ASSERT_EQ(fd, -EINVAL, "oversized signature rejected");
+ }
+ gen_loader_fixture_fini(&f);
+}
+
+static void signature_bad_keyring(void)
+{
+ static const __u8 junk[64] = {};
+ struct gen_loader_fixture f;
+ int fd;
+
+ if (gen_loader_fixture_init(&f) == 0) {
+ /*
+ * A present signature with a keyring_id that resolves to no key is
+ * rejected up front: bpf_prog_verify_signature() fails the keyring
+ * lookup (-EINVAL) before it ever looks at the signature bytes. A
+ * large positive serial takes the user-keyring path and won't exist.
+ */
+ fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
+ sizeof(junk), INT_MAX);
+ ASSERT_EQ(fd, -EINVAL, "signature with bad keyring_id rejected");
+ }
+ gen_loader_fixture_fini(&f);
+}
+
+/*
+ * A signed loader must ignore ctx-supplied map dimensions: the host cannot
+ * resize a signed program's maps via the loader ctx. Drive a one-map program
+ * through gen_loader, ask (via ctx) for every map to be resized to a bogus
+ * value, and confirm the created maps keep their attested size.
+ */
+#define GATING_BOGUS_MAX 0x4000
+
+static void metadata_ctx_max_entries_ignored(void)
+{
+ LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
+ struct test_signed_loader_map *skel;
+ __u8 excl[SHA256_DIGEST_LENGTH];
+ int nr_maps = 0, nr_progs = 0, i, checked = 0, r;
+ struct bpf_program *p;
+ struct bpf_map *m;
+ struct bpf_map_desc *md;
+ unsigned char *blob;
+ __u32 ctx_sz, data_sz;
+ void *ctx;
+ bool ran;
+
+ skel = test_signed_loader_map__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+ if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader"))
+ goto destroy;
+ if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load"))
+ goto destroy;
+
+ bpf_object__for_each_program(p, skel->obj)
+ nr_progs++;
+ bpf_object__for_each_map(m, skel->obj)
+ nr_maps++;
+ ctx_sz = sizeof(struct bpf_loader_ctx) +
+ nr_maps * sizeof(struct bpf_map_desc) +
+ nr_progs * sizeof(struct bpf_prog_desc);
+ ctx = calloc(1, ctx_sz);
+ if (!ASSERT_OK_PTR(ctx, "ctx_alloc"))
+ goto destroy;
+ ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
+
+ md = (struct bpf_map_desc *)((char *)ctx + sizeof(struct bpf_loader_ctx));
+ for (i = 0; i < nr_maps; i++)
+ md[i].max_entries = GATING_BOGUS_MAX;
+
+ libbpf_sha256(gopts.insns, gopts.insns_sz, excl);
+ data_sz = gopts.data_sz;
+ blob = malloc(data_sz);
+ if (!ASSERT_OK_PTR(blob, "blob_alloc"))
+ goto free_ctx;
+ memcpy(blob, gopts.data, data_sz);
+
+ r = run_gen_loader(gopts.insns, gopts.insns_sz, blob, data_sz,
+ excl, sizeof(excl), NULL, 0, true, ctx, ctx_sz, &ran);
+ if (!ASSERT_TRUE(ran, "loader ran") ||
+ !ASSERT_EQ(r, 0, "loader retval"))
+ goto free_blob;
+
+ for (i = 0; i < nr_maps; i++) {
+ struct bpf_map_info info;
+ __u32 ilen = sizeof(info);
+ int fd = md[i].map_fd;
+
+ if (fd <= 0)
+ continue;
+ memset(&info, 0, sizeof(info));
+ if (ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "map_info")) {
+ ASSERT_NEQ(info.max_entries, GATING_BOGUS_MAX,
+ "ctx max_entries ignored for signed loader");
+ checked++;
+ }
+ }
+ ASSERT_GT(checked, 0, "inspected a created map");
+
+free_blob:
+ free(blob);
+free_ctx:
+ close_loader_ctx_fds(ctx, nr_maps, nr_progs);
+ free(ctx);
+destroy:
+ test_signed_loader_map__destroy(skel);
+}
+
+/*
+ * A signed loader must also ignore ctx-supplied initial_value: the host cannot
+ * re-seed a signed program's map contents through the loader ctx. Drive a
+ * program with one initialized global (a .data map) through gen_loader, point
+ * every map's ctx initial_value at an adversarial buffer, and confirm the
+ * created map still holds the attested value, never the ctx bytes.
+ */
+#define DATA_MAGIC 0x5eed1234abad1deaULL
+
+static void metadata_ctx_initial_value_ignored(void)
+{
+ LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
+ struct test_signed_loader_data *skel;
+ __u8 excl[SHA256_DIGEST_LENGTH], evil[64];
+ int nr_maps = 0, nr_progs = 0, i, found = 0, r;
+ struct bpf_program *p;
+ struct bpf_map *m;
+ struct bpf_map_desc *md;
+ unsigned char *blob;
+ __u32 ctx_sz, data_sz;
+ void *ctx;
+ bool ran;
+
+ skel = test_signed_loader_data__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+ if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader"))
+ goto destroy;
+ if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load"))
+ goto destroy;
+
+ bpf_object__for_each_program(p, skel->obj)
+ nr_progs++;
+ bpf_object__for_each_map(m, skel->obj)
+ nr_maps++;
+ ctx_sz = sizeof(struct bpf_loader_ctx) +
+ nr_maps * sizeof(struct bpf_map_desc) +
+ nr_progs * sizeof(struct bpf_prog_desc);
+ ctx = calloc(1, ctx_sz);
+ if (!ASSERT_OK_PTR(ctx, "ctx_alloc"))
+ goto destroy;
+ ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
+
+ memset(evil, 0xAA, sizeof(evil));
+ md = (struct bpf_map_desc *)((char *)ctx + sizeof(struct bpf_loader_ctx));
+ for (i = 0; i < nr_maps; i++)
+ md[i].initial_value = ptr_to_u64(evil);
+
+ libbpf_sha256(gopts.insns, gopts.insns_sz, excl);
+ data_sz = gopts.data_sz;
+ blob = malloc(data_sz);
+ if (!ASSERT_OK_PTR(blob, "blob_alloc"))
+ goto free_ctx;
+ memcpy(blob, gopts.data, data_sz);
+
+ r = run_gen_loader(gopts.insns, gopts.insns_sz, blob, data_sz,
+ excl, sizeof(excl), NULL, 0, true, ctx, ctx_sz, &ran);
+ if (!ASSERT_TRUE(ran, "loader ran") ||
+ !ASSERT_EQ(r, 0, "loader retval"))
+ goto free_blob;
+
+ for (i = 0; i < nr_maps; i++) {
+ struct bpf_map_info info;
+ __u32 ilen = sizeof(info), key = 0;
+ __u8 value[64] = {};
+ __u64 got;
+ int fd = md[i].map_fd;
+
+ if (fd <= 0)
+ continue;
+ memset(&info, 0, sizeof(info));
+ if (!ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "map_info"))
+ continue;
+ if (info.value_size <= sizeof(value) &&
+ bpf_map_lookup_elem(fd, &key, value) == 0) {
+ memcpy(&got, value, sizeof(got));
+ /* attested .data survives; ctx bytes (0xAA..) ignored */
+ if (got == DATA_MAGIC)
+ found = 1;
+ ASSERT_NEQ(got, 0xAAAAAAAAAAAAAAAAULL,
+ "ctx initial_value ignored for signed loader");
+ }
+ }
+ ASSERT_EQ(found, 1, "attested .data value preserved");
+
+free_blob:
+ free(blob);
+free_ctx:
+ close_loader_ctx_fds(ctx, nr_maps, nr_progs);
+ free(ctx);
+destroy:
+ test_signed_loader_data__destroy(skel);
+}
+
+/*
+ * The load-time signature must authenticate the loader instructions: a valid
+ * signature loads, and the very same signature over one-byte-tampered insns is
+ * rejected. Uses ./verify_sig_setup.sh + ./sign-file at runtime, like
+ * verify_pkcs7_sig, and verifies against the session keyring the key was added
+ * to. (signature_enforced/_too_large only cover a malformed signature.)
+ */
+static void signature_authenticates_insns(void)
+{
+ LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
+ char dir_tmpl[] = "/tmp/signed_loaderXXXXXX", *dir;
+ struct test_signed_loader *skel = NULL;
+ __u8 excl[SHA256_DIGEST_LENGTH], sig[8192];
+ __u32 sig_sz = sizeof(sig), insns_sz, data_sz, ctx_sz;
+ unsigned char *insns = NULL, *tampered = NULL, *blob = NULL;
+ int nr_maps = 0, nr_progs = 0, r;
+ struct bpf_program *p;
+ struct bpf_map *m;
+ void *ctx = NULL;
+ bool ran;
+
+ syscall(__NR_request_key, "keyring", "_uid.0", NULL,
+ KEY_SPEC_SESSION_KEYRING);
+ dir = mkdtemp(dir_tmpl);
+ if (!ASSERT_OK_PTR(dir, "mkdtemp"))
+ return;
+ if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ rmdir(dir);
+ return;
+ }
+
+ skel = test_signed_loader__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ goto cleanup;
+ if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader"))
+ goto cleanup;
+ if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load"))
+ goto cleanup;
+
+ bpf_object__for_each_program(p, skel->obj)
+ nr_progs++;
+ bpf_object__for_each_map(m, skel->obj)
+ nr_maps++;
+ ctx_sz = sizeof(struct bpf_loader_ctx) +
+ nr_maps * sizeof(struct bpf_map_desc) +
+ nr_progs * sizeof(struct bpf_prog_desc);
+ insns_sz = gopts.insns_sz;
+ data_sz = gopts.data_sz;
+ ctx = calloc(1, ctx_sz);
+ insns = malloc(insns_sz);
+ tampered = malloc(insns_sz);
+ blob = malloc(data_sz);
+ if (!ASSERT_OK_PTR(ctx, "ctx") ||
+ !ASSERT_OK_PTR(insns, "insns") ||
+ !ASSERT_OK_PTR(tampered, "tampered") ||
+ !ASSERT_OK_PTR(blob, "blob"))
+ goto cleanup;
+ memcpy(insns, gopts.insns, insns_sz);
+ memcpy(blob, gopts.data, data_sz);
+ libbpf_sha256(insns, insns_sz, excl);
+
+ if (!ASSERT_OK(sign_buf(dir, insns, insns_sz, sig, &sig_sz), "sign-file"))
+ goto cleanup;
+
+ memset(ctx, 0, ctx_sz);
+ ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
+ r = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl),
+ sig, sig_sz, true, ctx, ctx_sz, &ran);
+ ASSERT_TRUE(ran, "valid signature: loader loaded and ran");
+ ASSERT_EQ(r, 0, "valid signature accepted");
+ close_loader_ctx_fds(ctx, nr_maps, nr_progs);
+
+ memcpy(tampered, insns, insns_sz);
+ tampered[insns_sz / 2] ^= 0xff;
+ memset(ctx, 0, ctx_sz);
+ ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
+ r = run_gen_loader(tampered, insns_sz, blob, data_sz, excl, sizeof(excl),
+ sig, sig_sz, true, ctx, ctx_sz, &ran);
+ ASSERT_FALSE(ran, "tampered loader rejected before run");
+ ASSERT_EQ(r, -EKEYREJECTED, "signature is bound to the instructions");
+cleanup:
+ free(insns);
+ free(tampered);
+ free(blob);
+ free(ctx);
+ test_signed_loader__destroy(skel);
+ run_setup("cleanup", dir);
+}
+
+static int make_excl_map(__u32 flags, __u32 value_size)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, opts);
+ __u8 hash[SHA256_DIGEST_LENGTH] = { 1 }; /* any 32-byte value */
+
+ opts.excl_prog_hash = hash;
+ opts.excl_prog_hash_size = sizeof(hash);
+ opts.map_flags = flags;
+ return bpf_map_create(BPF_MAP_TYPE_ARRAY, "md", 4, value_size, 1, &opts);
+}
+
+static void hash_requires_frozen(void)
+{
+ __u8 hbuf[SHA256_DIGEST_LENGTH], val[64] = {};
+ struct bpf_map_info info;
+ __u32 ilen, key = 0;
+ int fd;
+
+ fd = make_excl_map(0, sizeof(val));
+ if (!ASSERT_OK_FD(fd, "excl_map"))
+ return;
+ ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update");
+
+ memset(&info, 0, sizeof(info));
+ info.hash = ptr_to_u64(hbuf);
+ info.hash_size = sizeof(hbuf);
+ ilen = sizeof(info);
+ ASSERT_EQ(bpf_map_get_info_by_fd(fd, &info, &ilen), -EPERM,
+ "hash of unfrozen map rejected");
+ close(fd);
+}
+
+static void no_update_after_freeze(void)
+{
+ __u8 val[64] = {};
+ __u32 key = 0;
+ int fd;
+
+ fd = make_excl_map(0, sizeof(val));
+ if (!ASSERT_OK_FD(fd, "excl_map"))
+ return;
+ ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update");
+ ASSERT_OK(bpf_map_freeze(fd), "freeze");
+ ASSERT_EQ(bpf_map_update_elem(fd, &key, val, 0), -EPERM,
+ "update after freeze rejected");
+ close(fd);
+}
+
+static void freeze_writable_mmap(void)
+{
+ void *w;
+ int fd;
+
+ fd = make_excl_map(BPF_F_MMAPABLE, 4096);
+ if (!ASSERT_OK_FD(fd, "excl_mmapable_map"))
+ return;
+ w = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (ASSERT_OK_PTR(w, "writable_mmap")) {
+ ASSERT_EQ(bpf_map_freeze(fd), -EBUSY,
+ "freeze rejected while writable mmap held");
+ munmap(w, 4096);
+ }
+ close(fd);
+}
+
+static void no_writable_mmap_frozen(void)
+{
+ void *w;
+ int fd;
+
+ fd = make_excl_map(BPF_F_MMAPABLE, 4096);
+ if (!ASSERT_OK_FD(fd, "excl_mmapable_map"))
+ return;
+ ASSERT_OK(bpf_map_freeze(fd), "freeze");
+ w = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ ASSERT_EQ(w, MAP_FAILED, "writable mmap of frozen map rejected");
+ if (w != MAP_FAILED)
+ munmap(w, 4096);
+ close(fd);
+}
+
+static void map_hash_matches_libbpf(void)
+{
+ __u8 kbuf[SHA256_DIGEST_LENGTH], lbuf[SHA256_DIGEST_LENGTH], val[64] = {};
+ struct bpf_map_info info;
+ __u32 ilen, key = 0;
+ int fd, i;
+
+ /*
+ * The signing scheme assumes the kernel's map hash equals what libbpf
+ * computes over the same bytes (gen_loader bakes libbpf_sha256(blob);
+ * the kernel recomputes via array_map_get_hash). Pin that they agree.
+ */
+ for (i = 0; i < (int)sizeof(val); i++)
+ val[i] = i * 7 + 1;
+ fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "h", 4, sizeof(val), 1, NULL);
+ if (!ASSERT_OK_FD(fd, "array_map"))
+ return;
+ ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update");
+ ASSERT_OK(bpf_map_freeze(fd), "freeze");
+ memset(&info, 0, sizeof(info));
+ info.hash = ptr_to_u64(kbuf);
+ info.hash_size = sizeof(kbuf);
+ ilen = sizeof(info);
+ if (ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "get_hash")) {
+ libbpf_sha256(val, sizeof(val), lbuf);
+ ASSERT_EQ(memcmp(kbuf, lbuf, sizeof(kbuf)), 0,
+ "kernel map hash matches libbpf_sha256");
+ }
+ close(fd);
+}
+
+static void map_hash_multi_element(void)
+{
+ const __u32 nr = 8, value_size = 64;
+ __u8 kbuf[SHA256_DIGEST_LENGTH], lbuf[SHA256_DIGEST_LENGTH];
+ struct bpf_map_info info;
+ __u32 ilen, i, j;
+ __u8 *full;
+ int fd;
+
+ /*
+ * array_map_get_hash() hashes elem_size * max_entries (the whole value
+ * area), not just element 0. With an 8-aligned value_size elem_size has
+ * no padding, so pin that a >1-entry array's kernel hash equals
+ * libbpf_sha256() over the full, concatenated element contents.
+ */
+ fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "h", 4, value_size, nr, NULL);
+ if (!ASSERT_OK_FD(fd, "array_map"))
+ return;
+ full = calloc(nr, value_size);
+ if (!ASSERT_OK_PTR(full, "buf"))
+ goto close_fd;
+ for (i = 0; i < nr; i++) {
+ __u8 *v = full + i * value_size;
+
+ for (j = 0; j < value_size; j++)
+ v[j] = i * 31 + j * 7 + 1;
+ ASSERT_OK(bpf_map_update_elem(fd, &i, v, 0), "update");
+ }
+ ASSERT_OK(bpf_map_freeze(fd), "freeze");
+ memset(&info, 0, sizeof(info));
+ info.hash = ptr_to_u64(kbuf);
+ info.hash_size = sizeof(kbuf);
+ ilen = sizeof(info);
+ if (ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "get_hash")) {
+ libbpf_sha256(full, (size_t)nr * value_size, lbuf);
+ ASSERT_EQ(memcmp(kbuf, lbuf, sizeof(kbuf)), 0,
+ "kernel hash covers full multi-element value area");
+ }
+ free(full);
+close_fd:
+ close(fd);
+}
+
+static void map_hash_bad_size(void)
+{
+ __u8 kbuf[SHA256_DIGEST_LENGTH], val[64] = {};
+ struct bpf_map_info info;
+ __u32 ilen, key = 0;
+ int fd;
+
+ fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "h", 4, sizeof(val), 1, NULL);
+ if (!ASSERT_OK_FD(fd, "array_map"))
+ return;
+ ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update");
+ ASSERT_OK(bpf_map_freeze(fd), "freeze");
+ memset(&info, 0, sizeof(info));
+ info.hash = ptr_to_u64(kbuf);
+ info.hash_size = sizeof(kbuf) / 2;
+ ilen = sizeof(info);
+ ASSERT_EQ(bpf_map_get_info_by_fd(fd, &info, &ilen), -EINVAL,
+ "wrong hash_size rejected");
+ close(fd);
+}
+
+static void map_hash_unsupported_type(void)
+{
+ __u8 kbuf[SHA256_DIGEST_LENGTH];
+ struct bpf_map_info info;
+ __u32 ilen;
+ int fd;
+
+ /* Only arrays implement map_get_hash; a hash map must be refused. */
+ fd = bpf_map_create(BPF_MAP_TYPE_HASH, "h", 4, 8, 4, NULL);
+ if (!ASSERT_OK_FD(fd, "hash_map"))
+ return;
+ memset(&info, 0, sizeof(info));
+ info.hash = ptr_to_u64(kbuf);
+ info.hash_size = sizeof(kbuf);
+ ilen = sizeof(info);
+ ASSERT_EQ(bpf_map_get_info_by_fd(fd, &info, &ilen), -EINVAL,
+ "hash unsupported for non-array map");
+ close(fd);
+}
+
+static int setup_meta_map(const struct gen_loader_fixture *f)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, mopts,
+ .excl_prog_hash = f->excl,
+ .excl_prog_hash_size = sizeof(f->excl));
+ __u32 key = 0;
+ int fd;
+
+ fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map", 4,
+ f->data_sz, 1, &mopts);
+ if (fd < 0)
+ return -errno;
+ if (bpf_map_update_elem(fd, &key, f->blob, 0) || bpf_map_freeze(fd)) {
+ close(fd);
+ return -errno;
+ }
+ return fd;
+}
+
+static void lsm_signature_verdict(void)
+{
+ char dir_tmpl[] = "/tmp/signed_loader_lsmXXXXXX", *dir = NULL;
+ struct test_signed_loader_lsm *lsm = NULL;
+ int map_fd = -1, prog_fd = -1;
+ bool have_fixture = false;
+ struct gen_loader_fixture f;
+ __u32 sig_sz = 8192;
+ __s32 ses_serial;
+ __u8 sig[8192];
+
+ lsm = test_signed_loader_lsm__open_and_load();
+ if (!ASSERT_OK_PTR(lsm, "lsm_skel_load"))
+ return;
+ lsm->bss->monitored_tid = sys_gettid();
+ if (!ASSERT_OK(test_signed_loader_lsm__attach(lsm), "lsm_attach"))
+ goto out;
+
+ have_fixture = true;
+ if (gen_loader_fixture_init(&f) != 0)
+ goto out;
+
+ map_fd = setup_meta_map(&f);
+ if (!ASSERT_OK_FD(map_fd, "meta_map_unsigned"))
+ goto out;
+ lsm->bss->seen = 0;
+ prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, NULL, 0, 0);
+ close(map_fd);
+ map_fd = -1;
+ if (!ASSERT_OK_FD(prog_fd, "unsigned loader load"))
+ goto out;
+ close(prog_fd);
+ prog_fd = -1;
+ if (!ASSERT_NEQ(lsm->bss->seen, 0, "bpf LSM in the active LSM set"))
+ goto out;
+ ASSERT_EQ(lsm->bss->seen, 1, "unsigned: one observed load");
+ ASSERT_EQ(lsm->bss->sig_verdict, BPF_SIG_UNSIGNED, "unsigned verdict");
+ ASSERT_EQ(lsm->bss->sig_keyring_type, BPF_SIG_KEYRING_NONE, "unsigned keyring type");
+ ASSERT_EQ(lsm->bss->sig_keyring_serial, 0, "unsigned: no keyring serial");
+
+ syscall(__NR_request_key, "keyring", "_uid.0", NULL,
+ KEY_SPEC_SESSION_KEYRING);
+ dir = mkdtemp(dir_tmpl);
+ if (!ASSERT_OK_PTR(dir, "mkdtemp"))
+ goto out;
+ if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
+ rmdir(dir);
+ dir = NULL;
+ goto out;
+ }
+ if (!ASSERT_OK(sign_buf(dir, f.gopts.insns, f.gopts.insns_sz, sig,
+ &sig_sz), "sign-file"))
+ goto out;
+
+ map_fd = setup_meta_map(&f);
+ if (!ASSERT_OK_FD(map_fd, "meta_map_signed"))
+ goto out;
+ lsm->bss->seen = 0;
+ prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, sig,
+ sig_sz, KEY_SPEC_SESSION_KEYRING);
+ close(map_fd);
+ map_fd = -1;
+ if (!ASSERT_OK_FD(prog_fd, "signed loader load"))
+ goto out;
+ close(prog_fd);
+ prog_fd = -1;
+
+ ses_serial = syscall(__NR_keyctl, KEYCTL_GET_KEYRING_ID,
+ KEY_SPEC_SESSION_KEYRING, 0);
+ ASSERT_EQ(lsm->bss->seen, 1, "signed: one observed load");
+ ASSERT_EQ(lsm->bss->sig_verdict, BPF_SIG_VERIFIED, "signed verdict");
+ ASSERT_EQ(lsm->bss->sig_keyring_type, BPF_SIG_KEYRING_USER, "signed keyring type");
+ ASSERT_GT(ses_serial, 0, "session keyring serial resolved");
+ ASSERT_EQ(lsm->bss->sig_keyring_serial, ses_serial,
+ "signed: validated against session keyring");
+out:
+ if (map_fd >= 0)
+ close(map_fd);
+ if (prog_fd >= 0)
+ close(prog_fd);
+ if (have_fixture)
+ gen_loader_fixture_fini(&f);
+ if (dir)
+ run_setup("cleanup", dir);
+ test_signed_loader_lsm__destroy(lsm);
+}
+
+void test_signed_loader(void)
+{
+ if (test__start_subtest("metadata_check_shape"))
+ metadata_check_shape();
+ if (test__start_subtest("metadata_match"))
+ metadata_match();
+ if (test__start_subtest("metadata_sha_mismatch"))
+ metadata_sha_mismatch();
+ if (test__start_subtest("metadata_not_exclusive"))
+ metadata_not_exclusive();
+ if (test__start_subtest("metadata_hash_not_computed"))
+ metadata_hash_not_computed();
+ if (test__start_subtest("signature_enforced"))
+ signature_enforced();
+ if (test__start_subtest("signature_too_large"))
+ signature_too_large();
+ if (test__start_subtest("signature_bad_keyring"))
+ signature_bad_keyring();
+ if (test__start_subtest("metadata_ctx_max_entries_ignored"))
+ metadata_ctx_max_entries_ignored();
+ if (test__start_subtest("metadata_ctx_initial_value_ignored"))
+ metadata_ctx_initial_value_ignored();
+ if (test__start_subtest("signature_authenticates_insns"))
+ signature_authenticates_insns();
+ if (test__start_subtest("hash_requires_frozen"))
+ hash_requires_frozen();
+ if (test__start_subtest("no_update_after_freeze"))
+ no_update_after_freeze();
+ if (test__start_subtest("freeze_writable_mmap"))
+ freeze_writable_mmap();
+ if (test__start_subtest("no_writable_mmap_frozen"))
+ no_writable_mmap_frozen();
+ if (test__start_subtest("map_hash_matches_libbpf"))
+ map_hash_matches_libbpf();
+ if (test__start_subtest("map_hash_multi_element"))
+ map_hash_multi_element();
+ if (test__start_subtest("map_hash_bad_size"))
+ map_hash_bad_size();
+ if (test__start_subtest("map_hash_unsupported_type"))
+ map_hash_unsupported_type();
+ if (test__start_subtest("lsm_signature_verdict"))
+ lsm_signature_verdict();
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/sleepable_tracepoints.c b/tools/testing/selftests/bpf/prog_tests/sleepable_tracepoints.c
new file mode 100644
index 000000000000..19500b785ee3
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/sleepable_tracepoints.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
+
+#include <test_progs.h>
+#include <unistd.h>
+#include "test_sleepable_tracepoints.skel.h"
+#include "test_sleepable_tracepoints_fail.skel.h"
+
+static void run_test(struct test_sleepable_tracepoints *skel)
+{
+ char buf[PATH_MAX] = "/";
+
+ skel->bss->target_pid = getpid();
+ skel->bss->prog_triggered = 0;
+ skel->bss->err = 0;
+ skel->bss->copied_byte = 0;
+
+ syscall(__NR_getcwd, buf, sizeof(buf));
+
+ ASSERT_EQ(skel->bss->prog_triggered, 1, "prog_triggered");
+ ASSERT_EQ(skel->bss->err, 0, "err");
+ ASSERT_EQ(skel->bss->copied_byte, '/', "copied_byte");
+}
+
+static void run_auto_attach_test(struct bpf_program *prog,
+ struct test_sleepable_tracepoints *skel)
+{
+ struct bpf_link *link;
+
+ link = bpf_program__attach(prog);
+ if (!ASSERT_OK_PTR(link, "prog_attach"))
+ return;
+
+ run_test(skel);
+ bpf_link__destroy(link);
+}
+
+static void test_attach_only(struct bpf_program *prog)
+{
+ struct bpf_link *link;
+
+ link = bpf_program__attach(prog);
+ if (ASSERT_OK_PTR(link, "attach"))
+ bpf_link__destroy(link);
+}
+
+static void test_attach_reject(struct bpf_program *prog)
+{
+ struct bpf_link *link;
+
+ link = bpf_program__attach(prog);
+ if (!ASSERT_ERR_PTR(link, "attach_should_fail"))
+ bpf_link__destroy(link);
+}
+
+static void test_raw_tp_bare(struct test_sleepable_tracepoints *skel)
+{
+ struct bpf_link *link;
+
+ link = bpf_program__attach_raw_tracepoint(skel->progs.handle_raw_tp_bare,
+ "sys_enter");
+ if (ASSERT_OK_PTR(link, "attach"))
+ bpf_link__destroy(link);
+}
+
+static void test_tp_bare(struct test_sleepable_tracepoints *skel)
+{
+ struct bpf_link *link;
+
+ link = bpf_program__attach_tracepoint(skel->progs.handle_tp_bare,
+ "syscalls", "sys_enter_getcwd");
+ if (ASSERT_OK_PTR(link, "attach"))
+ bpf_link__destroy(link);
+}
+
+static void test_test_run(struct test_sleepable_tracepoints *skel)
+{
+ __u64 args[2] = {0x1234ULL, 0x5678ULL};
+ LIBBPF_OPTS(bpf_test_run_opts, topts,
+ .ctx_in = args,
+ .ctx_size_in = sizeof(args),
+ );
+ int fd, err;
+
+ fd = bpf_program__fd(skel->progs.handle_test_run);
+ err = bpf_prog_test_run_opts(fd, &topts);
+ ASSERT_OK(err, "test_run");
+ ASSERT_EQ(topts.retval, args[0] + args[1], "test_run_retval");
+}
+
+static void test_test_run_on_cpu_reject(struct test_sleepable_tracepoints *skel)
+{
+ __u64 args[2] = {};
+ LIBBPF_OPTS(bpf_test_run_opts, topts,
+ .ctx_in = args,
+ .ctx_size_in = sizeof(args),
+ .flags = BPF_F_TEST_RUN_ON_CPU,
+ );
+ int fd, err;
+
+ fd = bpf_program__fd(skel->progs.handle_test_run);
+ err = bpf_prog_test_run_opts(fd, &topts);
+ ASSERT_ERR(err, "test_run_on_cpu_reject");
+}
+
+void test_sleepable_tracepoints(void)
+{
+ struct test_sleepable_tracepoints *skel;
+
+ skel = test_sleepable_tracepoints__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+
+ if (test__start_subtest("tp_btf"))
+ run_auto_attach_test(skel->progs.handle_sys_enter_tp_btf, skel);
+ if (test__start_subtest("raw_tp"))
+ run_auto_attach_test(skel->progs.handle_sys_enter_raw_tp, skel);
+ if (test__start_subtest("tracepoint"))
+ run_auto_attach_test(skel->progs.handle_sys_enter_tp, skel);
+ if (test__start_subtest("sys_exit"))
+ run_auto_attach_test(skel->progs.handle_sys_exit_tp, skel);
+ if (test__start_subtest("tracepoint_alias"))
+ test_attach_only(skel->progs.handle_sys_enter_tp_alias);
+ if (test__start_subtest("raw_tracepoint_alias"))
+ test_attach_only(skel->progs.handle_sys_enter_raw_tp_alias);
+ if (test__start_subtest("raw_tp_bare"))
+ test_raw_tp_bare(skel);
+ if (test__start_subtest("tp_bare"))
+ test_tp_bare(skel);
+ if (test__start_subtest("test_run"))
+ test_test_run(skel);
+ if (test__start_subtest("test_run_on_cpu_reject"))
+ test_test_run_on_cpu_reject(skel);
+ if (test__start_subtest("raw_tp_non_faultable"))
+ test_attach_reject(skel->progs.handle_raw_tp_non_faultable);
+ if (test__start_subtest("tp_non_syscall"))
+ test_attach_reject(skel->progs.handle_tp_non_syscall);
+ if (test__start_subtest("tp_btf_non_faultable_reject"))
+ RUN_TESTS(test_sleepable_tracepoints_fail);
+
+ test_sleepable_tracepoints__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
index d2846579285f..cb3229711f93 100644
--- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
+++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
@@ -14,6 +14,7 @@
#include "test_sockmap_pass_prog.skel.h"
#include "test_sockmap_drop_prog.skel.h"
#include "test_sockmap_change_tail.skel.h"
+#include "test_sockmap_msg_pop_data.skel.h"
#include "bpf_iter_sockmap.skel.h"
#include "sockmap_helpers.h"
@@ -666,6 +667,51 @@ out:
test_sockmap_change_tail__destroy(skel);
}
+static void test_sockmap_msg_verdict_pop_data(void)
+{
+ struct test_sockmap_msg_pop_data *skel;
+ int err, map, verdict;
+ int c1 = -1, p1 = -1, sent;
+ int zero = 0;
+ char *buf;
+ const size_t len = 32 * 1024;
+
+ skel = test_sockmap_msg_pop_data__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+
+ verdict = bpf_program__fd(skel->progs.prog_msg_pop_data);
+ map = bpf_map__fd(skel->maps.sock_map);
+
+ err = bpf_prog_attach(verdict, map, BPF_SK_MSG_VERDICT, 0);
+ if (!ASSERT_OK(err, "bpf_prog_attach"))
+ goto out;
+
+ err = create_pair(AF_INET, SOCK_STREAM, &c1, &p1);
+ if (!ASSERT_OK(err, "create_pair"))
+ goto out;
+
+ err = bpf_map_update_elem(map, &zero, &c1, BPF_NOEXIST);
+ if (!ASSERT_OK(err, "bpf_map_update_elem"))
+ goto out_close;
+
+ buf = calloc(len, 1);
+ if (!ASSERT_OK_PTR(buf, "calloc"))
+ goto out_close;
+
+ sent = xsend(c1, buf, len, 0);
+ ASSERT_EQ(sent, (ssize_t)len, "xsend");
+ ASSERT_EQ(skel->data->pop_data_ret, -EINVAL, "pop_data_rejects overflow");
+
+ free(buf);
+
+out_close:
+ close(c1);
+ close(p1);
+out:
+ test_sockmap_msg_pop_data__destroy(skel);
+}
+
static void test_sockmap_skb_verdict_peek_helper(int map)
{
int err, c1, p1, zero = 0, sent, recvd, avail;
@@ -1373,6 +1419,8 @@ void test_sockmap_basic(void)
test_sockmap_skb_verdict_fionread(false);
if (test__start_subtest("sockmap skb_verdict change tail"))
test_sockmap_skb_verdict_change_tail();
+ if (test__start_subtest("sockmap msg_verdict pop_data overflow"))
+ test_sockmap_msg_verdict_pop_data();
if (test__start_subtest("sockmap skb_verdict msg_f_peek"))
test_sockmap_skb_verdict_peek();
if (test__start_subtest("sockmap skb_verdict msg_f_peek with link"))
diff --git a/tools/testing/selftests/bpf/prog_tests/spin_lock.c b/tools/testing/selftests/bpf/prog_tests/spin_lock.c
index bbe476f4c47d..5c3579438427 100644
--- a/tools/testing/selftests/bpf/prog_tests/spin_lock.c
+++ b/tools/testing/selftests/bpf/prog_tests/spin_lock.c
@@ -13,8 +13,8 @@ static struct {
const char *err_msg;
} spin_lock_fail_tests[] = {
{ "lock_id_kptr_preserve",
- "[0-9]\\+: (bf) r1 = r0 ; R0=ptr_foo(id=2,ref_obj_id=2)"
- " R1=ptr_foo(id=2,ref_obj_id=2) refs=2\n"
+ "[0-9]\\+: (bf) r1 = r0 ; R0=ptr_foo(id=2)"
+ " R1=ptr_foo(id=2) refs=2\n"
"[0-9]\\+: (85) call bpf_this_cpu_ptr#154\n"
"R1 type=ptr_ expected=percpu_ptr_" },
{ "lock_id_global_zero",
diff --git a/tools/testing/selftests/bpf/prog_tests/stack_arg.c b/tools/testing/selftests/bpf/prog_tests/stack_arg.c
new file mode 100644
index 000000000000..57193543f260
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/stack_arg.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <test_progs.h>
+#include <network_helpers.h>
+#include "stack_arg.skel.h"
+#include "stack_arg_kfunc.skel.h"
+
+static void run_subtest(struct bpf_program *prog, int expected)
+{
+ int err, prog_fd;
+ LIBBPF_OPTS(bpf_test_run_opts, topts,
+ .data_in = &pkt_v4,
+ .data_size_in = sizeof(pkt_v4),
+ .repeat = 1,
+ );
+
+ prog_fd = bpf_program__fd(prog);
+ err = bpf_prog_test_run_opts(prog_fd, &topts);
+ ASSERT_OK(err, "test_run");
+ ASSERT_EQ(topts.retval, expected, "retval");
+}
+
+static void test_global_many(void)
+{
+ struct stack_arg *skel;
+
+ skel = stack_arg__open();
+ if (!ASSERT_OK_PTR(skel, "open"))
+ return;
+
+ if (!skel->rodata->has_stack_arg) {
+ test__skip();
+ goto out;
+ }
+
+ if (!ASSERT_OK(stack_arg__load(skel), "load"))
+ goto out;
+
+ run_subtest(skel->progs.test_global_many_args, 55);
+
+out:
+ stack_arg__destroy(skel);
+}
+
+static void test_async_cb_many(void)
+{
+ struct stack_arg *skel;
+
+ skel = stack_arg__open();
+ if (!ASSERT_OK_PTR(skel, "open"))
+ return;
+
+ if (!skel->rodata->has_stack_arg) {
+ test__skip();
+ goto out;
+ }
+
+ if (!ASSERT_OK(stack_arg__load(skel), "load"))
+ goto out;
+
+ run_subtest(skel->progs.test_async_cb_many_args, 0);
+
+ /* Wait for the timer callback to fire and verify the result.
+ * 10+20+30+40+50+60+70+80+90+100 = 550
+ */
+ usleep(50);
+ ASSERT_EQ(skel->bss->timer_result, 550, "timer_result");
+
+out:
+ stack_arg__destroy(skel);
+}
+
+static void test_bpf2bpf(void)
+{
+ struct stack_arg *skel;
+
+ skel = stack_arg__open();
+ if (!ASSERT_OK_PTR(skel, "open"))
+ return;
+
+ if (!skel->rodata->has_stack_arg) {
+ test__skip();
+ goto out;
+ }
+
+ if (!ASSERT_OK(stack_arg__load(skel), "load"))
+ goto out;
+
+ run_subtest(skel->progs.test_bpf2bpf_ptr_stack_arg, 75);
+ run_subtest(skel->progs.test_bpf2bpf_mix_stack_args, 66);
+ run_subtest(skel->progs.test_bpf2bpf_nesting_stack_arg, 84);
+ run_subtest(skel->progs.test_bpf2bpf_dynptr_stack_arg, 99);
+ run_subtest(skel->progs.test_two_callees, 133);
+
+out:
+ stack_arg__destroy(skel);
+}
+
+static void test_kfunc(void)
+{
+ struct stack_arg_kfunc *skel;
+
+ skel = stack_arg_kfunc__open();
+ if (!ASSERT_OK_PTR(skel, "open"))
+ return;
+
+ if (!skel->rodata->has_stack_arg) {
+ test__skip();
+ goto out;
+ }
+
+ if (!ASSERT_OK(stack_arg_kfunc__load(skel), "load"))
+ goto out;
+
+ run_subtest(skel->progs.test_stack_arg_scalar, 55);
+ run_subtest(skel->progs.test_stack_arg_ptr, 75);
+ run_subtest(skel->progs.test_stack_arg_mix, 66);
+ run_subtest(skel->progs.test_stack_arg_dynptr, 99);
+ run_subtest(skel->progs.test_stack_arg_mem, 151);
+ run_subtest(skel->progs.test_stack_arg_iter, 145);
+ run_subtest(skel->progs.test_stack_arg_const_str, 45);
+ run_subtest(skel->progs.test_stack_arg_timer, 45);
+
+out:
+ stack_arg_kfunc__destroy(skel);
+}
+
+void test_stack_arg(void)
+{
+ if (test__start_subtest("global_many_args"))
+ test_global_many();
+ if (test__start_subtest("async_cb_many_args"))
+ test_async_cb_many();
+ if (test__start_subtest("bpf2bpf"))
+ test_bpf2bpf();
+ if (test__start_subtest("kfunc"))
+ test_kfunc();
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/stack_arg_fail.c b/tools/testing/selftests/bpf/prog_tests/stack_arg_fail.c
new file mode 100644
index 000000000000..090af1330953
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/stack_arg_fail.c
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <test_progs.h>
+#include "stack_arg_fail.skel.h"
+
+void test_stack_arg_fail(void)
+{
+ RUN_TESTS(stack_arg_fail);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/stack_arg_precision.c b/tools/testing/selftests/bpf/prog_tests/stack_arg_precision.c
new file mode 100644
index 000000000000..1ab041d66de3
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/stack_arg_precision.c
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <test_progs.h>
+#include "stack_arg_precision.skel.h"
+
+void test_stack_arg_precision(void)
+{
+ RUN_TESTS(stack_arg_precision);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/tailcalls.c b/tools/testing/selftests/bpf/prog_tests/tailcalls.c
index 7d534fde0af9..a5a226d0104c 100644
--- a/tools/testing/selftests/bpf/prog_tests/tailcalls.c
+++ b/tools/testing/selftests/bpf/prog_tests/tailcalls.c
@@ -8,6 +8,9 @@
#include "tailcall_freplace.skel.h"
#include "tc_bpf2bpf.skel.h"
#include "tailcall_fail.skel.h"
+#include "tailcall_cgrp_storage_owner.skel.h"
+#include "tailcall_cgrp_storage_no_storage.skel.h"
+#include "tailcall_cgrp_storage.skel.h"
#include "tailcall_sleepable.skel.h"
/* test_tailcall_1 checks basic functionality by patching multiple locations
@@ -1654,6 +1657,179 @@ static void test_tailcall_failure()
RUN_TESTS(tailcall_fail);
}
+static void test_tailcall_cgrp_storage(void)
+{
+ struct tailcall_cgrp_storage_owner *owner_skel = NULL;
+ struct tailcall_cgrp_storage *skel = NULL;
+ int err, key = 0, prog_array_fd, prog_fd, storage_map_fd;
+
+ owner_skel = tailcall_cgrp_storage_owner__open_and_load();
+ if (!ASSERT_OK_PTR(owner_skel, "owner_open_and_load"))
+ return;
+
+ prog_array_fd = bpf_map__fd(owner_skel->maps.prog_array);
+ storage_map_fd = bpf_map__fd(owner_skel->maps.storage_map);
+
+ skel = tailcall_cgrp_storage__open();
+ if (!ASSERT_OK_PTR(skel, "tailcall_cgrp_storage__open"))
+ goto out;
+
+ err = bpf_map__reuse_fd(skel->maps.prog_array, prog_array_fd);
+ if (!ASSERT_OK(err, "reuse_prog_array"))
+ goto out;
+
+ err = bpf_map__reuse_fd(skel->maps.storage_map, storage_map_fd);
+ if (!ASSERT_OK(err, "reuse_storage_map"))
+ goto out;
+
+ err = bpf_object__load(skel->obj);
+ if (!ASSERT_OK(err, "tailcall_cgrp_storage__load"))
+ goto out;
+
+ prog_fd = bpf_program__fd(skel->progs.callee_prog);
+ err = bpf_map_update_elem(prog_array_fd, &key, &prog_fd, BPF_ANY);
+ ASSERT_OK(err, "update_prog_array");
+out:
+ tailcall_cgrp_storage__destroy(skel);
+ tailcall_cgrp_storage_owner__destroy(owner_skel);
+}
+
+static void test_tailcall_cgrp_storage_diff_storage(void)
+{
+ struct tailcall_cgrp_storage_owner *owner_skel = NULL;
+ struct tailcall_cgrp_storage *skel = NULL;
+ int err, prog_array_fd;
+
+ owner_skel = tailcall_cgrp_storage_owner__open_and_load();
+ if (!ASSERT_OK_PTR(owner_skel, "owner_open_and_load"))
+ return;
+
+ prog_array_fd = bpf_map__fd(owner_skel->maps.prog_array);
+
+ skel = tailcall_cgrp_storage__open();
+ if (!ASSERT_OK_PTR(skel, "tailcall_cgrp_storage__open"))
+ goto out;
+
+ err = bpf_map__reuse_fd(skel->maps.prog_array, prog_array_fd);
+ if (!ASSERT_OK(err, "reuse_prog_array"))
+ goto out;
+
+ err = bpf_object__load(skel->obj);
+ ASSERT_ERR(err, "tailcall_cgrp_storage__load");
+out:
+ tailcall_cgrp_storage__destroy(skel);
+ tailcall_cgrp_storage_owner__destroy(owner_skel);
+}
+
+static void test_tailcall_cgrp_storage_no_storage(void)
+{
+ struct tailcall_cgrp_storage_owner *owner_skel = NULL;
+ struct tailcall_cgrp_storage_no_storage *skel = NULL;
+ int err, prog_array_fd;
+
+ owner_skel = tailcall_cgrp_storage_owner__open_and_load();
+ if (!ASSERT_OK_PTR(owner_skel, "owner_open_and_load"))
+ return;
+
+ prog_array_fd = bpf_map__fd(owner_skel->maps.prog_array);
+
+ skel = tailcall_cgrp_storage_no_storage__open();
+ if (!ASSERT_OK_PTR(skel, "tailcall_cgrp_storage_no_storage__open"))
+ goto out;
+
+ err = bpf_map__reuse_fd(skel->maps.prog_array, prog_array_fd);
+ if (!ASSERT_OK(err, "reuse_prog_array"))
+ goto out;
+
+ err = bpf_object__load(skel->obj);
+ ASSERT_ERR(err, "tailcall_cgrp_storage_no_storage__load");
+out:
+ tailcall_cgrp_storage_no_storage__destroy(skel);
+ tailcall_cgrp_storage_owner__destroy(owner_skel);
+}
+
+static void test_tailcall_cgrp_storage_no_storage_leaf(void)
+{
+ struct tailcall_cgrp_storage_owner *owner_skel = NULL;
+ struct tailcall_cgrp_storage_no_storage *skel = NULL;
+ int err, key = 0, prog_array_fd, prog_fd;
+
+ owner_skel = tailcall_cgrp_storage_owner__open_and_load();
+ if (!ASSERT_OK_PTR(owner_skel, "owner_open_and_load"))
+ return;
+
+ prog_array_fd = bpf_map__fd(owner_skel->maps.prog_array);
+
+ skel = tailcall_cgrp_storage_no_storage__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "tailcall_cgrp_storage_no_storage__open_and_load"))
+ goto out;
+
+ prog_fd = bpf_program__fd(skel->progs.leaf_prog);
+ err = bpf_map_update_elem(prog_array_fd, &key, &prog_fd, BPF_ANY);
+ if (!ASSERT_OK(err, "update_prog_array_leaf"))
+ goto out;
+
+ prog_fd = bpf_program__fd(skel->progs.caller_prog);
+ err = bpf_map_update_elem(prog_array_fd, &key, &prog_fd, BPF_ANY);
+ ASSERT_ERR(err, "update_prog_array_bridge");
+out:
+ tailcall_cgrp_storage_no_storage__destroy(skel);
+ tailcall_cgrp_storage_owner__destroy(owner_skel);
+}
+
+static void test_tailcall_cgrp_storage_no_storage_bridge(void)
+{
+ struct tailcall_cgrp_storage_owner *owner_skel = NULL;
+ struct tailcall_cgrp_storage_no_storage *bridge_skel = NULL;
+ struct tailcall_cgrp_storage *callee_skel = NULL;
+ int err, key = 0, prog_array_fd, prog_fd, storage_map_fd;
+
+ owner_skel = tailcall_cgrp_storage_owner__open_and_load();
+ if (!ASSERT_OK_PTR(owner_skel, "owner_open_and_load"))
+ return;
+
+ prog_array_fd = bpf_map__fd(owner_skel->maps.prog_array);
+ storage_map_fd = bpf_map__fd(owner_skel->maps.storage_map);
+
+ callee_skel = tailcall_cgrp_storage__open();
+ if (!ASSERT_OK_PTR(callee_skel, "tailcall_cgrp_storage__open"))
+ goto out;
+
+ bpf_program__set_autoload(callee_skel->progs.caller_prog, false);
+
+ err = bpf_map__reuse_fd(callee_skel->maps.prog_array, prog_array_fd);
+ if (!ASSERT_OK(err, "reuse_prog_array"))
+ goto out;
+
+ err = bpf_map__reuse_fd(callee_skel->maps.storage_map, storage_map_fd);
+ if (!ASSERT_OK(err, "reuse_storage_map"))
+ goto out;
+
+ err = bpf_object__load(callee_skel->obj);
+ if (!ASSERT_OK(err, "tailcall_cgrp_storage__load"))
+ goto out;
+
+ prog_fd = bpf_program__fd(callee_skel->progs.callee_prog);
+ err = bpf_map_update_elem(prog_array_fd, &key, &prog_fd, BPF_ANY);
+ if (!ASSERT_OK(err, "update_prog_array"))
+ goto out;
+
+ bridge_skel = tailcall_cgrp_storage_no_storage__open();
+ if (!ASSERT_OK_PTR(bridge_skel, "tailcall_cgrp_storage_no_storage__open"))
+ goto out;
+
+ err = bpf_map__reuse_fd(bridge_skel->maps.prog_array, prog_array_fd);
+ if (!ASSERT_OK(err, "reuse_prog_array"))
+ goto out;
+
+ err = bpf_object__load(bridge_skel->obj);
+ ASSERT_ERR(err, "tailcall_cgrp_storage_no_storage_bridge__load");
+out:
+ tailcall_cgrp_storage_no_storage__destroy(bridge_skel);
+ tailcall_cgrp_storage__destroy(callee_skel);
+ tailcall_cgrp_storage_owner__destroy(owner_skel);
+}
+
noinline void uprobe_sleepable_trigger(void)
{
asm volatile ("");
@@ -1781,4 +1957,14 @@ void test_tailcalls(void)
test_tailcall_failure();
if (test__start_subtest("tailcall_sleepable"))
test_tailcall_sleepable();
+ if (test__start_subtest("tailcall_cgrp_storage"))
+ test_tailcall_cgrp_storage();
+ if (test__start_subtest("tailcall_cgrp_storage_diff_storage"))
+ test_tailcall_cgrp_storage_diff_storage();
+ if (test__start_subtest("tailcall_cgrp_storage_no_storage"))
+ test_tailcall_cgrp_storage_no_storage();
+ if (test__start_subtest("tailcall_cgrp_storage_no_storage_leaf"))
+ test_tailcall_cgrp_storage_no_storage_leaf();
+ if (test__start_subtest("tailcall_cgrp_storage_no_storage_bridge"))
+ test_tailcall_cgrp_storage_no_storage_bridge();
}
diff --git a/tools/testing/selftests/bpf/prog_tests/task_kfunc.c b/tools/testing/selftests/bpf/prog_tests/task_kfunc.c
index 83b90335967a..e6e95c1416e6 100644
--- a/tools/testing/selftests/bpf/prog_tests/task_kfunc.c
+++ b/tools/testing/selftests/bpf/prog_tests/task_kfunc.c
@@ -68,6 +68,36 @@ cleanup:
task_kfunc_success__destroy(skel);
}
+static void run_syscall_success_test(const char *prog_name)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ struct task_kfunc_success *skel;
+ struct bpf_program *prog;
+ int err;
+
+ skel = open_load_task_kfunc_skel();
+ if (!ASSERT_OK_PTR(skel, "open_load_skel"))
+ return;
+
+ if (!ASSERT_OK(skel->bss->err, "pre_run_err"))
+ goto cleanup;
+
+ prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+ if (!ASSERT_OK_PTR(prog, "bpf_object__find_program_by_name"))
+ goto cleanup;
+
+ err = bpf_prog_test_run_opts(bpf_program__fd(prog), &opts);
+ if (!ASSERT_OK(err, "bpf_prog_test_run_opts"))
+ goto cleanup;
+ if (!ASSERT_EQ(opts.retval, 0, "retval"))
+ goto cleanup;
+
+ ASSERT_OK(skel->bss->err, "post_run_err");
+
+cleanup:
+ task_kfunc_success__destroy(skel);
+}
+
static int run_vpid_test(void *prog_name)
{
struct task_kfunc_success *skel;
@@ -140,7 +170,6 @@ static const char * const success_tests[] = {
"test_task_acquire_release_argument",
"test_task_acquire_release_current",
"test_task_acquire_leave_in_map",
- "test_task_xchg_release",
"test_task_map_acquire_release",
"test_task_current_acquire_release",
"test_task_from_pid_arg",
@@ -151,6 +180,10 @@ static const char * const success_tests[] = {
"test_task_kfunc_flavor_relo_not_found",
};
+static const char * const syscall_success_tests[] = {
+ "test_task_xchg_release",
+};
+
static const char * const vpid_success_tests[] = {
"test_task_from_vpid_current",
"test_task_from_vpid_invalid",
@@ -167,6 +200,13 @@ void test_task_kfunc(void)
run_success_test(success_tests[i]);
}
+ for (i = 0; i < ARRAY_SIZE(syscall_success_tests); i++) {
+ if (!test__start_subtest(syscall_success_tests[i]))
+ continue;
+
+ run_syscall_success_test(syscall_success_tests[i]);
+ }
+
for (i = 0; i < ARRAY_SIZE(vpid_success_tests); i++) {
if (!test__start_subtest(vpid_success_tests[i]))
continue;
diff --git a/tools/testing/selftests/bpf/prog_tests/task_local_storage.c b/tools/testing/selftests/bpf/prog_tests/task_local_storage.c
index 1b26c12f255a..5b2b56cc3a4f 100644
--- a/tools/testing/selftests/bpf/prog_tests/task_local_storage.c
+++ b/tools/testing/selftests/bpf/prog_tests/task_local_storage.c
@@ -47,6 +47,7 @@ static void test_sys_enter_exit(void)
skel->bss->target_pid = 0;
/* 2x gettid syscalls */
+ ASSERT_EQ(skel->bss->update_err, 0, "update_err");
ASSERT_EQ(skel->bss->enter_cnt, 2, "enter_cnt");
ASSERT_EQ(skel->bss->exit_cnt, 2, "exit_cnt");
ASSERT_EQ(skel->bss->mismatch_cnt, 0, "mismatch_cnt");
diff --git a/tools/testing/selftests/bpf/prog_tests/test_lsm.c b/tools/testing/selftests/bpf/prog_tests/test_lsm.c
index bdc4fc06bc5a..d7495efd4a56 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_lsm.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_lsm.c
@@ -5,36 +5,14 @@
*/
#include <test_progs.h>
-#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
-#include <malloc.h>
-#include <stdlib.h>
#include "lsm.skel.h"
#include "lsm_tailcall.skel.h"
char *CMD_ARGS[] = {"true", NULL};
-#define GET_PAGE_ADDR(ADDR, PAGE_SIZE) \
- (char *)(((unsigned long) (ADDR + PAGE_SIZE)) & ~(PAGE_SIZE-1))
-
-int stack_mprotect(void)
-{
- void *buf;
- long sz;
- int ret;
-
- sz = sysconf(_SC_PAGESIZE);
- if (sz < 0)
- return sz;
-
- buf = alloca(sz * 3);
- ret = mprotect(GET_PAGE_ADDR(buf, sz), sz,
- PROT_READ | PROT_WRITE | PROT_EXEC);
- return ret;
-}
-
int exec_cmd(int *monitored_pid)
{
int child_pid, child_status;
diff --git a/tools/testing/selftests/bpf/prog_tests/test_xdp_veth.c b/tools/testing/selftests/bpf/prog_tests/test_xdp_veth.c
index 3e98a1665936..1675b32753a8 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_xdp_veth.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_xdp_veth.c
@@ -456,7 +456,11 @@ static void xdp_veth_egress(u32 flags)
.remote_flags = flags,
}
};
- const char magic_mac[6] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
+ const unsigned char egress_macs[VETH_PAIRS_COUNT][ETH_ALEN] = {
+ { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01 },
+ { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x02 },
+ { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x03 },
+ };
struct xdp_redirect_multi_kern *xdp_redirect_multi_kern;
struct bpf_object *bpf_objs[VETH_EGRESS_SKEL_NB];
struct xdp_redirect_map *xdp_redirect_map;
@@ -512,7 +516,13 @@ static void xdp_veth_egress(u32 flags)
&net_config, prog_cfg, i))
goto destroy_xdp_redirect_map;
- err = bpf_map_update_elem(mac_map, &ifindex, magic_mac, 0);
+ {
+ __be64 mac = 0;
+
+ memcpy(&mac, egress_macs[i], ETH_ALEN);
+ err = bpf_map_update_elem(mac_map, &ifindex, &mac, 0);
+ }
+
if (!ASSERT_OK(err, "bpf_map_update_elem"))
goto destroy_xdp_redirect_map;
@@ -531,15 +541,162 @@ static void xdp_veth_egress(u32 flags)
for (i = 0; i < 2; i++) {
u32 key = i;
+ __be64 expected = 0;
u64 res;
err = bpf_map_lookup_elem(res_map, &key, &res);
if (!ASSERT_OK(err, "get MAC res"))
goto destroy_xdp_redirect_map;
- ASSERT_STRNEQ((const char *)&res, magic_mac, ETH_ALEN, "compare mac");
+ /* store_mac_1/2 run on the second/third remote veths. */
+ memcpy(&expected, egress_macs[i + 1], ETH_ALEN);
+ ASSERT_EQ(res, expected, "compare mac");
+ }
+
+destroy_xdp_redirect_map:
+ close_netns(nstoken);
+ xdp_redirect_map__destroy(xdp_redirect_map);
+destroy_xdp_redirect_multi_kern:
+ xdp_redirect_multi_kern__destroy(xdp_redirect_multi_kern);
+destroy_xdp_dummy:
+ xdp_dummy__destroy(xdp_dummy);
+
+ cleanup_network(&net_config);
+}
+
+static void xdp_veth_egress_last_dst(u32 flags)
+{
+ struct prog_configuration prog_cfg[VETH_PAIRS_COUNT] = {
+ {
+ .local_name = "xdp_redirect_map_all_prog",
+ .remote_name = "xdp_dummy_prog",
+ .local_flags = flags,
+ .remote_flags = flags,
+ },
+ {
+ .local_name = "xdp_redirect_map_all_prog",
+ .remote_name = "store_mac_1",
+ .local_flags = flags,
+ .remote_flags = flags,
+ },
+ {
+ .local_name = "xdp_redirect_map_all_prog",
+ .remote_name = "xdp_dummy_prog",
+ .local_flags = flags,
+ .remote_flags = flags,
+ }
+ };
+ const unsigned char egress_macs[VETH_PAIRS_COUNT][ETH_ALEN] = {
+ { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01 },
+ { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x02 },
+ { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x03 },
+ };
+ struct xdp_redirect_multi_kern *xdp_redirect_multi_kern;
+ struct bpf_object *bpf_objs[VETH_EGRESS_SKEL_NB];
+ struct xdp_redirect_map *xdp_redirect_map;
+ struct net_configuration net_config = {};
+ int mac_map, egress_map, res_map;
+ struct nstoken *nstoken = NULL;
+ struct xdp_dummy *xdp_dummy;
+ __be64 sentinel_mac = 0;
+ __be64 last_mac = 0;
+ __be64 res;
+ u32 key;
+ int err;
+ int i;
+
+ xdp_dummy = xdp_dummy__open_and_load();
+ if (!ASSERT_OK_PTR(xdp_dummy, "xdp_dummy__open_and_load"))
+ return;
+
+ xdp_redirect_multi_kern = xdp_redirect_multi_kern__open_and_load();
+ if (!ASSERT_OK_PTR(xdp_redirect_multi_kern, "xdp_redirect_multi_kern__open_and_load"))
+ goto destroy_xdp_dummy;
+
+ xdp_redirect_map = xdp_redirect_map__open_and_load();
+ if (!ASSERT_OK_PTR(xdp_redirect_map, "xdp_redirect_map__open_and_load"))
+ goto destroy_xdp_redirect_multi_kern;
+
+ if (!ASSERT_OK(create_network(&net_config), "create network"))
+ goto destroy_xdp_redirect_map;
+
+ mac_map = bpf_map__fd(xdp_redirect_multi_kern->maps.mac_map);
+ if (!ASSERT_OK_FD(mac_map, "open mac_map"))
+ goto destroy_xdp_redirect_map;
+
+ egress_map = bpf_map__fd(xdp_redirect_multi_kern->maps.map_egress);
+ if (!ASSERT_OK_FD(egress_map, "open map_egress"))
+ goto destroy_xdp_redirect_map;
+
+ bpf_objs[0] = xdp_dummy->obj;
+ bpf_objs[1] = xdp_redirect_multi_kern->obj;
+ bpf_objs[2] = xdp_redirect_map->obj;
+
+ nstoken = open_netns(net_config.ns0_name);
+ if (!ASSERT_OK_PTR(nstoken, "open NS0"))
+ goto destroy_xdp_redirect_map;
+
+ for (i = 0; i < VETH_PAIRS_COUNT; i++) {
+ struct bpf_devmap_val devmap_val = {};
+ int ifindex = if_nametoindex(net_config.veth_cfg[i].local_veth);
+ u32 key = i;
+
+ SYS(destroy_xdp_redirect_map,
+ "ip -n %s neigh add %s lladdr 00:00:00:00:00:01 dev %s",
+ net_config.veth_cfg[i].namespace, IP_NEIGH,
+ net_config.veth_cfg[i].remote_veth);
+
+ if (attach_programs_to_veth_pair(bpf_objs, VETH_EGRESS_SKEL_NB,
+ &net_config, prog_cfg, i))
+ goto destroy_xdp_redirect_map;
+
+ {
+ __be64 mac = 0;
+
+ memcpy(&mac, egress_macs[i], ETH_ALEN);
+ err = bpf_map_update_elem(mac_map, &ifindex, &mac, 0);
+ }
+
+ if (!ASSERT_OK(err, "bpf_map_update_elem"))
+ goto destroy_xdp_redirect_map;
+
+ devmap_val.ifindex = ifindex;
+ devmap_val.bpf_prog.fd = -1;
+
+ if (i == VETH_PAIRS_COUNT - 1)
+ devmap_val.bpf_prog.fd =
+ bpf_program__fd(xdp_redirect_multi_kern->progs.xdp_devmap_prog);
+
+ err = bpf_map_update_elem(egress_map, &key, &devmap_val, 0);
+ if (!ASSERT_OK(err, "bpf_map_update_elem"))
+ goto destroy_xdp_redirect_map;
}
+ res_map = bpf_map__fd(xdp_redirect_map->maps.rx_mac);
+ if (!ASSERT_OK_FD(res_map, "open rx_map"))
+ goto destroy_xdp_redirect_map;
+
+ memcpy(&sentinel_mac, egress_macs[VETH_PAIRS_COUNT - 1], ETH_ALEN);
+ memcpy(&last_mac, egress_macs[VETH_PAIRS_COUNT - 1], ETH_ALEN);
+
+ key = 0;
+ err = bpf_map_update_elem(res_map, &key, &sentinel_mac, 0);
+ if (!ASSERT_OK(err, "init rx mac"))
+ goto destroy_xdp_redirect_map;
+
+ SYS_NOFAIL("ip netns exec %s ping %s -i 0.1 -c 4 -W1 > /dev/null ",
+ net_config.veth_cfg[0].namespace, IP_NEIGH);
+
+ err = bpf_map_lookup_elem(res_map, &key, &res);
+ if (!ASSERT_OK(err, "get MAC res"))
+ goto destroy_xdp_redirect_map;
+
+ if (!ASSERT_NEQ(res, sentinel_mac, "rx_mac overwritten by store_mac_1"))
+ goto destroy_xdp_redirect_map;
+
+ if (!ASSERT_NEQ(res, last_mac, "earlier dst not rewritten by last dst"))
+ goto destroy_xdp_redirect_map;
+
destroy_xdp_redirect_map:
close_netns(nstoken);
xdp_redirect_map__destroy(xdp_redirect_map);
@@ -596,4 +753,7 @@ void test_xdp_veth_egress(void)
if (test__start_subtest("SKB_MODE/egress"))
xdp_veth_egress(XDP_FLAGS_SKB_MODE);
+
+ if (test__start_subtest("SKB_MODE/egress_last_dst"))
+ xdp_veth_egress_last_dst(XDP_FLAGS_SKB_MODE);
}
diff --git a/tools/testing/selftests/bpf/prog_tests/tracing_multi.c b/tools/testing/selftests/bpf/prog_tests/tracing_multi.c
new file mode 100644
index 000000000000..f02ffc7f41d7
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/tracing_multi.c
@@ -0,0 +1,960 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <bpf/btf.h>
+#include <search.h>
+#include "bpf/libbpf_internal.h"
+#include "tracing_multi.skel.h"
+#include "tracing_multi_module.skel.h"
+#include "tracing_multi_intersect.skel.h"
+#include "tracing_multi_session.skel.h"
+#include "tracing_multi_fail.skel.h"
+#include "tracing_multi_verifier.skel.h"
+#include "tracing_multi_bench.skel.h"
+#include "tracing_multi_rollback.skel.h"
+#include "trace_helpers.h"
+
+static __u64 bpf_fentry_test_cookies[] = {
+ 8, /* bpf_fentry_test1 */
+ 9, /* bpf_fentry_test2 */
+ 7, /* bpf_fentry_test3 */
+ 5, /* bpf_fentry_test4 */
+ 4, /* bpf_fentry_test5 */
+ 2, /* bpf_fentry_test6 */
+ 3, /* bpf_fentry_test7 */
+ 1, /* bpf_fentry_test8 */
+ 10, /* bpf_fentry_test9 */
+ 6, /* bpf_fentry_test10 */
+};
+
+static const char * const bpf_fentry_test[] = {
+ "bpf_fentry_test1",
+ "bpf_fentry_test2",
+ "bpf_fentry_test3",
+ "bpf_fentry_test4",
+ "bpf_fentry_test5",
+ "bpf_fentry_test6",
+ "bpf_fentry_test7",
+ "bpf_fentry_test8",
+ "bpf_fentry_test9",
+ "bpf_fentry_test10",
+};
+
+static const char * const bpf_testmod_fentry_test[] = {
+ "bpf_testmod_fentry_test1",
+ "bpf_testmod_fentry_test2",
+ "bpf_testmod_fentry_test3",
+ "bpf_testmod_fentry_test7",
+ "bpf_testmod_fentry_test11",
+};
+
+#define FUNCS_CNT (ARRAY_SIZE(bpf_fentry_test))
+
+static int get_random_funcs(const char **funcs)
+{
+ int i, cnt = 0;
+
+ for (i = 0; i < FUNCS_CNT; i++) {
+ if (rand() % 2)
+ funcs[cnt++] = bpf_fentry_test[i];
+ }
+ /* we always need at least one.. */
+ if (!cnt)
+ funcs[cnt++] = bpf_fentry_test[rand() % FUNCS_CNT];
+ return cnt;
+}
+
+static int compare(const void *ppa, const void *ppb)
+{
+ const char *pa = *(const char **) ppa;
+ const char *pb = *(const char **) ppb;
+
+ return strcmp(pa, pb);
+}
+
+static void tdestroy_free_nop(void *ptr)
+{
+}
+
+static __u32 *get_ids(const char * const funcs[], int funcs_cnt, const char *mod)
+{
+ struct btf *btf, *vmlinux_btf = NULL;
+ __u32 nr, type_id, cnt = 0;
+ void *root = NULL;
+ __u32 *ids = NULL;
+ int i, err = 0;
+
+ btf = btf__load_vmlinux_btf();
+ if (!ASSERT_OK_PTR(btf, "btf__load_vmlinux_btf"))
+ return NULL;
+
+ if (mod) {
+ vmlinux_btf = btf;
+ btf = btf__load_module_btf(mod, vmlinux_btf);
+ if (!ASSERT_OK_PTR(btf, "btf__load_module_btf")) {
+ btf__free(vmlinux_btf);
+ return NULL;
+ }
+ }
+
+ ids = calloc(funcs_cnt, sizeof(ids[0]));
+ if (!ids)
+ goto out;
+
+ /*
+ * We sort function names by name and search them
+ * below for each function.
+ */
+ for (i = 0; i < funcs_cnt; i++) {
+ if (!tsearch(&funcs[i], &root, compare)) {
+ ASSERT_FAIL("tsearch failed");
+ err = -1;
+ goto error;
+ }
+ }
+
+ nr = btf__type_cnt(btf);
+ for (type_id = 1; type_id < nr && cnt < funcs_cnt; type_id++) {
+ const struct btf_type *type;
+ const char *str, ***val;
+ unsigned int idx;
+
+ type = btf__type_by_id(btf, type_id);
+ if (!type) {
+ err = -1;
+ break;
+ }
+
+ if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC)
+ continue;
+
+ str = btf__name_by_offset(btf, type->name_off);
+ if (!str) {
+ err = -1;
+ break;
+ }
+
+ val = tfind(&str, &root, compare);
+ if (!val)
+ continue;
+
+ /*
+ * We keep pointer for each function name so we can get the original
+ * array index and have the resulting ids array matching the original
+ * function array.
+ *
+ * Doing it this way allow us to easily test the cookies support,
+ * because each cookie is attached to particular function/id.
+ */
+ idx = *val - funcs;
+ ids[idx] = type_id;
+ cnt++;
+ }
+
+error:
+ if (err) {
+ free(ids);
+ ids = NULL;
+ }
+
+out:
+ tdestroy(root, tdestroy_free_nop);
+ btf__free(vmlinux_btf);
+ btf__free(btf);
+ return ids;
+}
+
+static void tracing_multi_test_run(struct tracing_multi *skel)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+ int err, prog_fd;
+
+ prog_fd = bpf_program__fd(skel->progs.test_fentry);
+ err = bpf_prog_test_run_opts(prog_fd, &topts);
+ ASSERT_OK(err, "test_run");
+
+ /* extra +1 count for sleepable programs */
+ ASSERT_EQ(skel->bss->test_result_fentry, FUNCS_CNT + 1, "test_result_fentry");
+ ASSERT_EQ(skel->bss->test_result_fexit, FUNCS_CNT + 1, "test_result_fexit");
+}
+
+static void test_skel_api(void)
+{
+ struct tracing_multi *skel;
+ int err;
+
+ skel = tracing_multi__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi__open_and_load"))
+ return;
+
+ skel->bss->pid = getpid();
+
+ err = tracing_multi__attach(skel);
+ if (!ASSERT_OK(err, "tracing_multi__attach"))
+ goto cleanup;
+
+ tracing_multi_test_run(skel);
+
+cleanup:
+ tracing_multi__destroy(skel);
+}
+
+static void test_link_api_pattern(void)
+{
+ struct tracing_multi *skel;
+
+ skel = tracing_multi__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi__open_and_load"))
+ return;
+
+ skel->bss->pid = getpid();
+
+ skel->links.test_fentry = bpf_program__attach_tracing_multi(skel->progs.test_fentry,
+ "bpf_fentry_test*", NULL);
+ if (!ASSERT_OK_PTR(skel->links.test_fentry, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ skel->links.test_fexit = bpf_program__attach_tracing_multi(skel->progs.test_fexit,
+ "bpf_fentry_test*", NULL);
+ if (!ASSERT_OK_PTR(skel->links.test_fexit, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ skel->links.test_fentry_s = bpf_program__attach_tracing_multi(skel->progs.test_fentry_s,
+ "bpf_fentry_test1", NULL);
+ if (!ASSERT_OK_PTR(skel->links.test_fentry_s, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ skel->links.test_fexit_s = bpf_program__attach_tracing_multi(skel->progs.test_fexit_s,
+ "bpf_fentry_test1", NULL);
+ if (!ASSERT_OK_PTR(skel->links.test_fexit_s, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ tracing_multi_test_run(skel);
+
+cleanup:
+ tracing_multi__destroy(skel);
+}
+
+static void test_link_api_ids(bool test_cookies)
+{
+ LIBBPF_OPTS(bpf_tracing_multi_opts, opts);
+ struct tracing_multi *skel;
+ size_t cnt = FUNCS_CNT;
+ __u32 *ids;
+
+ skel = tracing_multi__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi__open_and_load"))
+ return;
+
+ skel->bss->pid = getpid();
+ skel->bss->test_cookies = test_cookies;
+
+ ids = get_ids(bpf_fentry_test, cnt, NULL);
+ if (!ASSERT_OK_PTR(ids, "get_ids"))
+ goto cleanup;
+
+ opts.ids = ids;
+ opts.cnt = cnt;
+
+ if (test_cookies)
+ opts.cookies = bpf_fentry_test_cookies;
+
+ skel->links.test_fentry = bpf_program__attach_tracing_multi(skel->progs.test_fentry,
+ NULL, &opts);
+ if (!ASSERT_OK_PTR(skel->links.test_fentry, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ skel->links.test_fexit = bpf_program__attach_tracing_multi(skel->progs.test_fexit,
+ NULL, &opts);
+ if (!ASSERT_OK_PTR(skel->links.test_fexit, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ /* Only bpf_fentry_test1 is allowed for sleepable programs. */
+ opts.cnt = 1;
+ skel->links.test_fentry_s = bpf_program__attach_tracing_multi(skel->progs.test_fentry_s,
+ NULL, &opts);
+ if (!ASSERT_OK_PTR(skel->links.test_fentry_s, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ skel->links.test_fexit_s = bpf_program__attach_tracing_multi(skel->progs.test_fexit_s,
+ NULL, &opts);
+ if (!ASSERT_OK_PTR(skel->links.test_fexit_s, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ tracing_multi_test_run(skel);
+
+cleanup:
+ tracing_multi__destroy(skel);
+ free(ids);
+}
+
+static void test_module_skel_api(void)
+{
+ struct tracing_multi_module *skel = NULL;
+ int err;
+
+ skel = tracing_multi_module__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi__open_and_load"))
+ return;
+
+ skel->bss->pid = getpid();
+
+ err = tracing_multi_module__attach(skel);
+ if (!ASSERT_OK(err, "tracing_multi__attach"))
+ goto cleanup;
+
+ ASSERT_OK(trigger_module_test_read(1), "trigger_read");
+ ASSERT_EQ(skel->bss->test_result_fentry, 5, "test_result_fentry");
+ ASSERT_EQ(skel->bss->test_result_fexit, 5, "test_result_fexit");
+
+cleanup:
+ tracing_multi_module__destroy(skel);
+}
+
+static void test_module_link_api_pattern(void)
+{
+ struct tracing_multi_module *skel = NULL;
+
+ skel = tracing_multi_module__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi_module__open_and_load"))
+ return;
+
+ skel->bss->pid = getpid();
+
+ skel->links.test_fentry = bpf_program__attach_tracing_multi(skel->progs.test_fentry,
+ "bpf_testmod:bpf_testmod_fentry_test*", NULL);
+ if (!ASSERT_OK_PTR(skel->links.test_fentry, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ skel->links.test_fexit = bpf_program__attach_tracing_multi(skel->progs.test_fexit,
+ "bpf_testmod:bpf_testmod_fentry_test*", NULL);
+ if (!ASSERT_OK_PTR(skel->links.test_fexit, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ ASSERT_OK(trigger_module_test_read(1), "trigger_read");
+ ASSERT_EQ(skel->bss->test_result_fentry, 5, "test_result_fentry");
+ ASSERT_EQ(skel->bss->test_result_fexit, 5, "test_result_fexit");
+
+cleanup:
+ tracing_multi_module__destroy(skel);
+}
+
+static void test_module_link_api_ids(void)
+{
+ size_t cnt = ARRAY_SIZE(bpf_testmod_fentry_test);
+ LIBBPF_OPTS(bpf_tracing_multi_opts, opts);
+ struct tracing_multi_module *skel = NULL;
+ __u32 *ids;
+
+ skel = tracing_multi_module__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi_module__open_and_load"))
+ return;
+
+ skel->bss->pid = getpid();
+
+ ids = get_ids(bpf_testmod_fentry_test, cnt, "bpf_testmod");
+ if (!ASSERT_OK_PTR(ids, "get_ids"))
+ goto cleanup;
+
+ opts.ids = ids;
+ opts.cnt = cnt;
+
+ skel->links.test_fentry = bpf_program__attach_tracing_multi(skel->progs.test_fentry,
+ NULL, &opts);
+ if (!ASSERT_OK_PTR(skel->links.test_fentry, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ skel->links.test_fexit = bpf_program__attach_tracing_multi(skel->progs.test_fexit,
+ NULL, &opts);
+ if (!ASSERT_OK_PTR(skel->links.test_fexit, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ ASSERT_OK(trigger_module_test_read(1), "trigger_read");
+ ASSERT_EQ(skel->bss->test_result_fentry, 5, "test_result_fentry");
+ ASSERT_EQ(skel->bss->test_result_fexit, 5, "test_result_fexit");
+
+cleanup:
+ tracing_multi_module__destroy(skel);
+ free(ids);
+}
+
+static bool is_set(__u32 mask, __u32 bit)
+{
+ return (1 << bit) & mask;
+}
+
+static void __test_intersect(__u32 mask, const struct bpf_program *progs[4], __u64 *test_results[4])
+{
+ LIBBPF_OPTS(bpf_tracing_multi_opts, opts);
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+ struct bpf_link *links[4] = { NULL };
+ const char *funcs[FUNCS_CNT];
+ __u64 expected[4];
+ __u32 *ids, i;
+ int err, cnt;
+
+ /*
+ * We have 4 programs in progs and the mask bits pick which
+ * of them gets attached to randomly chosen functions.
+ */
+ for (i = 0; i < 4; i++) {
+ if (!is_set(mask, i))
+ continue;
+
+ cnt = get_random_funcs(funcs);
+ ids = get_ids(funcs, cnt, NULL);
+ if (!ASSERT_OK_PTR(ids, "get_ids"))
+ goto cleanup;
+
+ opts.ids = ids;
+ opts.cnt = cnt;
+ links[i] = bpf_program__attach_tracing_multi(progs[i], NULL, &opts);
+ free(ids);
+
+ if (!ASSERT_OK_PTR(links[i], "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ expected[i] = *test_results[i] + cnt;
+ }
+
+ err = bpf_prog_test_run_opts(bpf_program__fd(progs[0]), &topts);
+ ASSERT_OK(err, "test_run");
+
+ for (i = 0; i < 4; i++) {
+ if (!is_set(mask, i))
+ continue;
+ ASSERT_EQ(*test_results[i], expected[i], "test_results");
+ }
+
+cleanup:
+ for (i = 0; i < 4; i++)
+ bpf_link__destroy(links[i]);
+}
+
+static void test_intersect(void)
+{
+ struct tracing_multi_intersect *skel;
+ const struct bpf_program *progs[4];
+ __u64 *test_results[4];
+ __u32 i;
+
+ skel = tracing_multi_intersect__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi_intersect__open_and_load"))
+ return;
+
+ skel->bss->pid = getpid();
+
+ progs[0] = skel->progs.fentry_1;
+ progs[1] = skel->progs.fexit_1;
+ progs[2] = skel->progs.fentry_2;
+ progs[3] = skel->progs.fexit_2;
+
+ test_results[0] = &skel->bss->test_result_fentry_1;
+ test_results[1] = &skel->bss->test_result_fexit_1;
+ test_results[2] = &skel->bss->test_result_fentry_2;
+ test_results[3] = &skel->bss->test_result_fexit_2;
+
+ for (i = 1; i < 16; i++)
+ __test_intersect(i, progs, test_results);
+
+ tracing_multi_intersect__destroy(skel);
+}
+
+static void test_session(void)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+ struct tracing_multi_session *skel;
+ int err, prog_fd;
+
+ skel = tracing_multi_session__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi_session__open_and_load"))
+ return;
+
+ skel->bss->pid = getpid();
+
+ err = tracing_multi_session__attach(skel);
+ if (!ASSERT_OK(err, "tracing_multi_session__attach"))
+ goto cleanup;
+
+ /* execute kernel session */
+ prog_fd = bpf_program__fd(skel->progs.test_session_1);
+ err = bpf_prog_test_run_opts(prog_fd, &topts);
+ ASSERT_OK(err, "test_run");
+
+ /* 10 for test_session_1, 1 for test_fsession_s */
+ ASSERT_EQ(skel->bss->test_result_fentry, 11, "test_result_fentry");
+ /* extra count (+1 for each fexit execution) for test_result_fexit cookie check/inc */
+ ASSERT_EQ(skel->bss->test_result_fexit, 22, "test_result_fexit");
+
+ skel->bss->test_result_fentry = 0;
+ skel->bss->test_result_fexit = 0;
+
+ /* execute bpf_testmo.ko session */
+ ASSERT_OK(trigger_module_test_read(1), "trigger_read");
+
+ /* 5 for test_session_2 */
+ ASSERT_EQ(skel->bss->test_result_fentry, 5, "test_result_fentry");
+ /* extra count (+1 for each fexit execution) for test_result_fexit cookie */
+ ASSERT_EQ(skel->bss->test_result_fexit, 10, "test_result_fexit");
+
+
+cleanup:
+ tracing_multi_session__destroy(skel);
+}
+
+static void test_attach_api_fails(void)
+{
+ LIBBPF_OPTS(bpf_tracing_multi_opts, opts);
+ static const char * const func[] = {
+ "bpf_fentry_test2",
+ };
+ struct tracing_multi_fail *skel = NULL;
+ __u32 ids[2] = {}, *ids2 = NULL;
+ __u64 cookies[2];
+
+ skel = tracing_multi_fail__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi_fail__open_and_load"))
+ return;
+
+ /* fail#1 (libbpf) pattern and opts NULL */
+ skel->links.test_fentry = bpf_program__attach_tracing_multi(skel->progs.test_fentry,
+ NULL, NULL);
+ if (!ASSERT_EQ(libbpf_get_error(skel->links.test_fentry), -EINVAL, "fail_1"))
+ goto cleanup;
+
+ /* fail#2 (libbpf) pattern and ids */
+ LIBBPF_OPTS_RESET(opts,
+ .ids = ids,
+ .cnt = 2,
+ );
+
+ skel->links.test_fentry = bpf_program__attach_tracing_multi(skel->progs.test_fentry,
+ "bpf_fentry_test*", &opts);
+ if (!ASSERT_EQ(libbpf_get_error(skel->links.test_fentry), -EINVAL, "fail_2"))
+ goto cleanup;
+
+ /* fail#3 (libbpf) pattern and cookies */
+ LIBBPF_OPTS_RESET(opts,
+ .ids = NULL,
+ .cnt = 2,
+ .cookies = cookies,
+ );
+
+ skel->links.test_fentry = bpf_program__attach_tracing_multi(skel->progs.test_fentry,
+ "bpf_fentry_test*", &opts);
+ if (!ASSERT_EQ(libbpf_get_error(skel->links.test_fentry), -EINVAL, "fail_3"))
+ goto cleanup;
+
+ /* fail#4 (libbpf) bogus pattern */
+ skel->links.test_fentry = bpf_program__attach_tracing_multi(skel->progs.test_fentry,
+ "bpf_not_really_a_function*", NULL);
+ if (!ASSERT_EQ(libbpf_get_error(skel->links.test_fentry), -EINVAL, "fail_4"))
+ goto cleanup;
+
+ /* fail#5 (kernel) abnormal cnt */
+ LIBBPF_OPTS_RESET(opts,
+ .ids = ids,
+ .cnt = INT_MAX,
+ );
+
+ skel->links.test_fentry = bpf_program__attach_tracing_multi(skel->progs.test_fentry,
+ NULL, &opts);
+ if (!ASSERT_EQ(libbpf_get_error(skel->links.test_fentry), -E2BIG, "fail_5"))
+ goto cleanup;
+
+ /* fail#6 (kernel) attach sleepable program to not-allowed function */
+ ids2 = get_ids(func, 1, NULL);
+ if (!ASSERT_OK_PTR(ids2, "get_ids"))
+ goto cleanup;
+
+ LIBBPF_OPTS_RESET(opts,
+ .ids = ids2,
+ .cnt = 1,
+ );
+
+ skel->links.test_fentry_s = bpf_program__attach_tracing_multi(skel->progs.test_fentry_s,
+ NULL, &opts);
+ if (!ASSERT_EQ(libbpf_get_error(skel->links.test_fentry_s), -EINVAL, "fail_6"))
+ goto cleanup;
+
+ /* fail#7 (kernel) attach with duplicate id */
+ ids[0] = ids2[0];
+ ids[1] = ids2[0];
+
+ LIBBPF_OPTS_RESET(opts,
+ .ids = ids,
+ .cnt = 2,
+ );
+
+ skel->links.test_fentry = bpf_program__attach_tracing_multi(skel->progs.test_fentry,
+ NULL, &opts);
+ ASSERT_EQ(libbpf_get_error(skel->links.test_fentry), -EINVAL, "fail_7");
+
+cleanup:
+ tracing_multi_fail__destroy(skel);
+ free(ids2);
+}
+
+void serial_test_tracing_multi_bench_attach(void)
+{
+ LIBBPF_OPTS(bpf_tracing_multi_opts, opts);
+ struct tracing_multi_bench *skel = NULL;
+ long attach_start_ns, attach_end_ns;
+ long detach_start_ns, detach_end_ns;
+ double attach_delta, detach_delta;
+ struct bpf_link *link = NULL;
+ size_t i, cap = 0, cnt = 0;
+ struct ksyms *ksyms = NULL;
+ void *root = NULL;
+ void *dups = NULL;
+ __u32 *ids = NULL;
+ __u32 nr, type_id;
+ struct btf *btf;
+ int err;
+
+#ifndef __x86_64__
+ test__skip();
+ return;
+#endif
+
+ btf = btf__load_vmlinux_btf();
+ if (!ASSERT_OK_PTR(btf, "btf__load_vmlinux_btf"))
+ return;
+
+ skel = tracing_multi_bench__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi_bench__open_and_load"))
+ goto cleanup;
+
+ if (!ASSERT_OK(bpf_get_ksyms(&ksyms, true), "get_syms"))
+ goto cleanup;
+
+ /* Get all ftrace 'safe' symbols.. */
+ for (i = 0; i < ksyms->filtered_cnt; i++) {
+ if (!tsearch(&ksyms->filtered_syms[i], &root, compare)) {
+ ASSERT_FAIL("tsearch failed");
+ goto cleanup;
+ }
+ }
+
+ /*
+ * Collect names that are not unique in kallsyms. The kernel resolves a
+ * tracing-multi BTF id to an address with kallsyms_lookup_name(), which
+ * returns the first symbol of that name. For a duplicate name that may
+ * be a different (non-ftrace-able) instance than the ftrace-able one in
+ * available_filter_functions, so attaching to it by BTF id fails with
+ * -ENOENT (e.g. t_start/t_next/t_stop). ksyms->syms is sorted by name,
+ * so equal names are adjacent.
+ */
+ for (i = 1; i < ksyms->sym_cnt; i++) {
+ if (strcmp(ksyms->syms[i].name, ksyms->syms[i - 1].name))
+ continue;
+ if (!tsearch(&ksyms->syms[i].name, &dups, compare)) {
+ ASSERT_FAIL("tsearch failed");
+ goto cleanup;
+ }
+ }
+
+ /* ..and filter them through BTF and btf_type_is_traceable_func. */
+ nr = btf__type_cnt(btf);
+ for (type_id = 1; type_id < nr; type_id++) {
+ const struct btf_type *type;
+ const char *str;
+
+ type = btf__type_by_id(btf, type_id);
+ if (!type)
+ break;
+
+ if (BTF_INFO_KIND(type->info) != BTF_KIND_FUNC)
+ continue;
+
+ str = btf__name_by_offset(btf, type->name_off);
+ if (!str)
+ break;
+
+ if (!tfind(&str, &root, compare))
+ continue;
+
+ /* Skip names that are not unique in kallsyms, see above. */
+ if (tfind(&str, &dups, compare))
+ continue;
+
+ if (!btf_type_is_traceable_func(btf, type))
+ continue;
+
+ err = libbpf_ensure_mem((void **) &ids, &cap, sizeof(*ids), cnt + 1);
+ if (err)
+ goto cleanup;
+
+ ids[cnt++] = type_id;
+ }
+
+ opts.ids = ids;
+ opts.cnt = cnt;
+
+ attach_start_ns = get_time_ns();
+ link = bpf_program__attach_tracing_multi(skel->progs.bench, NULL, &opts);
+ attach_end_ns = get_time_ns();
+
+ if (!ASSERT_OK_PTR(link, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ detach_start_ns = get_time_ns();
+ bpf_link__destroy(link);
+ detach_end_ns = get_time_ns();
+
+ attach_delta = (attach_end_ns - attach_start_ns) / 1000000000.0;
+ detach_delta = (detach_end_ns - detach_start_ns) / 1000000000.0;
+
+ printf("%s: found %lu functions\n", __func__, cnt);
+ printf("%s: attached in %7.3lfs\n", __func__, attach_delta);
+ printf("%s: detached in %7.3lfs\n", __func__, detach_delta);
+
+cleanup:
+ tracing_multi_bench__destroy(skel);
+ tdestroy(root, tdestroy_free_nop);
+ tdestroy(dups, tdestroy_free_nop);
+ free_kallsyms_local(ksyms);
+ free(ids);
+ btf__free(btf);
+}
+
+static void tracing_multi_rollback_run(struct tracing_multi_rollback *skel)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+ int err, prog_fd;
+
+ prog_fd = bpf_program__fd(skel->progs.test_fentry);
+ err = bpf_prog_test_run_opts(prog_fd, &topts);
+ ASSERT_OK(err, "test_run");
+
+ /* make sure the rollback code did not leave any program attached */
+ ASSERT_EQ(skel->bss->test_result_fentry, 0, "test_result_fentry");
+ ASSERT_EQ(skel->bss->test_result_fexit, 0, "test_result_fexit");
+}
+
+static void test_rollback_put(void)
+{
+ LIBBPF_OPTS(bpf_tracing_multi_opts, opts);
+ struct tracing_multi_rollback *skel = NULL;
+ size_t cnt = FUNCS_CNT;
+ __u32 *ids = NULL;
+ int err;
+
+ skel = tracing_multi_rollback__open();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi_rollback__open"))
+ return;
+
+ bpf_program__set_autoload(skel->progs.test_fentry, true);
+ bpf_program__set_autoload(skel->progs.test_fexit, true);
+
+ err = tracing_multi_rollback__load(skel);
+ if (!ASSERT_OK(err, "tracing_multi_rollback__load"))
+ goto cleanup;
+
+ ids = get_ids(bpf_fentry_test, cnt, NULL);
+ if (!ASSERT_OK_PTR(ids, "get_ids"))
+ goto cleanup;
+
+ /*
+ * Mangle last id to trigger rollback, which needs to do put
+ * on get-ed trampolines.
+ */
+ ids[9] = 0;
+
+ opts.ids = ids;
+ opts.cnt = cnt;
+
+ skel->bss->pid = getpid();
+
+ skel->links.test_fentry = bpf_program__attach_tracing_multi(skel->progs.test_fentry,
+ NULL, &opts);
+ if (!ASSERT_ERR_PTR(skel->links.test_fentry, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ skel->links.test_fexit = bpf_program__attach_tracing_multi(skel->progs.test_fexit,
+ NULL, &opts);
+ if (!ASSERT_ERR_PTR(skel->links.test_fexit, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ /* We don't really attach any program, but let's make sure. */
+ tracing_multi_rollback_run(skel);
+
+cleanup:
+ tracing_multi_rollback__destroy(skel);
+ free(ids);
+}
+
+static void fillers_cleanup(struct tracing_multi_rollback **skels, int cnt)
+{
+ int i;
+
+ for (i = 0; i < cnt; i++)
+ tracing_multi_rollback__destroy(skels[i]);
+
+ free(skels);
+}
+
+static struct tracing_multi_rollback *extra_load_and_link(void)
+{
+ struct tracing_multi_rollback *skel;
+ int err;
+
+ skel = tracing_multi_rollback__open();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi_rollback__open"))
+ goto cleanup;
+
+ bpf_program__set_autoload(skel->progs.extra, true);
+
+ err = tracing_multi_rollback__load(skel);
+ if (!ASSERT_OK(err, "tracing_multi_rollback__load"))
+ goto cleanup;
+
+ skel->links.extra = bpf_program__attach_trace(skel->progs.extra);
+ if (!ASSERT_OK_PTR(skel->links.extra, "bpf_program__attach_trace"))
+ goto cleanup;
+
+ return skel;
+
+cleanup:
+ tracing_multi_rollback__destroy(skel);
+ return NULL;
+}
+
+static struct tracing_multi_rollback **fillers_load_and_link(int max)
+{
+ struct tracing_multi_rollback **skels, *skel;
+ int i, err;
+
+ skels = calloc(max + 1, sizeof(*skels));
+ if (!ASSERT_OK_PTR(skels, "calloc"))
+ return NULL;
+
+ for (i = 0; i < max; i++) {
+ skel = skels[i] = tracing_multi_rollback__open();
+ if (!ASSERT_OK_PTR(skels[i], "tracing_multi_rollback__open"))
+ goto cleanup;
+
+ bpf_program__set_autoload(skel->progs.filler, true);
+
+ err = tracing_multi_rollback__load(skel);
+ if (!ASSERT_OK(err, "tracing_multi_rollback__load"))
+ goto cleanup;
+
+ skel->links.filler = bpf_program__attach_trace(skel->progs.filler);
+ if (!ASSERT_OK_PTR(skels[i]->links.filler, "bpf_program__attach_trace"))
+ goto cleanup;
+ }
+
+ return skels;
+
+cleanup:
+ fillers_cleanup(skels, i + 1);
+ return NULL;
+}
+
+static void test_rollback_unlink(void)
+{
+ struct tracing_multi_rollback *skel = NULL, *extra;
+ LIBBPF_OPTS(bpf_tracing_multi_opts, opts);
+ struct tracing_multi_rollback **fillers;
+ size_t cnt = FUNCS_CNT;
+ __u32 *ids = NULL;
+ int err, max;
+
+ max = get_bpf_max_tramp_links();
+ if (!ASSERT_GE(max, 1, "bpf_max_tramp_links"))
+ return;
+
+ /* Attach maximum allowed programs to bpf_fentry_test10 */
+ fillers = fillers_load_and_link(max);
+ if (!ASSERT_OK_PTR(fillers, "fillers_load_and_link"))
+ return;
+
+ extra = extra_load_and_link();
+ if (!ASSERT_OK_PTR(extra, "extra_load_and_link"))
+ goto cleanup;
+
+ skel = tracing_multi_rollback__open();
+ if (!ASSERT_OK_PTR(skel, "tracing_multi_rollback__open"))
+ goto cleanup;
+
+ bpf_program__set_autoload(skel->progs.test_fentry, true);
+ bpf_program__set_autoload(skel->progs.test_fexit, true);
+
+ /*
+ * Attach tracing_multi link on bpf_fentry_test1-10, which will
+ * fail on bpf_fentry_test10 function, because it already has
+ * maximum allowed programs attached.
+ *
+ * The rollback needs to unlink already link-ed trampolines and
+ * put all of them.
+ */
+ err = tracing_multi_rollback__load(skel);
+ if (!ASSERT_OK(err, "tracing_multi_rollback__load"))
+ goto cleanup;
+
+ ids = get_ids(bpf_fentry_test, cnt, NULL);
+ if (!ASSERT_OK_PTR(ids, "get_ids"))
+ goto cleanup;
+
+ opts.ids = ids;
+ opts.cnt = cnt;
+
+ skel->bss->pid = getpid();
+
+ skel->links.test_fentry = bpf_program__attach_tracing_multi(skel->progs.test_fentry,
+ NULL, &opts);
+ if (!ASSERT_ERR_PTR(skel->links.test_fentry, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ skel->links.test_fexit = bpf_program__attach_tracing_multi(skel->progs.test_fexit,
+ NULL, &opts);
+ if (!ASSERT_ERR_PTR(skel->links.test_fexit, "bpf_program__attach_tracing_multi"))
+ goto cleanup;
+
+ tracing_multi_rollback_run(skel);
+
+cleanup:
+ fillers_cleanup(fillers, max);
+ tracing_multi_rollback__destroy(extra);
+ tracing_multi_rollback__destroy(skel);
+ free(ids);
+}
+
+void serial_test_tracing_multi_attach_rollback(void)
+{
+ if (test__start_subtest("put"))
+ test_rollback_put();
+ if (test__start_subtest("unlink"))
+ test_rollback_unlink();
+}
+
+void test_tracing_multi_test(void)
+{
+#ifndef __x86_64__
+ test__skip();
+ return;
+#endif
+
+ if (test__start_subtest("skel_api"))
+ test_skel_api();
+ if (test__start_subtest("link_api_pattern"))
+ test_link_api_pattern();
+ if (test__start_subtest("link_api_ids"))
+ test_link_api_ids(false);
+ if (test__start_subtest("module_skel_api"))
+ test_module_skel_api();
+ if (test__start_subtest("module_link_api_pattern"))
+ test_module_link_api_pattern();
+ if (test__start_subtest("module_link_api_ids"))
+ test_module_link_api_ids();
+ if (test__start_subtest("intersect"))
+ test_intersect();
+ if (test__start_subtest("cookies"))
+ test_link_api_ids(true);
+ if (test__start_subtest("session"))
+ test_session();
+ if (test__start_subtest("attach_api_fails"))
+ test_attach_api_fails();
+ RUN_TESTS(tracing_multi_verifier);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c b/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
index 56cbea280fbd..f0baf5738b75 100644
--- a/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
+++ b/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
@@ -2,6 +2,7 @@
#include <unistd.h>
#include <pthread.h>
+#include <fcntl.h>
#include <test_progs.h>
#include "uprobe_multi.skel.h"
#include "uprobe_multi_bench.skel.h"
@@ -536,7 +537,37 @@ static void test_attach_api_fails(void)
link_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
if (!ASSERT_ERR(link_fd, "link_fd"))
goto cleanup;
- ASSERT_EQ(link_fd, -EINVAL, "pid_is_wrong");
+ if (!ASSERT_EQ(link_fd, -EINVAL, "pid_is_wrong"))
+ goto cleanup;
+
+ /* wrong path_fd */
+ LIBBPF_OPTS_RESET(opts,
+ .uprobe_multi.path = NULL,
+ .uprobe_multi.path_fd = -1,
+ .uprobe_multi.flags = BPF_F_UPROBE_MULTI_PATH_FD,
+ .uprobe_multi.offsets = (unsigned long *)&offset,
+ .uprobe_multi.cnt = 1,
+ );
+
+ link_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
+ if (!ASSERT_ERR(link_fd, "link_fd"))
+ goto cleanup;
+ if (!ASSERT_EQ(link_fd, -EBADF, "path_fd_is_wrong"))
+ goto cleanup;
+
+ /* path and path_fd both set with BPF_F_UPROBE_MULTI_PATH_FD flag */
+ LIBBPF_OPTS_RESET(opts,
+ .uprobe_multi.path = path,
+ .uprobe_multi.path_fd = 1,
+ .uprobe_multi.flags = BPF_F_UPROBE_MULTI_PATH_FD,
+ .uprobe_multi.offsets = (unsigned long *)&offset,
+ .uprobe_multi.cnt = 1,
+ );
+
+ link_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
+ if (!ASSERT_ERR(link_fd, "link_fd"))
+ goto cleanup;
+ ASSERT_EQ(link_fd, -EINVAL, "path_and_path_fd_together");
cleanup:
if (link_fd >= 0)
@@ -757,6 +788,65 @@ static void test_link_api(void)
__test_link_api(&child);
}
+static void test_link_api_path_fd(void)
+{
+ LIBBPF_OPTS(bpf_link_create_opts, opts);
+ const char *resolve_path = "/proc/self/exe";
+ int prog_fd, link_fd = -1, path_fd = -1;
+ struct uprobe_multi *skel = NULL;
+ unsigned long *offsets = NULL;
+ const char *syms[3] = {
+ "uprobe_multi_func_1",
+ "uprobe_multi_func_2",
+ "uprobe_multi_func_3",
+ };
+ int err;
+
+ err = elf_resolve_syms_offsets(resolve_path, ARRAY_SIZE(syms), syms,
+ &offsets, STT_FUNC);
+ if (!ASSERT_OK(err, "elf_resolve_syms_offsets"))
+ return;
+
+ path_fd = open(resolve_path, O_RDONLY);
+ if (!ASSERT_GE(path_fd, 0, "path_fd"))
+ goto cleanup;
+
+ opts.uprobe_multi.path_fd = path_fd;
+ opts.uprobe_multi.offsets = offsets;
+ opts.uprobe_multi.cnt = ARRAY_SIZE(syms);
+ opts.uprobe_multi.flags = BPF_F_UPROBE_MULTI_PATH_FD;
+
+ skel = uprobe_multi__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "uprobe_multi__open_and_load"))
+ goto cleanup;
+
+ prog_fd = bpf_program__fd(skel->progs.uprobe);
+ link_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
+ if (!ASSERT_GE(link_fd, 0, "bpf_link_create"))
+ goto cleanup;
+
+ skel->bss->uprobe_multi_func_1_addr = (__u64)uprobe_multi_func_1;
+ skel->bss->uprobe_multi_func_2_addr = (__u64)uprobe_multi_func_2;
+ skel->bss->uprobe_multi_func_3_addr = (__u64)uprobe_multi_func_3;
+ skel->bss->pid = getpid();
+
+ uprobe_multi_func_1();
+ uprobe_multi_func_2();
+ uprobe_multi_func_3();
+
+ ASSERT_EQ(skel->bss->uprobe_multi_func_1_result, 1, "uprobe_multi_func_1_result");
+ ASSERT_EQ(skel->bss->uprobe_multi_func_2_result, 1, "uprobe_multi_func_2_result");
+ ASSERT_EQ(skel->bss->uprobe_multi_func_3_result, 1, "uprobe_multi_func_3_result");
+
+cleanup:
+ if (link_fd >= 0)
+ close(link_fd);
+ if (path_fd >= 0)
+ close(path_fd);
+ uprobe_multi__destroy(skel);
+ free(offsets);
+}
+
static struct bpf_program *
get_program(struct uprobe_multi_consumers *skel, int prog)
{
@@ -1354,6 +1444,8 @@ void test_uprobe_multi_test(void)
test_attach_api_syms();
if (test__start_subtest("link_api"))
test_link_api();
+ if (test__start_subtest("link_api_path_fd"))
+ test_link_api_path_fd();
if (test__start_subtest("bench_uprobe"))
test_bench_attach_uprobe();
if (test__start_subtest("bench_usdt"))
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 06cd24e37b3f..8a3d69e2453c 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -38,6 +38,7 @@
#include "verifier_div0.skel.h"
#include "verifier_div_mod_bounds.skel.h"
#include "verifier_div_overflow.skel.h"
+#include "verifier_flow_keys.skel.h"
#include "verifier_global_subprogs.skel.h"
#include "verifier_global_ptr_args.skel.h"
#include "verifier_gotol.skel.h"
@@ -92,6 +93,8 @@
#include "verifier_sockmap_mutate.skel.h"
#include "verifier_spill_fill.skel.h"
#include "verifier_spin_lock.skel.h"
+#include "verifier_stack_arg.skel.h"
+#include "verifier_stack_arg_order.skel.h"
#include "verifier_stack_ptr.skel.h"
#include "verifier_store_release.skel.h"
#include "verifier_subprog_precision.skel.h"
@@ -115,6 +118,7 @@
#include "verifier_xdp.skel.h"
#include "verifier_xdp_direct_packet_access.skel.h"
#include "verifier_bits_iter.skel.h"
+#include "verifier_set_retval.skel.h"
#include "verifier_lsm.skel.h"
#include "verifier_jit_inline.skel.h"
#include "irq.skel.h"
@@ -187,6 +191,7 @@ void test_verifier_direct_stack_access_wraparound(void) { RUN(verifier_direct_st
void test_verifier_div0(void) { RUN(verifier_div0); }
void test_verifier_div_mod_bounds(void) { RUN(verifier_div_mod_bounds); }
void test_verifier_div_overflow(void) { RUN(verifier_div_overflow); }
+void test_verifier_flow_keys(void) { RUN(verifier_flow_keys); }
void test_verifier_global_subprogs(void) { RUN(verifier_global_subprogs); }
void test_verifier_global_ptr_args(void) { RUN(verifier_global_ptr_args); }
void test_verifier_gotol(void) { RUN(verifier_gotol); }
@@ -240,6 +245,8 @@ void test_verifier_sock_addr(void) { RUN(verifier_sock_addr); }
void test_verifier_sockmap_mutate(void) { RUN(verifier_sockmap_mutate); }
void test_verifier_spill_fill(void) { RUN(verifier_spill_fill); }
void test_verifier_spin_lock(void) { RUN(verifier_spin_lock); }
+void test_verifier_stack_arg(void) { RUN(verifier_stack_arg); }
+void test_verifier_stack_arg_order(void) { RUN(verifier_stack_arg_order); }
void test_verifier_stack_ptr(void) { RUN(verifier_stack_ptr); }
void test_verifier_store_release(void) { RUN(verifier_store_release); }
void test_verifier_subprog_precision(void) { RUN(verifier_subprog_precision); }
@@ -262,6 +269,7 @@ void test_verifier_xadd(void) { RUN(verifier_xadd); }
void test_verifier_xdp(void) { RUN(verifier_xdp); }
void test_verifier_xdp_direct_packet_access(void) { RUN(verifier_xdp_direct_packet_access); }
void test_verifier_bits_iter(void) { RUN(verifier_bits_iter); }
+void test_verifier_set_retval(void) { RUN(verifier_set_retval); }
void test_verifier_lsm(void) { RUN(verifier_lsm); }
void test_irq(void) { RUN(irq); }
void test_verifier_mtu(void) { RUN(verifier_mtu); }
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier_log.c b/tools/testing/selftests/bpf/prog_tests/verifier_log.c
index c01c0114af1b..4542bb586d72 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier_log.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier_log.c
@@ -317,6 +317,7 @@ static void verif_btf_log_subtest(bool bad_btf)
res = load_btf(&opts, true);
ASSERT_EQ(res, -ENOSPC, "half_log_fd");
ASSERT_EQ(strlen(logs.buf), 24, "log_fixed_25");
+ strscpy(op_name, "log_fixed", sizeof(op_name));
ASSERT_STRNEQ(logs.buf, logs.reference, 24, op_name);
/* validate rolling verifier log logic: try all variations of log buf
diff --git a/tools/testing/selftests/bpf/prog_tests/vmlinux.c b/tools/testing/selftests/bpf/prog_tests/vmlinux.c
index 6fb2217d940b..b5fdd593910d 100644
--- a/tools/testing/selftests/bpf/prog_tests/vmlinux.c
+++ b/tools/testing/selftests/bpf/prog_tests/vmlinux.c
@@ -14,21 +14,61 @@ static void nsleep()
(void)syscall(__NR_nanosleep, &ts, NULL);
}
+static const char *hrtimer_func = "hrtimer_start_range_ns";
+
+static int setup_hrtimer_progs(struct test_vmlinux *skel)
+{
+ int err;
+
+ if (libbpf_find_vmlinux_btf_id("hrtimer_start_range_ns_user", BPF_TRACE_FENTRY) > 0)
+ hrtimer_func = "hrtimer_start_range_ns_user";
+
+ err = bpf_program__set_attach_target(skel->progs.handle__fentry, 0, hrtimer_func);
+ if (err)
+ return err;
+
+ /*
+ * Bare SEC("kprobe") has no target function, so attach it manually
+ * later after selecting the hrtimer function to probe.
+ */
+ bpf_program__set_autoattach(skel->progs.handle__kprobe, false);
+
+ return 0;
+}
+
void test_vmlinux(void)
{
int err;
struct test_vmlinux* skel;
struct test_vmlinux__bss *bss;
+ struct bpf_link *kprobe_link = NULL;
- skel = test_vmlinux__open_and_load();
- if (!ASSERT_OK_PTR(skel, "test_vmlinux__open_and_load"))
+ skel = test_vmlinux__open();
+ if (!ASSERT_OK_PTR(skel, "test_vmlinux__open"))
return;
+
+ err = setup_hrtimer_progs(skel);
+ if (!ASSERT_OK(err, "setup_hrtimer_progs"))
+ goto cleanup;
+
+ err = test_vmlinux__load(skel);
+ if (!ASSERT_OK(err, "test_vmlinux__load"))
+ goto cleanup;
+
bss = skel->bss;
err = test_vmlinux__attach(skel);
if (!ASSERT_OK(err, "test_vmlinux__attach"))
goto cleanup;
+ /* manually attach kprobe with the selected function */
+ if (hrtimer_func) {
+ kprobe_link = bpf_program__attach_kprobe(skel->progs.handle__kprobe,
+ false /* retprobe */, hrtimer_func);
+ if (!ASSERT_OK_PTR(kprobe_link, "bpf_program__attach_kprobe"))
+ goto cleanup;
+ }
+
/* trigger everything */
nsleep();
@@ -39,5 +79,6 @@ void test_vmlinux(void)
ASSERT_TRUE(bss->fentry_called, "fentry");
cleanup:
+ bpf_link__destroy(kprobe_link);
test_vmlinux__destroy(skel);
}
diff --git a/tools/testing/selftests/bpf/prog_tests/wakeup_source.c b/tools/testing/selftests/bpf/prog_tests/wakeup_source.c
new file mode 100644
index 000000000000..ebfdc03271b9
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/wakeup_source.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2026 Google LLC */
+
+#include <test_progs.h>
+#include <bpf/btf.h>
+#include <fcntl.h>
+#include "test_wakeup_source.skel.h"
+#include "wakeup_source_fail.skel.h"
+#include "progs/wakeup_source.h"
+
+static int lock_ws(const char *name)
+{
+ int fd;
+ ssize_t bytes;
+
+ fd = open("/sys/power/wake_lock", O_WRONLY);
+ if (!ASSERT_OK_FD(fd, "open /sys/power/wake_lock"))
+ return -1;
+
+ bytes = write(fd, name, strlen(name));
+ close(fd);
+ if (!ASSERT_EQ(bytes, strlen(name), "write to wake_lock"))
+ return -1;
+
+ return 0;
+}
+
+static void unlock_ws(const char *name)
+{
+ int fd;
+
+ fd = open("/sys/power/wake_unlock", O_WRONLY);
+ if (fd < 0)
+ return;
+
+ write(fd, name, strlen(name));
+ close(fd);
+}
+
+struct rb_ctx {
+ const char *name;
+ bool found;
+ long long active_time_ns;
+ long long total_time_ns;
+};
+
+static int process_sample(void *ctx, void *data, size_t len)
+{
+ struct rb_ctx *rb_ctx = ctx;
+ struct wakeup_event_t *e = data;
+
+ if (strcmp(e->name, rb_ctx->name) == 0) {
+ rb_ctx->found = true;
+ rb_ctx->active_time_ns = e->active_time_ns;
+ rb_ctx->total_time_ns = e->total_time_ns;
+ }
+ return 0;
+}
+
+void test_wakeup_source(void)
+{
+ struct btf *btf;
+ int id;
+
+ btf = btf__load_vmlinux_btf();
+ if (!ASSERT_OK_PTR(btf, "btf_vmlinux"))
+ return;
+
+ id = btf__find_by_name_kind(btf, "bpf_wakeup_sources_get_head", BTF_KIND_FUNC);
+ btf__free(btf);
+
+ if (id < 0) {
+ printf("%s:SKIP:bpf_wakeup_sources_get_head kfunc not found in BTF\n", __func__);
+ test__skip();
+ return;
+ }
+
+ if (test__start_subtest("iterate_and_verify_times")) {
+ struct test_wakeup_source *skel;
+ struct ring_buffer *rb = NULL;
+ struct rb_ctx rb_ctx = {
+ .name = "bpf_selftest_ws_times",
+ .found = false,
+ };
+ int err;
+
+ skel = test_wakeup_source__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+ return;
+
+ rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), process_sample, &rb_ctx, NULL);
+ if (!ASSERT_OK_PTR(rb, "ring_buffer__new"))
+ goto destroy;
+
+ /* Create a temporary wakeup source */
+ if (!ASSERT_OK(lock_ws(rb_ctx.name), "lock_ws"))
+ goto unlock;
+
+ err = bpf_prog_test_run_opts(bpf_program__fd(
+ skel->progs.iterate_wakeupsources), NULL);
+ ASSERT_OK(err, "bpf_prog_test_run");
+
+ ring_buffer__consume(rb);
+
+ ASSERT_TRUE(rb_ctx.found, "found_test_ws_in_rb");
+ ASSERT_GT(rb_ctx.active_time_ns, 0, "active_time_gt_0");
+ ASSERT_GT(rb_ctx.total_time_ns, 0, "total_time_gt_0");
+
+unlock:
+ unlock_ws(rb_ctx.name);
+destroy:
+ if (rb)
+ ring_buffer__free(rb);
+ test_wakeup_source__destroy(skel);
+ }
+
+ RUN_TESTS(wakeup_source_fail);
+}