kernel.ftrace_enabled=0 silently kills BPF trampolines (fentry/fexit): attach still succeeds, the hook stops firing with no error, and re-enabling silently restores it with no indication anything happened. This is a regression, not a new gap: BPF trampolines were already protected against this the same way livepatch protects itself, via FTRACE_OPS_FL_PERMANENT on the shared trampoline ftrace_ops. That protection was silently dropped during a later refactoring and never restored, so this has been broken for years. Both the shared direct_ops (CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS arches) and the per-trampoline ops allocated on other arches (arm64, s390) are BPF-trampoline-private, not shared with any unrelated subsystem, so restoring the same unconditional flag is safe and needs no kernel/trace/ftrace.c changes -- its existing generic sysctl gate and attach-time refusal already scan the ops list and pick this up for free. Fixes: 00963a2e75a8 ("bpf: Support bpf_trampoline on functions with IPMODIFY (e.g. livepatch)") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Andrey Grodzovsky --- kernel/bpf/trampoline.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c index 129d07db117e..3d5069091db7 100644 --- a/kernel/bpf/trampoline.c +++ b/kernel/bpf/trampoline.c @@ -224,6 +224,8 @@ void bpf_image_ksym_del(struct bpf_ksym *ksym) */ struct ftrace_ops direct_ops = { .ops_func = bpf_tramp_ftrace_ops_func, + /* Same protection livepatch gives its own ftrace_ops. */ + .flags = FTRACE_OPS_FL_PERMANENT, }; static int direct_ops_alloc(struct bpf_trampoline *tr) @@ -303,6 +305,8 @@ static int direct_ops_alloc(struct bpf_trampoline *tr) return -ENOMEM; tr->fops->private = tr; tr->fops->ops_func = bpf_tramp_ftrace_ops_func; + /* See the direct_ops initializer above for why. */ + tr->fops->flags |= FTRACE_OPS_FL_PERMANENT; return 0; } -- 2.34.1