Skip to contentSkip to conversation

tracing: Fixes for v7.3

PATCH New Steven Rostedt 1 participant 8 messages Aug 27

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

cover tracing: Fixes for v7.3

tracing fixes for v7.3:

- Fix error output of boot instance creation failure

Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead.

While at it, print the error code of the failure.

- Fix use-after-free for same named historgrams

Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel.

Free the duplicate variables along with the duplicated histogram data.

- Check return value of kthread_run() in event self test

The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned.

- Fix race between reading trace_pipe and updating subbuffer size

If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size.

- Fix eventfs_inode on failure path in creation of the events directory

In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false.

Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation.

- Check return value of kthread_run() in ftrace direct sample code

The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed.

git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git
trace/fixes

Head SHA1: 6727b7618f49401acf373fa3ec5712e2ec52e5cf

Deepanshu Kartikey (2):
tracing: Fix use-after-free in trace_pipe read on sub-buffer order change
eventfs: Initialize ei->children and ei->list in init_ei()

Haotian Zhang (2):
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify

Hui Su (2):
tracing: Fix use-after-free with same-name named triggers
tracing: Fix crash passing ERR_PTR to kthread_stop()

Vincent Donnefort (1):
tracing: Fix logged instance name on creation failure

----

 fs/tracefs/event_inode.c                    |  7 ++-----
 kernel/trace/trace.c                        |  6 +++++-
 kernel/trace/trace_events.c                 |  2 ++
 kernel/trace/trace_events_hist.c            |  4 +++-
 samples/ftrace/ftrace-direct-modify.c       | 12 +++++++++---
 samples/ftrace/ftrace-direct-multi-modify.c | 12 +++++++++---
 6 files changed, 30 insertions(+), 13 deletions(-)
#1 lore raw

PATCH 1/7 tracing: Fix logged instance name on creation failure

From: Vincent Donnefort <[email protected]>

When boot instance creation fails, the kernel incorrectly logs "(null)" as the instance name because strsep() consumes curr_str entirely during parsing.

Print the properly parsed name variable instead. And while at it log the error code.

---
 kernel/trace/trace.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
kernel/trace/trace.c +2 -1
index 89dc1c0ebb90..740c5f358b75 100644
@@ -9726,7 +9726,8 @@ __init static void enable_instances(void)
 
 		tr = trace_array_create_systems(name, NULL, addr, size);
 		if (IS_ERR(tr)) {
-			pr_warn("Tracing: Failed to create instance buffer %s\n", curr_str);
+			pr_warn("Tracing: Failed to create instance buffer '%s' (%ld)\n", name,
+				PTR_ERR(tr));
 			continue;
 		}
 
Signature · 1 line
2.53.0
#2 lore raw

PATCH 2/7 tracing: Fix use-after-free with same-name named triggers

In reply to Steven Rostedt

From: Hui Su <[email protected]>

When two hist triggers on different events are registered with the same name=, the second one reuses the first as named_data. Both are added to tr->hist_vars by save_hist_vars() during event_hist_trigger_parse(), because save_hist_vars() is called before event_trigger_register() while the named reuse is only detected later, in hist_register_trigger().

In the named-data branch hist_register_trigger() then frees the second histogram's hist_data via destroy_hist_data(), but never removes its tr->hist_vars list entry, leaving a dangling pointer and leaking the trace_array reference it holds.

A later hist trigger that references a variable makes find_var_file() walk tr->hist_vars and dereference the freed hist_data. The bug is reproducible from userspace by writing three hist triggers to tracefs:

cd /sys/kernel/tracing
echo 'hist:keys=common_pid:x=common_pid:name=mh' > events/sched/sched_switch/trigger echo 'hist:keys=common_pid:x=common_pid:name=mh' > events/sched/sched_process_fork/trigger echo 'hist:keys=common_pid:vals=$x' > events/sched/sched_process_exit/trigger

The third write panics the kernel:

  BUG: KASAN: slab-use-after-free in find_var_file.part.0+0x272/0x290
  Read of size 8 at addr ffff888001f8a0e0 by task sh/1
  CPU: 1 UID: 0 PID: 1 Comm: sh Tainted: G      D          N
  Call Trace:
    find_var_file.part.0
    find_event_var
    parse_atom
    parse_expr
    __create_val_field
    event_hist_trigger_parse
    trigger_process_regex
    event_trigger_write
    vfs_write
    ksys_write
    do_syscall_64
    entry_SYSCALL_64_after_hwframe
  Allocated by task 1:
    event_hist_trigger_parse
  Freed by task 1:
    hist_register_trigger+0x618/0xa30
    event_hist_trigger_parse
  The buggy address belongs to freed 2048-byte region
  Oops: general protection fault ... RIP: find_var_file.part.0
  Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b

Fix by removing the hist_data from tr->hist_vars and releasing the trace_array reference in the named-data branch of hist_register_trigger() before freeing the hist_data.

Fixes 6f86bdeab633 ("tracing: Fix bad hist from corrupting named_triggers list")
---
 kernel/trace/trace_events_hist.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
