* [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; 8+ 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] 8+ 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; 8+ 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] 8+ 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-29 0:13 ` Steven Rostedt 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, 1 reply; 8+ 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] 8+ messages in thread
* Re: [PATCH v5 2/5] tracefs: add read-only eventfs filesystem at /sys/kernel/events 2026-07-15 13:52 ` [PATCH v5 2/5] tracefs: add read-only eventfs filesystem at /sys/kernel/events Anubhav Shelat @ 2026-07-29 0:13 ` Steven Rostedt 0 siblings, 0 replies; 8+ messages in thread From: Steven Rostedt @ 2026-07-29 0:13 UTC (permalink / raw) To: Anubhav Shelat Cc: acme, peterz, Masami Hiramatsu, Mathieu Desnoyers, Shivank Garg, Ackerley Tng, Fuad Tabba, Christian Brauner, Sean Christopherson, linux-kernel, linux-trace-kernel, linux-perf-users BTW, the tracing subsystem always capitalizes the subject: tracefs: Add read-only eventfs filesystem at /sys/kernel/events On Wed, 15 Jul 2026 09:52:23 -0400 Anubhav Shelat <ashelat@redhat.com> wrote: > 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> Anyway, I didn't like the way this patch duplicated the eventfs_inodes as that just wastes memory. It duplicates all the events for just making them have some read only files to show. So I took another approach. I started with this patch and then changed it to instead have two look up functions. One normal, and one for the read only file system. It adds a "read_only" field to the eventfs_entry (which is just what describes what files to print for every event). For the fields we want to show to the normal user, the "read_only" field is set to true. Then when the /sys/kernel/events directory is read, it uses the "read_only" version of the look up and it will only show the events that are marked as "read_only". It also changes the mode to allow normal users to read the files. I replaced the eventfs_create_events_dir_ro() with eventfs_create_events_ro_copy(). It takes a name and an eventfs_inode to use to copy as the "read_only" version. Below is the patch I used to replace this one. Feel free to start with it, clean it up a bit, and resubmit a v6. Thanks! -- Steve diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 39c7a34531e8..007cdf1a67f3 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -151,7 +151,11 @@ static inline struct eventfs_inode *get_ei(struct eventfs_inode *ei) static struct dentry *eventfs_root_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags); +static struct dentry *eventfs_root_lookup_ro(struct inode *dir, + struct dentry *dentry, + unsigned int flags); static int eventfs_iterate(struct file *file, struct dir_context *ctx); +static int eventfs_ro_iterate(struct file *file, struct dir_context *ctx); static void update_attr(struct eventfs_attr *attr, struct iattr *iattr) { @@ -229,6 +233,10 @@ static const struct inode_operations eventfs_dir_inode_operations = { .setattr = eventfs_set_attr, }; +static const struct inode_operations eventfs_ro_dir_inode_operations = { + .lookup = eventfs_root_lookup_ro, +}; + static const struct inode_operations eventfs_file_inode_operations = { .setattr = eventfs_set_attr, }; @@ -239,6 +247,12 @@ static const struct file_operations eventfs_file_operations = { .llseek = generic_file_llseek, }; +static const struct file_operations eventfs_ro_file_operations = { + .read = generic_read_dir, + .iterate_shared = eventfs_ro_iterate, + .llseek = generic_file_llseek, +}; + static void eventfs_set_attrs(struct eventfs_inode *ei, bool update_uid, kuid_t uid, bool update_gid, kgid_t gid, int level) { @@ -402,7 +416,7 @@ static struct dentry *lookup_file(struct eventfs_inode *parent_ei, * a eventfs_inode. */ static struct dentry *lookup_dir_entry(struct dentry *dentry, - struct eventfs_inode *pei, struct eventfs_inode *ei) + struct eventfs_inode *pei, struct eventfs_inode *ei, bool ro) { struct inode *inode; umode_t mode = S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO; @@ -411,8 +425,13 @@ static struct dentry *lookup_dir_entry(struct dentry *dentry, if (unlikely(!inode)) return ERR_PTR(-ENOMEM); - inode->i_op = &eventfs_dir_inode_operations; - inode->i_fop = &eventfs_file_operations; + if (ro) { + inode->i_op = &eventfs_ro_dir_inode_operations; + inode->i_fop = &eventfs_ro_file_operations; + } else { + inode->i_op = &eventfs_dir_inode_operations; + inode->i_fop = &eventfs_file_operations; + } /* All directories will have the same inode number */ inode->i_ino = eventfs_dir_ino(ei); @@ -514,9 +533,10 @@ lookup_file_dentry(struct dentry *dentry, * list, if @dentry found go ahead and create the file/dir */ -static struct dentry *eventfs_root_lookup(struct inode *dir, - struct dentry *dentry, - unsigned int flags) +static struct dentry *__eventfs_root_lookup(struct inode *dir, + struct dentry *dentry, + unsigned int flags, + bool ro) { struct eventfs_inode *ei_child; struct tracefs_inode *ti; @@ -539,7 +559,7 @@ static struct dentry *eventfs_root_lookup(struct inode *dir, /* A child is freed and removed from the list at the same time */ if (WARN_ON_ONCE(ei_child->is_freed)) return NULL; - return lookup_dir_entry(dentry, ei, ei_child); + return lookup_dir_entry(dentry, ei, ei_child, ro); } for (int i = 0; i < ei->nr_entries; i++) { @@ -551,20 +571,40 @@ static struct dentry *eventfs_root_lookup(struct inode *dir, if (strcmp(name, entry->name) != 0) continue; + if (ro && !entry->read_only) + return NULL; + data = ei->data; if (entry->callback(name, &mode, &data, &fops) <= 0) return NULL; + if (ro) + mode |= 0444; + return lookup_file_dentry(dentry, ei, i, mode, data, fops); } return NULL; } +static struct dentry *eventfs_root_lookup(struct inode *dir, + struct dentry *dentry, + unsigned int flags) +{ + return __eventfs_root_lookup(dir, dentry, flags, false); +} + +static struct dentry *eventfs_root_lookup_ro(struct inode *dir, + struct dentry *dentry, + unsigned int flags) +{ + return __eventfs_root_lookup(dir, dentry, flags, true); +} + /* * Walk the children of a eventfs_inode to fill in getdents(). */ -static int eventfs_iterate(struct file *file, struct dir_context *ctx) +static int __eventfs_iterate(struct file *file, struct dir_context *ctx, bool ro) { const struct file_operations *fops; struct inode *f_inode = file_inode(file); @@ -606,6 +646,9 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx) entry = &ei->entries[i]; name = entry->name; + if (ro && !entry->read_only) + continue; + /* If ei->is_freed then just bail here, nothing more to do */ scoped_guard(mutex, &eventfs_mutex) { if (ei->is_freed) @@ -650,6 +693,16 @@ static int eventfs_iterate(struct file *file, struct dir_context *ctx) return 1; } +static int eventfs_iterate(struct file *file, struct dir_context *ctx) +{ + return __eventfs_iterate(file, ctx, false); +} + +static int eventfs_ro_iterate(struct file *file, struct dir_context *ctx) +{ + return __eventfs_iterate(file, ctx, true); +} + /** * eventfs_create_dir - Create the eventfs_inode for this directory * @name: The name of the directory to create. @@ -812,6 +865,47 @@ 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. + */ +int eventfs_create_events_ro_copy(const char *name, struct eventfs_inode *ei) +{ + struct dentry *dentry; + struct tracefs_inode *ti; + struct inode *inode; + + dentry = eventfs_ro_get_root(); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + inode = d_inode(dentry); + + 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_ro_dir_inode_operations; + inode->i_fop = &eventfs_ro_file_operations; + + /* This is never freed */ + dentry->d_fsdata = get_ei(ei); + + return 0; +} + /** * 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/tracefs.h b/include/linux/tracefs.h index bc354d340046..41e97a6bd69a 100644 --- a/include/linux/tracefs.h +++ b/include/linux/tracefs.h @@ -75,6 +75,7 @@ struct eventfs_entry { const char *name; eventfs_callback callback; eventfs_release release; + bool read_only; }; struct eventfs_inode; @@ -87,6 +88,8 @@ struct eventfs_inode *eventfs_create_dir(const char *name, struct eventfs_inode const struct eventfs_entry *entries, int size, void *data); +int eventfs_create_events_ro_copy(const char *name, struct eventfs_inode *ei); + 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_events.c b/kernel/trace/trace_events.c index 9874ad524aa7..ab14b4b6e70f 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -3235,17 +3235,20 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file) { .name = "format", .callback = event_callback, + .read_only = true, }, #ifdef CONFIG_PERF_EVENTS { .name = "id", .callback = event_callback, + .read_only = true, }, #endif #ifdef CONFIG_BPF_EVENTS { .name = "btf_ids", .callback = event_callback, + .read_only = true, }, #endif #define NR_RO_EVENT_ENTRIES (1 + IS_ENABLED(CONFIG_PERF_EVENTS) + \ @@ -4652,6 +4655,7 @@ static int events_callback(const char *name, umode_t *mode, void **data, 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; @@ -4659,10 +4663,12 @@ create_event_toplevel_files(struct dentry *parent, struct trace_array *tr) { .name = "header_page", .callback = events_callback, + .read_only = true, }, { .name = "header_event", .callback = events_callback, + .read_only = true, }, #define NR_RO_TOP_ENTRIES 2 /* Readonly files must be above this line and counted by NR_RO_TOP_ENTRIES. */ @@ -4705,6 +4711,13 @@ 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)) { + int ret; + + ret = eventfs_create_events_ro_copy("events", e_events); + if (!ret) + event_dir_ro_created = true; + } return 0; } ^ permalink raw reply related [flat|nested] 8+ 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; 8+ 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] 8+ 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; 8+ 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] 8+ 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; 8+ 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] 8+ messages in thread
[parent not found: <20260714183150.292861-2-ashelat@redhat.com>]
* [PATCH v5 2/5] tracefs: add read-only eventfs filesystem at /sys/kernel/events [not found] <20260714183150.292861-2-ashelat@redhat.com> @ 2026-07-14 18:31 ` Anubhav Shelat 0 siblings, 0 replies; 8+ messages in thread From: Anubhav Shelat @ 2026-07-14 18:31 UTC (permalink / raw) To: mpetlan, Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers, Ackerley Tng, Fuad Tabba, David Hildenbrand, Anubhav Shelat, Christian Brauner, linux-kernel, linux-trace-kernel 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] 8+ messages in thread
end of thread, other threads:[~2026-07-29 0:13 UTC | newest]
Thread overview: 8+ 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-29 0:13 ` Steven Rostedt
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
[not found] <20260714183150.292861-2-ashelat@redhat.com>
2026-07-14 18:31 ` [PATCH v5 2/5] tracefs: add read-only eventfs filesystem at /sys/kernel/events Anubhav Shelat
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox