From: Mykyta Yatsenko Add functional tests for sleepable tracepoint programs that attach to the nanosleep syscall and use bpf_copy_from_user() to read user memory: - tp_btf: BTF-based raw tracepoint using SEC("tp_btf.s/sys_enter") with PT_REGS_PARM1_SYSCALL (non-CO-RE macro for BTF programs). - classic: Classic raw tracepoint using SEC("raw_tp.s/sys_enter") with PT_REGS_PARM1_CORE_SYSCALL (CO-RE macro needed for classic). - tracepoint: Classic tracepoint using SEC("tp.s/syscalls/sys_enter_nanosleep") receiving struct syscall_trace_enter with direct access to args[]. Add a negative test (handle_sched_switch) that verifies sleepable programs are rejected on non-faultable tracepoints (sched_switch). Update verifier/sleepable.c tests: - Add "sleepable raw tracepoint accept" test for sys_enter. - Rename reject test and update error message to match the new descriptive "Sleepable program cannot attach to non-faultable tracepoint" message. Signed-off-by: Mykyta Yatsenko --- .../bpf/prog_tests/sleepable_tracepoints.c | 121 +++++++++++++++++++++ .../bpf/progs/test_sleepable_tracepoints.c | 117 ++++++++++++++++++++ .../bpf/progs/test_sleepable_tracepoints_fail.c | 18 +++ tools/testing/selftests/bpf/verifier/sleepable.c | 17 ++- 4 files changed, 271 insertions(+), 2 deletions(-) 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..46b7de1262b8 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/sleepable_tracepoints.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ + +#include +#include +#include "test_sleepable_tracepoints.skel.h" +#include "test_sleepable_tracepoints_fail.skel.h" + +static void trigger_nanosleep(void) +{ + syscall(__NR_nanosleep, &(struct timespec){ .tv_nsec = 555 }, NULL); +} + +static void run_test(struct test_sleepable_tracepoints *skel) +{ + skel->bss->target_pid = getpid(); + skel->bss->triggered = 0; + skel->bss->err = 0; + skel->bss->copied_tv_nsec = 0; + + trigger_nanosleep(); + + ASSERT_EQ(skel->bss->triggered, 1, "triggered"); + ASSERT_EQ(skel->bss->err, 0, "err"); + ASSERT_EQ(skel->bss->copied_tv_nsec, 555, "copied_tv_nsec"); +} + +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); +} + +void test_sleepable_tracepoints(void) +{ + struct test_sleepable_tracepoints *skel; + struct bpf_link *link; + + skel = test_sleepable_tracepoints__open_and_load(); + if (!ASSERT_OK_PTR(skel, "skel_open_and_load")) + return; + + /* Primary functional tests: full bpf_copy_from_user exercise */ + + 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); + + /* Alias SEC variants: verify libbpf prefix parsing */ + + if (test__start_subtest("tracepoint_alias")) { + link = bpf_program__attach(skel->progs.handle_sys_enter_tp_alias); + if (ASSERT_OK_PTR(link, "tp_alias_attach")) + bpf_link__destroy(link); + } + + if (test__start_subtest("raw_tracepoint_alias")) { + link = bpf_program__attach(skel->progs.handle_sys_enter_raw_tp_alias); + if (ASSERT_OK_PTR(link, "raw_tp_alias_attach")) + bpf_link__destroy(link); + } + + /* Bare SEC variants: verify manual attach */ + + if (test__start_subtest("raw_tp_bare")) { + link = bpf_program__attach_raw_tracepoint(skel->progs.handle_raw_tp_bare, + "sys_enter"); + if (ASSERT_OK_PTR(link, "raw_tp_bare_attach")) + bpf_link__destroy(link); + } + + if (test__start_subtest("tp_bare")) { + link = bpf_program__attach_tracepoint(skel->progs.handle_tp_bare, "syscalls", + "sys_enter_nanosleep"); + if (ASSERT_OK_PTR(link, "tp_bare_attach")) + bpf_link__destroy(link); + } + + /* Sys exit test */ + + if (test__start_subtest("sys_exit")) { + link = bpf_program__attach(skel->progs.handle_sys_exit_tp); + if (ASSERT_OK_PTR(link, "sys_exit_attach")) { + skel->bss->target_pid = getpid(); + skel->bss->exit_triggered = 0; + + trigger_nanosleep(); + + ASSERT_EQ(skel->bss->exit_triggered, 1, "exit_triggered"); + bpf_link__destroy(link); + } + } + + /* Negative: attach-time rejection on non-faultable tracepoints */ + + if (test__start_subtest("raw_tp_non_faultable")) { + link = bpf_program__attach(skel->progs.handle_raw_tp_non_faultable); + ASSERT_ERR_PTR(link, "raw_tp_non_faultable_attach"); + } + + if (test__start_subtest("tp_non_syscall")) { + link = bpf_program__attach(skel->progs.handle_tp_non_syscall); + ASSERT_ERR_PTR(link, "tp_non_syscall_attach"); + } + + test_sleepable_tracepoints__destroy(skel); + + /* Negative: load-time rejection (separate BPF object) */ + RUN_TESTS(test_sleepable_tracepoints_fail); +} diff --git a/tools/testing/selftests/bpf/progs/test_sleepable_tracepoints.c b/tools/testing/selftests/bpf/progs/test_sleepable_tracepoints.c new file mode 100644 index 000000000000..bc8a7fd43ccc --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_sleepable_tracepoints.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ + +#include +#include +#include +#include +#include + +char _license[] SEC("license") = "GPL"; + +int target_pid; +int triggered; +int exit_triggered; +long err; +long copied_tv_nsec; + +static int copy_nanosleep_arg(struct __kernel_timespec *ts) +{ + long tv_nsec; + + err = bpf_copy_from_user(&tv_nsec, sizeof(tv_nsec), &ts->tv_nsec); + if (err) + return err; + + copied_tv_nsec = tv_nsec; + triggered = 1; + return 0; +} + +static __always_inline bool is_target_nanosleep(long id) +{ + return (bpf_get_current_pid_tgid() >> 32) == target_pid && + id == __NR_nanosleep; +} + +/* Primary functional tests: full bpf_copy_from_user exercise */ + +SEC("tp_btf.s/sys_enter") +int BPF_PROG(handle_sys_enter_tp_btf, struct pt_regs *regs, long id) +{ + if (!is_target_nanosleep(id)) + return 0; + + return copy_nanosleep_arg((void *)PT_REGS_PARM1_SYSCALL(regs)); +} + +SEC("raw_tp.s/sys_enter") +int BPF_PROG(handle_sys_enter_raw_tp, struct pt_regs *regs, long id) +{ + if (!is_target_nanosleep(id)) + return 0; + + return copy_nanosleep_arg((void *)PT_REGS_PARM1_CORE_SYSCALL(regs)); +} + +SEC("tp.s/syscalls/sys_enter_nanosleep") +int handle_sys_enter_tp(struct syscall_trace_enter *args) +{ + if ((bpf_get_current_pid_tgid() >> 32) != target_pid) + return 0; + + return copy_nanosleep_arg((void *)args->args[0]); +} + +SEC("tp.s/syscalls/sys_exit_nanosleep") +int handle_sys_exit_tp(struct syscall_trace_exit *args) +{ + if ((bpf_get_current_pid_tgid() >> 32) != target_pid) + return 0; + + exit_triggered = 1; + return 0; +} + +/* Bare SEC variants: test manual attach without tracepoint in section name */ + +SEC("raw_tp.s") +int BPF_PROG(handle_raw_tp_bare, struct pt_regs *regs, long id) +{ + return 0; +} + +SEC("tp.s") +int handle_tp_bare(void *ctx) +{ + return 0; +} + +/* Alias SEC variants: test libbpf prefix parsing for long-form names */ + +SEC("tracepoint.s/syscalls/sys_enter_nanosleep") +int handle_sys_enter_tp_alias(struct syscall_trace_enter *args) +{ + return 0; +} + +SEC("raw_tracepoint.s/sys_enter") +int BPF_PROG(handle_sys_enter_raw_tp_alias, struct pt_regs *regs, long id) +{ + return 0; +} + +/* Negative: sleepable on non-faultable tracepoint (attach-time rejection) */ + +SEC("raw_tp.s/sched_switch") +int BPF_PROG(handle_raw_tp_non_faultable, bool preempt, + struct task_struct *prev, struct task_struct *next) +{ + return 0; +} + +SEC("tp.s/sched/sched_switch") +int handle_tp_non_syscall(void *ctx) +{ + return 0; +} diff --git a/tools/testing/selftests/bpf/progs/test_sleepable_tracepoints_fail.c b/tools/testing/selftests/bpf/progs/test_sleepable_tracepoints_fail.c new file mode 100644 index 000000000000..1a0748a9520b --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_sleepable_tracepoints_fail.c @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ + +#include +#include +#include +#include "bpf_misc.h" + +char _license[] SEC("license") = "GPL"; + +/* Sleepable program on a non-faultable tracepoint should fail to load */ +SEC("tp_btf.s/sched_switch") +__failure __msg("Sleepable program cannot attach to non-faultable tracepoint") +int BPF_PROG(handle_sched_switch, bool preempt, + struct task_struct *prev, struct task_struct *next) +{ + return 0; +} diff --git a/tools/testing/selftests/bpf/verifier/sleepable.c b/tools/testing/selftests/bpf/verifier/sleepable.c index 1f0d2bdc673f..6dabc5522945 100644 --- a/tools/testing/selftests/bpf/verifier/sleepable.c +++ b/tools/testing/selftests/bpf/verifier/sleepable.c @@ -76,7 +76,20 @@ .runs = -1, }, { - "sleepable raw tracepoint reject", + "sleepable raw tracepoint accept", + .insns = { + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .prog_type = BPF_PROG_TYPE_TRACING, + .expected_attach_type = BPF_TRACE_RAW_TP, + .kfunc = "sys_enter", + .result = ACCEPT, + .flags = BPF_F_SLEEPABLE, + .runs = -1, +}, +{ + "sleepable raw tracepoint reject non-faultable", .insns = { BPF_MOV64_IMM(BPF_REG_0, 0), BPF_EXIT_INSN(), @@ -85,7 +98,7 @@ .expected_attach_type = BPF_TRACE_RAW_TP, .kfunc = "sched_switch", .result = REJECT, - .errstr = "Only fentry/fexit/fmod_ret, lsm, iter, uprobe, and struct_ops programs can be sleepable", + .errstr = "Sleepable program cannot attach to non-faultable tracepoint", .flags = BPF_F_SLEEPABLE, .runs = -1, }, -- 2.52.0