Linux Trace Kernel
 help / color / mirror / Atom feed
* [PATCH v5 0/5] Enable perf tracing for unprivileged users
@ 2026-07-14 18:39 Anubhav Shelat
  0 siblings, 0 replies; 7+ messages in thread
From: Anubhav Shelat @ 2026-07-14 18:39 UTC (permalink / raw)
  To: rostedt, mhiramat, mathieu.desnoyers, peterz, mingo, acme,
	namhyung, mark.rutland, alexander.shishkin, jolsa, irogers,
	adrian.hunter
  Cc: linux-kernel, linux-trace-kernel, linux-perf-users,
	Anubhav Shelat

Enable users to use perf-trace to trace their own processes, like strace
but without the overhead of ptrace(). Ensure that users cannot access
other users' or systemwide tracing data.

Changes in v5:
- Move event_define_fields() before directory creation. If
  event_define_fields() fails then we don't need to cleanup whatever
  dirs were created. 
- New read-only eventfs file system with the same structure as
  /sys/kernel/tracing/events/ to handle files read by unprivileged
  users.
- Allow unprivileged users to fall back to /sys/kernel/events/ when they
  cannot access /sys/kernel/tracing/events/.
- Factor out reused code into helper function that checks if a
  tracepoint should be restricted in commit 5.

Changes in v4:
- Preserve security_perf_event_open(PERF_SECURITY_KERNEL) LSM hook in
  the tp_bypass path.
- Lift the PERF_SAMPLE_IP check out of the tp_bypass path above the
  PERF_SAMPLE_RAW branch so it applies to counting and sampling. This
  also allows us to ensure PERF_SAMPLE_IP is set for uprobes.
- Block counting path for TRACE_EVENT_FL_CAP_ANY for unprivileged users
  with sysctl_perf_event_paranoid > 1.

Changes in v3:
- Don't set PERF_SAMPLE_IP for unprivileged tracepoints. This allows us
  to exclude PERF_SAMPLE_IP from kaddr_leak without weakening KASLR.
- Mount tracefs as world-traversable so users can access eventfs
  directories.

Anubhav Shelat (5):
  eventfs: define event fields before directory creation
  tracefs: add read-only eventfs filesystem at /sys/kernel/events
  perf tools: fall back to eventfs for unprivileged event discovery
  perf evsel: don't set PERF_SAMPLE_IP for unprivileged tracepoints
  perf: enable unprivileged syscall tracing with perf trace

 fs/tracefs/event_inode.c           |  61 ++++++++++++++++++
 fs/tracefs/inode.c                 |  95 ++++++++++++++++++++++++++-
 fs/tracefs/internal.h              |   3 +
 include/linux/trace_events.h       |   1 +
 include/linux/tracefs.h            |   4 ++
 include/uapi/linux/magic.h         |   1 +
 kernel/events/core.c               |  28 +++++++-
 kernel/trace/trace.h               |   2 +
 kernel/trace/trace_event_perf.c    |  28 +++++++-
 kernel/trace/trace_events.c        | 100 +++++++++++++++++++++++++++--
 tools/lib/api/fs/fs.c              |  10 +++
 tools/lib/api/fs/fs.h              |   1 +
 tools/lib/api/fs/tracing_path.c    |  52 +++++++++++++--
 tools/lib/api/fs/tracing_path.h    |   1 +
 tools/perf/util/evsel.c            |  14 +++-
 tools/perf/util/tp_pmu.c           |   5 +-
 tools/perf/util/trace-event-info.c |  19 +++---
 17 files changed, 395 insertions(+), 30 deletions(-)

-- 
2.54.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v5 0/5] Enable perf tracing for unprivileged users
@ 2026-07-15 13:52 Anubhav Shelat
  2026-07-15 13:52 ` [PATCH v5 1/5] eventfs: define event fields before directory creation Anubhav Shelat
                   ` (4 more replies)
  0 siblings, 5 replies; 7+ messages in thread
From: Anubhav Shelat @ 2026-07-15 13:52 UTC (permalink / raw)
  To: rostedt, acme, peterz
  Cc: linux-kernel, linux-trace-kernel, linux-perf-users,
	Anubhav Shelat

Enable users to use perf-trace to trace their own processes, like strace
but without the overhead of ptrace(). Ensure that users cannot access
other users' or systemwide tracing data.

Changes in v5:
- Move event_define_fields() before directory creation. If
  event_define_fields() fails then we don't need to cleanup whatever
  dirs were created. 
- New read-only eventfs file system with the same structure as
  /sys/kernel/tracing/events/ to handle files read by unprivileged
  users.
- Allow unprivileged users to fall back to /sys/kernel/events/ when they
  cannot access /sys/kernel/tracing/events/.
- Factor out reused code into helper function that checks if a
  tracepoint should be restricted in commit 5.

Changes in v4:
- Preserve security_perf_event_open(PERF_SECURITY_KERNEL) LSM hook in
  the tp_bypass path.
- Lift the PERF_SAMPLE_IP check out of the tp_bypass path above the
  PERF_SAMPLE_RAW branch so it applies to counting and sampling. This
  also allows us to ensure PERF_SAMPLE_IP is set for uprobes.
- Block counting path for TRACE_EVENT_FL_CAP_ANY for unprivileged users
  with sysctl_perf_event_paranoid > 1.

Changes in v3:
- Don't set PERF_SAMPLE_IP for unprivileged tracepoints. This allows us
  to exclude PERF_SAMPLE_IP from kaddr_leak without weakening KASLR.
- Mount tracefs as world-traversable so users can access eventfs
  directories.