kernel/trace/trace_events_hist.c +3 -1
index 893bd8b0e48a..963e0d6b61fd 100644
@@ -6661,8 +6661,10 @@ static int hist_register_trigger(char *glob,
 		tracing_set_filter_buffering(file->tr, true);
 	}
 
-	if (named_data)
+	if (named_data) {
+		remove_hist_vars(hist_data);
 		destroy_hist_data(hist_data);
+	}
  out:
 	return ret;
 }
Signature · 1 line
2.53.0
#3 lore raw

PATCH 3/7 tracing: Fix crash passing ERR_PTR to kthread_stop()

In reply to Steven Rostedt

From: Hui Su <[email protected]>

event_test_stuff() calls kthread_run() and unconditionally passes the returned task_struct pointer to kthread_stop(). kthread_run() returns an error pointer such as ERR_PTR(-ENOMEM) when kthread creation fails, for example under memory pressure during the boot-time event self-test. kthread_stop() then dereferences the invalid pointer, crashing the kernel.

Check the result of kthread_run() before passing it to kthread_stop(). Use WARN_ON() so that a failure to create the self-test thread does not go unnoticed, matching the ring-buffer self-test fix in commit 91542863abad ("ring-buffer: Fix crash passing ERR_PTR to kthread_stop()").

Fixes e6187007d6c3 ("tracing/events: add startup tests for events")
---
 kernel/trace/trace_events.c | 2 ++
 1 file changed, 2 insertions(+)
kernel/trace/trace_events.c +2 -0
index 9f8f2d02276c..1d39eaf6a0f7 100644
@@ -5019,6 +5019,8 @@ static __init void event_test_stuff(void)
 	struct task_struct *test_thread;
 
 	test_thread = kthread_run(event_test_thread, NULL, "test-events");
+	if (WARN_ON(IS_ERR(test_thread)))
+		return;
 	msleep(1);
 	kthread_stop(test_thread);
 }
Signature · 1 line
2.53.0
#4 lore raw

PATCH 4/7 tracing: Fix use-after-free in trace_pipe read on sub-buffer order change

In reply to Steven Rostedt

From: Deepanshu Kartikey <[email protected]>

Writing to buffer_subbuf_size_kb calls ring_buffer_subbuf_order_set(), which frees every sub-buffer of the ring buffer, including the reader page, and replaces them with newly allocated ones.

Readers of trace_pipe hold pointers into those pages. ring_buffer_peek() looks up an event under cpu_buffer->reader_lock but returns the event pointer after dropping the lock, and peek_next_entry() then calls ring_buffer_event_length() and ring_buffer_event_data() on it. If the sub-buffer order is changed in that window, the reader dereferences freed memory:

  BUG: KASAN: use-after-free in ring_buffer_peek+0x3e0/0x430
  Read of size 1 at addr ffff88802a4cf010 by task syz-executor989/6002

Freed by:

   free_buffer_page kernel/trace/ring_buffer.c:398 [inline]
   ring_buffer_subbuf_order_set+0x1325/0x18e0 kernel/trace/ring_buffer.c:7444
   buffer_subbuf_size_write+0x182/0x280 kernel/trace/trace.c:8221

Take trace_access_lock(RING_BUFFER_ALL_CPUS) around the order change. This is the lock trace_pipe readers already hold across their entire peek-and-print loop, so the swap can no longer race with a reader that is dereferencing a peeked event.

---
 kernel/trace/trace.c | 3 +++
 1 file changed, 3 insertions(+)
