Thank you for your contribution! Sashiko AI review found 1 potential issue(s) 
to consider:
- [High] tracing/user_events: permanent leak of non-persistent event in 
destroy_user_event()
--

commit 426e76d7362e855d5b85cce6bb2e395443a0a474
Author: David Carlier <[email protected]>

tracing/user_events: Don't free an event that a dynamic event still references

This commit prevents destroy_user_event() from freeing a user_event while a
dynamic event still holds a reference on user->call by checking
trace_event_dyn_busy(&user->call) and returning -EBUSY.

> diff --git a/kernel/trace/trace_events_user.c 
> b/kernel/trace/trace_events_user.c
> index f658c3a77aa7a..49b857a7f1d78 100644
> --- a/kernel/trace/trace_events_user.c
> +++ b/kernel/trace/trace_events_user.c
> @@ -1506,6 +1506,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;

[Severity: High]
Does returning -EBUSY here cause non-persistent user_event instances to leak
and become permanently undeletable if their last reference is dropped while
user->call.refcnt is non-zero?

When a non-persistent event is created via DIAG_IOCSREG without
USER_EVENT_REG_PERSIST, user->refcnt tracks active user and probe references.
If a dynamic event consumer holds a reference on user->call (such as an
eprobe via find_and_get_event(), a histogram action via
trace_get_event_file(), or an enable_event trigger during teardown in
event_enable_trigger_free() before trigger_kthread runs
enable_trigger_private_data_free()) when the last user->refcnt reference is
dropped in user_event_put(), delayed_destroy_user_event() runs:

kernel/trace/trace_events_user.c:delayed_destroy_user_event() {
        ...
        if (!refcount_dec_and_test(&user->refcnt))
                goto out;

        if (destroy_user_event(user)) {
                ...
                pr_warn("user_events: Unable to delete event\n");
                refcount_set(&user->refcnt, 1);
        }
out:
        mutex_unlock(&event_mutex);
}

Because destroy_user_event() now returns -EBUSY, delayed_destroy_user_event()
resets user->refcnt to 1 without setting USER_EVENT_REG_PERSIST in
user->reg_flags, and with no owner holding that reference.

Once the eprobe or trigger later drops its reference on user->call, how can
this user_event ever be freed?

1. Manual deletion via dynamic_events in user_event_free(), via DIAG_IOCSDEL
in delete_user_event(), or via dyn_events_release_all() ->
user_event_is_busy() all check user_event_last_ref():

kernel/trace/trace_events_user.c:user_event_last_ref() {
        int last = 0;

        if (user->reg_flags & USER_EVENT_REG_PERSIST)
                last = 1;

        return refcount_read(&user->refcnt) == last;
}

Because USER_EVENT_REG_PERSIST is not set, user_event_last_ref() requires
user->refcnt to be 0, so manual deletion permanently fails with -EBUSY.

2. Re-opening and closing the event increments user->refcnt from 1 to 2 and
decrements it back to 1 in user_event_put(), never reaching 0 to re-queue
delayed_destroy_user_event().

As a result, the struct user_event, its fields and validators, its tracefs
entry, and a current_user_events quota slot appear to be leaked until reboot.

-- 
Sashiko AI review ยท 
https://sashiko.dev/#/patchset/[email protected]?part=1

Reply via email to