Anubhav Shelat (5):
  eventfs: define event fields before directory creation
  tracefs: add read-only eventfs filesystem at /sys/kernel/events
  perf tools: fall back to eventfs for unprivileged event discovery
  perf evsel: don't set PERF_SAMPLE_IP for unprivileged tracepoints
  perf: enable unprivileged syscall tracing with perf trace

 fs/tracefs/event_inode.c           |  61 ++++++++++++++++++
 fs/tracefs/inode.c                 |  95 ++++++++++++++++++++++++++-
 fs/tracefs/internal.h              |   3 +
 include/linux/trace_events.h       |   1 +
 include/linux/tracefs.h            |   4 ++
 include/uapi/linux/magic.h         |   1 +
 kernel/events/core.c               |  28 +++++++-
 kernel/trace/trace.h               |   2 +
 kernel/trace/trace_event_perf.c    |  28 +++++++-
 kernel/trace/trace_events.c        | 100 +++++++++++++++++++++++++++--
 tools/lib/api/fs/fs.c              |  10 +++
 tools/lib/api/fs/fs.h              |   1 +
 tools/lib/api/fs/tracing_path.c    |  52 +++++++++++++--
 tools/lib/api/fs/tracing_path.h    |   1 +
 tools/perf/util/evsel.c            |  14 +++-
 tools/perf/util/tp_pmu.c           |   5 +-
 tools/perf/util/trace-event-info.c |  19 +++---
 17 files changed, 395 insertions(+), 30 deletions(-)

-- 
2.54.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v5 1/5] eventfs: define event fields before directory creation
  2026-07-15 13:52 [PATCH v5 0/5] Enable perf tracing for unprivileged users Anubhav Shelat
@ 2026-07-15 13:52 ` Anubhav Shelat
  2026-07-15 13:52 ` [PATCH v5 2/5] tracefs: add read-only eventfs filesystem at /sys/kernel/events Anubhav Shelat
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 7+ messages in thread
From: Anubhav Shelat @ 2026-07-15 13:52 UTC (permalink / raw)
  To: rostedt, acme, peterz, Masami Hiramatsu, Mathieu Desnoyers,
	linux-kernel, linux-trace-kernel
  Cc: linux-perf-users, Anubhav Shelat

Move the event_define_fields() call in event_create_dir() before the
eventfs directory creation. Previously, a failure after directory
creation wouldn't clean up eventfs_inode because the error path didn't
call eventfs_remove_dir(). This eliminates the need to clean up the
eventfs directories if event_define_fields() fails.

Signed-off-by: Anubhav Shelat <ashelat@redhat.com>
---
 kernel/trace/trace_events.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index c46e623e7e0d..ddb6932a3ee7 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -3190,6 +3190,13 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
 	if (WARN_ON_ONCE(strcmp(call->class->system, TRACE_SYSTEM) == 0))
 		return -ENODEV;
 
+	ret = event_define_fields(call);
+	if (ret < 0) {
+		pr_warn("Could not initialize trace point events/%s\n",
+			trace_event_name(call));
+		return ret;
+	}
+
 	e_events = event_subsystem_dir(tr, call->class->system, file, parent);
 	if (!e_events)
 		return -ENOMEM;
@@ -3208,12 +3215,6 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
 
 	file->ei = ei;
 