kernel/trace/trace.c +3 -0
index 740c5f358b75..60c87977f1e3 100644
@@ -8214,6 +8214,8 @@ buffer_subbuf_size_write(struct file *filp, const char __user *ubuf,
 	/* Do not allow tracing while changing the order of the ring buffer */
 	tracing_stop_tr(tr);
 
+	trace_access_lock(RING_BUFFER_ALL_CPUS);
+
 	old_order = ring_buffer_subbuf_order_get(tr->array_buffer.buffer);
 	if (old_order == order)
 		goto out;
@@ -8253,6 +8255,7 @@ buffer_subbuf_size_write(struct file *filp, const char __user *ubuf,
 #endif
 	(*ppos)++;
  out:
+	trace_access_unlock(RING_BUFFER_ALL_CPUS);
 	if (ret)
 		cnt = ret;
 	tracing_start_tr(tr);
Signature · 1 line
2.53.0
#5 lore raw

PATCH 5/7 eventfs: Initialize ei->children and ei->list in init_ei()

In reply to Steven Rostedt

From: Deepanshu Kartikey <[email protected]>

eventfs_create_dir() allocates the eventfs_inode and initializes it with init_ei(). But this does not initialize the eventfs_inode list_heads. If the eventfs_create_dir() fails due to memory pressure, it will call free_ei() before it initialized the lists, and that checks to make sure the eventfs_inode has no children. But because the list wasn't initialized, it will give a false warning.

Fix it by moving the list initialization into init_ei().

Fixes 5790b1fb3d67 ("eventfs: Remove eventfs_file and just use eventfs_inode")
Reported-by [email protected]
[ Rewrote change log ]
---
 fs/tracefs/event_inode.c | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)
fs/tracefs/event_inode.c +2 -5
index 604ba3e841d2..6e3513b13cfa 100644
@@ -438,6 +438,8 @@ static inline struct eventfs_inode *init_ei(struct eventfs_inode *ei, const char
 	if (!ei->name)
 		return NULL;
 	kref_init(&ei->kref);
+	INIT_LIST_HEAD(&ei->children);
+	INIT_LIST_HEAD(&ei->list);
 	return ei;
 }
 
@@ -729,8 +731,6 @@ struct eventfs_inode *eventfs_create_dir(const char *name, struct eventfs_inode
 	ei->entries = entries;
 	ei->nr_entries = size;
 	ei->data = data;
-	INIT_LIST_HEAD(&ei->children);
-	INIT_LIST_HEAD(&ei->list);
 
 	scoped_guard(mutex, &eventfs_mutex) {
 		if (!parent->is_freed)
@@ -802,9 +802,6 @@ struct eventfs_inode *eventfs_create_events_dir(const char *name, struct dentry
 	ei->attr.uid = uid;
 	ei->attr.gid = gid;
 
-	INIT_LIST_HEAD(&ei->children);
-	INIT_LIST_HEAD(&ei->list);
-
 	ti = get_tracefs(inode);
 	ti->flags |= TRACEFS_EVENT_INODE;
 	ti->private = ei;
Signature · 1 line
2.53.0
#6 lore raw

PATCH 6/7 samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify

In reply to Steven Rostedt

From: Haotian Zhang <[email protected]>

ftrace_direct_init() assigns kthread_run()'s return value to simple_tsk without an IS_ERR() check. When kthread_run() fails it returns ERR_PTR(-ENOMEM), but init still returns 0, so the module loads with simple_tsk holding an error pointer. On unload, ftrace_direct_exit() then passes that ERR_PTR to kthread_stop(), leading to a null-pointer-dereference.

Check the return value of kthread_run() with IS_ERR(); on failure, unregister the ftrace direct call and propagate the error code.

Fixes ae0cc3b7e7f5 ("ftrace/samples: Add a sample module that implements modify_ftrace_direct()")
---
 samples/ftrace/ftrace-direct-modify.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)
samples/ftrace/ftrace-direct-modify.c +9 -3
index 1ba1927b548e..164d9dd6fd92 100644
@@ -320,9 +320,15 @@ static int __init ftrace_direct_init(void)
 	ftrace_set_filter_ip(&direct, (unsigned long) my_ip, 0, 0);
 	ret = register_ftrace_direct(&direct, my_tramp);
 
-	if (!ret)
-		simple_tsk = kthread_run(simple_thread, NULL, "event-sample-fn");
-	return ret;
+	if (ret)
+		return ret;
+	simple_tsk = kthread_run(simple_thread, NULL, "event-sample-fn");
+	if (IS_ERR(simple_tsk)) {
+		unregister_ftrace_direct(&direct, my_tramp, true);
+		return PTR_ERR(simple_tsk);
+	}
+
+	return 0;
 }
 
 static void __exit ftrace_direct_exit(void)
Signature · 1 line
2.53.0
#7 lore raw

PATCH 7/7 samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify

In reply to Steven Rostedt

From: Haotian Zhang <[email protected]>

ftrace_direct_multi_init() assigns kthread_run()'s return value to simple_tsk without an IS_ERR() check. When kthread_run() fails it returns ERR_PTR(-ENOMEM), but init still returns 0, so the module loads with simple_tsk holding an error pointer. On unload, ftrace_direct_multi_exit() then passes that ERR_PTR to kthread_stop(), leading to a null-pointer-dereference.

Check the return value of kthread_run() with IS_ERR(); on failure, unregister the ftrace direct call and propagate the error code.

Fixes e1067a07cfbc ("ftrace/samples: Add module to test multi direct modify interface")
---
 samples/ftrace/ftrace-direct-multi-modify.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)
samples/ftrace/ftrace-direct-multi-modify.c +9 -3
index 7a7822dfeb50..b03766c6217b 100644
@@ -364,9 +364,15 @@ static int __init ftrace_direct_multi_init(void)
 
 	ret = register_ftrace_direct(&direct, my_tramp);
 
-	if (!ret)
-		simple_tsk = kthread_run(simple_thread, NULL, "event-sample-fn");
-	return ret;
+	if (ret)
+		return ret;
+	simple_tsk = kthread_run(simple_thread, NULL, "event-sample-fn");
+	if (IS_ERR(simple_tsk)) {
+		unregister_ftrace_direct(&direct, my_tramp, true);
+		return PTR_ERR(simple_tsk);
+	}
+
+	return 0;
 }
 
 static void __exit ftrace_direct_multi_exit(void)
Signature · 1 line
2.53.0