* [PATCH v2 1/3] tracing/user_events: Serialize duplicate enabler lookup with event removal
2026-10-06 16:47 ` [PATCH v2 0/3] tracing/user_events: Share state with " Jeff Barnes
@ 2026-10-06 16:47 ` Jeff Barnes
2026-10-06 16:47 ` [PATCH v2 2/3] tracing/user_events: Share event state with existing threads Jeff Barnes
` (2 subsequent siblings)
3 siblings, 0 replies; 10+ messages in thread
From: Jeff Barnes @ 2026-10-06 16:47 UTC (permalink / raw)
To: rostedt, mhiramat, shuah
Cc: mathieu.desnoyers, reddybalavignesh9979, richard.weiyang,
michael.bommarito, linux-kernel, linux-trace-kernel,
linux-kselftest
user_event_enabler_exists() performs a plain traversal of the per-mm
enabler list. Registration invokes it while holding the event group's
reg_mutex, which serializes registration and unregister operations from
that group.
However, deferred fault cleanup can remove an enabler asynchronously
while holding event_mutex without holding the group reg_mutex. The plain
list traversal can therefore race with list_del_rcu() despite the
registration mutex being held.
Take event_mutex around user_event_enabler_exists() so the traversal is
serialized with all enabler removal paths. Pass the group into
current_user_event_enabler_exists() and assert that its reg_mutex is
already held.
This preserves the established lock order of group->reg_mutex followed
by event_mutex.
Signed-off-by: Jeff Barnes <jeffbarnes@linux.microsoft.com>
---
kernel/trace/trace_events_user.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c
index f658c3a77aa7..24983f68d075 100644
--- a/kernel/trace/trace_events_user.c
+++ b/kernel/trace/trace_events_user.c
@@ -890,16 +890,21 @@ void user_event_mm_dup(struct task_struct *t, struct user_event_mm *old_mm)
user_event_mm_destroy(mm);
}
-static bool current_user_event_enabler_exists(unsigned long uaddr,
- unsigned char bit)
+static bool
+current_user_event_enabler_exists(struct user_event_group *group,
+ unsigned long uaddr, unsigned char bit)
{
struct user_event_mm *user_mm = current_user_event_mm();
bool exists;
+ lockdep_assert_held(&group->reg_mutex);
+
if (!user_mm)
return false;
+ mutex_lock(&event_mutex);
exists = user_event_enabler_exists(user_mm, uaddr, bit);
+ mutex_unlock(&event_mutex);
user_event_mm_put(user_mm);
@@ -2510,7 +2515,8 @@ static long user_events_ioctl_reg(struct user_event_file_info *info,
* for user processes that is far easier to debug if this is explicitly
* an error upon registering.
*/
- if (current_user_event_enabler_exists((unsigned long)reg.enable_addr,
+ if (current_user_event_enabler_exists(info->group,
+ (unsigned long)reg.enable_addr,
reg.enable_bit))
return -EADDRINUSE;
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v2 2/3] tracing/user_events: Share event state with existing threads
2026-10-06 16:47 ` [PATCH v2 0/3] tracing/user_events: Share state with " Jeff Barnes
2026-10-06 16:47 ` [PATCH v2 1/3] tracing/user_events: Serialize duplicate enabler lookup with event removal Jeff Barnes
@ 2026-10-06 16:47 ` Jeff Barnes
2026-10-06 16:47 ` [PATCH v2 3/3] selftests/user_events: Test registration from " Jeff Barnes
2026-10-19 14:23 ` [PATCH v2 0/3] tracing/user_events: Share state with " Jeff Barnes
3 siblings, 0 replies; 10+ messages in thread
From: Jeff Barnes @ 2026-10-06 16:47 UTC (permalink / raw)
To: rostedt, mhiramat, shuah
Cc: mathieu.desnoyers, reddybalavignesh9979, richard.weiyang,
michael.bommarito, linux-kernel, linux-trace-kernel,
linux-kselftest
Threads created before the first user-events registration share an
mm_struct but can each retain a NULL user_event_mm pointer. After one
thread registers an event and publishes a user_event_mm for the shared
address space, a pre-existing sibling can otherwise allocate a second
wrapper with an independent enabler list.
When duplicate-enabler lookup runs for a task with no user_event_mm,
look up the wrapper already associated with current->mm and attach the
task to it. The group reg_mutex serializes registration paths, while
user_event_mms_lock protects the global lookup and interlocks attachment
with the final task-count decrement and list removal. Increment tasks
before publishing the wrapper through current->user_event_mm so exit or
exec will account for the attachment.
Hold user_event_mms_lock only for lookup, reference accounting, and task
attachment. Release it before acquiring event_mutex to inspect the
shared enabler list.
user_event_mm_attach() cannot be reused for this operation because it
publishes a newly allocated wrapper on the global list rather than
attaching a task to an already-published wrapper.
Signed-off-by: Jeff Barnes <jeffbarnes@linux.microsoft.com>
---
kernel/trace/trace_events_user.c | 44 +++++++++++++++++++++++++++-----
1 file changed, 37 insertions(+), 7 deletions(-)
diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c
index 24983f68d075..da07b873cd48 100644
--- a/kernel/trace/trace_events_user.c
+++ b/kernel/trace/trace_events_user.c
@@ -754,6 +754,20 @@ 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;
@@ -817,14 +831,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);
@@ -894,11 +912,25 @@ static bool
current_user_event_enabler_exists(struct user_event_group *group,
unsigned long uaddr, unsigned char bit)
{
- struct user_event_mm *user_mm = current_user_event_mm();
+ struct user_event_mm *user_mm = current->user_event_mm;
+ unsigned long flags;
bool exists;
lockdep_assert_held(&group->reg_mutex);
+ if (!user_mm) {
+ 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);
+ current->user_event_mm = user_mm;
+ }
+
+ spin_unlock_irqrestore(&user_event_mms_lock, flags);
+ }
+
if (!user_mm)
return false;
@@ -906,8 +938,6 @@ current_user_event_enabler_exists(struct user_event_group *group,
exists = user_event_enabler_exists(user_mm, uaddr, bit);
mutex_unlock(&event_mutex);
- user_event_mm_put(user_mm);
-
return exists;
}
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v2 3/3] selftests/user_events: Test registration from existing threads
2026-10-06 16:47 ` [PATCH v2 0/3] tracing/user_events: Share state with " Jeff Barnes
2026-10-06 16:47 ` [PATCH v2 1/3] tracing/user_events: Serialize duplicate enabler lookup with event removal Jeff Barnes
2026-10-06 16:47 ` [PATCH v2 2/3] tracing/user_events: Share event state with existing threads Jeff Barnes
@ 2026-10-06 16:47 ` Jeff Barnes
2026-10-19 14:23 ` [PATCH v2 0/3] tracing/user_events: Share state with " Jeff Barnes
3 siblings, 0 replies; 10+ messages in thread
From: Jeff Barnes @ 2026-10-06 16:47 UTC (permalink / raw)
To: rostedt, mhiramat, shuah
Cc: mathieu.desnoyers, reddybalavignesh9979, richard.weiyang,
michael.bommarito, linux-kernel, linux-trace-kernel,
linux-kselftest
Create two threads before either registers a user event. Have the first
thread register an event using a shared enable address and bit while it
remains alive, then have the second thread register a different event
using the same address and bit.
The second registration should fail with EADDRINUSE because both threads
share the same address space.
This test fails without the accompanying kernel fix because each thread
can create a separate user_event_mm with an independent enabler list.
Signed-off-by: Jeff Barnes <jeffbarnes@linux.microsoft.com>
---
.../testing/selftests/user_events/abi_test.c | 125 ++++++++++++++++++
1 file changed, 125 insertions(+)
diff --git a/tools/testing/selftests/user_events/abi_test.c b/tools/testing/selftests/user_events/abi_test.c
index b71813eaf5c0..7aef3078556e 100644
--- a/tools/testing/selftests/user_events/abi_test.c
+++ b/tools/testing/selftests/user_events/abi_test.c
@@ -11,6 +11,7 @@
#include <errno.h>
#include <linux/user_events.h>
#include <stdio.h>
+#include <pthread.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
@@ -258,6 +259,130 @@ FIXTURE_TEARDOWN(user) {
USER_EVENT_FIXTURE_TEARDOWN(self->umount);
}
+struct thread_registration {
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ __u32 enable;
+ bool start;
+ bool first_done;
+ bool second_done;
+ int first_ret;
+ int first_errno;
+ int first_unreg_ret;
+ int second_ret;
+ int second_errno;
+};
+
+static int reg_enable_name(void *enable, const char *name, int *saved_errno)
+{
+ struct user_reg reg = {0};
+ int fd = open(data_file, O_RDWR);
+ int ret;
+
+ if (fd < 0) {
+ *saved_errno = errno;
+ return -1;
+ }
+
+ reg.size = sizeof(reg);
+ reg.name_args = (__u64)name;
+ reg.enable_bit = 0;
+ reg.enable_addr = (__u64)enable;
+ reg.enable_size = sizeof(__u32);
+
+ errno = 0;
+ ret = ioctl(fd, DIAG_IOCSREG, ®);
+ *saved_errno = errno;
+
+ close(fd);
+
+ return ret;
+}
+
+static void *register_first(void *arg)
+{
+ struct thread_registration *registration = arg;
+
+ pthread_mutex_lock(®istration->lock);
+ while (!registration->start)
+ pthread_cond_wait(®istration->cond, ®istration->lock);
+ pthread_mutex_unlock(®istration->lock);
+
+ registration->first_ret = reg_enable_name(®istration->enable,
+ "__abi_event_thread_a",
+ ®istration->first_errno);
+
+ pthread_mutex_lock(®istration->lock);
+ registration->first_done = true;
+ pthread_cond_broadcast(®istration->cond);
+
+ while (!registration->second_done)
+ pthread_cond_wait(®istration->cond, ®istration->lock);
+ pthread_mutex_unlock(®istration->lock);
+
+ if (!registration->first_ret)
+ registration->first_unreg_ret =
+ reg_disable(®istration->enable, 0);
+
+ return NULL;
+}
+
+static void *register_second(void *arg)
+{
+ struct thread_registration *registration = arg;
+
+ pthread_mutex_lock(®istration->lock);
+ while (!registration->start)
+ pthread_cond_wait(®istration->cond, ®istration->lock);
+ while (!registration->first_done)
+ pthread_cond_wait(®istration->cond, ®istration->lock);
+ pthread_mutex_unlock(®istration->lock);
+
+ registration->second_ret = reg_enable_name(®istration->enable,
+ "__abi_event_thread_b",
+ ®istration->second_errno);
+
+ pthread_mutex_lock(®istration->lock);
+ registration->second_done = true;
+ pthread_cond_broadcast(®istration->cond);
+ pthread_mutex_unlock(®istration->lock);
+
+ if (!registration->second_ret)
+ reg_disable(®istration->enable, 0);
+
+ return NULL;
+}
+
+TEST_F(user, preexisting_threads_same_address) {
+ struct thread_registration registration = {
+ .lock = PTHREAD_MUTEX_INITIALIZER,
+ .cond = PTHREAD_COND_INITIALIZER,
+ };
+ pthread_t first;
+ pthread_t second;
+
+ ASSERT_EQ(0, pthread_create(&first, NULL, register_first, ®istration));
+ ASSERT_EQ(0, pthread_create(&second, NULL, register_second, ®istration));
+
+ pthread_mutex_lock(®istration.lock);
+ registration.start = true;
+ pthread_cond_broadcast(®istration.cond);
+ pthread_mutex_unlock(®istration.lock);
+
+ ASSERT_EQ(0, pthread_join(first, NULL));
+ ASSERT_EQ(0, pthread_join(second, NULL));
+
+ pthread_cond_destroy(®istration.cond);
+ pthread_mutex_destroy(®istration.lock);
+
+ ASSERT_EQ(0, registration.first_ret);
+ ASSERT_EQ(0, registration.first_errno);
+ ASSERT_EQ(0, registration.first_unreg_ret);
+
+ ASSERT_EQ(-1, registration.second_ret);
+ ASSERT_EQ(EADDRINUSE, registration.second_errno);
+}
+
TEST_F(user, enablement) {
/* Changes should be reflected immediately */
ASSERT_EQ(0, self->check);
--
2.43.0
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH v2 0/3] tracing/user_events: Share state with existing threads
2026-10-06 16:47 ` [PATCH v2 0/3] tracing/user_events: Share state with " Jeff Barnes
` (2 preceding siblings ...)
2026-10-06 16:47 ` [PATCH v2 3/3] selftests/user_events: Test registration from " Jeff Barnes
@ 2026-10-19 14:23 ` Jeff Barnes
2026-10-19 14:39 ` Steven Rostedt
3 siblings, 1 reply; 10+ messages in thread
From: Jeff Barnes @ 2026-10-19 14:23 UTC (permalink / raw)
To: rostedt, mhiramat, shuah
Cc: beaub, mathieu.desnoyers, linux-trace-kernel, linux-kselftest,
linux-kernel
Gentle ping on this series.
Please let me know if there are any further comments or changes you'd like me to make.
Thanks,
Jeff
^ permalink raw reply [flat|nested] 10+ messages in thread* Re: [PATCH v2 0/3] tracing/user_events: Share state with existing threads
2026-10-19 14:23 ` [PATCH v2 0/3] tracing/user_events: Share state with " Jeff Barnes
@ 2026-10-19 14:39 ` Steven Rostedt
2026-11-10 22:50 ` Jeff Barnes
0 siblings, 1 reply; 10+ messages in thread
From: Steven Rostedt @ 2026-10-19 14:39 UTC (permalink / raw)
To: Jeff Barnes
Cc: mhiramat, shuah, beaub, mathieu.desnoyers, linux-trace-kernel,
linux-kselftest, linux-kernel
On Mon, 19 Oct 2026 10:23:01 -0400
Jeff Barnes <jeffbarnes@linux.microsoft.com> wrote:
> Gentle ping on this series.
>
> Please let me know if there are any further comments or changes you'd like me to make.
Hi Jeff,
FYI, always start a new thread when sending out a new series. Don't make it
a reply to an old one. That makes it confusing to know which series to look
at.
Also, I've was traveling between September 14th and October 10th and my
backlog built up quite a bit.
If Beau can review this series, that would be great. I'm currently putting
together several branches for the merge window. Unfortunately, due to my
backlog, there's patches that have been sent this month that will need to
be postponed to the next window.
As user_events are agnostic to other changes, there's still a chance your
changes can get added this window.
-- Steve
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 0/3] tracing/user_events: Share state with existing threads
2026-10-19 14:39 ` Steven Rostedt
@ 2026-11-10 22:50 ` Jeff Barnes
0 siblings, 0 replies; 10+ messages in thread
From: Jeff Barnes @ 2026-11-10 22:50 UTC (permalink / raw)
To: Steven Rostedt
Cc: mhiramat, shuah, beaub, mathieu.desnoyers, linux-trace-kernel,
linux-kselftest, linux-kernel
Hi Steve, Beau,
Gentle ping on this v2 series. I don't have any outstanding changes to
make at this point.
Just to clarify, this is a separate patch from the Fail fork one.
Please let me know if anything further is needed from my side.
Thanks, Jeff
On Oct 19 2026, at 10:39 am, Steven Rostedt <rostedt@goodmis.org> wrote:
> On Mon, 19 Oct 2026 10:23:01 -0400
> Jeff Barnes <jeffbarnes@linux.microsoft.com> wrote:
>
>> Gentle ping on this series.
>>
>> Please let me know if there are any further comments or changes you'd
>> like me to make.
>
> Hi Jeff,
>
> FYI, always start a new thread when sending out a new series. Don't
> make it
> a reply to an old one. That makes it confusing to know which series to look
> at.
>
> Also, I've was traveling between September 14th and October 10th and my
> backlog built up quite a bit.
>
> If Beau can review this series, that would be great. I'm currently putting
> together several branches for the merge window. Unfortunately, due to my
> backlog, there's patches that have been sent this month that will need to
> be postponed to the next window.
>
> As user_events are agnostic to other changes, there's still a chance your
> changes can get added this window.
>
> -- Steve
>
^ permalink raw reply [flat|nested] 10+ messages in thread