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/20240426073410.17154-1-Tze-nan.Wu@mediatek.com/ Link: https://lore.kernel.org/linux-trace-kernel/20240502090315.448cba46@gandalf.local.home
Showing the changes to kernel (1 of 3 files). Show all
Unified · Split
| @@ -2552,6 +2552,14 @@ static int event_callback(const char *name, umode_t *mode, void **data, · 2551 unmodified lines | ||
| 2552 | 2552 | return 0; |
| 2553 | 2553 | } |
| 2554 | 2554 | |
| 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 | + | |
| 2555 | 2563 | static int |
| 2556 | 2564 | event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file) |
| 2557 | 2565 | { |
| @@ -2566,6 +2574,7 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file) · 8 unmodified lines | ||
| 2566 | 2574 | { |
| 2567 | 2575 | .name = "enable", |
| 2568 | 2576 | .callback = event_callback, |
| 2577 | + .release = event_release, | |
| 2569 | 2578 | }, |
| 2570 | 2579 | { |
| 2571 | 2580 | .name = "filter", |
| @@ -2634,6 +2643,9 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file) · 62 unmodified lines | ||
| 2634 | 2643 | return ret; |
| 2635 | 2644 | } |
| 2636 | 2645 | |
| 2646 | + /* Gets decremented on freeing of the "enable" file */ | |
| 2647 | + event_file_get(file); | |
| 2648 | + | |
| 2637 | 2649 | return 0; |
| 2638 | 2650 | } |
| 2639 | 2651 | |