-	ret = event_define_fields(call);
-	if (ret < 0) {
-		pr_warn("Could not initialize trace point events/%s\n", name);
-		return ret;
-	}
-
 	/* Gets decremented on freeing of the "enable" file */
 	event_file_get(file);
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v5 2/5] tracefs: add read-only eventfs filesystem at /sys/kernel/events
  2026-07-15 13:52 [PATCH v5 0/5] Enable perf tracing for unprivileged users Anubhav Shelat
  2026-07-15 13:52 ` [PATCH v5 1/5] eventfs: define event fields before directory creation Anubhav Shelat
@ 2026-07-15 13:52 ` Anubhav Shelat
  2026-07-15 13:52 ` [PATCH v5 3/5] perf tools: fall back to eventfs for unprivileged event discovery Anubhav Shelat
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 7+ messages in thread
From: Anubhav Shelat @ 2026-07-15 13:52 UTC (permalink / raw)
  To: rostedt, acme, peterz, Masami Hiramatsu, Mathieu Desnoyers,
	Shivank Garg, Ackerley Tng, Fuad Tabba, Christian Brauner,
	Sean Christopherson, Anubhav Shelat, linux-kernel,
	linux-trace-kernel
  Cc: linux-perf-users

Introduce a new read-only pseudo-filesystem "eventfs" mounted at
/sys/kernel/events that exposes trace event format and id files
(mode 0444) to unprivileged users. This allows tools like perf to
discover event formats without requiring access to the full
tracefs/debugfs mount.

The eventfs filesystem reuses the existing eventfs_inode lazy-lookup
infrastructure. A new set of super_operations
(eventfs_ro_super_operations) shares the tracefs inode allocator so
that eventfs_get_inode() and get_tracefs() work on the RO superblock.
The superblock is manually initialized to ensure the root inode is
allocated with tracefs_alloc_inode, allowing the root to serve
directly as the events directory without another events subdirectory.

Each qualifying event gets a subsystem directory containing format
and id files. The top-level events directory also exposes header_page
and header_event. Similar to tracefs, a change will need to be made in
systemd to mount this filesystem automatically.

Assisted-by: CLAUDE:claude-opus-4 Apogee
Signed-off-by: Anubhav Shelat <ashelat@redhat.com>
---
 fs/tracefs/event_inode.c     | 61 +++++++++++++++++++++++
 fs/tracefs/inode.c           | 95 +++++++++++++++++++++++++++++++++++-
 fs/tracefs/internal.h        |  3 ++
 include/linux/trace_events.h |  1 +
 include/linux/tracefs.h      |  4 ++
 include/uapi/linux/magic.h   |  1 +
 kernel/trace/trace.h         |  2 +
 kernel/trace/trace_events.c  | 87 +++++++++++++++++++++++++++++++++
 8 files changed, 252 insertions(+), 2 deletions(-)

diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c
index 39c7a34531e8..fd6f63ec3ce0 100644
--- a/fs/tracefs/event_inode.c
+++ b/fs/tracefs/event_inode.c
@@ -812,6 +812,67 @@ struct eventfs_inode *eventfs_create_events_dir(const char *name, struct dentry
 	return ERR_PTR(-ENOMEM);
 }
 
+/**
+ * eventfs_create_events_dir_ro - create a read-only events directory
+ * @name: The name of the top level directory to create.
+ * @entries: A list of entries that represent the files under this directory
+ * @size: The number of @entries
+ * @data: The default data to pass to the files (an entry may override it).
+ *
+ * This function configures the eventfs filesystem root as a read-only
+ * trace event directory using the existing eventfs_inode lazy-lookup
+ * infrastructure.
+ *
+ * See eventfs_create_dir() for use of @entries.
+ */
+struct eventfs_inode *eventfs_create_events_dir_ro(const char *name,
+						   const struct eventfs_entry *entries,
+						   int size, void *data)
+{
+	struct dentry *dentry;
+	struct eventfs_root_inode *rei;
+	struct eventfs_inode *ei;
+	struct tracefs_inode *ti;
+	struct inode *inode;
+
+	dentry = eventfs_ro_get_root();
+	if (IS_ERR(dentry))
+		return ERR_CAST(dentry);
+
+	inode = d_inode(dentry);
+
+	ei = alloc_root_ei(name);
+	if (!ei)
+		goto fail;
+
+	rei = get_root_inode(ei);
+	rei->events_dir = dentry;
+
+	ei->entries = entries;
+	ei->nr_entries = size;
+	ei->data = data;
+
+	INIT_LIST_HEAD(&ei->children);
+	INIT_LIST_HEAD(&ei->list);
+
+	ti = get_tracefs(inode);
+	ti->flags |= TRACEFS_EVENT_INODE;
+	ti->private = ei;
+
+	inode->i_op = &eventfs_dir_inode_operations;
+	inode->i_fop = &eventfs_file_operations;
+
+	dentry->d_fsdata = get_ei(ei);
+
+	return ei;
+
+ fail:
+	cleanup_ei(ei);
+	dput(dentry);
+	eventfs_ro_put_root();
+	return ERR_PTR(-ENOMEM);
+}
+
 /**
  * eventfs_remove_rec - remove eventfs dir or file from list
  * @ei: eventfs_inode to be removed.
diff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c
index f3d6188a3b7b..fd064d79d940 100644
--- a/fs/tracefs/inode.c
+++ b/fs/tracefs/inode.c
@@ -30,6 +30,9 @@ static struct vfsmount *tracefs_mount;
 static int tracefs_mount_count;
 static bool tracefs_registered;
 
+static struct vfsmount *eventfs_ro_mount;
+static int eventfs_ro_mount_count;
+
 /*
  * Keep track of all tracefs_inodes in order to update their
  * flags if necessary on a remount.
@@ -423,6 +426,14 @@ static const struct super_operations tracefs_super_operations = {
 	.show_options	= tracefs_show_options,
 };
 
+static const struct super_operations eventfs_ro_super_operations = {
+	.alloc_inode    = tracefs_alloc_inode,
+	.free_inode     = tracefs_free_inode,
+	.destroy_inode  = tracefs_destroy_inode,
+	.drop_inode     = tracefs_drop_inode,
+	.statfs		= simple_statfs,
+};
+
 /*
  * It would be cleaner if eventfs had its own dentry ops.
  *
@@ -523,6 +534,79 @@ static struct file_system_type trace_fs_type = {
 };
 MODULE_ALIAS_FS("tracefs");
 
+static int eventfs_ro_fill_super(struct super_block *sb, struct fs_context *fc)
+{
+	struct inode *inode;
+	struct dentry *root;
+
+	sb->s_blocksize = PAGE_SIZE;
+	sb->s_blocksize_bits = PAGE_SHIFT;
+	sb->s_magic = EVENTFS_SUPER_MAGIC;
+	sb->s_op = &eventfs_ro_super_operations;
+	sb->s_time_gran = 1;
+	sb->s_flags |= SB_RDONLY;
+
+	inode = new_inode(sb);
+	if (!inode)
+		return -ENOMEM;
+
+	inode->i_ino = 1;
+	inode->i_mode = S_IFDIR | 0555;
+	simple_inode_init_ts(inode);
+	inode->i_op = &simple_dir_inode_operations;
+	inode->i_fop = &simple_dir_operations;
+	set_nlink(inode, 2);
+
+	set_default_d_op(sb, &tracefs_dentry_operations);
+
+	root = d_make_root(inode);
+	if (!root)
+		return -ENOMEM;
+
+	sb->s_root = root;
+
+	return 0;
+}
+
+static int eventfs_ro_get_tree(struct fs_context *fc)
+{
+	return get_tree_single(fc, eventfs_ro_fill_super);
+}
+
+static const struct fs_context_operations eventfs_ro_context_ops = {
+	.get_tree	= eventfs_ro_get_tree,
+};
+
+static int eventfs_ro_init_fs_context(struct fs_context *fc)
+{
+	fc->ops = &eventfs_ro_context_ops;
+	return 0;
+}
+
+static struct file_system_type eventfs_ro_fs_type = {
+	.owner =	THIS_MODULE,
+	.name =		"eventfs",
+	.init_fs_context = eventfs_ro_init_fs_context,
+	.kill_sb =	kill_anon_super,
+};
+
+struct dentry *eventfs_ro_get_root(void)
+{
+	int error;
+
+	error = simple_pin_fs(&eventfs_ro_fs_type, &eventfs_ro_mount,
+			      &eventfs_ro_mount_count);
+	if (error)
+		return ERR_PTR(error);
+
+	return dget(eventfs_ro_mount->mnt_root);
+}
+
+void eventfs_ro_put_root(void)
+{
+	simple_release_fs(&eventfs_ro_mount, &eventfs_ro_mount_count);
+}
+
 struct dentry *tracefs_start_creating(const char *name, struct dentry *parent)
 {
 	struct dentry *dentry;
@@ -801,8 +885,15 @@ static int __init tracefs_init(void)
 		return -EINVAL;
 
 	retval = register_filesystem(&trace_fs_type);
-	if (!retval)
-		tracefs_registered = true;
+	if (retval)
+		return retval;
+	tracefs_registered = true;
+
+	retval = sysfs_create_mount_point(kernel_kobj, "events");
+	if (retval)
+		return retval;
+
+	retval = register_filesystem(&eventfs_ro_fs_type);
 
 	return retval;
 }
diff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h
index a4a7f8431aff..0440413f959b 100644
--- a/fs/tracefs/internal.h
+++ b/fs/tracefs/internal.h
@@ -73,6 +73,9 @@ struct dentry *tracefs_end_creating(struct dentry *dentry);
 struct dentry *tracefs_failed_creating(struct dentry *dentry);
 struct inode *tracefs_get_inode(struct super_block *sb);
 
+struct dentry *eventfs_ro_get_root(void);
+void eventfs_ro_put_root(void);
+
 void eventfs_remount(struct tracefs_inode *ti, bool update_uid, bool update_gid);
 void eventfs_d_release(struct dentry *dentry);
 
diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h
index 308c76b57d13..957695fbb015 100644
--- a/include/linux/trace_events.h
+++ b/include/linux/trace_events.h
@@ -648,6 +648,7 @@ struct trace_event_file {
 	struct trace_event_call		*event_call;
 	struct event_filter __rcu	*filter;
 	struct eventfs_inode		*ei;
+	struct eventfs_inode		*ei_ro;
 	struct trace_array		*tr;
 	struct trace_subsystem_dir	*system;
 	struct list_head		triggers;
diff --git a/include/linux/tracefs.h b/include/linux/tracefs.h
index bc354d340046..c175efc51d20 100644
--- a/include/linux/tracefs.h
+++ b/include/linux/tracefs.h
@@ -87,6 +87,10 @@ struct eventfs_inode *eventfs_create_dir(const char *name, struct eventfs_inode
 					 const struct eventfs_entry *entries,
 					 int size, void *data);
 
+struct eventfs_inode *eventfs_create_events_dir_ro(const char *name,
+						   const struct eventfs_entry *entries,
+						   int size, void *data);
+
 void eventfs_remove_events_dir(struct eventfs_inode *ei);
 void eventfs_remove_dir(struct eventfs_inode *ei);
 
diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h
index 4f2da935a76c..7cf8f1a1ae38 100644
--- a/include/uapi/linux/magic.h
+++ b/include/uapi/linux/magic.h
@@ -75,6 +75,7 @@
 #define STACK_END_MAGIC		0x57AC6E9D
 
 #define TRACEFS_MAGIC          0x74726163
+#define EVENTFS_SUPER_MAGIC    0x65766673
 
 #define V9FS_MAGIC		0x01021997
 
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 80fe152af1dd..00c35aaa5069 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -416,6 +416,7 @@ struct trace_array {
 	struct dentry		*options;
 	struct dentry		*percpu_dir;
 	struct eventfs_inode	*event_dir;
+	struct eventfs_inode	*event_dir_ro;
 	struct trace_options	*topts;
 	struct list_head	systems;
 	struct list_head	events;
@@ -1604,6 +1605,7 @@ struct trace_subsystem_dir {
 	struct event_subsystem		*subsystem;
 	struct trace_array		*tr;
 	struct eventfs_inode		*ei;
+	struct eventfs_inode		*ei_ro;
 	int				ref_count;
 	int				nr_events;
 };
diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index ddb6932a3ee7..9662cb24a92c 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -1279,6 +1279,7 @@ static void remove_subsystem(struct trace_subsystem_dir *dir)
 
 	if (!--dir->nr_events) {
 		eventfs_remove_dir(dir->ei);
+		eventfs_remove_dir(dir->ei_ro);
 		list_del(&dir->list);
 		__put_system_dir(dir);
 	}
@@ -1308,6 +1309,7 @@ void event_file_put(struct trace_event_file *file)
 static void remove_event_file_dir(struct trace_event_file *file)
 {
 	eventfs_remove_dir(file->ei);
+	eventfs_remove_dir(file->ei_ro);
 	list_del(&file->list);
 	remove_subsystem(file->system);
 	free_event_filter(file->filter);
@@ -2986,6 +2988,7 @@ event_subsystem_dir(struct trace_array *tr, const char *name,
 	}
 
 	dir->ei = ei;
+	dir->ei_ro = NULL;
 	dir->tr = tr;
 	dir->ref_count = 1;
 	dir->nr_events = 1;
@@ -3126,6 +3129,33 @@ static void event_release(const char *name, void *data)
 	event_file_put(file);
 }
 
+static int event_callback_ro(const char *name, umode_t *mode, void **data,
+			     const struct file_operations **fops)
+{
+	int ret = event_callback(name, mode, data, fops);
+
+	/* Skip writable entries in the read-only tree */
+	if (ret && (*mode & 0222))
+		return 0;
+	if (ret)
+		*mode = 0444;
+	return ret;
+}
+
+static struct eventfs_entry event_entries_ro[] = {
+	{
+		.name		= "format",
+		.callback	= event_callback_ro,
+		.release	= event_release,
+	},
+#ifdef CONFIG_PERF_EVENTS
+	{
+		.name		= "id",
+		.callback	= event_callback_ro,
+	},
+#endif
+};
+
 static int
 event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
 {
@@ -3218,6 +3248,28 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
 	/* Gets decremented on freeing of the "enable" file */
 	event_file_get(file);
 
+	/* Create read only eventfs directory */
+	if (!(call->flags & TRACE_EVENT_FL_DYNAMIC) &&
+	    !IS_ERR_OR_NULL(tr->event_dir_ro)) {
+		struct trace_subsystem_dir *sdir = file->system;
+
+		if (!sdir->ei_ro) {
+			sdir->ei_ro = eventfs_create_dir(call->class->system,
+					tr->event_dir_ro, NULL, 0, sdir);
+			if (IS_ERR(sdir->ei_ro))
+				sdir->ei_ro = NULL;
+		}
+		if (sdir->ei_ro) {
+			file->ei_ro = eventfs_create_dir(name, sdir->ei_ro,
+					event_entries_ro,
+					ARRAY_SIZE(event_entries_ro), file);
+			if (IS_ERR(file->ei_ro))
+				file->ei_ro = NULL;
+			else
+				event_file_get(file);
+		}
+	}
+
 	return 0;
 }
 
@@ -4539,10 +4591,35 @@ static int events_callback(const char *name, umode_t *mode, void **data,
 	return 1;
 }
 
+static int events_callback_ro(const char *name, umode_t *mode, void **data,
+			      const struct file_operations **fops)
+{
+	int ret = events_callback(name, mode, data, fops);
+
+	/* Skip writable entries in the read-only tree */
+	if (ret && (*mode & 0222))
+		return 0;
+	if (ret)
+		*mode = 0444;
+	return ret;
+}
+
+static struct eventfs_entry events_entries_ro[] = {
+	{
+		.name		= "header_page",
+		.callback	= events_callback_ro,
+	},
+	{
+		.name		= "header_event",
+		.callback	= events_callback_ro,
+	},
+};
+
 /* Expects to have event_mutex held when called */
 static int
 create_event_toplevel_files(struct dentry *parent, struct trace_array *tr)
 {
+	static bool event_dir_ro_created;
 	struct eventfs_inode *e_events;
 	struct dentry *entry;
 	int nr_entries;
@@ -4596,6 +4673,16 @@ create_event_toplevel_files(struct dentry *parent, struct trace_array *tr)
 
 	tr->event_dir = e_events;
 
+	if (!event_dir_ro_created && (tr->flags & TRACE_ARRAY_FL_GLOBAL)) {
+		tr->event_dir_ro = eventfs_create_events_dir_ro(
+				"events", events_entries_ro,
+				ARRAY_SIZE(events_entries_ro), tr);
+		if (IS_ERR(tr->event_dir_ro))
+			tr->event_dir_ro = NULL;
+		else
+			event_dir_ro_created = true;
+	}
+
 	return 0;
 }
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v5 3/5] perf tools: fall back to eventfs for unprivileged event discovery
  2026-07-15 13:52 [PATCH v5 0/5] Enable perf tracing for unprivileged users Anubhav Shelat
  2026-07-15 13:52 ` [PATCH v5 1/5] eventfs: define event fields before directory creation Anubhav Shelat
  2026-07-15 13:52 ` [PATCH v5 2/5] tracefs: add read-only eventfs filesystem at /sys/kernel/events Anubhav Shelat
@ 2026-07-15 13:52 ` Anubhav Shelat
  2026-07-15 13:52 ` [PATCH v5 4/5] perf evsel: don't set PERF_SAMPLE_IP for unprivileged tracepoints Anubhav Shelat
  2026-07-15 13:52 ` [PATCH v5 5/5] perf: enable unprivileged syscall tracing with perf trace Anubhav Shelat
  4 siblings, 0 replies; 7+ messages in thread
