An action that names a field on another event needs that field as a
variable, so create_field_var_hist() registers a second hist trigger on
that event to provide it, and records it in
target_hist_data->field_var_hists[].

If a later part of the same command fails, create_actions() returns an
error and event_hist_trigger_parse() jumps to out_free. The write fails
with -EINVAL and sched_switch gets no trigger, but:

  # echo 'hist:keys=pid:ts0=common_timestamp.usecs' >> \
      events/sched/sched_waking/trigger
  # echo 'my_synth u64 lat; int a; int b' >> synthetic_events
  # echo 'hist:keys=next_pid:wakeup_lat=common_timestamp.usecs-$ts0:\
      onmatch(sched.sched_waking).my_synth($wakeup_lat,prio,nosuchfld)'\
      >> events/sched/sched_switch/trigger
  # cat events/sched/sched_waking/trigger
  
hist:keys=pid:vals=hitcount:ts0=common_timestamp.usecs:sort=hitcount:size=2048:clock=global
 [active]
  hist:keys=pid:vals=hitcount:synthetic_prio=prio:sort=hitcount:size=2048 
[active]

The second histogram is the one created for 'prio'. It is still active and
still counting.

out_free reaches destroy_field_var_hists(), which frees the
field_var_hists[] entries, but never unregister_field_var_hists(), which is
what removes the triggers and runs only from event_hist_trigger_free(). The
entry is freed and the trigger is left with nothing tracking it.

Unregister the field variable histograms in out_free as well, and record
each one as soon as its trigger is registered rather than after the lookup
that follows it, so no exit can leave a registered trigger untracked. The
entry then belongs to field_var_hists[] and is released by
destroy_field_var_hists(), so freeing it on that exit would be a double
free.

Cc: [email protected]
Fixes: 02205a6752f2 ("tracing: Add support for 'field variables'")
Reported-by: [email protected]
Closes: https://lore.kernel.org/all/[email protected]/
Signed-off-by: Donggeun Yoo <[email protected]>
Assisted-by: Claude:claude-fable-5
---
Tested under QEMU on 704340f1cd0d, x86_64, KASAN + PROVE_LOCKING, 2 vCPU,
unfixed arm first.

  command                                      before     after
  valid onmatch on another event's field       works      works
  removing that trigger                        works      works
  onmatch with a trailing bad field            orphan     clean
  the same bad command, five more times        orphan     clean
  two field variables, then a bad field        2 orphans  clean
  '!hist' that matches nothing                 clean      clean

orphan = an extra [active] histogram left on the matched event.

ftracetest test.d/trigger: identical per-test verdicts on both arms.
trigger-synthetic-event-dynstring.tc fails on both because busybox execs
its ping applet as /proc/self/exe, so the test's grep cannot match.

The exit that reports a missing synthetic variable is not reachable
through onmatch, so no run covers it.

 kernel/trace/trace_events_hist.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
index 8af97fd4ee2d..90e6e3990c25 100644
--- a/kernel/trace/trace_events_hist.c
+++ b/kernel/trace/trace_events_hist.c
@@ -3134,20 +3134,18 @@ create_field_var_hist(struct hist_trigger_data 
*target_hist_data,
 
        kfree(cmd);
 
+       n = target_hist_data->n_field_var_hists;
+       target_hist_data->field_var_hists[n] = var_hist;
+       target_hist_data->n_field_var_hists++;
+
        /* If we can't find the variable, something went wrong */
        event_var = find_synthetic_field_var(target_hist_data, subsys_name,
                                             event_name, field_name);
        if (IS_ERR_OR_NULL(event_var)) {
-               kfree(var_hist->cmd);
-               kfree(var_hist);
                hist_err(tr, HIST_ERR_SYNTH_VAR_NOT_FOUND, errpos(field_name));
                return ERR_PTR(-EINVAL);
        }
 
-       n = target_hist_data->n_field_var_hists;
-       target_hist_data->field_var_hists[n] = var_hist;
-       target_hist_data->n_field_var_hists++;
-
        return event_var;
 }
 
@@ -6980,6 +6978,8 @@ static int event_hist_trigger_parse(struct event_command 
*cmd_ops,
 
        trigger_data_free(trigger_data);
 
+       unregister_field_var_hists(hist_data);
+
        destroy_hist_data(hist_data);
        goto out;
 }
-- 
2.53.0


Reply via email to