Skip to content

Commit b63db58

Browse files
committed
eventfs/tracing: Add callback for release of an eventfs_inode
Synthetic events create and destroy tracefs files when they are created and removed. The tracing subsystem has its own file descriptor representing the state of the events attached to the tracefs files. There's a race between the eventfs files and this file descriptor of the tracing system where the following can cause an issue: With two scripts 'A' and 'B' doing: Script 'A': echo "hello int aaa" > /sys/kernel/tracing/synthetic_events while : do echo 0 > /sys/kernel/tracing/events/synthetic/hello/enable done Script 'B': echo > /sys/kernel/tracing/synthetic_events Script 'A' creates a synthetic event "hello" and then just writes zero into its enable file. Script 'B' removes all synthetic events (including the newly created "hello" event). What happens is that the opening of the "enable" file has: { struct trace_event_file *file = inode->i_private; int ret; ret = tracing_check_open_get_tr(file->tr); [..] But deleting the events frees the "file" descriptor, and a "use after free" happens with the dereference at "file->tr". The file descriptor does have a reference counter, but there needs to be a way to decrement it from the eventfs when the eventfs_inode is removed that represents this file descriptor. Add an optional "release" callback to the eventfs_entry array structure, that gets called when the eventfs file is about to be removed. This allows for the creating on the eventfs file to increment the tracing file descriptor ref counter. When the eventfs file is deleted, it can call the release function that will call the put function for the tracing file descriptor. This will protect the tracing file from being freed while a eventfs file that references it is being opened. Link: https://lore.kernel.org/linux-trace-kernel/[email protected]/ Link: https://lore.kernel.org/linux-trace-kernel/[email protected] Cc: [email protected] Cc: Masami Hiramatsu <[email protected]> Cc: Mathieu Desnoyers <[email protected]> Fixes: 5790b1f ("eventfs: Remove eventfs_file and just use eventfs_inode") Reported-by: Tze-nan wu <[email protected]> Tested-by: Tze-nan Wu (吳澤南) <[email protected]> Signed-off-by: Steven Rostedt (Google) <[email protected]>
1 parent e67572c commit b63db58

File tree

3 files changed

+36
-2
lines changed

3 files changed

+36
-2
lines changed

fs/tracefs/event_inode.c

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,17 @@ enum {
8484
static void release_ei(struct kref *ref)
8585
{
8686
struct eventfs_inode *ei = container_of(ref, struct eventfs_inode, kref);
87+
const struct eventfs_entry *entry;
8788
struct eventfs_root_inode *rei;
8889

8990
WARN_ON_ONCE(!ei->is_freed);
9091

92+
for (int i = 0; i < ei->nr_entries; i++) {
93+
entry = &ei->entries[i];
94+
if (entry->release)
95+
entry->release(entry->name, ei->data);
96+
}
97+
9198
kfree(ei->entry_attrs);
9299
kfree_const(ei->name);
93100
if (ei->is_events) {
@@ -112,6 +119,18 @@ static inline void free_ei(struct eventfs_inode *ei)
112119
}
113120
}
114121

122+
/*
123+
* Called when creation of an ei fails, do not call release() functions.
124+
*/
125+
static inline void cleanup_ei(struct eventfs_inode *ei)
126+
{
127+
if (ei) {
128+
/* Set nr_entries to 0 to prevent release() function being called */
129+
ei->nr_entries = 0;
130+
free_ei(ei);
131+
}
132+
}
133+
115134
static inline struct eventfs_inode *get_ei(struct eventfs_inode *ei)
116135
{
117136
if (ei)
@@ -734,7 +753,7 @@ struct eventfs_inode *eventfs_create_dir(const char *name, struct eventfs_inode
734753

735754
/* Was the parent freed? */
736755
if (list_empty(&ei->list)) {
737-
free_ei(ei);
756+
cleanup_ei(ei);
738757
ei = NULL;
739758
}
740759
return ei;
@@ -835,7 +854,7 @@ struct eventfs_inode *eventfs_create_events_dir(const char *name, struct dentry
835854
return ei;
836855

837856
fail:
838-
free_ei(ei);
857+
cleanup_ei(ei);
839858
tracefs_failed_creating(dentry);
840859
return ERR_PTR(-ENOMEM);
841860
}

include/linux/tracefs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ struct eventfs_file;
6262
typedef int (*eventfs_callback)(const char *name, umode_t *mode, void **data,
6363
const struct file_operations **fops);
6464

65+
typedef void (*eventfs_release)(const char *name, void *data);
66+
6567
/**
6668
* struct eventfs_entry - dynamically created eventfs file call back handler
6769
* @name: Then name of the dynamic file in an eventfs directory
@@ -72,6 +74,7 @@ typedef int (*eventfs_callback)(const char *name, umode_t *mode, void **data,
7274
struct eventfs_entry {
7375
const char *name;
7476
eventfs_callback callback;
77+
eventfs_release release;
7578
};
7679

7780
struct eventfs_inode;

kernel/trace/trace_events.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2552,6 +2552,14 @@ static int event_callback(const char *name, umode_t *mode, void **data,
25522552
return 0;
25532553
}
25542554

2555+
/* The file is incremented on creation and freeing the enable file decrements it */
2556+
static void event_release(const char *name, void *data)
2557+
{
2558+
struct trace_event_file *file = data;
2559+
2560+
event_file_put(file);
2561+
}
2562+
25552563
static int
25562564
event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
25572565
{
@@ -2566,6 +2574,7 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
25662574
{
25672575
.name = "enable",
25682576
.callback = event_callback,
2577+
.release = event_release,
25692578
},
25702579
{
25712580
.name = "filter",
@@ -2634,6 +2643,9 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
26342643
return ret;
26352644
}
26362645

2646+
/* Gets decremented on freeing of the "enable" file */
2647+
event_file_get(file);
2648+
26372649
return 0;
26382650
}
26392651

0 commit comments

Comments
 (0)