[PATCH v2] perf/bpf: Fix lockless access to parent_event->prog in perf_event_alloc()

From: Deepanshu Kartikey

Date: Tue Aug 25 2026 - 00:45:39 EST


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 <kartikey406@xxxxxxxxx>
---
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