CVE-2026-46106
Description
In the Linux kernel, the following vulnerability has been resolved:
eventfs: Hold eventfs_mutex and SRCU when remount walks events
Commit 340f0c7067a9 ("eventfs: Update all the eventfs_inodes from the events descriptor") had eventfs_set_attrs() recurse through ei->children on remount. The walk only holds the rcu_read_lock() taken by tracefs_apply_options() over tracefs_inodes, which is wrong:
- list_for_each_entry over ei->children races with the list_del_rcu() in eventfs_remove_rec() -- LIST_POISON1 deref, same shape as d2603279c7d6. - eventfs_inodes are freed via call_srcu(&eventfs_srcu, ...). rcu_read_lock() does not extend an SRCU grace period, so ti->private can be reclaimed under the walk. - The writes to ei->attr race with eventfs_set_attr(), which holds eventfs_mutex.
Reproducer:
while :; do mount -o remount,uid=$((RANDOM%1000)) /sys/kernel/tracing; done & while :; do echo "p:kp submit_bio" > /sys/kernel/tracing/kprobe_events echo > /sys/kernel/tracing/kprobe_events done
Wrap the events portion of tracefs_apply_options() in eventfs_remount_lock()/_unlock() that take eventfs_mutex and srcu_read_lock(&eventfs_srcu). eventfs_set_attrs() doesn't sleep so the nested rcu_read_lock() is fine; lockdep_assert_held() pins the contract.
Comment in tracefs_drop_inode() said "RCU cycle" -- it is SRCU.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A race condition in Linux kernel's eventfs subsystem allows use-after-free during remount, potentially leading to privilege escalation.
Vulnerability
The vulnerability resides in eventfs_set_attrs(), which recursively walks ei->children without proper locking. The walk only holds rcu_read_lock(), but the list uses list_del_rcu() and call_srcu() for freeing, so it races with removal and use-after-free. Affected versions are those containing commit 340f0c7067a9 (likely 6.x). The fix is in commit ae9cd0b46b1890040006a2fc5e905c5d6053fd02 [1].
Exploitation
An attacker needs local access to mount and remount tracefs (/sys/kernel/tracing) and create/destroy kprobe events. The reproducer shows a loop of remount with random UID and concurrent kprobe creation/deletion. The race window is small but can be triggered repeatedly [1].
Impact
Successful exploitation leads to a use-after-free, potentially allowing an attacker to corrupt kernel memory and escalate privileges. The exact impact depends on memory layout, but it could lead to arbitrary code execution in kernel context [1].
Mitigation
The fix is in commit ae9cd0b46b1890040006a2fc5e905c5d6053fd02, which wraps the walk in eventfs_remount_lock()/_unlock() that takes eventfs_mutex and srcu_read_lock(&eventfs_srcu). Users should apply the kernel patch. No workaround is mentioned; the vulnerability is not listed in KEV [1].
AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
1007004a8c4b57eventfs: Hold eventfs_mutex and SRCU when remount walks events
3 files changed · +21 −2
fs/tracefs/event_inode.c+14 −0 modifieddiff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 8dd554508828b8..26b6453de30ef6 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -244,6 +244,8 @@ static void eventfs_set_attrs(struct eventfs_inode *ei, bool update_uid, kuid_t { struct eventfs_inode *ei_child; + lockdep_assert_held(&eventfs_mutex); + /* Update events/<system>/<event> */ if (WARN_ON_ONCE(level > 3)) return; @@ -886,3 +888,15 @@ void eventfs_remove_events_dir(struct eventfs_inode *ei) d_invalidate(dentry); d_make_discardable(dentry); } + +int eventfs_remount_lock(void) +{ + mutex_lock(&eventfs_mutex); + return srcu_read_lock(&eventfs_srcu); +} + +void eventfs_remount_unlock(int srcu_idx) +{ + srcu_read_unlock(&eventfs_srcu, srcu_idx); + mutex_unlock(&eventfs_mutex); +}
fs/tracefs/inode.c+4 −1 modifieddiff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 5602baf980f685..1e8a78c5e996a6 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -313,6 +313,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) struct inode *inode = d_inode(sb->s_root); struct tracefs_inode *ti; bool update_uid, update_gid; + int srcu_idx; umode_t tmp_mode; /* @@ -337,6 +338,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) update_uid = fsi->opts & BIT(Opt_uid); update_gid = fsi->opts & BIT(Opt_gid); + srcu_idx = eventfs_remount_lock(); rcu_read_lock(); list_for_each_entry_rcu(ti, &tracefs_inodes, list) { if (update_uid) { @@ -358,6 +360,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) eventfs_remount(ti, update_uid, update_gid); } rcu_read_unlock(); + eventfs_remount_unlock(srcu_idx); } return 0; @@ -403,7 +406,7 @@ static int tracefs_drop_inode(struct inode *inode) * This inode is being freed and cannot be used for * eventfs. Clear the flag so that it doesn't call into * eventfs during the remount flag updates. The eventfs_inode - * gets freed after an RCU cycle, so the content will still + * gets freed after an SRCU cycle, so the content will still * be safe if the iteration is going on now. */ ti->flags &= ~TRACEFS_EVENT_INODE;
fs/tracefs/internal.h+3 −1 modifieddiff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h index d83c2a25f288e0..a4a7f8431affb0 100644 --- a/fs/tracefs/internal.h +++ b/fs/tracefs/internal.h @@ -76,4 +76,7 @@ struct inode *tracefs_get_inode(struct super_block *sb); void eventfs_remount(struct tracefs_inode *ti, bool update_uid, bool update_gid); void eventfs_d_release(struct dentry *dentry); +int eventfs_remount_lock(void); +void eventfs_remount_unlock(int srcu_idx); + #endif /* _TRACEFS_INTERNAL_H */ -- cgit 1.3-korg
ae9cd0b46b18eventfs: Hold eventfs_mutex and SRCU when remount walks events
3 files changed · +21 −2
fs/tracefs/event_inode.c+14 −0 modifieddiff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 4190e615504490..f98315e91e99b4 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -310,6 +310,8 @@ static void eventfs_set_attrs(struct eventfs_inode *ei, bool update_uid, kuid_t { struct eventfs_inode *ei_child; + lockdep_assert_held(&eventfs_mutex); + /* Update events/<system>/<event> */ if (WARN_ON_ONCE(level > 3)) return; @@ -985,3 +987,15 @@ void eventfs_remove_events_dir(struct eventfs_inode *ei) d_invalidate(dentry); dput(dentry); } + +int eventfs_remount_lock(void) +{ + mutex_lock(&eventfs_mutex); + return srcu_read_lock(&eventfs_srcu); +} + +void eventfs_remount_unlock(int srcu_idx) +{ + srcu_read_unlock(&eventfs_srcu, srcu_idx); + mutex_unlock(&eventfs_mutex); +}
fs/tracefs/inode.c+4 −1 modifieddiff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 6b70965063d739..6c7b4877094800 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -362,6 +362,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) struct tracefs_mount_opts *opts = &fsi->mount_opts; struct tracefs_inode *ti; bool update_uid, update_gid; + int srcu_idx; umode_t tmp_mode; /* @@ -386,6 +387,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) update_uid = opts->opts & BIT(Opt_uid); update_gid = opts->opts & BIT(Opt_gid); + srcu_idx = eventfs_remount_lock(); rcu_read_lock(); list_for_each_entry_rcu(ti, &tracefs_inodes, list) { if (update_uid) @@ -398,6 +400,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) eventfs_remount(ti, update_uid, update_gid); } rcu_read_unlock(); + eventfs_remount_unlock(srcu_idx); } return 0; @@ -444,7 +447,7 @@ static int tracefs_drop_inode(struct inode *inode) * This inode is being freed and cannot be used for * eventfs. Clear the flag so that it doesn't call into * eventfs during the remount flag updates. The eventfs_inode - * gets freed after an RCU cycle, so the content will still + * gets freed after an SRCU cycle, so the content will still * be safe if the iteration is going on now. */ ti->flags &= ~TRACEFS_EVENT_INODE;
fs/tracefs/internal.h+3 −1 modifieddiff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h index d83c2a25f288e0..a4a7f8431affb0 100644 --- a/fs/tracefs/internal.h +++ b/fs/tracefs/internal.h @@ -76,4 +76,7 @@ struct inode *tracefs_get_inode(struct super_block *sb); void eventfs_remount(struct tracefs_inode *ti, bool update_uid, bool update_gid); void eventfs_d_release(struct dentry *dentry); +int eventfs_remount_lock(void); +void eventfs_remount_unlock(int srcu_idx); + #endif /* _TRACEFS_INTERNAL_H */ -- cgit 1.3-korg
44e64d8a4328eventfs: Hold eventfs_mutex and SRCU when remount walks events
3 files changed · +21 −2
fs/tracefs/event_inode.c+14 −0 modifieddiff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 93c231601c8e23..0d2bc92b760f3f 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -250,6 +250,8 @@ static void eventfs_set_attrs(struct eventfs_inode *ei, bool update_uid, kuid_t { struct eventfs_inode *ei_child; + lockdep_assert_held(&eventfs_mutex); + /* Update events/<system>/<event> */ if (WARN_ON_ONCE(level > 3)) return; @@ -912,3 +914,15 @@ void eventfs_remove_events_dir(struct eventfs_inode *ei) d_invalidate(dentry); dput(dentry); } + +int eventfs_remount_lock(void) +{ + mutex_lock(&eventfs_mutex); + return srcu_read_lock(&eventfs_srcu); +} + +void eventfs_remount_unlock(int srcu_idx) +{ + srcu_read_unlock(&eventfs_srcu, srcu_idx); + mutex_unlock(&eventfs_mutex); +}
fs/tracefs/inode.c+4 −1 modifieddiff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 9f15d606dfde79..8e5db8cc42188e 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -336,6 +336,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) struct inode *inode = d_inode(sb->s_root); struct tracefs_inode *ti; bool update_uid, update_gid; + int srcu_idx; umode_t tmp_mode; /* @@ -360,6 +361,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) update_uid = fsi->opts & BIT(Opt_uid); update_gid = fsi->opts & BIT(Opt_gid); + srcu_idx = eventfs_remount_lock(); rcu_read_lock(); list_for_each_entry_rcu(ti, &tracefs_inodes, list) { if (update_uid) { @@ -381,6 +383,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) eventfs_remount(ti, update_uid, update_gid); } rcu_read_unlock(); + eventfs_remount_unlock(srcu_idx); } return 0; @@ -426,7 +429,7 @@ static int tracefs_drop_inode(struct inode *inode) * This inode is being freed and cannot be used for * eventfs. Clear the flag so that it doesn't call into * eventfs during the remount flag updates. The eventfs_inode - * gets freed after an RCU cycle, so the content will still + * gets freed after an SRCU cycle, so the content will still * be safe if the iteration is going on now. */ ti->flags &= ~TRACEFS_EVENT_INODE;
fs/tracefs/internal.h+3 −1 modifieddiff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h index d83c2a25f288e0..a4a7f8431affb0 100644 --- a/fs/tracefs/internal.h +++ b/fs/tracefs/internal.h @@ -76,4 +76,7 @@ struct inode *tracefs_get_inode(struct super_block *sb); void eventfs_remount(struct tracefs_inode *ti, bool update_uid, bool update_gid); void eventfs_d_release(struct dentry *dentry); +int eventfs_remount_lock(void); +void eventfs_remount_unlock(int srcu_idx); + #endif /* _TRACEFS_INTERNAL_H */ -- cgit 1.3-korg
52b109f1b875eventfs: Hold eventfs_mutex and SRCU when remount walks events
3 files changed · +21 −2
fs/tracefs/event_inode.c+14 −0 modifieddiff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 93c231601c8e23..0d2bc92b760f3f 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -250,6 +250,8 @@ static void eventfs_set_attrs(struct eventfs_inode *ei, bool update_uid, kuid_t { struct eventfs_inode *ei_child; + lockdep_assert_held(&eventfs_mutex); + /* Update events/<system>/<event> */ if (WARN_ON_ONCE(level > 3)) return; @@ -912,3 +914,15 @@ void eventfs_remove_events_dir(struct eventfs_inode *ei) d_invalidate(dentry); dput(dentry); } + +int eventfs_remount_lock(void) +{ + mutex_lock(&eventfs_mutex); + return srcu_read_lock(&eventfs_srcu); +} + +void eventfs_remount_unlock(int srcu_idx) +{ + srcu_read_unlock(&eventfs_srcu, srcu_idx); + mutex_unlock(&eventfs_mutex); +}
fs/tracefs/inode.c+4 −1 modifieddiff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 35c0cc557fbb75..363a5d75541198 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -336,6 +336,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) struct inode *inode = d_inode(sb->s_root); struct tracefs_inode *ti; bool update_uid, update_gid; + int srcu_idx; umode_t tmp_mode; /* @@ -360,6 +361,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) update_uid = fsi->opts & BIT(Opt_uid); update_gid = fsi->opts & BIT(Opt_gid); + srcu_idx = eventfs_remount_lock(); rcu_read_lock(); list_for_each_entry_rcu(ti, &tracefs_inodes, list) { if (update_uid) { @@ -381,6 +383,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) eventfs_remount(ti, update_uid, update_gid); } rcu_read_unlock(); + eventfs_remount_unlock(srcu_idx); } return 0; @@ -426,7 +429,7 @@ static int tracefs_drop_inode(struct inode *inode) * This inode is being freed and cannot be used for * eventfs. Clear the flag so that it doesn't call into * eventfs during the remount flag updates. The eventfs_inode - * gets freed after an RCU cycle, so the content will still + * gets freed after an SRCU cycle, so the content will still * be safe if the iteration is going on now. */ ti->flags &= ~TRACEFS_EVENT_INODE;
fs/tracefs/internal.h+3 −1 modifieddiff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h index d83c2a25f288e0..a4a7f8431affb0 100644 --- a/fs/tracefs/internal.h +++ b/fs/tracefs/internal.h @@ -76,4 +76,7 @@ struct inode *tracefs_get_inode(struct super_block *sb); void eventfs_remount(struct tracefs_inode *ti, bool update_uid, bool update_gid); void eventfs_d_release(struct dentry *dentry); +int eventfs_remount_lock(void); +void eventfs_remount_unlock(int srcu_idx); + #endif /* _TRACEFS_INTERNAL_H */ -- cgit 1.3-korg
ed2ad73bcb0aeventfs: Hold eventfs_mutex and SRCU when remount walks events
3 files changed · +21 −2
fs/tracefs/event_inode.c+14 −0 modifieddiff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 8e5ac464b32849..af3387eebef5b9 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -250,6 +250,8 @@ static void eventfs_set_attrs(struct eventfs_inode *ei, bool update_uid, kuid_t { struct eventfs_inode *ei_child; + lockdep_assert_held(&eventfs_mutex); + /* Update events/<system>/<event> */ if (WARN_ON_ONCE(level > 3)) return; @@ -912,3 +914,15 @@ void eventfs_remove_events_dir(struct eventfs_inode *ei) d_invalidate(dentry); d_make_discardable(dentry); } + +int eventfs_remount_lock(void) +{ + mutex_lock(&eventfs_mutex); + return srcu_read_lock(&eventfs_srcu); +} + +void eventfs_remount_unlock(int srcu_idx) +{ + srcu_read_unlock(&eventfs_srcu, srcu_idx); + mutex_unlock(&eventfs_mutex); +}
fs/tracefs/inode.c+4 −1 modifieddiff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 8ba72c5a435cae..40477513cce1e4 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -336,6 +336,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) struct inode *inode = d_inode(sb->s_root); struct tracefs_inode *ti; bool update_uid, update_gid; + int srcu_idx; umode_t tmp_mode; /* @@ -360,6 +361,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) update_uid = fsi->opts & BIT(Opt_uid); update_gid = fsi->opts & BIT(Opt_gid); + srcu_idx = eventfs_remount_lock(); rcu_read_lock(); list_for_each_entry_rcu(ti, &tracefs_inodes, list) { if (update_uid) { @@ -381,6 +383,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) eventfs_remount(ti, update_uid, update_gid); } rcu_read_unlock(); + eventfs_remount_unlock(srcu_idx); } return 0; @@ -426,7 +429,7 @@ static int tracefs_drop_inode(struct inode *inode) * This inode is being freed and cannot be used for * eventfs. Clear the flag so that it doesn't call into * eventfs during the remount flag updates. The eventfs_inode - * gets freed after an RCU cycle, so the content will still + * gets freed after an SRCU cycle, so the content will still * be safe if the iteration is going on now. */ ti->flags &= ~TRACEFS_EVENT_INODE;
fs/tracefs/internal.h+3 −1 modifieddiff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h index d83c2a25f288e0..a4a7f8431affb0 100644 --- a/fs/tracefs/internal.h +++ b/fs/tracefs/internal.h @@ -76,4 +76,7 @@ struct inode *tracefs_get_inode(struct super_block *sb); void eventfs_remount(struct tracefs_inode *ti, bool update_uid, bool update_gid); void eventfs_d_release(struct dentry *dentry); +int eventfs_remount_lock(void); +void eventfs_remount_unlock(int srcu_idx); + #endif /* _TRACEFS_INTERNAL_H */ -- cgit 1.3-korg
07004a8c4b57eventfs: Hold eventfs_mutex and SRCU when remount walks events
3 files changed · +21 −2
fs/tracefs/event_inode.c+14 −0 modifieddiff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 8dd554508828b8..26b6453de30ef6 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -244,6 +244,8 @@ static void eventfs_set_attrs(struct eventfs_inode *ei, bool update_uid, kuid_t { struct eventfs_inode *ei_child; + lockdep_assert_held(&eventfs_mutex); + /* Update events/<system>/<event> */ if (WARN_ON_ONCE(level > 3)) return; @@ -886,3 +888,15 @@ void eventfs_remove_events_dir(struct eventfs_inode *ei) d_invalidate(dentry); d_make_discardable(dentry); } + +int eventfs_remount_lock(void) +{ + mutex_lock(&eventfs_mutex); + return srcu_read_lock(&eventfs_srcu); +} + +void eventfs_remount_unlock(int srcu_idx) +{ + srcu_read_unlock(&eventfs_srcu, srcu_idx); + mutex_unlock(&eventfs_mutex); +}
fs/tracefs/inode.c+4 −1 modifieddiff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 5602baf980f685..1e8a78c5e996a6 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -313,6 +313,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) struct inode *inode = d_inode(sb->s_root); struct tracefs_inode *ti; bool update_uid, update_gid; + int srcu_idx; umode_t tmp_mode; /* @@ -337,6 +338,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) update_uid = fsi->opts & BIT(Opt_uid); update_gid = fsi->opts & BIT(Opt_gid); + srcu_idx = eventfs_remount_lock(); rcu_read_lock(); list_for_each_entry_rcu(ti, &tracefs_inodes, list) { if (update_uid) { @@ -358,6 +360,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) eventfs_remount(ti, update_uid, update_gid); } rcu_read_unlock(); + eventfs_remount_unlock(srcu_idx); } return 0; @@ -403,7 +406,7 @@ static int tracefs_drop_inode(struct inode *inode) * This inode is being freed and cannot be used for * eventfs. Clear the flag so that it doesn't call into * eventfs during the remount flag updates. The eventfs_inode - * gets freed after an RCU cycle, so the content will still + * gets freed after an SRCU cycle, so the content will still * be safe if the iteration is going on now. */ ti->flags &= ~TRACEFS_EVENT_INODE;
fs/tracefs/internal.h+3 −1 modifieddiff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h index d83c2a25f288e0..a4a7f8431affb0 100644 --- a/fs/tracefs/internal.h +++ b/fs/tracefs/internal.h @@ -76,4 +76,7 @@ struct inode *tracefs_get_inode(struct super_block *sb); void eventfs_remount(struct tracefs_inode *ti, bool update_uid, bool update_gid); void eventfs_d_release(struct dentry *dentry); +int eventfs_remount_lock(void); +void eventfs_remount_unlock(int srcu_idx); + #endif /* _TRACEFS_INTERNAL_H */ -- cgit 1.3-korg
44e64d8a4328eventfs: Hold eventfs_mutex and SRCU when remount walks events
3 files changed · +21 −2
fs/tracefs/event_inode.c+14 −0 modifieddiff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 93c231601c8e23..0d2bc92b760f3f 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -250,6 +250,8 @@ static void eventfs_set_attrs(struct eventfs_inode *ei, bool update_uid, kuid_t { struct eventfs_inode *ei_child; + lockdep_assert_held(&eventfs_mutex); + /* Update events/<system>/<event> */ if (WARN_ON_ONCE(level > 3)) return; @@ -912,3 +914,15 @@ void eventfs_remove_events_dir(struct eventfs_inode *ei) d_invalidate(dentry); dput(dentry); } + +int eventfs_remount_lock(void) +{ + mutex_lock(&eventfs_mutex); + return srcu_read_lock(&eventfs_srcu); +} + +void eventfs_remount_unlock(int srcu_idx) +{ + srcu_read_unlock(&eventfs_srcu, srcu_idx); + mutex_unlock(&eventfs_mutex); +}
fs/tracefs/inode.c+4 −1 modifieddiff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 9f15d606dfde79..8e5db8cc42188e 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -336,6 +336,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) struct inode *inode = d_inode(sb->s_root); struct tracefs_inode *ti; bool update_uid, update_gid; + int srcu_idx; umode_t tmp_mode; /* @@ -360,6 +361,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) update_uid = fsi->opts & BIT(Opt_uid); update_gid = fsi->opts & BIT(Opt_gid); + srcu_idx = eventfs_remount_lock(); rcu_read_lock(); list_for_each_entry_rcu(ti, &tracefs_inodes, list) { if (update_uid) { @@ -381,6 +383,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) eventfs_remount(ti, update_uid, update_gid); } rcu_read_unlock(); + eventfs_remount_unlock(srcu_idx); } return 0; @@ -426,7 +429,7 @@ static int tracefs_drop_inode(struct inode *inode) * This inode is being freed and cannot be used for * eventfs. Clear the flag so that it doesn't call into * eventfs during the remount flag updates. The eventfs_inode - * gets freed after an RCU cycle, so the content will still + * gets freed after an SRCU cycle, so the content will still * be safe if the iteration is going on now. */ ti->flags &= ~TRACEFS_EVENT_INODE;
fs/tracefs/internal.h+3 −1 modifieddiff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h index d83c2a25f288e0..a4a7f8431affb0 100644 --- a/fs/tracefs/internal.h +++ b/fs/tracefs/internal.h @@ -76,4 +76,7 @@ struct inode *tracefs_get_inode(struct super_block *sb); void eventfs_remount(struct tracefs_inode *ti, bool update_uid, bool update_gid); void eventfs_d_release(struct dentry *dentry); +int eventfs_remount_lock(void); +void eventfs_remount_unlock(int srcu_idx); + #endif /* _TRACEFS_INTERNAL_H */ -- cgit 1.3-korg
52b109f1b875eventfs: Hold eventfs_mutex and SRCU when remount walks events
3 files changed · +21 −2
fs/tracefs/event_inode.c+14 −0 modifieddiff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 93c231601c8e23..0d2bc92b760f3f 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -250,6 +250,8 @@ static void eventfs_set_attrs(struct eventfs_inode *ei, bool update_uid, kuid_t { struct eventfs_inode *ei_child; + lockdep_assert_held(&eventfs_mutex); + /* Update events/<system>/<event> */ if (WARN_ON_ONCE(level > 3)) return; @@ -912,3 +914,15 @@ void eventfs_remove_events_dir(struct eventfs_inode *ei) d_invalidate(dentry); dput(dentry); } + +int eventfs_remount_lock(void) +{ + mutex_lock(&eventfs_mutex); + return srcu_read_lock(&eventfs_srcu); +} + +void eventfs_remount_unlock(int srcu_idx) +{ + srcu_read_unlock(&eventfs_srcu, srcu_idx); + mutex_unlock(&eventfs_mutex); +}
fs/tracefs/inode.c+4 −1 modifieddiff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 35c0cc557fbb75..363a5d75541198 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -336,6 +336,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) struct inode *inode = d_inode(sb->s_root); struct tracefs_inode *ti; bool update_uid, update_gid; + int srcu_idx; umode_t tmp_mode; /* @@ -360,6 +361,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) update_uid = fsi->opts & BIT(Opt_uid); update_gid = fsi->opts & BIT(Opt_gid); + srcu_idx = eventfs_remount_lock(); rcu_read_lock(); list_for_each_entry_rcu(ti, &tracefs_inodes, list) { if (update_uid) { @@ -381,6 +383,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) eventfs_remount(ti, update_uid, update_gid); } rcu_read_unlock(); + eventfs_remount_unlock(srcu_idx); } return 0; @@ -426,7 +429,7 @@ static int tracefs_drop_inode(struct inode *inode) * This inode is being freed and cannot be used for * eventfs. Clear the flag so that it doesn't call into * eventfs during the remount flag updates. The eventfs_inode - * gets freed after an RCU cycle, so the content will still + * gets freed after an SRCU cycle, so the content will still * be safe if the iteration is going on now. */ ti->flags &= ~TRACEFS_EVENT_INODE;
fs/tracefs/internal.h+3 −1 modifieddiff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h index d83c2a25f288e0..a4a7f8431affb0 100644 --- a/fs/tracefs/internal.h +++ b/fs/tracefs/internal.h @@ -76,4 +76,7 @@ struct inode *tracefs_get_inode(struct super_block *sb); void eventfs_remount(struct tracefs_inode *ti, bool update_uid, bool update_gid); void eventfs_d_release(struct dentry *dentry); +int eventfs_remount_lock(void); +void eventfs_remount_unlock(int srcu_idx); + #endif /* _TRACEFS_INTERNAL_H */ -- cgit 1.3-korg
ae9cd0b46b18eventfs: Hold eventfs_mutex and SRCU when remount walks events
3 files changed · +21 −2
fs/tracefs/event_inode.c+14 −0 modifieddiff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 4190e615504490..f98315e91e99b4 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -310,6 +310,8 @@ static void eventfs_set_attrs(struct eventfs_inode *ei, bool update_uid, kuid_t { struct eventfs_inode *ei_child; + lockdep_assert_held(&eventfs_mutex); + /* Update events/<system>/<event> */ if (WARN_ON_ONCE(level > 3)) return; @@ -985,3 +987,15 @@ void eventfs_remove_events_dir(struct eventfs_inode *ei) d_invalidate(dentry); dput(dentry); } + +int eventfs_remount_lock(void) +{ + mutex_lock(&eventfs_mutex); + return srcu_read_lock(&eventfs_srcu); +} + +void eventfs_remount_unlock(int srcu_idx) +{ + srcu_read_unlock(&eventfs_srcu, srcu_idx); + mutex_unlock(&eventfs_mutex); +}
fs/tracefs/inode.c+4 −1 modifieddiff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 6b70965063d739..6c7b4877094800 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -362,6 +362,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) struct tracefs_mount_opts *opts = &fsi->mount_opts; struct tracefs_inode *ti; bool update_uid, update_gid; + int srcu_idx; umode_t tmp_mode; /* @@ -386,6 +387,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) update_uid = opts->opts & BIT(Opt_uid); update_gid = opts->opts & BIT(Opt_gid); + srcu_idx = eventfs_remount_lock(); rcu_read_lock(); list_for_each_entry_rcu(ti, &tracefs_inodes, list) { if (update_uid) @@ -398,6 +400,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) eventfs_remount(ti, update_uid, update_gid); } rcu_read_unlock(); + eventfs_remount_unlock(srcu_idx); } return 0; @@ -444,7 +447,7 @@ static int tracefs_drop_inode(struct inode *inode) * This inode is being freed and cannot be used for * eventfs. Clear the flag so that it doesn't call into * eventfs during the remount flag updates. The eventfs_inode - * gets freed after an RCU cycle, so the content will still + * gets freed after an SRCU cycle, so the content will still * be safe if the iteration is going on now. */ ti->flags &= ~TRACEFS_EVENT_INODE;
fs/tracefs/internal.h+3 −1 modifieddiff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h index d83c2a25f288e0..a4a7f8431affb0 100644 --- a/fs/tracefs/internal.h +++ b/fs/tracefs/internal.h @@ -76,4 +76,7 @@ struct inode *tracefs_get_inode(struct super_block *sb); void eventfs_remount(struct tracefs_inode *ti, bool update_uid, bool update_gid); void eventfs_d_release(struct dentry *dentry); +int eventfs_remount_lock(void); +void eventfs_remount_unlock(int srcu_idx); + #endif /* _TRACEFS_INTERNAL_H */ -- cgit 1.3-korg
ed2ad73bcb0aeventfs: Hold eventfs_mutex and SRCU when remount walks events
3 files changed · +21 −2
fs/tracefs/event_inode.c+14 −0 modifieddiff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 8e5ac464b32849..af3387eebef5b9 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -250,6 +250,8 @@ static void eventfs_set_attrs(struct eventfs_inode *ei, bool update_uid, kuid_t { struct eventfs_inode *ei_child; + lockdep_assert_held(&eventfs_mutex); + /* Update events/<system>/<event> */ if (WARN_ON_ONCE(level > 3)) return; @@ -912,3 +914,15 @@ void eventfs_remove_events_dir(struct eventfs_inode *ei) d_invalidate(dentry); d_make_discardable(dentry); } + +int eventfs_remount_lock(void) +{ + mutex_lock(&eventfs_mutex); + return srcu_read_lock(&eventfs_srcu); +} + +void eventfs_remount_unlock(int srcu_idx) +{ + srcu_read_unlock(&eventfs_srcu, srcu_idx); + mutex_unlock(&eventfs_mutex); +}
fs/tracefs/inode.c+4 −1 modifieddiff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 8ba72c5a435cae..40477513cce1e4 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -336,6 +336,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) struct inode *inode = d_inode(sb->s_root); struct tracefs_inode *ti; bool update_uid, update_gid; + int srcu_idx; umode_t tmp_mode; /* @@ -360,6 +361,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) update_uid = fsi->opts & BIT(Opt_uid); update_gid = fsi->opts & BIT(Opt_gid); + srcu_idx = eventfs_remount_lock(); rcu_read_lock(); list_for_each_entry_rcu(ti, &tracefs_inodes, list) { if (update_uid) { @@ -381,6 +383,7 @@ static int tracefs_apply_options(struct super_block *sb, bool remount) eventfs_remount(ti, update_uid, update_gid); } rcu_read_unlock(); + eventfs_remount_unlock(srcu_idx); } return 0; @@ -426,7 +429,7 @@ static int tracefs_drop_inode(struct inode *inode) * This inode is being freed and cannot be used for * eventfs. Clear the flag so that it doesn't call into * eventfs during the remount flag updates. The eventfs_inode - * gets freed after an RCU cycle, so the content will still + * gets freed after an SRCU cycle, so the content will still * be safe if the iteration is going on now. */ ti->flags &= ~TRACEFS_EVENT_INODE;
fs/tracefs/internal.h+3 −1 modifieddiff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h index d83c2a25f288e0..a4a7f8431affb0 100644 --- a/fs/tracefs/internal.h +++ b/fs/tracefs/internal.h @@ -76,4 +76,7 @@ struct inode *tracefs_get_inode(struct super_block *sb); void eventfs_remount(struct tracefs_inode *ti, bool update_uid, bool update_gid); void eventfs_d_release(struct dentry *dentry); +int eventfs_remount_lock(void); +void eventfs_remount_unlock(int srcu_idx); + #endif /* _TRACEFS_INTERNAL_H */ -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Insufficient locking in the remount walk of eventfs_inodes — the traversal of ei->children only held rcu_read_lock() instead of eventfs_mutex and SRCU read lock, allowing concurrent list removal, use-after-free, and attribute races."
Attack vector
An attacker with local access can trigger a race condition by concurrently running a remount loop (e.g., `mount -o remount,uid=$((RANDOM%1000)) /sys/kernel/tracing`) and a kprobe create/remove loop (e.g., `echo "p:kp submit_bio" > /sys/kernel/tracing/kprobe_events` followed by clearing it) [patch_id=2898675]. The remount walk of `ei->children` via `list_for_each_entry` races with `list_del_rcu()` in `eventfs_remove_rec()`, causing a LIST_POISON1 dereference. Additionally, `eventfs_inodes` are freed via `call_srcu(&eventfs_srcu, ...)` but the walk only holds `rcu_read_lock()`, which does not extend an SRCU grace period, so `ti->private` can be reclaimed under the walk. Writes to `ei->attr` also race with `eventfs_set_attr()` which holds `eventfs_mutex` [patch_id=2898675].
Affected code
The vulnerability is in `fs/tracefs/event_inode.c` in the `eventfs_set_attrs()` function and in `fs/tracefs/inode.c` in `tracefs_apply_options()`. The remount path in `tracefs_apply_options()` iterates `tracefs_inodes` and calls `eventfs_remount()`, which recurses through `ei->children` via `eventfs_set_attrs()` [patch_id=2898675].
What the fix does
The patch wraps the events portion of `tracefs_apply_options()` in new `eventfs_remount_lock()`/`eventfs_remount_unlock()` helpers that acquire `eventfs_mutex` and take `srcu_read_lock(&eventfs_srcu)` [patch_id=2898675]. This prevents the three races: `eventfs_mutex` serializes the `ei->children` list walk against concurrent `eventfs_remove_rec()` and `eventfs_set_attr()`; `srcu_read_lock()` ensures the SRCU grace period protects `ti->private` from being freed during the walk. A `lockdep_assert_held(&eventfs_mutex)` is also added to `eventfs_set_attrs()` to pin the contract. The comment in `tracefs_drop_inode()` is corrected from "RCU cycle" to "SRCU cycle" [patch_id=2898675].
Preconditions
- authLocal access to the system with ability to mount/remount tracefs and create/remove kprobes
- inputThe attacker must be able to run concurrent processes to trigger the race condition
Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- git.kernel.org/stable/c/07004a8c4b572171934390148ee48c4175c77eednvd
- git.kernel.org/stable/c/44e64d8a432837308f4dda3ffe819f1ec092a0banvd
- git.kernel.org/stable/c/52b109f1b875b912d4ab2c5fdd8c322d47119d9bnvd
- git.kernel.org/stable/c/ae9cd0b46b1890040006a2fc5e905c5d6053fd02nvd
- git.kernel.org/stable/c/ed2ad73bcb0a7a6cc934097d4853b6d5124c317envd
News mentions
0No linked articles in our index yet.