From: Anubhav Shelat @ 2026-07-15 13:52 UTC (permalink / raw)
  To: rostedt, acme, peterz, Ingo Molnar, Namhyung Kim, Mark Rutland,
	Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
	James Clark, Anubhav Shelat, linux-kernel, linux-perf-users
  Cc: linux-trace-kernel

When tracefs events are not readable by unprivileged users, fall back
to /sys/kernel/events (the read-only eventfs mount) for tracepoint
format and id file discovery. This allows perf trace to work for
unprivileged users on kernels that expose the eventfs filesystem.

The fallback is transparent: get_events_file() tries the tracefs path
first and only switches to eventfs when access() fails. On kernels
without eventfs, the existing error path is preserved.

Assisted-by: CLAUDE:claude-opus-4 Apogee
Signed-off-by: Anubhav Shelat <ashelat@redhat.com>
---
 tools/lib/api/fs/fs.c              | 10 ++++++
 tools/lib/api/fs/fs.h              |  1 +
 tools/lib/api/fs/tracing_path.c    | 52 ++++++++++++++++++++++++++----
 tools/lib/api/fs/tracing_path.h    |  1 +
 tools/perf/util/tp_pmu.c           |  5 +--
 tools/perf/util/trace-event-info.c | 19 ++++++-----
 6 files changed, 71 insertions(+), 17 deletions(-)

