]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
perf sched: Use is_idle_sample() for idle thread runtime cast guard
authorArnaldo Carvalho de Melo <acme@redhat.com>
Fri, 5 Jun 2026 22:01:35 +0000 (19:01 -0300)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Fri, 5 Jun 2026 22:17:31 +0000 (19:17 -0300)
timehist_sched_change_event() uses thread__tid(thread) == 0 to decide
whether to cast thread_runtime to idle_thread_runtime.  However, a
crafted perf.data can set common_pid=0 and common_tid=0 (the perf_sample
fields) while prev_pid != 0 (the tracepoint field).  is_idle_sample()
returns false (it checks prev_pid for sched_switch), so
timehist_get_thread() goes through machine__findnew_thread() and returns
the machine's TID 0 thread — whose priv data is a regular thread_runtime,
not the larger idle_thread_runtime allocated by init_idle_thread().

The subsequent cast to idle_thread_runtime reads past the thread_runtime
allocation, accessing itr->last_thread, itr->cursor, and itr->callchain
from adjacent heap memory.  Writing to itr->last_thread corrupts the
heap; calling thread__put() on the OOB value frees an arbitrary pointer.

Replace the thread__tid() == 0 check with is_idle_sample(), which uses
the tracepoint-specific prev_pid field and correctly identifies whether
the sample originated from an idle thread with idle_thread_runtime priv.

Fixes: 5d8f17fb5822 ("perf sched timehist: Add -I/--idle-hist option")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Assisted-by: Claude:claude-opus-4.6
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/builtin-sched.c

index e4378cc9ab3ed48b106bab07c6591036dae61077..4600d70b486104dd894081496f2600f1c5ddcbcf 100644 (file)
@@ -2902,7 +2902,13 @@ static int timehist_sched_change_event(const struct perf_tool *tool,
                        t = ptime->end;
        }
 
-       if (!sched->idle_hist || thread__tid(thread) == 0) {
+       /*
+        * Use is_idle_sample() not thread__tid() == 0: a crafted perf.data
+        * can set common_pid=0 with prev_pid!=0, giving us a machine thread
+        * whose priv is thread_runtime, not idle_thread_runtime — the cast
+        * below would read past the allocation.
+        */
+       if (!sched->idle_hist || is_idle_sample(sample)) {
                if (!cpu_list || (sample->cpu < MAX_NR_CPUS &&
                                 test_bit(sample->cpu, cpu_bitmap)))
                        timehist_update_runtime_stats(tr, t, tprev);