Commit e9dbfae5 authored by Steven Rostedt's avatar Steven Rostedt Committed by Steven Rostedt

tracing: Fix bug when reading system filters on module removal

The event system is freed when its nr_events is set to zero. This happens
when a module created an event system and then later the module is
removed. Modules may share systems, so the system is allocated when
it is created and freed when the modules are unloaded and all the
events under the system are removed (nr_events set to zero).

The problem arises when a task opened the "filter" file for the
system. If the module is unloaded and it removed the last event for
that system, the system structure is freed. If the task that opened
the filter file accesses the "filter" file after the system has
been freed, the system will access an invalid pointer.

By adding a ref_count, and using it to keep track of what
is using the event system, we can free it after all users
are finished with the event system.

Cc: <stable@kernel.org>
Reported-by: default avatarJohannes Berg <johannes.berg@intel.com>
Signed-off-by: default avatarSteven Rostedt <rostedt@goodmis.org>
parent 140fe3b1
...@@ -677,6 +677,7 @@ struct event_subsystem { ...@@ -677,6 +677,7 @@ struct event_subsystem {
struct dentry *entry; struct dentry *entry;
struct event_filter *filter; struct event_filter *filter;
int nr_events; int nr_events;
int ref_count;
}; };
#define FILTER_PRED_INVALID ((unsigned short)-1) #define FILTER_PRED_INVALID ((unsigned short)-1)
......
...@@ -244,6 +244,35 @@ static void ftrace_clear_events(void) ...@@ -244,6 +244,35 @@ static void ftrace_clear_events(void)
mutex_unlock(&event_mutex); mutex_unlock(&event_mutex);
} }
static void __put_system(struct event_subsystem *system)
{
struct event_filter *filter = system->filter;
WARN_ON_ONCE(system->ref_count == 0);
if (--system->ref_count)
return;
if (filter) {
kfree(filter->filter_string);
kfree(filter);
}
kfree(system->name);
kfree(system);
}
static void __get_system(struct event_subsystem *system)
{
WARN_ON_ONCE(system->ref_count == 0);
system->ref_count++;
}
static void put_system(struct event_subsystem *system)
{
mutex_lock(&event_mutex);
__put_system(system);
mutex_unlock(&event_mutex);
}
/* /*
* __ftrace_set_clr_event(NULL, NULL, NULL, set) will set/unset all events. * __ftrace_set_clr_event(NULL, NULL, NULL, set) will set/unset all events.
*/ */
...@@ -826,6 +855,47 @@ event_filter_write(struct file *filp, const char __user *ubuf, size_t cnt, ...@@ -826,6 +855,47 @@ event_filter_write(struct file *filp, const char __user *ubuf, size_t cnt,
return cnt; return cnt;
} }
static LIST_HEAD(event_subsystems);
static int subsystem_open(struct inode *inode, struct file *filp)
{
struct event_subsystem *system = NULL;
int ret;
/* Make sure the system still exists */
mutex_lock(&event_mutex);
list_for_each_entry(system, &event_subsystems, list) {
if (system == inode->i_private) {
/* Don't open systems with no events */
if (!system->nr_events) {
system = NULL;
break;
}
__get_system(system);
break;
}
}
mutex_unlock(&event_mutex);
if (system != inode->i_private)
return -ENODEV;
ret = tracing_open_generic(inode, filp);
if (ret < 0)
put_system(system);
return ret;
}
static int subsystem_release(struct inode *inode, struct file *file)
{
struct event_subsystem *system = inode->i_private;
put_system(system);
return 0;
}
static ssize_t static ssize_t
subsystem_filter_read(struct file *filp, char __user *ubuf, size_t cnt, subsystem_filter_read(struct file *filp, char __user *ubuf, size_t cnt,
loff_t *ppos) loff_t *ppos)
...@@ -963,10 +1033,11 @@ static const struct file_operations ftrace_event_filter_fops = { ...@@ -963,10 +1033,11 @@ static const struct file_operations ftrace_event_filter_fops = {
}; };
static const struct file_operations ftrace_subsystem_filter_fops = { static const struct file_operations ftrace_subsystem_filter_fops = {
.open = tracing_open_generic, .open = subsystem_open,
.read = subsystem_filter_read, .read = subsystem_filter_read,
.write = subsystem_filter_write, .write = subsystem_filter_write,
.llseek = default_llseek, .llseek = default_llseek,
.release = subsystem_release,
}; };
static const struct file_operations ftrace_system_enable_fops = { static const struct file_operations ftrace_system_enable_fops = {
...@@ -1002,8 +1073,6 @@ static struct dentry *event_trace_events_dir(void) ...@@ -1002,8 +1073,6 @@ static struct dentry *event_trace_events_dir(void)
return d_events; return d_events;
} }
static LIST_HEAD(event_subsystems);
static struct dentry * static struct dentry *
event_subsystem_dir(const char *name, struct dentry *d_events) event_subsystem_dir(const char *name, struct dentry *d_events)
{ {
...@@ -1013,6 +1082,7 @@ event_subsystem_dir(const char *name, struct dentry *d_events) ...@@ -1013,6 +1082,7 @@ event_subsystem_dir(const char *name, struct dentry *d_events)
/* First see if we did not already create this dir */ /* First see if we did not already create this dir */
list_for_each_entry(system, &event_subsystems, list) { list_for_each_entry(system, &event_subsystems, list) {
if (strcmp(system->name, name) == 0) { if (strcmp(system->name, name) == 0) {
__get_system(system);
system->nr_events++; system->nr_events++;
return system->entry; return system->entry;
} }
...@@ -1035,6 +1105,7 @@ event_subsystem_dir(const char *name, struct dentry *d_events) ...@@ -1035,6 +1105,7 @@ event_subsystem_dir(const char *name, struct dentry *d_events)
} }
system->nr_events = 1; system->nr_events = 1;
system->ref_count = 1;
system->name = kstrdup(name, GFP_KERNEL); system->name = kstrdup(name, GFP_KERNEL);
if (!system->name) { if (!system->name) {
debugfs_remove(system->entry); debugfs_remove(system->entry);
...@@ -1184,16 +1255,9 @@ static void remove_subsystem_dir(const char *name) ...@@ -1184,16 +1255,9 @@ static void remove_subsystem_dir(const char *name)
list_for_each_entry(system, &event_subsystems, list) { list_for_each_entry(system, &event_subsystems, list) {
if (strcmp(system->name, name) == 0) { if (strcmp(system->name, name) == 0) {
if (!--system->nr_events) { if (!--system->nr_events) {
struct event_filter *filter = system->filter;
debugfs_remove_recursive(system->entry); debugfs_remove_recursive(system->entry);
list_del(&system->list); list_del(&system->list);
if (filter) { __put_system(system);
kfree(filter->filter_string);
kfree(filter);
}
kfree(system->name);
kfree(system);
} }
break; break;
} }
......
...@@ -1886,6 +1886,12 @@ int apply_subsystem_event_filter(struct event_subsystem *system, ...@@ -1886,6 +1886,12 @@ int apply_subsystem_event_filter(struct event_subsystem *system,
mutex_lock(&event_mutex); mutex_lock(&event_mutex);
/* Make sure the system still has events */
if (!system->nr_events) {
err = -ENODEV;
goto out_unlock;
}
if (!strcmp(strstrip(filter_string), "0")) { if (!strcmp(strstrip(filter_string), "0")) {
filter_free_subsystem_preds(system); filter_free_subsystem_preds(system);
remove_filter_string(system->filter); remove_filter_string(system->filter);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment