Skip to contentSkip to conversation

tracing: Fixes for v7.3

GIT PULL Merged Steven Rostedt 2 participants 2 messages Sep 13

Subsystems (7)
lore
Share & tools
lore

Steven Rostedt

author maintainer
#0 lore raw

Linus,

tracing fixes for 7.3:

- Don't destroy user event fields when removal fails

User event fields are destroyed before the event is removed from visibility. But that can fail leaving the still visible event with no fields. Move the destroying of the fields to after the event is successfully removed from visibility.

- Initialize function graph state is fork before calling copy_exec_state()

For non-CLONE_VM forks, copy_exec_state() allocates a new task_exec_state. If that allocation fails, ftrace_graph_exit_task() will free the tasks ret_stack pointer. Since that pointer is still using the parent's ret_stack, it mistakenly frees the parent's pointer too.

Call ftrace_graph_init() on the task first which will NULL out the new tasks's ret_stack and if the copy fails, it will not free anything.

- Remove FGRAPH_MAX_INDEX

The macro FGRAPH_MAX_INDEX was added but never used. Remove it.

- Save ent_size in function graph printing of nested functions

The function graph tracer needs to look at the next event to see if the next event is the return of the current function entry. If it is, it prints a single line:

    ktime_get();

Otherwise it prints it like a nested function:

    tick_nohz_irq_exit() {
      ktime_get();
      kcpustat_irq_exit();
    }

In order to look at the next event, it must save the current event so that it has the information to print from it. It saves the event in the iterator descriptor called "ent". What it doesn't save is the ent_size of the event which is now used to know if the function graph arguments are to be printed. The peek doesn't save the size so the size used happens to be that of the size of the last event that was seen.

Save the entry event size in the iterator descriptor so that the correct size is used.

- Fix several errors with freeing data in the histogram code

The histogram code had a lot of leaked or or incorrect accounting when failures happen. Correct them.

- Fix histogram regression of .percent and .graph modifiers

Up until 6.3 histogram values could have "percent" or "graph" modifiers that changed how they were printed. But a change that added restricting histograms values from being strings, stack traces and other modifiers inadvertently prevented them from using the percent and graph modifiers, which were legal use cases for values.

Put back the percent and graph modifiers.

- Fix various typos in the comments

- Set the trace_clock before initializing a histogram with clock argument

The histogram API allows the user to specific which trace clock to use via a "clock=" string. The histogram is set up first before the clock is checked. If the passed in clock is not valid, it exits without fully fixing up the histogram leaving it on the list and a use-after-free can trigger.

Update the clock argument first and if it fails then exit gracefully before the histogram trigger is placed on any lists.

- Restore :mod: trailer after parsing in ftrace_set_clr_event

The function ftrace_set_clr_event() modifies the parse string and needs to put it back to what was passed in. It searches for ":mod:" via a strsep() but fails to put back the first ':' in the string.

Add back the ':' in the passed in string.

- Take trace_array reference when opening a tracer options file

The options files are dynamically created and some tracers add their own options. When a tracer adds their own list of options, the trace_array holding them has an array to hold the list of options for each tracer. This array increases in size via a krealloc(), and the new entry gets a newly allocated array to hold the options of the new tracer being added.

The element in each entry of the tracer's option array holds a pointer back to the trace_array, a pointer to the tracer it is associated to, a pointer to the flags of the option.

The issue is that these arrays are freed when the trace_array is freed when its instance it represents is removed from the instances directory. There's a race that an open of one of these options files can happen when the instance is being removed.

Add a new helper function to be called by the open function of the options file to iterate all existing trace_arrays under a lock and find the one that has the given option element in one of it's tracer arrays. If found, then update the associated trace_array's reference counter to keep it from being freed. If not found, have the open call return -ENODEV.

- Disable interrupts when acquiring the lock in rb_wake_up_waiters()

The function rb_wake_up_waiters() assumes it will be called in interrupt context and does not disable irqs when taking cpu_buffer->reader_lock, which can be called in hard interrupt context. The issue is in PREEMPT_RT, this function is called in thread context leaving this lock open to a deadlock.

Take the lock with interrupts disabled.

- Use rcu_assign_pointer() for tmp_ops filter hash

The tmp_ops used in update_ftrace_direct_mod() assigns its filter_hash field directly, but that field is annotated as __rcu and sparse complains. Assign it with rcu_assign_pointer()

- Fix use-after-free in enable_trigger_private_data_free()

