[PATCH 1/2] selftests/user_events: Test registration from existing threads
From: Jeff Barnes
Date: Fri Oct 02 2026 - 11:40:10 EST
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@xxxxxxxxxxxxxxxxxxx>
---
.../testing/selftests/user_events/abi_test.c | 132 ++++++++++++++++++
1 file changed, 132 insertions(+)
diff --git a/tools/testing/selftests/user_events/abi_test.c b/tools/testing/selftests/user_events/abi_test.c
index b71813eaf5c0..bb1a17ce00da 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,137 @@ FIXTURE_TEARDOWN(user) {
USER_EVENT_FIXTURE_TEARDOWN(self->umount);
}
+struct thread_registration {
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ __u32 enable __attribute__((aligned(sizeof(__u32))));
+ bool start;
+ bool first_done;
+ bool second_done;
+ int first_ret;
+ int first_errno;
+ int first_unreg_ret;
+ int second_ret;
+ int second_errno;
+ int second_unreg_ret;
+};
+
+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)
+ registration->second_unreg_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);
+
+ if (!registration.first_ret)
+ ASSERT_EQ(0, registration.first_unreg_ret);
+
+ ASSERT_EQ(-1, registration.second_ret);
+ ASSERT_EQ(EADDRINUSE, registration.second_errno);
+
+ if (!registration.second_ret)
+ ASSERT_EQ(0, registration.second_unreg_ret);
+}
+
TEST_F(user, enablement) {
/* Changes should be reflected immediately */
ASSERT_EQ(0, self->check);
--
2.43.0