[PATCH v11 30/33] perf timechart: Bounds check CPU

From: Ian Rogers

Date: Sun Apr 12 2026 - 21:32:08 EST


Ensure the CPU is within expected bounds to avoid out of bound memory
accesses in arrays tracking CPU state.

Signed-off-by: Ian Rogers <irogers@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 64f69bd67c6b..64903b54e8a7 100644
--- a/tools/perf/builtin-timechart.c
+++ b/tools/perf/builtin-timechart.c
@@ -607,6 +607,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
@@ -622,6 +626,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;
}
@@ -635,6 +643,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;
}
@@ -648,6 +660,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;
@@ -662,6 +678,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;
}
@@ -671,6 +691,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;
}
@@ -683,6 +707,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;
}
@@ -694,10 +722,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));
@@ -1517,6 +1544,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.53.0.1213.gd9a14994de-goog