During fork(), perf_event_alloc() reads parent_event->prog locklessly which can race with concurrent detach clearing and freeing the prog via perf_event_free_bpf_handler() or perf_event_detach_bpf_prog(). This can result in a NULL pointer dereference or use-after-free in bpf_prog_inc(). Fix by using READ_ONCE() to atomically read parent_event->prog into a local variable and bpf_prog_inc_not_zero() to safely increment the reference count only if the program is still alive. This handles both tracing and non-tracing event types without requiring any additional locking. Fixes: 85192dbf4de0 ("bpf: Convert bpf_prog refcnt to atomic64_t") Signed-off-by: Deepanshu Kartikey --- v2: - Drop bpf_event_mutex approach which was wrong for non-tracing events as perf_event_free_bpf_handler() operates locklessly - Use READ_ONCE() + bpf_prog_inc_not_zero() instead which handles both tracing and non-tracing event types safely without any lock - Remove extern bpf_event_mutex from perf_event.h (no longer needed) - Remove static removal from bpf_trace.c (no longer needed) --- kernel/events/core.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index ba5bd6a78fe7..39755bfacc55 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -13433,11 +13433,13 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu, overflow_handler = parent_event->overflow_handler; context = parent_event->overflow_handler_context; #if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_EVENT_TRACING) - if (parent_event->prog) { - struct bpf_prog *prog = parent_event->prog; - - bpf_prog_inc(prog); - event->prog = prog; + struct bpf_prog *prog; + + prog = READ_ONCE(parent_event->prog); + if (prog) { + prog = bpf_prog_inc_not_zero(prog); + if (!IS_ERR(prog)) + event->prog = prog; } #endif } -- 2.43.0