The trace_event_call is accessed through the event_trigger_data's trace_event_file pointer to put the trace_event_call on freeing. The issue is that the trace_event_file data may have been freed already causing a use-after-free. Add a field to the event_trigger_data that points directly to the trace_event_call so that it can decrement its reference directly without needing to go through the trace_event_file.

- Fix accounting of buffer data remote headers

trace_buffer_desc_size() and trace_remote_alloc_buffer() undercount the number of pages is needed for the asked for size as it doesn't take into account the meta data on each page. Add a helper function to do the calculation properly and use that in these functions.

- Catch nr_page_va overflow in ring_buffer_desc sizing

The number of pages per remote ring buffer is capped by ring_buffer_desc::nr_page_va (32 bits). A buffer_size large enough to overflow that field would silently allocate a descriptor smaller than what was asked for.

- Do not resize the subbuf order if any per_cpu buffer is disabled

The mmapping of ring buffers disables resizing the subbuffers, but it is done per-cpu whereas the subbuf size change is done for all the per_cpu buffers under the buffer->mutex. It could change the size of some while the mapping is happening on others. Have the resize of the subbuf order check all the per_cpu buffers under the lock to see if any of them is disabled before starting and causing an inconsistency between buffers that are being mapped.

Please pull the latest trace-v7.3-rc2 tree, which can be found at:

git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git
trace-v7.3-rc2

Tag SHA1: 28d78ea955e8311d11077a6e760e840df869ee20 Head SHA1: d860c67c051685abb0460b593b193f0f45f4fa92

Ali Ahmet Memiş (1):
tracing: Don't dereference trace_event_file in deferred trigger free

David Carlier (1):
ring-buffer: Check resize_disabled before publishing the new subbuf order

Donggeun Yoo (12):
fgraph: Remove unused FGRAPH_MAX_INDEX
function_graph: Use the saved entry's size when reprinting it
tracing: Free histogram var refs regardless of how often they are referenced
tracing: Free histogram the var ref when its initialization fails
tracing: Free histogram the field rejected for a bad modifier
tracing: Keep the entry count when the histogram stats allocation fails
tracing: Let histogram values keep the percent and graph modifiers
tracing: Set the trace clock before registering the histogram trigger
tracing: Take the reference before publishing the named histogram trigger
tracing: Undo the registration when enabling the histogram trigger fails
tracing: Fix memory corruption from the histogram stacktrace modifier
tracing: Fix memory corruption from a "STACKTRACE" histogram key

Hemanth Selam (2):
tracing: Fix typo "availabe" in comment
tracing: Fix typo "preceeded" in comment

Henry Martin (1):
tracing/user_events: Don't destroy fields when event removal fails

Jérémy Jean (1):
ftrace: fork: Initialize function graph state before copy_exec_state()

Karl Mehltretter (1):
tracing: Fix ring_buffer_read_page_size() kernel-doc

Leon Hwang (1):
ftrace: Use rcu_assign_pointer() for tmp_ops filter hash

Sebastian Andrzej Siewior (1):
ring-buffer: Acquire the lock with irqsave in rb_wake_up_waiters()

Steven Rostedt (1):
tracing: Take trace_array reference when opening a tracer options file

Thomas Weißschuh (1):
tracing: Restore :mod: trailer after parsing in ftrace_set_clr_event()

Vincent Donnefort (2):
tracing/remotes: Account for ring buffer page header in size calculation tracing/remotes: Catch nr_page_va overflow in ring_buffer_desc sizing

----

 include/linux/ring_buffer.h          | 19 +++++++-
 include/trace/events/timer.h         |  2 +-
 kernel/fork.c                        |  7 ++-
 kernel/trace/fgraph.c                |  3 --
 kernel/trace/ftrace.c                |  2 +-
 kernel/trace/rethook.c               |  2 +-
 kernel/trace/ring_buffer.c           | 23 ++++++----
 kernel/trace/trace.c                 | 46 ++++++++++++++++++-
 kernel/trace/trace.h                 |  2 +
 kernel/trace/trace_events.c          |  2 +
 kernel/trace/trace_events_hist.c     | 86 +++++++++++++++++++-----------------
 kernel/trace/trace_events_trigger.c  |  4 +-
 kernel/trace/trace_events_user.c     | 26 ++++++++---
 kernel/trace/trace_functions_graph.c |  3 ++
 kernel/trace/trace_remote.c          |  6 ++-
 15 files changed, 165 insertions(+), 68 deletions(-)

---------------------------

include/linux/ring_buffer.h +17 -2
index afc7daa6ee7d..eac3e9080c3c 100644
@@ -3,8 +3,9 @@
 #define _LINUX_RING_BUFFER_H
 
 #include <linux/mm.h>
