Threads created before the first user-events registration can share an mm_struct while each has a NULL user_event_mm pointer.
When one thread registers an event, current_user_event_mm() creates a user_event_mm and attaches it only to that task. A pre-existing sibling thread sharing the same mm_struct can later create another user_event_mm. The two wrappers have independent enabler lists, allowing both threads to register different events using the same enable address and bit despite the EADDRINUSE check. Look for an active user_event_mm associated with the current mm_struct before allocating a new one. Attach the current task to that wrapper and increment its task count. Serialize lookup and attachment with the final task-count decrement and list removal so that a task cannot attach to a wrapper after its last-task transition has begun. Without this change, the new selftest reports that the second registration succeeds. With the change, it is rejected with EADDRINUSE, and all user_events ABI tests pass. Signed-off-by: Jeff Barnes <[email protected]> --- kernel/trace/trace_events_user.c | 78 ++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index f658c3a77aa7..950d8a3cd0e3 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -210,6 +210,7 @@ static int user_event_parse(struct user_event_group *group, char *name, static struct user_event_mm *user_event_mm_get(struct user_event_mm *mm); static struct user_event_mm *user_event_mm_get_all(struct user_event *user); +static void user_event_mm_destroy(struct user_event_mm *mm); static void user_event_mm_put(struct user_event_mm *mm); static int destroy_user_event(struct user_event *user); static bool user_fields_match(struct user_event *user, int argc, @@ -754,33 +755,78 @@ static struct user_event_mm *user_event_mm_alloc(struct task_struct *t) return user_mm; } +static struct user_event_mm * +user_event_mm_find_locked(struct mm_struct *mm) +{ + struct user_event_mm *user_mm; + + lockdep_assert_held(&user_event_mms_lock); + + list_for_each_entry(user_mm, &user_event_mms, mms_link) + if (user_mm->mm == mm) + return user_mm; + + return NULL; +} + static void user_event_mm_attach(struct user_event_mm *user_mm, struct task_struct *t) { unsigned long flags; spin_lock_irqsave(&user_event_mms_lock, flags); list_add_rcu(&user_mm->mms_link, &user_event_mms); - spin_unlock_irqrestore(&user_event_mms_lock, flags); - t->user_event_mm = user_mm; + spin_unlock_irqrestore(&user_event_mms_lock, flags); } static struct user_event_mm *current_user_event_mm(void) { + struct user_event_mm *new_mm; struct user_event_mm *user_mm = current->user_event_mm; + unsigned long flags; if (user_mm) - goto inc; + return user_event_mm_get(user_mm); - user_mm = user_event_mm_alloc(current); + spin_lock_irqsave(&user_event_mms_lock, flags); - if (!user_mm) - goto error; + user_mm = user_event_mm_find_locked(current->mm); + + if (user_mm) { + refcount_inc(&user_mm->tasks); + current->user_event_mm = user_mm; + user_event_mm_get(user_mm); + } + + spin_unlock_irqrestore(&user_event_mms_lock, flags); + + if (user_mm) + return user_mm; + + new_mm = user_event_mm_alloc(current); + + spin_lock_irqsave(&user_event_mms_lock, flags); + + user_mm = user_event_mm_find_locked(current->mm); + + if (user_mm) { + refcount_inc(&user_mm->tasks); + } else if (new_mm) { + user_mm = new_mm; + list_add_rcu(&user_mm->mms_link, &user_event_mms); + } else { + spin_unlock_irqrestore(&user_event_mms_lock, flags); + return NULL; + } + + current->user_event_mm = user_mm; + user_event_mm_get(user_mm); + + spin_unlock_irqrestore(&user_event_mms_lock, flags); + + if (new_mm && new_mm != user_mm) + user_event_mm_destroy(new_mm); - user_event_mm_attach(user_mm, current); -inc: - refcount_inc(&user_mm->refcnt); -error: return user_mm; } @@ -817,14 +863,18 @@ void user_event_mm_remove(struct task_struct *t) might_sleep(); mm = t->user_event_mm; + + spin_lock_irqsave(&user_event_mms_lock, flags); + t->user_event_mm = NULL; - /* Clone will increment the tasks, only remove if last clone */ - if (!refcount_dec_and_test(&mm->tasks)) + /* Clones and attached tasks increment this count. */ + if (!refcount_dec_and_test(&mm->tasks)) { + spin_unlock_irqrestore(&user_event_mms_lock, flags); return; + } - /* Remove the mm from the list, so it can no longer be enabled */ - spin_lock_irqsave(&user_event_mms_lock, flags); + /* Prevent new tasks from attaching after the last-task transition. */ list_del_rcu(&mm->mms_link); spin_unlock_irqrestore(&user_event_mms_lock, flags); -- 2.43.0
