An eprobe takes a reference on the call of the event it attaches to, and kprobe, uprobe, fprobe and synth events all refuse to go away while trace_event_dyn_busy() says such a reference exists. user_events never checks it, so a user event can be freed under an eprobe: echo 'u:test u32 a' >> dynamic_events echo 'e:ep user_events/test a=$a' >> dynamic_events echo '-:user_events/test' >> dynamic_events echo '-:eprobes/ep' >> dynamic_events The last write drops the reference on the freed call. If the last fd of a non-persistent event closes while such a reference is held, the delayed destroy now fails. Mark the event persistent then, so it can still be deleted once the reference is gone instead of being leaked. Return -EBUSY from destroy_user_event() in that case. Fixes: 7f5a08c79df3 ("user_events: Add minimal support for trace_event into ftrace") Cc: stable@vger.kernel.org Signed-off-by: David Carlier --- v2: keep an event whose delayed destroy fails deletable by marking it persistent (Sashiko). Deleting it then needs CAP_PERFMON, like any persistent event. v1: https://lore.kernel.org/all/20261031210420.501187-1-devnexen@gmail.com/ kernel/trace/trace_events_user.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index f658c3a77aa7..ea6a20a30cf5 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -256,6 +256,11 @@ static void delayed_destroy_user_event(struct work_struct *work) * attempt to delete it later. */ pr_warn("user_events: Unable to delete event\n"); + /* + * Keep it as persistent so it can be still deleted + * since nobody own this event + */ + user->reg_flags |= USER_EVENT_REG_PERSIST; refcount_set(&user->refcnt, 1); } out: @@ -1506,6 +1511,10 @@ static int destroy_user_event(struct user_event *user) lockdep_assert_held(&event_mutex); + /* If there is a reference to a dynamic event */ + if (trace_event_dyn_busy(&user->call)) + return -EBUSY; + /* * Detach the fields before removing the call. Removing the event * frees the field list memory (trace_destroy_fields() is run on -- 2.55.0