summaryrefslogtreecommitdiff
path: root/tools/testing
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-10-01 13:10:20 +0100
committerMark Brown <broonie@kernel.org>2026-10-01 13:10:20 +0100
commit38ef0be0bdb89f2cb43634162e097aec49af52f6 (patch)
tree6d4763a078ec1dd0949177ae9e539a5ae25e8cef /tools/testing
parent72d11e69641c037c6f8e37d7baa3494f8f60e3c1 (diff)
parent564b7e8e09ec9b47921dc11de0de05da358335d9 (diff)
downloadlinux-next-38ef0be0bdb89f2cb43634162e097aec49af52f6.tar.gz
linux-next-38ef0be0bdb89f2cb43634162e097aec49af52f6.zip
Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf.git/
Diffstat (limited to 'tools/testing')
-rw-r--r--tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c34
-rw-r--r--tools/testing/selftests/bpf/prog_tests/tramp_prog_detach.c188
-rw-r--r--tools/testing/selftests/bpf/progs/tramp_prog_detach.c56
-rw-r--r--tools/testing/selftests/bpf/testing_helpers.c28
-rw-r--r--tools/testing/selftests/bpf/testing_helpers.h2
5 files changed, 275 insertions, 33 deletions
diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c b/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c
index ecc3d47919ad..f8497e764beb 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c
@@ -55,38 +55,6 @@ static void *load_module_thread(void *p)
return p;
}
-static int sys_userfaultfd(int flags)
-{
- return syscall(__NR_userfaultfd, flags);
-}
-
-static int test_setup_uffd(void *fault_addr)
-{
- struct uffdio_register uffd_register = {};
- struct uffdio_api uffd_api = {};
- int uffd;
-
- uffd = sys_userfaultfd(O_CLOEXEC);
- if (uffd < 0)
- return -errno;
-
- uffd_api.api = UFFD_API;
- uffd_api.features = 0;
- if (ioctl(uffd, UFFDIO_API, &uffd_api)) {
- close(uffd);
- return -1;
- }
-
- uffd_register.range.start = (unsigned long)fault_addr;
- uffd_register.range.len = getpagesize();
- uffd_register.mode = UFFDIO_REGISTER_MODE_MISSING;
- if (ioctl(uffd, UFFDIO_REGISTER, &uffd_register)) {
- close(uffd);
- return -1;
- }
- return uffd;
-}
-
static void test_bpf_mod_race_config(const struct test_config *config)
{
void *fault_addr, *skel_fail;
@@ -117,7 +85,7 @@ static void test_bpf_mod_race_config(const struct test_config *config)
if (!ASSERT_OK(bpf_mod_race__attach(skel), "bpf_mod_kfunc_race__attach"))
goto end_destroy;
- uffd = test_setup_uffd(fault_addr);
+ uffd = uffd_block_page(fault_addr);
if (!ASSERT_GE(uffd, 0, "userfaultfd open + register address"))
goto end_destroy;
diff --git a/tools/testing/selftests/bpf/prog_tests/tramp_prog_detach.c b/tools/testing/selftests/bpf/prog_tests/tramp_prog_detach.c
new file mode 100644
index 000000000000..1eb0d7237605
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/tramp_prog_detach.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <pthread.h>
+#include <poll.h>
+#include <sys/mman.h>
+#include <linux/userfaultfd.h>
+#include "tramp_prog_detach.skel.h"
+#include "testing_helpers.h"
+
+/*
+ * Detach and free progs while a task sleeps in the prog that runs before them
+ * in the same trampoline image, then let that task continue through the
+ * image. It must not call into the freed progs.
+ *
+ * The task is held in a sleepable prog with userfaultfd, like bpf_mod_race
+ * does.
+ */
+
+static struct bpf_program *pick_prog(struct tramp_prog_detach *skel,
+ bool fexit, bool sleepable)
+{
+ if (fexit)
+ return sleepable ? skel->progs.fexit_sleepable :
+ skel->progs.fexit_victim;
+ return sleepable ? skel->progs.fentry_sleepable :
+ skel->progs.fentry_victim;
+}
+
+static struct bpf_program *sleepable_prog;
+
+static void *run_sleepable(void *arg)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+
+ /* calls bpf_fentry_test1() */
+ return (void *)(long)bpf_prog_test_run_opts(bpf_program__fd(sleepable_prog),
+ &topts);
+}
+
+static struct tramp_prog_detach *load_one(bool fexit, bool sleepable)
+{
+ struct tramp_prog_detach *skel;
+ int err;
+
+ skel = tramp_prog_detach__open();
+ if (!ASSERT_OK_PTR(skel, "open"))
+ return NULL;
+
+ bpf_program__set_autoload(pick_prog(skel, fexit, sleepable), true);
+ err = tramp_prog_detach__load(skel);
+ if (!ASSERT_OK(err, "load"))
+ goto err;
+ skel->bss->pid = getpid();
+ err = tramp_prog_detach__attach(skel);
+ if (!ASSERT_OK(err, "attach"))
+ goto err;
+ return skel;
+err:
+ tramp_prog_detach__destroy(skel);
+ return NULL;
+}
+
+/* The .bss map of a destroyed skeleton goes away when its prog is freed */
+static bool wait_for_map_free(__u32 map_id)
+{
+ int i, fd;
+
+ for (i = 0; i < 100; i++) {
+ fd = bpf_map_get_fd_by_id(map_id);
+ if (fd < 0)
+ return true;
+ close(fd);
+ usleep(100 * 1000);
+ }
+ return false;
+}
+
+static void test_detach(bool sleepable_fexit, bool victim_fexit)
+{
+ struct tramp_prog_detach *sleepable = NULL, *victims[2] = {};
+ struct pollfd pfd = { .events = POLLIN };
+ struct uffdio_copy uffd_copy = {};
+ struct bpf_map_info map_info = {};
+ __u32 map_info_len = sizeof(map_info);
+ struct uffd_msg uffd_msg;
+ void *fault_page, *src_page = MAP_FAILED;
+ long page_size = getpagesize();
+ bool started = false;
+ void *thread_ret;
+ pthread_t thread;
+ int i, uffd = -1;
+
+ fault_page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (!ASSERT_NEQ(fault_page, MAP_FAILED, "mmap fault_page"))
+ return;
+ src_page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (!ASSERT_NEQ(src_page, MAP_FAILED, "mmap src_page"))
+ goto out;
+
+ /* The most recently attached prog runs first */
+ for (i = 0; i < ARRAY_SIZE(victims); i++) {
+ victims[i] = load_one(victim_fexit, false);
+ if (!victims[i])
+ goto out;
+ }
+ sleepable = load_one(sleepable_fexit, true);
+ if (!sleepable)
+ goto out;
+ sleepable_prog = pick_prog(sleepable, sleepable_fexit, true);
+
+ /* Not armed yet so this doesn't block, make sure sleepable runs first */
+ if (!ASSERT_OK((long)run_sleepable(NULL), "dry run"))
+ goto out;
+ for (i = 0; i < ARRAY_SIZE(victims); i++)
+ if (!ASSERT_LT(sleepable->bss->ts, victims[i]->bss->ts, "prog order"))
+ goto out;
+
+ uffd = uffd_block_page(fault_page);
+ if (!ASSERT_GE(uffd, 0, "userfaultfd open + register address"))
+ goto out;
+ sleepable->bss->fault_addr = fault_page;
+
+ if (!ASSERT_OK(pthread_create(&thread, NULL, run_sleepable, NULL),
+ "pthread_create"))
+ goto out;
+ started = true;
+
+ /* Wait for the thread to sleep in bpf_copy_from_user() */
+ pfd.fd = uffd;
+ if (!ASSERT_EQ(poll(&pfd, 1, 10000), 1, "poll uffd"))
+ goto out;
+ if (!ASSERT_EQ(read(uffd, &uffd_msg, sizeof(uffd_msg)), sizeof(uffd_msg),
+ "read uffd"))
+ goto out;
+ if (!ASSERT_EQ(uffd_msg.event, UFFD_EVENT_PAGEFAULT, "uffd pagefault"))
+ goto out;
+
+ /*
+ * Detach and unload the victim progs and wait for them to be freed.
+ * After the first one, the task is in an image that isn't the
+ * trampoline's current one anymore.
+ */
+ for (i = 0; i < ARRAY_SIZE(victims); i++) {
+ if (!ASSERT_OK(bpf_map_get_info_by_fd(bpf_map__fd(victims[i]->maps.bss),
+ &map_info, &map_info_len),
+ "victim bss info"))
+ goto out;
+ tramp_prog_detach__destroy(victims[i]);
+ victims[i] = NULL;
+ if (!ASSERT_TRUE(wait_for_map_free(map_info.id), "victim freed"))
+ goto out;
+ }
+
+out:
+ /* Let the thread proceed with the rest of the trampoline */
+ if (uffd >= 0) {
+ uffd_copy.dst = (unsigned long)fault_page;
+ uffd_copy.src = (unsigned long)src_page;
+ uffd_copy.len = page_size;
+ ASSERT_OK(ioctl(uffd, UFFDIO_COPY, &uffd_copy), "uffd copy");
+ close(uffd);
+ }
+ if (started &&
+ ASSERT_OK(pthread_join(thread, &thread_ret), "pthread_join"))
+ ASSERT_NULL(thread_ret, "blocking run");
+
+ for (i = 0; i < ARRAY_SIZE(victims); i++)
+ tramp_prog_detach__destroy(victims[i]);
+ tramp_prog_detach__destroy(sleepable);
+ if (src_page != MAP_FAILED)
+ munmap(src_page, page_size);
+ munmap(fault_page, page_size);
+}
+
+void serial_test_tramp_prog_detach(void)
+{
+ /* a task sleeping before the original function is called */
+ if (test__start_subtest("fentry"))
+ test_detach(false, false);
+ /* a task sleeping after the original function returned */
+ if (test__start_subtest("fexit"))
+ test_detach(true, true);
+ /* the original function runs in between and must still be called */
+ if (test__start_subtest("fentry_fexit"))
+ test_detach(false, true);
+}
diff --git a/tools/testing/selftests/bpf/progs/tramp_prog_detach.c b/tools/testing/selftests/bpf/progs/tramp_prog_detach.c
new file mode 100644
index 000000000000..507d372167ec
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tramp_prog_detach.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+int pid;
+void *fault_addr;
+__u64 ts;
+
+static int do_sleepable(void)
+{
+ char dst;
+
+ if (bpf_get_current_pid_tgid() >> 32 != pid)
+ return 0;
+
+ ts = bpf_ktime_get_ns();
+ /* blocks for as long as user space wants when fault_addr is armed */
+ bpf_copy_from_user(&dst, sizeof(dst), fault_addr);
+ return 0;
+}
+
+static int do_victim(void)
+{
+ if (bpf_get_current_pid_tgid() >> 32 != pid)
+ return 0;
+
+ ts = bpf_ktime_get_ns();
+ return 0;
+}
+
+SEC("?fentry.s/bpf_fentry_test1")
+int BPF_PROG(fentry_sleepable, int a)
+{
+ return do_sleepable();
+}
+
+SEC("?fentry/bpf_fentry_test1")
+int BPF_PROG(fentry_victim, int a)
+{
+ return do_victim();
+}
+
+SEC("?fexit.s/bpf_fentry_test1")
+int BPF_PROG(fexit_sleepable, int a, int ret)
+{
+ return do_sleepable();
+}
+
+SEC("?fexit/bpf_fentry_test1")
+int BPF_PROG(fexit_victim, int a, int ret)
+{
+ return do_victim();
+}
diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c
index c970e7793dfc..9f672d0c9dda 100644
--- a/tools/testing/selftests/bpf/testing_helpers.c
+++ b/tools/testing/selftests/bpf/testing_helpers.c
@@ -13,6 +13,7 @@
#include "test_progs.h"
#include "testing_helpers.h"
#include <linux/membarrier.h>
+#include <linux/userfaultfd.h>
int parse_num_list(const char *s, bool **num_set, int *num_set_len)
{
@@ -459,6 +460,33 @@ int kern_sync_rcu(void)
return syscall(__NR_membarrier, MEMBARRIER_CMD_SHARED, 0, 0);
}
+int uffd_block_page(void *fault_addr)
+{
+ struct uffdio_register uffd_register = {};
+ struct uffdio_api uffd_api = {};
+ int uffd;
+
+ uffd = syscall(__NR_userfaultfd, O_CLOEXEC);
+ if (uffd < 0)
+ return -errno;
+
+ uffd_api.api = UFFD_API;
+ uffd_api.features = 0;
+ if (ioctl(uffd, UFFDIO_API, &uffd_api)) {
+ close(uffd);
+ return -1;
+ }
+
+ uffd_register.range.start = (unsigned long)fault_addr;
+ uffd_register.range.len = getpagesize();
+ uffd_register.mode = UFFDIO_REGISTER_MODE_MISSING;
+ if (ioctl(uffd, UFFDIO_REGISTER, &uffd_register)) {
+ close(uffd);
+ return -1;
+ }
+ return uffd;
+}
+
int get_xlated_program(int fd_prog, struct bpf_insn **buf, __u32 *cnt)
{
__u32 buf_element_size = sizeof(struct bpf_insn);
diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h
index 2edc6fb7fc52..1f83b6080716 100644
--- a/tools/testing/selftests/bpf/testing_helpers.h
+++ b/tools/testing/selftests/bpf/testing_helpers.h
@@ -36,6 +36,8 @@ __u64 read_perf_max_sample_freq(void);
int load_bpf_testmod(bool verbose);
int unload_bpf_testmod(bool verbose);
int kern_sync_rcu(void);
+/* returns a userfaultfd that makes accesses to fault_addr's page block */
+int uffd_block_page(void *fault_addr);
int finit_module(int fd, const char *param_values, int flags);
int delete_module(const char *name, int flags);
int load_module(const char *path, bool verbose);