-#include <linux/seq_file.h>
 #include <linux/poll.h>
+#include <linux/ring_buffer_types.h>
+#include <linux/seq_file.h>
 
 #include <uapi/linux/trace_mmap.h>
 
@@ -279,11 +280,25 @@ static inline struct ring_buffer_desc *__first_ring_buffer_desc(struct trace_buf
 	return (struct ring_buffer_desc *)(&desc->__data[0]);
 }
 
+/*
+ * Returns the number of pages for a ring_buffer_desc. The caller must ensure it
+ * does not overflow ring_buffer_desc::nr_page_va.
+ */
+static inline unsigned long __calc_nr_pages_ring_buffer_desc(size_t size)
+{
+	/* Takes into account the reader page */
+	return max(DIV_ROUND_UP(size, PAGE_SIZE - BUF_PAGE_HDR_SIZE), 2UL) + 1;
+}
+
 static inline size_t trace_buffer_desc_size(size_t buffer_size, unsigned int nr_cpus)
 {
-	unsigned int nr_pages = max(DIV_ROUND_UP(buffer_size, PAGE_SIZE), 2UL) + 1;
+	unsigned long nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size);
 	struct ring_buffer_desc *rbdesc;
 
+	/* Capped by ring_buffer_desc::nr_page_va */
+	if (nr_pages > UINT_MAX)
+		return SIZE_MAX;
+
 	return size_add(offsetof(struct trace_buffer_desc, __data),
 			size_mul(nr_cpus, struct_size(rbdesc, page_va, nr_pages)));
 }
