From: Mykyta Yatsenko Cover all three sleepable tracepoint types (tp_btf.s, raw_tp.s, tp.s) and sys_exit (via bpf_task_pt_regs) with functional tests using bpf_copy_from_user() on nanosleep. Verify alias and bare SEC variants, bpf_prog_test_run_raw_tp() with BPF_F_TEST_RUN_ON_CPU rejection, attach-time rejection on non-faultable tracepoints, and load-time rejection for sleepable tp_btf on non-faultable tracepoints. Acked-by: Kumar Kartikeya Dwivedi Signed-off-by: Mykyta Yatsenko --- .../bpf/prog_tests/sleepable_tracepoints.c | 154 +++++++++++++++++++++ .../bpf/progs/test_sleepable_tracepoints.c | 125 +++++++++++++++++ .../bpf/progs/test_sleepable_tracepoints_fail.c | 18 +++ tools/testing/selftests/bpf/verifier/sleepable.c | 17 ++- 4 files changed, 312 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..cd2b0e916fab --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/sleepable_tracepoints.c @@ -0,0 +1,154 @@ +// 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 run_test(struct test_sleepable_tracepoints *skel) +{ + skel->bss->target_pid = getpid(); + skel->bss->prog_triggered = 0; + skel->bss->err = 0; + skel->bss->copied_tv_nsec = 0; + + syscall(__NR_nanosleep, &(struct timespec){ .tv_nsec = 555 }, NULL); + + ASSERT_EQ(skel->bss->prog_triggered, 1, "prog_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; + int err, i; + + 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 */ + { + struct { + const char *name; + struct bpf_program *prog; + } func_tests[] = { + { "tp_btf", skel->progs.handle_sys_enter_tp_btf }, + { "raw_tp", skel->progs.handle_sys_enter_raw_tp }, + { "tracepoint", skel->progs.handle_sys_enter_tp }, + { "sys_exit", skel->progs.handle_sys_exit_tp }, + }; + + for (i = 0; i < ARRAY_SIZE(func_tests); i++) { + if (test__start_subtest(func_tests[i].name)) + run_auto_attach_test(func_tests[i].prog, skel); + } + } + + /* Attach-only tests: verify libbpf prefix parsing for aliases */ + { + struct { + const char *name; + struct bpf_program *prog; + } attach_tests[] = { + { "tracepoint_alias", skel->progs.handle_sys_enter_tp_alias }, + { "raw_tracepoint_alias", skel->progs.handle_sys_enter_raw_tp_alias }, + }; + + for (i = 0; i < ARRAY_SIZE(attach_tests); i++) { + if (!test__start_subtest(attach_tests[i].name)) + continue; + link = bpf_program__attach(attach_tests[i].prog); + if (ASSERT_OK_PTR(link, "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); + } + + /* BPF_PROG_TEST_RUN: exercise bpf_prog_test_run_raw_tp() */ + { + struct { + const char *name; + __u32 flags; + bool expect_err; + } run_tests[] = { + { "test_run", 0, false }, + { "test_run_on_cpu_reject", BPF_F_TEST_RUN_ON_CPU, true }, + }; + + for (i = 0; i < ARRAY_SIZE(run_tests); i++) { + __u64 args[2] = {0x1234ULL, 0x5678ULL}; + LIBBPF_OPTS(bpf_test_run_opts, topts, + .ctx_in = args, + .ctx_size_in = sizeof(args), + .flags = run_tests[i].flags, + ); + int fd; + + if (!test__start_subtest(run_tests[i].name)) + continue; + + fd = bpf_program__fd(skel->progs.handle_test_run); + err = bpf_prog_test_run_opts(fd, &topts); + if (!run_tests[i].expect_err) { + ASSERT_OK(err, "test_run"); + ASSERT_EQ(topts.retval, args[0] + args[1], "test_run_retval"); + } else { + ASSERT_ERR(err, "test_run_err"); + } + } + } + + /* Negative: attach-time rejection on non-faultable tracepoints */ + { + struct { + const char *name; + struct bpf_program *prog; + } neg_tests[] = { + { "raw_tp_non_faultable", skel->progs.handle_raw_tp_non_faultable }, + { "tp_non_syscall", skel->progs.handle_tp_non_syscall }, + }; + + for (i = 0; i < ARRAY_SIZE(neg_tests); i++) { + if (!test__start_subtest(neg_tests[i].name)) + continue; + link = bpf_program__attach(neg_tests[i].prog); + ASSERT_ERR_PTR(link, "attach_should_fail"); + } + } + + 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..907c04510a72 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_sleepable_tracepoints.c @@ -0,0 +1,125 @@ +// 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 prog_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; + prog_triggered = 1; + return 0; +} + +/* 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 ((bpf_get_current_pid_tgid() >> 32) != target_pid || + id != __NR_nanosleep) + 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 ((bpf_get_current_pid_tgid() >> 32) != target_pid || + id != __NR_nanosleep) + 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) +{ + struct pt_regs *regs; + + if ((bpf_get_current_pid_tgid() >> 32) != target_pid) + return 0; + + regs = (struct pt_regs *)bpf_task_pt_regs(bpf_get_current_task_btf()); + return copy_nanosleep_arg((void *)PT_REGS_PARM1_CORE_SYSCALL(regs)); +} + +/* 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; +} + +/* BPF_PROG_TEST_RUN: sleepable raw_tp invoked via bpf_prog_test_run_raw_tp */ + +SEC("raw_tp.s/sys_enter") +int BPF_PROG(handle_test_run, struct pt_regs *regs, long id) +{ + if ((__u64)regs == 0x1234ULL && (__u64)id == 0x5678ULL) + return (__u64)regs + (__u64)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