[PATCH v14 29/32] perf timechart: Bounds check CPU

From: Ian Rogers

Date: Wed May 20 2026 - 15:19:48 EST


Prevent out-of-bounds writes/reads in CPU state tracking arrays by enforcing
strict MAX_CPUS bounds checks in timechart's tracepoint handlers.

Ensure that cpu_id retrieved from idle/frequency and sched tracepoints is less
than MAX_CPUS before indexing into cpus_cstate_state, cpus_cstate_start_times,
and similar tracking arrays. Also, fix an off-by-one error in the CPU iteration
loop inside end_sample_processing() by changing the loop condition from
'cpu <= tchart->numcpus' to 'cpu < tchart->numcpus'.

Signed-off-by: Ian Rogers <irogers@xxxxxxxxxx>
Acked-by: Namhyung Kim <namhyung@xxxxxxxxxx>
---
tools/perf/builtin-timechart.c | 35 +++++++++++++++++++++++++++++++---
1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c
index d2e15d02304e..630756bebe32 100644
--- a/tools/perf/builtin-timechart.c
+++ b/tools/perf/builtin-timechart.c
@@ -605,6 +605,10 @@ process_sample_cpu_idle(struct timechart *tchart __maybe_unused,
u32 state = perf_sample__intval(sample, "state");
u32 cpu_id = perf_sample__intval(sample, "cpu_id");

+ if (cpu_id >= MAX_CPUS) {
+ pr_debug("Out-of-bounds cpu_id %u\n", cpu_id);
+ return -1;
+ }
if (state == (u32)PWR_EVENT_EXIT)
c_state_end(tchart, cpu_id, sample->time);
else
@@ -620,6 +624,10 @@ process_sample_cpu_frequency(struct timechart *tchart,
u32 state = perf_sample__intval(sample, "state");
u32 cpu_id = perf_sample__intval(sample, "cpu_id");

+ if (cpu_id >= MAX_CPUS) {
+ pr_debug("Out-of-bounds cpu_id %u\n", cpu_id);
+ return -1;
+ }
p_state_change(tchart, cpu_id, sample->time, state);
return 0;
}
@@ -633,6 +641,10 @@ process_sample_sched_wakeup(struct timechart *tchart,
int waker = perf_sample__intval(sample, "common_pid");
int wakee = perf_sample__intval(sample, "pid");

+ if (sample->cpu >= MAX_CPUS) {
+ pr_debug("Out-of-bounds cpu %u\n", sample->cpu);
+ return -1;
+ }
sched_wakeup(tchart, sample->cpu, sample->time, waker, wakee, flags, backtrace);
return 0;
}
@@ -646,6 +658,10 @@ process_sample_sched_switch(struct timechart *tchart,
int next_pid = perf_sample__intval(sample, "next_pid");
u64 prev_state = perf_sample__intval(sample, "prev_state");

+ if (sample->cpu >= MAX_CPUS) {
+ pr_debug("Out-of-bounds cpu %u\n", sample->cpu);
+ return -1;
+ }
sched_switch(tchart, sample->cpu, sample->time, prev_pid, next_pid,
prev_state, backtrace);
return 0;
@@ -660,6 +676,10 @@ process_sample_power_start(struct timechart *tchart __maybe_unused,
u64 cpu_id = perf_sample__intval(sample, "cpu_id");
u64 value = perf_sample__intval(sample, "value");

+ if (cpu_id >= MAX_CPUS) {
+ pr_debug("Out-of-bounds cpu_id %llu\n", (unsigned long long)cpu_id);
+ return -1;
+ }
c_state_start(cpu_id, sample->time, value);
return 0;
}
@@ -669,6 +689,10 @@ process_sample_power_end(struct timechart *tchart,
struct perf_sample *sample,
const char *backtrace __maybe_unused)
{
+ if (sample->cpu >= MAX_CPUS) {
+ pr_debug("Out-of-bounds cpu %u\n", sample->cpu);
+ return -1;
+ }
c_state_end(tchart, sample->cpu, sample->time);
return 0;
}
@@ -681,6 +705,10 @@ process_sample_power_frequency(struct timechart *tchart,
u64 cpu_id = perf_sample__intval(sample, "cpu_id");
u64 value = perf_sample__intval(sample, "value");

+ if (cpu_id >= MAX_CPUS) {
+ pr_debug("Out-of-bounds cpu_id %llu\n", (unsigned long long)cpu_id);
+ return -1;
+ }
p_state_change(tchart, cpu_id, sample->time, value);
return 0;
}
@@ -692,10 +720,9 @@ process_sample_power_frequency(struct timechart *tchart,
*/
static void end_sample_processing(struct timechart *tchart)
{
- u64 cpu;
- struct power_event *pwr;
+ for (u64 cpu = 0; cpu < tchart->numcpus; cpu++) {
+ struct power_event *pwr;

- for (cpu = 0; cpu <= tchart->numcpus; cpu++) {
/* C state */
#if 0
pwr = zalloc(sizeof(*pwr));
@@ -1515,6 +1542,8 @@ static int process_header(struct perf_file_section *section __maybe_unused,
switch (feat) {
case HEADER_NRCPUS:
tchart->numcpus = ph->env.nr_cpus_avail;
+ if (tchart->numcpus > MAX_CPUS)
+ tchart->numcpus = MAX_CPUS;
break;

case HEADER_CPU_TOPOLOGY:
--
2.54.0.746.g67dd491aae-goog