* [PATCH 1/2] tracing/user_events: Handle ioctls from 32-bit tasks
@ 2026-09-28 17:14 Danish Khateeb
2026-09-28 17:14 ` [PATCH 2/2] selftests/user_events: Fix pointer casts for 32-bit builds Danish Khateeb
0 siblings, 1 reply; 2+ messages in thread
From: Danish Khateeb @ 2026-09-28 17:14 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu
Cc: Mathieu Desnoyers, Beau Belgrave, Shuah Khan, linux-trace-kernel,
linux-kselftest, linux-kernel, Danish Khateeb, stable
The DIAG_IOCSREG, DIAG_IOCSDEL and DIAG_IOCSUNREG ioctl numbers are
defined with pointer types, so their size field is 4 for 32-bit
userspace and 8 for 64-bit userspace. user_events_data has no
compat_ioctl handler, so on a 64-bit kernel every one of these ioctls
from a 32-bit task fails with -ENOTTY and such a task cannot register
any user events.
struct user_reg and struct user_unreg are packed and only use fixed-size
fields, so they have the same layout for 32-bit and 64-bit tasks. Add a
compat_ioctl handler that maps the 32-bit ioctl numbers to the native
ones and passes the argument through compat_ptr().
Fixes: 7f5a08c79df3 ("user_events: Add minimal support for trace_event into ftrace")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Danish Khateeb <danishkhateeb03@gmail.com>
---
Notes:
Tested on x86_64 in QEMU (v7.3-rc5 plus this series, KASAN and lockdep):
- A 32-bit program that registers and unregisters an event gets -ENOTTY
from DIAG_IOCSREG before and works after.
- tools/testing/selftests/user_events built with -m32 (with 2/2): all
four tests fail before and pass after, in three runs each. The
64-bit build passes before and after. No splats.
Also built with CONFIG_COMPAT=n on x86_64 and with CONFIG_COMPAT=y on
riscv64, both with W=1.
kernel/trace/trace_events_user.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c
index f658c3a77aa7..07cfbb5a17de 100644
--- a/kernel/trace/trace_events_user.c
+++ b/kernel/trace/trace_events_user.c
@@ -8,6 +8,7 @@
#include <linux/bitmap.h>
#include <linux/cdev.h>
+#include <linux/compat.h>
#include <linux/hashtable.h>
#include <linux/list.h>
#include <linux/io.h>
@@ -2737,6 +2738,36 @@ static long user_events_ioctl(struct file *file, unsigned int cmd,
return ret;
}
+#ifdef CONFIG_COMPAT
+/*
+ * These IOCTLs encode the size of a pointer and thus change their numbers
+ * between 32 and 64 bits. The structs they point to have the same layout.
+ */
+#define DIAG_IOCSREG_COMPAT _IOWR(DIAG_IOC_MAGIC, 0, compat_uptr_t)
+#define DIAG_IOCSDEL_COMPAT _IOW(DIAG_IOC_MAGIC, 1, compat_uptr_t)
+#define DIAG_IOCSUNREG_COMPAT _IOW(DIAG_IOC_MAGIC, 2, compat_uptr_t)
+
+static long user_events_compat_ioctl(struct file *file, unsigned int cmd,
+ unsigned long uarg)
+{
+ switch (cmd) {
+ case DIAG_IOCSREG_COMPAT:
+ cmd = DIAG_IOCSREG;
+ break;
+ case DIAG_IOCSDEL_COMPAT:
+ cmd = DIAG_IOCSDEL;
+ break;
+ case DIAG_IOCSUNREG_COMPAT:
+ cmd = DIAG_IOCSUNREG;
+ break;
+ }
+
+ return user_events_ioctl(file, cmd, (unsigned long)compat_ptr(uarg));
+}
+#else
+#define user_events_compat_ioctl NULL
+#endif
+
/*
* Handles the final close of the file from user mode.
*/
@@ -2787,6 +2818,7 @@ static const struct file_operations user_data_fops = {
.write = user_events_write,
.write_iter = user_events_write_iter,
.unlocked_ioctl = user_events_ioctl,
+ .compat_ioctl = user_events_compat_ioctl,
.release = user_events_release,
};
base-commit: 72d3fcf802c45d00b300f25b848a93c3a2bd7c7e
--
2.55.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH 2/2] selftests/user_events: Fix pointer casts for 32-bit builds
2026-09-28 17:14 [PATCH 1/2] tracing/user_events: Handle ioctls from 32-bit tasks Danish Khateeb
@ 2026-09-28 17:14 ` Danish Khateeb
0 siblings, 0 replies; 2+ messages in thread
From: Danish Khateeb @ 2026-09-28 17:14 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu
Cc: Mathieu Desnoyers, Beau Belgrave, Shuah Khan, linux-trace-kernel,
linux-kselftest, linux-kernel, Danish Khateeb
The tests cast pointers directly to __u64. On 32-bit builds GCC
sign-extends such casts, so addresses above 2 GiB, like the stack of a
32-bit task on a 64-bit kernel, become 0xffffffffxxxxxxxx and every
registration fails.
Cast through uintptr_t so the tests also work when built for 32-bit and
cover the compat ioctl path.
Assisted-by: LLM
Signed-off-by: Danish Khateeb <danishkhateeb03@gmail.com>
---
Notes:
Built with and without -m32. With 1/2 applied, the 32-bit tests pass on
x86_64; the 64-bit binaries are unchanged in behaviour and still pass.
The -Wpointer-to-int-cast warnings left in a 32-bit build come from
kselftest_harness.h, not from these tests.
.../testing/selftests/user_events/abi_test.c | 10 +++---
.../testing/selftests/user_events/dyn_test.c | 6 ++--
.../selftests/user_events/ftrace_test.c | 32 +++++++++----------
.../testing/selftests/user_events/perf_test.c | 10 +++---
4 files changed, 29 insertions(+), 29 deletions(-)
diff --git a/tools/testing/selftests/user_events/abi_test.c b/tools/testing/selftests/user_events/abi_test.c
index b71813eaf5c0..21e1e3cca197 100644
--- a/tools/testing/selftests/user_events/abi_test.c
+++ b/tools/testing/selftests/user_events/abi_test.c
@@ -179,10 +179,10 @@ static int reg_enable_multi(void *enable, int size, int bit, int flags,
}
reg.size = sizeof(reg);
- reg.name_args = (__u64)full_args;
+ reg.name_args = (__u64)(uintptr_t)full_args;
reg.flags = USER_EVENT_REG_MULTI_FORMAT | flags;
reg.enable_bit = bit;
- reg.enable_addr = (__u64)enable;
+ reg.enable_addr = (__u64)(uintptr_t)enable;
reg.enable_size = size;
ret = ioctl(fd, DIAG_IOCSREG, ®);
@@ -202,10 +202,10 @@ static int reg_enable_flags(void *enable, int size, int bit, int flags)
return -1;
reg.size = sizeof(reg);
- reg.name_args = (__u64)"__abi_event";
+ reg.name_args = (__u64)(uintptr_t)"__abi_event";
reg.flags = flags;
reg.enable_bit = bit;
- reg.enable_addr = (__u64)enable;
+ reg.enable_addr = (__u64)(uintptr_t)enable;
reg.enable_size = size;
ret = ioctl(fd, DIAG_IOCSREG, ®);
@@ -231,7 +231,7 @@ static int reg_disable(void *enable, int bit)
reg.size = sizeof(reg);
reg.disable_bit = bit;
- reg.disable_addr = (__u64)enable;
+ reg.disable_addr = (__u64)(uintptr_t)enable;
ret = ioctl(fd, DIAG_IOCSUNREG, ®);
diff --git a/tools/testing/selftests/user_events/dyn_test.c b/tools/testing/selftests/user_events/dyn_test.c
index 78e3c33f4015..32f159544d0b 100644
--- a/tools/testing/selftests/user_events/dyn_test.c
+++ b/tools/testing/selftests/user_events/dyn_test.c
@@ -58,9 +58,9 @@ static int reg_event(int fd, int *check, int bit, const char *value)
struct user_reg reg = {0};
reg.size = sizeof(reg);
- reg.name_args = (__u64)value;
+ reg.name_args = (__u64)(uintptr_t)value;
reg.enable_bit = bit;
- reg.enable_addr = (__u64)check;
+ reg.enable_addr = (__u64)(uintptr_t)check;
reg.enable_size = sizeof(*check);
if (ioctl(fd, DIAG_IOCSREG, ®) == -1)
@@ -75,7 +75,7 @@ static int unreg_event(int fd, int *check, int bit)
unreg.size = sizeof(unreg);
unreg.disable_bit = bit;
- unreg.disable_addr = (__u64)check;
+ unreg.disable_addr = (__u64)(uintptr_t)check;
return ioctl(fd, DIAG_IOCSUNREG, &unreg);
}
diff --git a/tools/testing/selftests/user_events/ftrace_test.c b/tools/testing/selftests/user_events/ftrace_test.c
index decce06b9ba8..b9c75202f0bd 100644
--- a/tools/testing/selftests/user_events/ftrace_test.c
+++ b/tools/testing/selftests/user_events/ftrace_test.c
@@ -127,7 +127,7 @@ static int clear(int *check)
unreg.size = sizeof(unreg);
unreg.disable_bit = 31;
- unreg.disable_addr = (__u64)check;
+ unreg.disable_addr = (__u64)(uintptr_t)check;
fd = open(data_file, O_RDWR);
@@ -174,9 +174,9 @@ static int check_print_fmt(const char *event, const char *expected, int *check)
return fd;
reg.size = sizeof(reg);
- reg.name_args = (__u64)event;
+ reg.name_args = (__u64)(uintptr_t)event;
reg.enable_bit = 31;
- reg.enable_addr = (__u64)check;
+ reg.enable_addr = (__u64)(uintptr_t)check;
reg.enable_size = sizeof(*check);
/* Register should work */
@@ -239,14 +239,14 @@ TEST_F(user, register_events) {
struct user_unreg unreg = {0};
reg.size = sizeof(reg);
- reg.name_args = (__u64)"__test_event u32 field1; u32 field2";
+ reg.name_args = (__u64)(uintptr_t)"__test_event u32 field1; u32 field2";
reg.enable_bit = 31;
- reg.enable_addr = (__u64)&self->check;
+ reg.enable_addr = (__u64)(uintptr_t)&self->check;
reg.enable_size = sizeof(self->check);
unreg.size = sizeof(unreg);
unreg.disable_bit = 31;
- unreg.disable_addr = (__u64)&self->check;
+ unreg.disable_addr = (__u64)(uintptr_t)&self->check;
/* Register should work */
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®));
@@ -263,13 +263,13 @@ TEST_F(user, register_events) {
/* Register without separator spacing should still match */
reg.enable_bit = 29;
- reg.name_args = (__u64)"__test_event u32 field1;u32 field2";
+ reg.name_args = (__u64)(uintptr_t)"__test_event u32 field1;u32 field2";
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®));
ASSERT_EQ(0, reg.write_index);
/* Multiple registers to same name but different args should fail */
reg.enable_bit = 29;
- reg.name_args = (__u64)"__test_event u32 field1;";
+ reg.name_args = (__u64)(uintptr_t)"__test_event u32 field1;";
ASSERT_EQ(-1, ioctl(self->data_fd, DIAG_IOCSREG, ®));
ASSERT_EQ(EADDRINUSE, errno);
@@ -310,9 +310,9 @@ TEST_F(user, write_events) {
int before = 0, after = 0;
reg.size = sizeof(reg);
- reg.name_args = (__u64)"__test_event u32 field1; u32 field2";
+ reg.name_args = (__u64)(uintptr_t)"__test_event u32 field1; u32 field2";
reg.enable_bit = 31;
- reg.enable_addr = (__u64)&self->check;
+ reg.enable_addr = (__u64)(uintptr_t)&self->check;
reg.enable_size = sizeof(self->check);
field1 = 1;
@@ -367,9 +367,9 @@ TEST_F(user, write_empty_events) {
int before = 0, after = 0;
reg.size = sizeof(reg);
- reg.name_args = (__u64)"__test_event";
+ reg.name_args = (__u64)(uintptr_t)"__test_event";
reg.enable_bit = 31;
- reg.enable_addr = (__u64)&self->check;
+ reg.enable_addr = (__u64)(uintptr_t)&self->check;
reg.enable_size = sizeof(self->check);
io[0].iov_base = ®.write_index;
@@ -401,9 +401,9 @@ TEST_F(user, write_fault) {
void *anon;
reg.size = sizeof(reg);
- reg.name_args = (__u64)"__test_event u64 anon";
+ reg.name_args = (__u64)(uintptr_t)"__test_event u64 anon";
reg.enable_bit = 31;
- reg.enable_addr = (__u64)&self->check;
+ reg.enable_addr = (__u64)(uintptr_t)&self->check;
reg.enable_size = sizeof(self->check);
anon = mmap(NULL, l, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
@@ -439,9 +439,9 @@ TEST_F(user, write_validator) {
int before = 0, after = 0;
reg.size = sizeof(reg);
- reg.name_args = (__u64)"__test_event __rel_loc char[] data";
+ reg.name_args = (__u64)(uintptr_t)"__test_event __rel_loc char[] data";
reg.enable_bit = 31;
- reg.enable_addr = (__u64)&self->check;
+ reg.enable_addr = (__u64)(uintptr_t)&self->check;
reg.enable_size = sizeof(self->check);
/* Register should work */
diff --git a/tools/testing/selftests/user_events/perf_test.c b/tools/testing/selftests/user_events/perf_test.c
index 5727cb5b914c..b444d114b2f0 100644
--- a/tools/testing/selftests/user_events/perf_test.c
+++ b/tools/testing/selftests/user_events/perf_test.c
@@ -89,7 +89,7 @@ static int clear(int *check)
unreg.size = sizeof(unreg);
unreg.disable_bit = 31;
- unreg.disable_addr = (__u64)check;
+ unreg.disable_addr = (__u64)(uintptr_t)check;
int fd = open(data_file, O_RDWR);
@@ -160,9 +160,9 @@ TEST_F(user, perf_write) {
__u32 *val;
reg.size = sizeof(reg);
- reg.name_args = (__u64)"__test_event u32 field1; u32 field2";
+ reg.name_args = (__u64)(uintptr_t)"__test_event u32 field1; u32 field2";
reg.enable_bit = 31;
- reg.enable_addr = (__u64)&self->check;
+ reg.enable_addr = (__u64)(uintptr_t)&self->check;
reg.enable_size = sizeof(self->check);
/* Register should work */
@@ -224,9 +224,9 @@ TEST_F(user, perf_empty_events) {
__u32 *val;
reg.size = sizeof(reg);
- reg.name_args = (__u64)"__test_event";
+ reg.name_args = (__u64)(uintptr_t)"__test_event";
reg.enable_bit = 31;
- reg.enable_addr = (__u64)&self->check;
+ reg.enable_addr = (__u64)(uintptr_t)&self->check;
reg.enable_size = sizeof(self->check);
/* Register should work */
--
2.55.0
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-28 17:14 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-28 17:14 [PATCH 1/2] tracing/user_events: Handle ioctls from 32-bit tasks Danish Khateeb
2026-09-28 17:14 ` [PATCH 2/2] selftests/user_events: Fix pointer casts for 32-bit builds Danish Khateeb
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®