include/trace/events/timer.h +1 -1
index ca82fd62dc30..3aa0608c6361 100644
@@ -302,7 +302,7 @@ DECLARE_EVENT_CLASS(hrtimer_class,
  * hrtimer_start_expired - Invoked when a expired timer was started
  * @hrtimer:	pointer to struct hrtimer
  *
- * Preceeded by a hrtimer_start tracepoint.
+ * Preceded by a hrtimer_start tracepoint.
  */
 DEFINE_EVENT(hrtimer_class, hrtimer_start_expired,
 
kernel/fork.c +5 -2
index 416758c8a3d4..a5934a317634 100644
@@ -2133,6 +2133,11 @@ __latent_entropy struct task_struct *copy_process(
 	p = dup_task_struct(current, node);
 	if (!p)
 		goto fork_out;
+	/*
+	 * Must run before the first fallible op, so error paths never
+	 * free the parent's ret_stack.
+	 */
+	ftrace_graph_init_task(p);
 	retval = copy_exec_state(clone_flags, p);
 	if (retval)
 		goto bad_fork_free;
@@ -2159,8 +2164,6 @@ __latent_entropy struct task_struct *copy_process(
 	 */
 	p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? args->child_tid : NULL;
 
-	ftrace_graph_init_task(p);
-
 	rt_mutex_init_task(p);
 	raw_spin_lock_init(&p->blocked_lock);
 
kernel/trace/fgraph.c +0 -3
index 40d373d65f9b..ed455b53513b 100644
@@ -143,9 +143,6 @@ enum {
 #define FGRAPH_DATA_INDEX_MASK	GENMASK(FGRAPH_DATA_INDEX_BITS - 1, 0)
 #define FGRAPH_DATA_INDEX_SHIFT	(FGRAPH_DATA_SHIFT + FGRAPH_DATA_BITS)
 
-#define FGRAPH_MAX_INDEX	\
-	((FGRAPH_INDEX_SIZE << FGRAPH_DATA_BITS) + FGRAPH_RET_INDEX)
-
 #define FGRAPH_ARRAY_SIZE	FGRAPH_INDEX_BITS
 
 /*
kernel/trace/ftrace.c +1 -1
index 53d5db60bfa5..673a54fdf392 100644
@@ -6675,7 +6675,7 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b
 
 	/* Enable the tmp_ops to have the same functions as the hash object. */
 	ftrace_ops_init(&tmp_ops);
-	tmp_ops.func_hash->filter_hash = hash;
+	rcu_assign_pointer(tmp_ops.func_hash->filter_hash, hash);
 
 	err = register_ftrace_function_nolock(&tmp_ops);
 	if (err)
kernel/trace/rethook.c +1 -1
index 5a8bdf88999a..87a27f3aa4a6 100644
@@ -171,7 +171,7 @@ struct rethook_node *rethook_try_get(struct rethook *rh)
 	 * This expects the caller will set up a rethook on a function entry.
 	 * When the function returns, the rethook will eventually be reclaimed
 	 * or released in the rethook_recycle() with call_rcu().
-	 * This means the caller must be run in the RCU-availabe context.
+	 * This means the caller must be run in the RCU-available context.
 	 */
 	if (unlikely(!rcu_is_watching()))
 		return NULL;
kernel/trace/ring_buffer.c +15 -8
index 9c03a555a6ba..04bb94c29f58 100644
@@ -904,14 +904,13 @@ static void rb_wake_up_waiters(struct irq_work *work)
 		struct ring_buffer_per_cpu *cpu_buffer =
 			container_of(rbwork, struct ring_buffer_per_cpu, irq_work);
 
-		/* Called from interrupt context */
-		raw_spin_lock(&cpu_buffer->reader_lock);
-		rbwork->wakeup_full = false;
-		rbwork->full_waiters_pending = false;
+		scoped_guard(raw_spinlock_irqsave, &cpu_buffer->reader_lock) {
+			rbwork->wakeup_full = false;
+			rbwork->full_waiters_pending = false;
 
-		/* Waking up all waiters, they will reset the shortest full */
-		cpu_buffer->shortest_full = 0;
-		raw_spin_unlock(&cpu_buffer->reader_lock);
+			/* Waking up all waiters, they will reset the shortest full */
+			cpu_buffer->shortest_full = 0;
+		}
 
 		wake_up_all(&rbwork->full_waiters);
 	}
@@ -7384,7 +7383,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_read_page_data);
 
 /**
  * ring_buffer_read_page_size - get size of the read page.
- * @page:  the page to get the size from
+ * @rpage:  the page to get the size from
  *
  * Returns size of the page in bytes.
  */
@@ -7474,6 +7473,14 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order)
 
 	old_capacity = rb_subbuf_capacity(buffer);
 
+	/* The mmap fast path reads subbuf_order without buffer->mutex. */
+	for_each_buffer_cpu(buffer, cpu) {
+		if (!cpumask_test_cpu(cpu, buffer->cpumask))
+			continue;
+		if (atomic_read(&buffer->buffers[cpu]->resize_disabled))
+			return -EBUSY;
+	}
+
 	atomic_inc(&buffer->record_disabled);
 
 	/* Make sure all commits have finished */
kernel/trace/trace.c +45 -1

63 lines not on this page · raw message

kernel/trace/trace.h +2 -0
index 5e76f94e7a80..3749485a7d85 100644
@@ -227,6 +227,7 @@ struct array_buffer {
 struct trace_options {
 	struct tracer			*tracer;
 	struct trace_option_dentry	*topts;
+	int				nr_topts;
 };
 
 struct trace_pid_list *trace_pid_list_alloc(void);
@@ -1952,6 +1953,7 @@ struct event_trigger_data {
 
 struct enable_trigger_data {
 	struct trace_event_file		*file;
+	struct trace_event_call		*call;
 	bool				enable;
 	bool				hist;
 };
kernel/trace/trace_events.c +2 -0
index 9dbc2441763b..30c0ddf90887 100644
@@ -1489,6 +1489,8 @@ int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set)
 	/* Put back the colon to allow this to be called again */
 	if (buf)
 		*(buf - 1) = ':';
+	if (mod)
+		*(mod - 5) = ':';
 
 	return ret;
 }
kernel/trace/trace_events_hist.c +45 -41

233 lines not on this page · raw message

kernel/trace/trace_events_trigger.c +3 -1
index 149300cc5e8a..4d2fde996c0f 100644
@@ -1728,7 +1728,8 @@ static void enable_trigger_private_data_free(struct event_trigger_data *data)
 {
 	struct enable_trigger_data *enable_data = data->private_data;
 
-	trace_event_put_ref(enable_data->file->event_call);
+	/* The file may already be freed here, only the call is kept alive */
+	trace_event_put_ref(enable_data->call);
 	kfree(enable_data);
 }
 
@@ -1801,6 +1802,7 @@ int event_enable_trigger_parse(struct event_command *cmd_ops,
 	enable_data->hist = hist;
 	enable_data->enable = enable;
 	enable_data->file = event_enable_file;
+	enable_data->call = event_enable_file->event_call;
 
 	trigger_data = trigger_data_alloc(cmd_ops, cmd, param, enable_data);
 	if (!trigger_data)
kernel/trace/trace_events_user.c +20 -6
index 93cda2f6f269..f658c3a77aa7 100644
@@ -1122,10 +1122,9 @@ static void user_event_destroy_validators(struct user_event *user)
 	}
 }
 
-static void user_event_destroy_fields(struct user_event *user)
+static void user_event_destroy_fields(struct list_head *head)
 {
 	struct ftrace_event_field *field, *next;
-	struct list_head *head = &user->fields;
 
 	list_for_each_entry_safe(field, next, head, link) {
 		list_del(&field->link);
@@ -1502,17 +1501,32 @@ static int user_event_set_call_visible(struct user_event *user, bool visible)
 
 static int destroy_user_event(struct user_event *user)
 {
+	LIST_HEAD(fields);
 	int ret = 0;
 
 	lockdep_assert_held(&event_mutex);
 
-	/* Must destroy fields before call removal */
-	user_event_destroy_fields(user);
+	/*
+	 * Detach the fields before removing the call. Removing the event
+	 * frees the field list memory (trace_destroy_fields() is run on
+	 * successful removal and kmem_cache_free()s the fields), but the
+	 * fields here are allocated and owned by user_events. Destroy
+	 * them separately once removal has succeeded.
+	 */
+	list_splice_init(&user->fields, &fields);
 
 	ret = user_event_set_call_visible(user, false);
 
-	if (ret)
+	if (ret) {
+		/*
+		 * Removal failed and the event stays registered, recover
+		 * the fields so it is left in a consistent state.
+		 */
+		list_splice(&fields, &user->fields);
 		return ret;
+	}
+
+	user_event_destroy_fields(&fields);
 
 	dyn_event_remove(&user->devent);
 	hash_del(&user->node);
@@ -2212,7 +2226,7 @@ static int user_event_parse(struct user_event_group *group, char *name,
 put_user_lock:
 	mutex_unlock(&event_mutex);
 put_user:
-	user_event_destroy_fields(user);
+	user_event_destroy_fields(&user->fields);
 	user_event_destroy_validators(user);
 	kfree(user->call.print_fmt);
 
kernel/trace/trace_functions_graph.c +3 -0
index ff7cb1a76b95..c5befd0c5b9a 100644
@@ -52,6 +52,7 @@ struct fgraph_data {
 	};
 	struct ftrace_graph_ret_entry	ret;
 	int				failed;
+	int				ent_size;
 	int				cpu;
 };
 
@@ -1274,6 +1275,7 @@ print_graph_entry(struct ftrace_graph_ent_entry *field, struct trace_seq *s,
 		if (s->full) {
 			data->failed = 1;
 			data->cpu = cpu;
+			data->ent_size = iter->ent_size;
 		} else
 			data->failed = 0;
 	}
@@ -1457,6 +1459,7 @@ print_graph_function_flags(struct trace_iterator *iter, u32 flags)
 	if (data && data->failed) {
 		field = &data->ent.ent;
 		iter->cpu = data->cpu;
+		iter->ent_size = data->ent_size;
 		ret = print_graph_entry(field, s, iter, flags);
 		if (ret == TRACE_TYPE_HANDLED && iter->cpu != cpu) {
 			per_cpu_ptr(data->cpu_data, iter->cpu)->ignore = 1;
kernel/trace/trace_remote.c +5 -1
index 75fa1ffc4c96..2d5bc423efca 100644
@@ -980,9 +980,12 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size,
 			      const struct cpumask *cpumask)
 {
 	size_t min_desc_size = trace_buffer_desc_size(buffer_size, cpumask_weight(cpumask));
-	unsigned int nr_pages = max(DIV_ROUND_UP(buffer_size, PAGE_SIZE), 2UL) + 1;
 	struct ring_buffer_desc *rb_desc;
 	int cpu, ret = -ENOMEM;
+	unsigned int nr_pages;
+
+	if (min_desc_size == SIZE_MAX)
+		return -E2BIG;
 
 	if (desc_size < min_desc_size)
 		return -EINVAL;
@@ -991,6 +994,7 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size,
 	desc->struct_len = min_desc_size;
 
 	rb_desc = __first_ring_buffer_desc(desc);
+	nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size);
 
 	for_each_cpu(cpu, cpumask) {
 		unsigned int id;
merge bot Merged
#1 lore raw
The pull request you sent on Sun, 13 Sep 2026 13:35:06 -0400:

The pull request you sent on Sun, 13 Sep 2026 13:35:06 -0400:

Quoted 1 line

git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git trace-v7.3-rc2

has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/22098763a10d9c1340827fcf6edab66f153b27f0

Thank you!

Signature · 2 lines
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html