Cover the FTRACE_OPS_FL_PERMANENT fix on BPF trampolines and ftrace-based kprobes/kretprobes: attach a fentry, fexit, kprobe, and kretprobe program in turn and confirm kernel.ftrace_enabled=0 is refused with EBUSY while attached, then succeeds once detached. Also confirm a new fentry/kprobe attach is itself refused while ftrace_enabled=0 is already in effect. kprobe.multi/kretprobe.multi/kprobe.session are intentionally not covered, matching the production change's scope. Skip the kprobe/kretprobe subtests when CONFIG_KPROBES_ON_FTRACE is off (e.g. arm64), read back via skel->kconfig->CONFIG_KPROBES_ON_FTRACE (same __kconfig idiom as progs/test_fill_link_info.c). Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Andrey Grodzovsky --- .../bpf/prog_tests/ftrace_permanent.c | 144 ++++++++++++++++++ .../selftests/bpf/progs/ftrace_permanent.c | 43 ++++++ 2 files changed, 187 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/ftrace_permanent.c create mode 100644 tools/testing/selftests/bpf/progs/ftrace_permanent.c diff --git a/tools/testing/selftests/bpf/prog_tests/ftrace_permanent.c b/tools/testing/selftests/bpf/prog_tests/ftrace_permanent.c new file mode 100644 index 000000000000..dbe78009a291 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/ftrace_permanent.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 CrowdStrike */ +#include +#include "ftrace_permanent.skel.h" + +/* + * fentry/fexit/kprobe/kretprobe now carry FTRACE_OPS_FL_PERMANENT, so + * kernel.ftrace_enabled=0 must be refused with EBUSY while any of them + * is attached, and allowed again once detached. + * + * kprobe.multi/kretprobe.multi/kprobe.session are out of scope. + * kprobe/kretprobe subtests need CONFIG_KPROBES_ON_FTRACE (e.g. not on + * arm64) and are skipped otherwise. + */ + +#define FTRACE_ENABLED_PATH "/proc/sys/kernel/ftrace_enabled" + +static int read_ftrace_enabled(int *val) +{ + char buf[16] = {}; + int fd, n; + + fd = open(FTRACE_ENABLED_PATH, O_RDONLY); + if (fd < 0) + return -errno; + n = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (n <= 0) + return -EIO; + *val = atoi(buf); + return 0; +} + +/* Returns 0 on success, or -errno on write failure. */ +static int write_ftrace_enabled(int val) +{ + char buf[4]; + int fd, n, len, err = 0; + + fd = open(FTRACE_ENABLED_PATH, O_WRONLY); + if (fd < 0) + return -errno; + len = snprintf(buf, sizeof(buf), "%d", val); + n = write(fd, buf, len); + if (n < 0) + err = -errno; + close(fd); + return err; +} + +/* + * Attach @prog, assert kernel.ftrace_enabled=0 is refused while attached + * and stays at 1, then detach and assert the disable now succeeds. + */ +static void check_blocks_disable(struct bpf_program *prog, const char *name) +{ + struct bpf_link *link; + int val, err; + + link = bpf_program__attach(prog); + if (!ASSERT_OK_PTR(link, name)) + return; + + err = write_ftrace_enabled(0); + ASSERT_EQ(err, -EBUSY, "disable_refused"); + if (!ASSERT_OK(read_ftrace_enabled(&val), "read_back")) + goto detach; + ASSERT_EQ(val, 1, "still_enabled"); + +detach: + bpf_link__destroy(link); + + ASSERT_OK(write_ftrace_enabled(0), "disable_after_detach"); + ASSERT_OK(write_ftrace_enabled(1), "reenable"); +} + +/* Attach @prog while ftrace_enabled=0 and assert it is refused. */ +static void check_attach_while_disabled_refused(struct bpf_program *prog, const char *name) +{ + struct bpf_link *link; + + if (!ASSERT_OK(write_ftrace_enabled(0), "disable")) + return; + + link = bpf_program__attach(prog); + if (!ASSERT_ERR_PTR(link, name)) + bpf_link__destroy(link); + + ASSERT_OK(write_ftrace_enabled(1), "reenable"); +} + +void test_ftrace_permanent(void) +{ + struct ftrace_permanent *skel; + bool kprobes_on_ftrace; + int orig = 1; + + /* Save and always restore ftrace_enabled. */ + if (read_ftrace_enabled(&orig)) { + test__skip(); + return; + } + + skel = ftrace_permanent__open_and_load(); + if (!ASSERT_OK_PTR(skel, "skel_open_and_load")) + goto restore; + + kprobes_on_ftrace = skel->kconfig->CONFIG_KPROBES_ON_FTRACE; + + if (test__start_subtest("fentry_blocks_disable")) + check_blocks_disable(skel->progs.test_fentry, "attach_fentry"); + + if (test__start_subtest("fexit_blocks_disable")) + check_blocks_disable(skel->progs.test_fexit, "attach_fexit"); + + if (test__start_subtest("kprobe_blocks_disable")) { + if (kprobes_on_ftrace) + check_blocks_disable(skel->progs.test_kprobe, "attach_kprobe"); + else + test__skip(); + } + + if (test__start_subtest("kretprobe_blocks_disable")) { + if (kprobes_on_ftrace) + check_blocks_disable(skel->progs.test_kretprobe, "attach_kretprobe"); + else + test__skip(); + } + + if (test__start_subtest("fentry_attach_while_disabled_refused")) + check_attach_while_disabled_refused(skel->progs.test_fentry, "attach_fentry"); + + if (test__start_subtest("kprobe_attach_while_disabled_refused")) { + if (kprobes_on_ftrace) + check_attach_while_disabled_refused(skel->progs.test_kprobe, + "attach_kprobe"); + else + test__skip(); + } + + ftrace_permanent__destroy(skel); +restore: + write_ftrace_enabled(orig); +} diff --git a/tools/testing/selftests/bpf/progs/ftrace_permanent.c b/tools/testing/selftests/bpf/progs/ftrace_permanent.c new file mode 100644 index 000000000000..ca706d8edc23 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/ftrace_permanent.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 CrowdStrike */ +#include "vmlinux.h" +#include +#include +#include + +char _license[] SEC("license") = "GPL"; + +extern bool CONFIG_KPROBES_ON_FTRACE __kconfig __weak; + +/* This function is here to have CONFIG_KPROBES_ON_FTRACE used and + * added to object BTF, so the userspace side can read it back via + * skel->kconfig->CONFIG_KPROBES_ON_FTRACE. + */ +int unused(void) +{ + return CONFIG_KPROBES_ON_FTRACE ? 0 : 1; +} + +SEC("fentry/bpf_fentry_test1") +int BPF_PROG(test_fentry, int a) +{ + return 0; +} + +SEC("fexit/bpf_fentry_test2") +int BPF_PROG(test_fexit, int a, __u64 b) +{ + return 0; +} + +SEC("kprobe/bpf_fentry_test3") +int test_kprobe(struct pt_regs *ctx) +{ + return 0; +} + +SEC("kretprobe/bpf_fentry_test4") +int BPF_KRETPROBE(test_kretprobe) +{ + return 0; +} -- 2.34.1