
hulk inclusion category: bugfix bugzilla: https://gitee.com/openeuler/kernel/issues/IBRD5T -------------------------------- There is a race condition in the tracing_open_file_tr() function when obtaining `inode->i_private`. The function __trace_remove_event_dirs() recursively deletes and releases `trace_event_file` (which is `inode->i_private`) and the function tracing_open_file_tr() might be concurrently reading and using it, leading to use-after-free. [instance_rmdir] [event_hist_open] event_trace_del_tracer tracepoint_synchronize_unregister tracing_open_file_tr file = inode->i_private __trace_remove_event_dirs event_put_file(file) tracing_check_open_get_tr(file->tr) Fix this by explicitly setting `inode->i_private` to NULL when deleting those files which ops depend on i_private to obtain trace_event_file, and moving `inode->i_private` inside the event_mutex lock when opening those kind of files. Fixes: 321a6c777dd0 ("tracing: Have trace_event_file have ref counters") Signed-off-by: Tengda Wu <wutengda2@huawei.com> --- kernel/trace/trace.c | 19 +++++++++++++------ kernel/trace/trace_events.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 462d2a70a835..39c37b7d2d12 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -4508,14 +4508,21 @@ int tracing_open_generic_tr(struct inode *inode, struct file *filp) */ int tracing_open_file_tr(struct inode *inode, struct file *filp) { - struct trace_event_file *file = inode->i_private; + struct trace_event_file *file; int ret; + mutex_lock(&event_mutex); + file = inode->i_private; + if (!file) { + mutex_unlock(&event_mutex); + return -ENODEV; + } + ret = tracing_check_open_get_tr(file->tr); - if (ret) + if (ret) { + mutex_unlock(&event_mutex); return ret; - - mutex_lock(&event_mutex); + } /* Fail if the file is marked for removal */ if (file->flags & EVENT_FILE_FL_FREED) { @@ -4529,14 +4536,14 @@ int tracing_open_file_tr(struct inode *inode, struct file *filp) if (ret) return ret; - filp->private_data = inode->i_private; + filp->private_data = file; return 0; } int tracing_release_file_tr(struct inode *inode, struct file *filp) { - struct trace_event_file *file = inode->i_private; + struct trace_event_file *file = filp->private_data; trace_array_put(file->tr); event_file_put(file); diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index c9ee8b730e43..32b5d043203a 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -764,9 +764,41 @@ void event_file_put(struct trace_event_file *file) } } +static bool file_op_depends_on_i_priv(const struct dentry *dentry) +{ + const struct file_operations *fop = NULL; + + if (!d_really_is_positive(dentry) || !d_inode(dentry)->i_fop) + return false; + + fop = d_inode(dentry)->i_fop; + if (fop->open == tracing_open_file_tr) + return true; +#ifdef CONFIG_HIST_TRIGGERS + if (fop == &event_hist_fops) + return true; +#endif +#ifdef CONFIG_HIST_TRIGGERS_DEBUG + if (fop == &event_hist_debug_fops) + return true; +#endif + + return false; +} + static void remove_event_file_dir(struct trace_event_file *file) { struct dentry *dir = file->dir; + struct dentry *child; + + if (dir) { + spin_lock(&dir->d_lock); /* probably unneeded */ + list_for_each_entry(child, &dir->d_subdirs, d_child) { + if (file_op_depends_on_i_priv(child)) + d_inode(child)->i_private = NULL; + } + spin_unlock(&dir->d_lock); + } tracefs_remove(dir); -- 2.34.1