diff --git a/tools/lib/api/fs/fs.c b/tools/lib/api/fs/fs.c
index cbd8eab0d1df..abc3581a9703 100644
--- a/tools/lib/api/fs/fs.c
+++ b/tools/lib/api/fs/fs.c
@@ -46,6 +46,10 @@
 #define BPF_FS_MAGIC           0xcafe4a11
 #endif
 
+#ifndef EVENTFS_SUPER_MAGIC
+#define EVENTFS_SUPER_MAGIC    0x65766673
+#endif
+
 static const char * const sysfs__known_mountpoints[] = {
 	"/sys",
 	0,
@@ -88,6 +92,11 @@ static const char * const bpf_fs__known_mountpoints[] = {
 	0,
 };
 
+static const char * const eventfs__known_mountpoints[] = {
+	"/sys/kernel/events",
+	0,
+};
+
 struct fs {
 	const char *		 const name;
 	const char * const *	 const mounts;
@@ -150,6 +159,7 @@ FS(debugfs, debugfs, DEBUGFS);
 FS(tracefs, tracefs, TRACEFS);
 FS(hugetlbfs, hugetlbfs, HUGETLBFS);
 FS(bpf_fs, bpf, BPF_FS);
+FS(eventfs, eventfs, EVENTFS_SUPER);
 
 static bool fs__read_mounts(struct fs *fs)
 {
diff --git a/tools/lib/api/fs/fs.h b/tools/lib/api/fs/fs.h
index aa222ca30311..57790c93f80e 100644
--- a/tools/lib/api/fs/fs.h
+++ b/tools/lib/api/fs/fs.h
@@ -36,6 +36,7 @@ FS(debugfs)
 FS(tracefs)
 FS(hugetlbfs)
 FS(bpf_fs)
+FS(eventfs)
 
 #undef FS
 
diff --git a/tools/lib/api/fs/tracing_path.c b/tools/lib/api/fs/tracing_path.c
index 834fd64c7130..de4ed7c61415 100644
--- a/tools/lib/api/fs/tracing_path.c
+++ b/tools/lib/api/fs/tracing_path.c
@@ -3,6 +3,8 @@
 # define _GNU_SOURCE
 #endif
 
+#include <dirent.h>
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -80,13 +82,46 @@ void put_tracing_file(char *file)
 	free(file);
 }
 
+char *get_events_dir(void)
+{
+	const char *eventfs;
+	char *path;
+	int saved_errno;
+
+	path = get_tracing_file("events");
+	if (path && faccessat(AT_FDCWD, path, R_OK, AT_EACCESS) == 0)
+		return path;
+
+	saved_errno = errno;
+	put_tracing_file(path);
+
+	eventfs = eventfs__mount();
+	if (eventfs && faccessat(AT_FDCWD, eventfs, R_OK, AT_EACCESS) == 0 &&
+	    eventfs__configured())
+		return strdup(eventfs);
+
+	/*
+	 * Prefer EACCES over other errors: it tells the user that events
+	 * exist but are not accessible, which is more actionable than
+	 * ENOENT from a missing filesystem.
+	 */
+	if (errno != EACCES)
+		errno = saved_errno;
+	return NULL;
+}
+
 char *get_events_file(const char *name)
 {
-	char *file;
+	char *dir, *file;
 
-	if (asprintf(&file, "%s/events/%s", tracing_path_mount(), name) < 0)
+	dir = get_events_dir();
+	if (!dir)
 		return NULL;
 
+	if (asprintf(&file, "%s/%s", dir, name) < 0)
+		file = NULL;
+
+	free(dir);
 	return file;
 }
 
@@ -97,8 +132,8 @@ void put_events_file(char *file)
 
 DIR *tracing_events__opendir(void)
 {
+	char *path = get_events_dir();
 	DIR *dir = NULL;
-	char *path = get_tracing_file("events");
 
 	if (path) {
 		dir = opendir(path);
@@ -110,7 +145,7 @@ DIR *tracing_events__opendir(void)
 
 int tracing_events__scandir_alphasort(struct dirent ***namelist)
 {
-	char *path = get_tracing_file("events");
+	char *path = get_events_dir();
 	int ret;
 
 	if (!path) {
@@ -121,6 +156,9 @@ int tracing_events__scandir_alphasort(struct dirent ***namelist)
 	ret = scandir(path, namelist, NULL, alphasort);
 	put_events_file(path);
 
+	if (ret < 0)
+		*namelist = NULL;
+
 	return ret;
 }
 
@@ -162,12 +200,12 @@ int tracing_path__strerror_open_tp(int err, char *buf, size_t size,
 			 "Hint:\tIs the debugfs/tracefs filesystem mounted?\n"
 			 "Hint:\tTry 'sudo mount -t debugfs nodev /sys/kernel/debug'");
 		break;
-	case EACCES: {
+	case EACCES:
 		snprintf(buf, size,
 			 "Error:\tNo permissions to read %s/events/%s\n"
-			 "Hint:\tTry 'sudo mount -o remount,mode=755 %s'\n",
+			 "Hint:\tTry 'sudo mount -o remount,mode=755 %s'\n"
+			 "Hint:\tOr check if /sys/kernel/events is available\n",
 			 tracing_path, filename, tracing_path_mount());
-	}
 		break;
 	default:
 		snprintf(buf, size, "%s", str_error_r(err, sbuf, sizeof(sbuf)));
diff --git a/tools/lib/api/fs/tracing_path.h b/tools/lib/api/fs/tracing_path.h
index fc6347c11deb..a93befb38f80 100644
--- a/tools/lib/api/fs/tracing_path.h
+++ b/tools/lib/api/fs/tracing_path.h
@@ -14,6 +14,7 @@ const char *tracing_path_mount(void);
 char *get_tracing_file(const char *name);
 void put_tracing_file(char *file);
 
+char *get_events_dir(void);
 char *get_events_file(const char *name);
 void put_events_file(char *file);
 
diff --git a/tools/perf/util/tp_pmu.c b/tools/perf/util/tp_pmu.c
index c2be8c9f9084..4f45bd816945 100644
--- a/tools/perf/util/tp_pmu.c
+++ b/tools/perf/util/tp_pmu.c
@@ -6,6 +6,7 @@
 #include <api/io_dir.h>
 #include <linux/kernel.h>
 #include <errno.h>
+#include <stdlib.h>
 #include <string.h>
 
 int tp_pmu__id(const char *sys, const char *name)
@@ -15,7 +16,7 @@ int tp_pmu__id(const char *sys, const char *name)
 	int id, err;
 
 	if (!tp_dir)
-		return -1;
+		return -errno;
 
 	scnprintf(path, PATH_MAX, "%s/%s/id", tp_dir, name);
 	put_events_file(tp_dir);
@@ -66,7 +67,7 @@ int tp_pmu__for_each_tp_sys(void *state, tp_sys_callback cb)
 	struct io_dirent64 *events_ent;
 	struct io_dir events_dir;
 	int ret = 0;
-	char *events_dir_path = get_tracing_file("events");
+	char *events_dir_path = get_events_dir();
 
 	if (!events_dir_path)
 		return -errno;
diff --git a/tools/perf/util/trace-event-info.c b/tools/perf/util/trace-event-info.c
index 45774722f249..643451d2c08d 100644
--- a/tools/perf/util/trace-event-info.c
+++ b/tools/perf/util/trace-event-info.c
@@ -92,8 +92,9 @@ static int record_header_files(void)
 	int err = -EIO;
 
 	if (!path) {
+		err = -errno;
 		pr_debug("can't get tracing/events/header_page");
-		return -ENOMEM;
+		return err;
 	}
 
 	if (stat(path, &st) < 0) {
@@ -115,8 +116,8 @@ static int record_header_files(void)
 
 	path = get_events_file("header_event");
 	if (!path) {
+		err = -errno;
 		pr_debug("can't get tracing/events/header_event");
-		err = -ENOMEM;
 		goto out;
 	}
 
@@ -228,13 +229,14 @@ static int record_ftrace_files(struct tracepoint_path *tps)
 
 	path = get_events_file("ftrace");
 	if (!path) {
+		ret = -errno;
 		pr_debug("can't get tracing/events/ftrace");
-		return -ENOMEM;
+		return ret;
 	}
 
 	ret = copy_event_system(path, tps);
 
-	put_tracing_file(path);
+	put_events_file(path);
 
 	return ret;
 }
@@ -256,15 +258,16 @@ static int record_event_files(struct tracepoint_path *tps)
 	struct stat st;
 	char *path;
 	char *sys;
-	DIR *dir;
+	DIR *dir = NULL;
 	int count = 0;
 	int ret;
 	int err;
 
-	path = get_tracing_file("events");
+	path = get_events_dir();
 	if (!path) {
+		err = -errno;
 		pr_debug("can't get tracing/events");
-		return -ENOMEM;
+		goto out;
 	}
 
 	dir = opendir(path);
@@ -315,7 +318,7 @@ static int record_event_files(struct tracepoint_path *tps)
 out:
 	if (dir)
 		closedir(dir);
-	put_tracing_file(path);
+	put_events_file(path);
 
 	return err;
 }
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v5 4/5] perf evsel: don't set PERF_SAMPLE_IP for unprivileged tracepoints
  2026-07-15 13:52 [PATCH v5 0/5] Enable perf tracing for unprivileged users Anubhav Shelat
                   ` (2 preceding siblings ...)
  2026-07-15 13:52 ` [PATCH v5 3/5] perf tools: fall back to eventfs for unprivileged event discovery Anubhav Shelat
@ 2026-07-15 13:52 ` Anubhav Shelat
  2026-07-15 13:52 ` [PATCH v5 5/5] perf: enable unprivileged syscall tracing with perf trace Anubhav Shelat
  4 siblings, 0 replies; 7+ messages in thread
From: Anubhav Shelat @ 2026-07-15 13:52 UTC (permalink / raw)
  To: rostedt, acme, peterz, Ingo Molnar, Namhyung Kim, Mark Rutland,
	Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
	James Clark, Thomas Falcon, linux-perf-users, linux-kernel
  Cc: linux-trace-kernel, Anubhav Shelat

For tracepoint events the IP is a static kernel address.
It doesn't vary by sample and provides no useful information for
unprivileged users. Skipping setting PERF_SAMPLE_IP for unprivileged
tracepoints avoids exposing a kernel address that reveals the KASLR base
offset.

Make an exception for uprobes, which are registered as
PERF_TYPE_TRACEPOINT, because the IP is important for their
functionality and is a safe userspace address. Detect them with
__probe_ip (entry) and __probe_ret_ip (return) using evsel__field().

Assisted-by: CLAUDE:claude-opus-4 Apogee
Signed-off-by: Anubhav Shelat <ashelat@redhat.com>
---
 tools/perf/util/evsel.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index ea9fa04429f0..1d85e5503cf3 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -1571,7 +1571,19 @@ void evsel__config(struct evsel *evsel, const struct record_opts *opts,
 	attr->write_backward = opts->overwrite ? 1 : 0;
 	attr->read_format   = PERF_FORMAT_LOST;
 
-	evsel__set_sample_bit(evsel, IP);
+	/*
+	 * Don't set PERF_SAMPLE_IP for unprivileged kernel tracepoints to
+	 * avoid exposing kernel addresses. Uprobes expose only userspace
+	 * addresses so they're safe. Detect both entry and return uprobes.
+	 */
+	if (attr->type != PERF_TYPE_TRACEPOINT || perf_event_paranoid_check(1)
+#ifdef HAVE_LIBTRACEEVENT
+	    || evsel__field(evsel, "__probe_ip")
+	    || evsel__field(evsel, "__probe_ret_ip")
+#endif
+	    )
+		evsel__set_sample_bit(evsel, IP);
+
 	evsel__set_sample_bit(evsel, TID);
 
 	if (evsel->sample_read) {
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v5 5/5] perf: enable unprivileged syscall tracing with perf trace
  2026-07-15 13:52 [PATCH v5 0/5] Enable perf tracing for unprivileged users Anubhav Shelat
                   ` (3 preceding siblings ...)
  2026-07-15 13:52 ` [PATCH v5 4/5] perf evsel: don't set PERF_SAMPLE_IP for unprivileged tracepoints Anubhav Shelat
@ 2026-07-15 13:52 ` Anubhav Shelat
  4 siblings, 0 replies; 7+ messages in thread
From: Anubhav Shelat @ 2026-07-15 13:52 UTC (permalink / raw)
  To: rostedt, acme, peterz, Ingo Molnar, Namhyung Kim, Mark Rutland,
	Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
	James Clark, Masami Hiramatsu, Mathieu Desnoyers,
	linux-perf-users, linux-kernel, linux-trace-kernel
  Cc: Anubhav Shelat

Allow unprivileged users to trace their own processes' syscalls using
perf trace, similar to strace without the overhead of ptrace().

Currently, perf trace requires CAP_PERFMON or paranoid level ≤ 1 even
though the kernel has existing infrastructure (TRACE_EVENT_FL_CAP_ANY)
designed to mark syscall tracepoints as safe for unprivileged access.
To fix this:

1. Loosen the condition in perf_event_open() which requires privileges
   for all events with exclude_kernel=0. This allows perf_event_open() to
   bypass the paranoid check for task-attached tracepoint events. Ensure
   that sample types which can expose kernel addresses to unprivileged
   users are blocked. Ensure the PERF_SECURITY_KERNEL LSM hook is
   preserved.

2. Add a check to perf_trace_event_perm() to block PERF_SAMPLE_IP on
   kernel tracepoints for unprivileged users to prevent KASLR bypass. We do
   this here rather than in kaddr_leak because perf_trace_event_perm() can
   distinguish between kernel tracepoints and uprobe tracepoints, where the
   IP is a safe user space address and is necessary for uprobe
   functionality.

3. Restrict pure counting events (no PERF_SAMPLE_RAW) to
   TRACE_EVENT_FL_CAP_ANY tracepoints preventing unprivileged users from
   counting internal kernel tracepoints while preserving current
   behavior for exclude_kernel=1 events.

Example usage after this change:
  $ perf trace ls          # works as unprivileged user
  $ perf trace             # system-wide, still requires privileges
  $ perf trace -p 1234     # requires ptrace permission on pid 1234

Assisted-by: CLAUDE:claude-opus-4 Apogee
Signed-off-by: Anubhav Shelat <ashelat@redhat.com>
---
 kernel/events/core.c            | 28 +++++++++++++++++++++++++---
 kernel/trace/trace_event_perf.c | 28 +++++++++++++++++++++++++++-
 2 files changed, 52 insertions(+), 4 deletions(-)

diff --git a/kernel/events/core.c b/kernel/events/core.c
index 954c36e28101..48bfff07ae02 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -13910,9 +13910,31 @@ SYSCALL_DEFINE5(perf_event_open,
 		return err;
 
 	if (!attr.exclude_kernel) {
-		err = perf_allow_kernel();
-		if (err)
-			return err;
+		bool tp_bypass = false;
+
+		/* Check unprivileged tracepoints */
+		if (attr.type == PERF_TYPE_TRACEPOINT && pid != -1) {
+			/*
+			 * Block sample types that expose kernel addresses to
+			 * prevent KASLR bypass
+			 */
+			u64 kaddr_leak = PERF_SAMPLE_CALLCHAIN |
+					 PERF_SAMPLE_BRANCH_STACK |
+					 PERF_SAMPLE_ADDR |
+					 PERF_SAMPLE_REGS_INTR;
+
+			tp_bypass = !(attr.sample_type & kaddr_leak);
+		}
+
+		if (!tp_bypass) {
+			err = perf_allow_kernel();
+			if (err)
+				return err;
+		} else {
+			err = security_perf_event_open(PERF_SECURITY_KERNEL);
+			if (err)
+				return err;
+		}
 	}
 
 	if (attr.namespaces) {
diff --git a/kernel/trace/trace_event_perf.c b/kernel/trace/trace_event_perf.c
index 5b272856e5ab..a264154b460e 100644
--- a/kernel/trace/trace_event_perf.c
+++ b/kernel/trace/trace_event_perf.c
@@ -24,6 +24,16 @@ typedef typeof(unsigned long [PERF_MAX_TRACE_SIZE / sizeof(unsigned long)])
 /* Count the events in use (per event id, not per instance) */
 static int	total_ref_count;
 
+/* Check if perf tracepoint is restricted for unprivileged users */
+static bool perf_tp_is_restricted(struct perf_event *p_event)
+{
+	if (p_event->attr.exclude_kernel)
+		return false;
+	if (sysctl_perf_event_paranoid <= 1 || perfmon_capable())
+		return false;
+	return true;
+}
+
 static int perf_trace_event_perm(struct trace_event_call *tp_event,
 				 struct perf_event *p_event)
 {
@@ -72,9 +82,25 @@ static int perf_trace_event_perm(struct trace_event_call *tp_event,
 			return -EINVAL;
 	}
 
+	/*
+	 * PERF_SAMPLE_IP on kernel tracepoints exposes a kernel text
+	 * address, weakening KASLR. Block for unprivileged users unless
+	 * the tracepoint is a uprobe (userspace IP, safe to expose).
+	 */
+	if ((p_event->attr.sample_type & PERF_SAMPLE_IP) &&
+	    !(tp_event->flags & TRACE_EVENT_FL_UPROBE) &&
+	    perf_tp_is_restricted(p_event))
+		return -EACCES;
+
 	/* No tracing, just counting, so no obvious leak */
-	if (!(p_event->attr.sample_type & PERF_SAMPLE_RAW))
+	if (!(p_event->attr.sample_type & PERF_SAMPLE_RAW)) {
+		/* Prevent unprivileged users from counting kernel tracepoints */
+		if (perf_tp_is_restricted(p_event) &&
+		    !(p_event->attach_state == PERF_ATTACH_TASK &&
+		      (tp_event->flags & TRACE_EVENT_FL_CAP_ANY)))
+			return -EACCES;
 		return 0;
+	}
 
 	/* Some events are ok to be traced by non-root users... */
 	if (p_event->attach_state == PERF_ATTACH_TASK) {
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-07-15 13:53 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-15 13:52 [PATCH v5 0/5] Enable perf tracing for unprivileged users Anubhav Shelat
2026-07-15 13:52 ` [PATCH v5 1/5] eventfs: define event fields before directory creation Anubhav Shelat
2026-07-15 13:52 ` [PATCH v5 2/5] tracefs: add read-only eventfs filesystem at /sys/kernel/events Anubhav Shelat
2026-07-15 13:52 ` [PATCH v5 3/5] perf tools: fall back to eventfs for unprivileged event discovery Anubhav Shelat
2026-07-15 13:52 ` [PATCH v5 4/5] perf evsel: don't set PERF_SAMPLE_IP for unprivileged tracepoints Anubhav Shelat
2026-07-15 13:52 ` [PATCH v5 5/5] perf: enable unprivileged syscall tracing with perf trace Anubhav Shelat
  -- strict thread matches above, loose matches on Subject: below --
2026-07-14 18:39 [PATCH v5 0/5] Enable perf tracing for unprivileged users Anubhav Shelat

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox