tracing: Fixes for v6.14
Subsystems (4)
Steven Rostedt
maintainercover tracing: Fixes for v6.14
Tracing fixes for v6.14:
- Fix crash from bad histogram entry
An error path in the histogram creation could leave an entry in a link list that gets freed. Then when a new entry is added it can cause a u-a-f bug. This is fixed by restructuring the code so that the histogram is consistent on failure and everything is cleaned up appropriately.
- Fix fprobe self test
The fprobe self test relies on no function being attached by ftrace. BPF programs can attach to functions via ftrace and systemd now does so. This causes those functions to appear in the enabled_functions list which holds all functions attached by ftrace. The selftest also uses that file to see if functions are being connected correctly. It counts the functions in the file, but if there's already functions in the file, it fails. Instead, add the number of functions in the file at the start of the test to all the calculations during the test.
- Fix potential division by zero of the function profiler stddev
The calculated divisor that calculates the standard deviation of the function times can overflow. If the overflow happens to land on zero, that can cause a division by zero. Check for zero from the calculation before doing the division.
TODO: Catch when it ever overflows and report it accordingly.
For now, just prevent the system from crashing.
git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git
trace/fixes
Head SHA1: a1a7eb89ca0b89dc1c326eeee2596f263291aca3
Heiko Carstens (1):
selftests/ftrace: Let fprobe test consider already enabled functions
Nikolay Kuratov (1):
ftrace: Avoid potential division by zero in function_stat_show()
Steven Rostedt (1):
tracing: Fix bad hist from corrupting named_triggers list
----
kernel/trace/ftrace.c | 27 +++++++++---------- kernel/trace/trace_events_hist.c | 30 +++++++++++----------- .../ftrace/test.d/dynevent/add_remove_fprobe.tc | 18 ++++++++----- 3 files changed, 38 insertions(+), 37 deletions(-)
Steven Rostedt
maintainerPATCH 1/3 tracing: Fix bad hist from corrupting named_triggers list
From: Steven Rostedt <[email protected]>
The following commands causes a crash:
~# cd /sys/kernel/tracing/events/rcu/rcu_callback
~# echo 'hist:name=bad:keys=common_pid:onmax(bogus).save(common_pid)' > trigger
bash: echo: write error: Invalid argument
~# echo 'hist:name=bad:keys=common_pid' > trigger
Because the following occurs:
event_trigger_write() {
trigger_process_regex() {
event_hist_trigger_parse() {
data = event_trigger_alloc(..);
event_trigger_register(.., data) {
cmd_ops->reg(.., data, ..) [hist_register_trigger()] {
data->ops->init() [event_hist_trigger_init()] {
save_named_trigger(name, data) {
list_add(&data->named_list, &named_triggers);
}
}
}
}
ret = create_actions(); (return -EINVAL)
if (ret)
goto out_unreg;
[..]
ret = hist_trigger_enable(data, ...) {
list_add_tail_rcu(&data->list, &file->triggers); <<<---- SKIPPED!!! (this is important!)
[..]
out_unreg:
event_hist_unregister(.., data) {
cmd_ops->unreg(.., data, ..) [hist_unregister_trigger()] {
list_for_each_entry(iter, &file->triggers, list) {
if (!hist_trigger_match(data, iter, named_data, false)) <- never matches
continue;
[..]
test = iter;
}
if (test && test->ops->free) <<<-- test is NULL
test->ops->free(test) [event_hist_trigger_free()] {
[..]
if (data->name)
del_named_trigger(data) {
list_del(&data->named_list); <<<<-- NEVER gets removed!
}
}
}
}
[..]
kfree(data); <<<-- frees item but it is still on listThe next time a hist with name is registered, it causes an u-a-f bug and the kernel can crash.
Move the code around such that if event_trigger_register() succeeds, the next thing called is hist_trigger_enable() which adds it to the list.
A bunch of actions is called if get_named_trigger_data() returns false. But that doesn't need to be called after event_trigger_register(), so it can be moved up, allowing event_trigger_register() to be called just before hist_trigger_enable() keeping them together and allowing the file->triggers to be properly populated.
--- kernel/trace/trace_events_hist.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-)
kernel/trace/trace_events_hist.c +15 -15
@@ -6724,27 +6724,27 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops,if (existing_hist_update_only (glob, trigger_data, file)) goto out_free; - ret =event_trigger_register (cmd_ops, file, glob, trigger_data); -if (ret <0 ) - goto out_free; +if (!get_named_trigger_data (trigger_data)) { -if (get_named_trigger_data (trigger_data)) - goto enable; + ret =create_actions (hist_data); +if (ret) + goto out_free; - ret =create_actions (hist_data); -if (ret) - goto out_unreg; +if (has_hist_vars (hist_data) || hist_data->n_var_refs) { + ret =save_hist_vars (hist_data); +if (ret) + goto out_free; + } -if (has_hist_vars (hist_data) || hist_data->n_var_refs) { - ret =save_hist_vars (hist_data); + ret =tracing_map_init (hist_data->map);if (ret) - goto out_unreg; + goto out_free; } - ret =tracing_map_init (hist_data->map); -if (ret) - goto out_unreg; -enable: + ret =event_trigger_register (cmd_ops, file, glob, trigger_data); +if (ret <0 ) + goto out_free; + ret =hist_trigger_enable (trigger_data, file);if (ret) goto out_unreg;
Signature · 1 line
2.47.2
Steven Rostedt
maintainerPATCH 2/3 selftests/ftrace: Let fprobe test consider already enabled functions
In reply to Steven RostedtFrom: Heiko Carstens <[email protected]>
The fprobe test fails on Fedora 41 since the fprobe test assumption that the number of enabled_functions is zero before the test starts is not necessarily true. Some user space tools, like systemd, add BPF programs that attach to functions. Those will show up in the enabled_functions table and must be taken into account by the fprobe test.
Therefore count the number of lines of enabled_functions before tests start, and use that as base when comparing expected results.
--- .../test.d/dynevent/add_remove_fprobe.tc | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-)
tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe.tc +11 -7
@@ -10,12 +10,16 @@ PLACE=$FUNCTION_FORK PLACE2="kmem_cache_free" PLACE3="schedule_timeout" +# Some functions may have BPF programs attached, therefore +# count already enabled_functions before tests start +ocnt=`cat enabled_functions | wc -l` + echo "f:myevent1 $PLACE" >> dynamic_events # Make sure the event is attached and is the only one grep -q $PLACE enabled_functions cnt=`cat enabled_functions | wc -l` -if [ $cnt -ne 1 ]; then +if [ $cnt -ne $((ocnt + 1)) ]; then exit_fail fi
@@ -23,7 +27,7 @@ echo "f:myevent2 $PLACE%return" >> dynamic_events # It should till be the only attached function cnt=`cat enabled_functions | wc -l` -if [ $cnt -ne 1 ]; then +if [ $cnt -ne $((ocnt + 1)) ]; then exit_fail fi
@@ -32,7 +36,7 @@ echo "f:myevent3 $PLACE2" >> dynamic_events grep -q $PLACE2 enabled_functions cnt=`cat enabled_functions | wc -l` -if [ $cnt -ne 2 ]; then +if [ $cnt -ne $((ocnt + 2)) ]; then exit_fail fi
@@ -49,7 +53,7 @@ grep -q myevent1 dynamic_events # should still have 2 left cnt=`cat enabled_functions | wc -l` -if [ $cnt -ne 2 ]; then +if [ $cnt -ne $((ocnt + 2)) ]; then exit_fail fi
@@ -57,7 +61,7 @@ echo > dynamic_events # Should have none left cnt=`cat enabled_functions | wc -l` -if [ $cnt -ne 0 ]; then +if [ $cnt -ne $ocnt ]; then exit_fail fi
@@ -65,7 +69,7 @@ echo "f:myevent4 $PLACE" >> dynamic_events # Should only have one enabled cnt=`cat enabled_functions | wc -l` -if [ $cnt -ne 1 ]; then +if [ $cnt -ne $((ocnt + 1)) ]; then exit_fail fi
@@ -73,7 +77,7 @@ echo > dynamic_events # Should have none left cnt=`cat enabled_functions | wc -l` -if [ $cnt -ne 0 ]; then +if [ $cnt -ne $ocnt ]; then exit_fail fi
Signature · 1 line
2.47.2
Steven Rostedt
maintainerPATCH 3/3 ftrace: Avoid potential division by zero in function_stat_show()
In reply to Steven RostedtFrom: Nikolay Kuratov <[email protected]>
Check whether denominator expression x * (x - 1) * 1000 mod {2^32, 2^64} produce zero and skip stddev computation in that case.
For now don't care about rec->counter * rec->counter overflow because rec->time * rec->time overflow will likely happen earlier.
--- kernel/trace/ftrace.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-)
kernel/trace/ftrace.c +12 -15
@@ -540,6 +540,7 @@ static int function_stat_show(struct seq_file *m, void *v)static struct trace_seq s;unsigned long long avg;unsigned long long stddev; +unsigned long long stddev_denom;#endif guard (mutex)(&ftrace_profile_lock);
@@ -559,23 +560,19 @@ static int function_stat_show(struct seq_file *m, void *v)#ifdef CONFIG_FUNCTION_GRAPH_TRACER seq_puts (m," " ); -/* Sample standard deviation (s^2) */ -if (rec->counter <=1 ) - stddev =0 ; -else { -/* -* Apply Welford's method: -* s^2 = 1 / (n * (n-1)) * (n * \Sum (x_i)^2 - (\Sum x_i)^2) -*/ +/* +* Variance formula: +* s^2 = 1 / (n * (n-1)) * (n * \Sum (x_i)^2 - (\Sum x_i)^2) +* Maybe Welford's method is better here? +* Divide only by 1000 for ns^2 -> us^2 conversion. +* trace_print_graph_duration will divide by 1000 again. +*/ + stddev =0 ; + stddev_denom = rec->counter * (rec->counter -1 ) *1000 ; +if (stddev_denom) { stddev = rec->counter * rec->time_squared - rec->time * rec->time; - -/* -* Divide only 1000 for ns^2 -> us^2 conversion. -* trace_print_graph_duration will divide 1000 again. -*/ - stddev =div64_ul (stddev, - rec->counter * (rec->counter -1 ) *1000 ); + stddev =div64_ul (stddev, stddev_denom); }trace_seq_init (&s);
Signature · 1 line
2.47.2