perf kwork: Bounds check work->cpu before indexing cpus_runtime[]

work->cpu comes from sample->cpu which is (u32)-1 when
PERF_SAMPLE_CPU is absent.  Stored as int, this becomes -1
which passes the signed BUG_ON(work->cpu >= MAX_NR_CPUS) but
causes an out-of-bounds access on cpus_runtime[-1].

Replace the BUG_ON in top_calc_total_runtime() with an unsigned
bounds check that skips entries with invalid CPU values, counting
them for a summary warning.

Guard the same index in profile_event_match() (bitmap OOB),
top_calc_idle_time(), top_calc_irq_runtime(), top_calc_cpu_usage(),
and top_calc_load_runtime().  Also guard against division by zero
in top_calc_cpu_usage() when no runtime was accumulated.

Reported-by: sashiko-bot@kernel.org # Running on a local machine
Reviewed-by: Ian Rogers <irogers@google.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Yang Jihong <yangjihong@bytedance.com>
Assisted-by: Claude:claude-opus-4.6-1m
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Arnaldo Carvalho de Melo 2026-05-03 13:05:51 -03:00
parent a2e59fb79f
commit 48d004c074
2 changed files with 40 additions and 6 deletions

View File

@ -467,7 +467,9 @@ static bool profile_event_match(struct perf_kwork *kwork,
u64 time = sample->time;
struct perf_time_interval *ptime = &kwork->ptime;
if ((kwork->cpu_list != NULL) && !test_bit(cpu, kwork->cpu_bitmap))
/* Guard test_bit: cpu == -1 (absent PERF_SAMPLE_CPU) would index past the bitmap */
if ((kwork->cpu_list != NULL) &&
((unsigned int)cpu >= MAX_NR_CPUS || !test_bit(cpu, kwork->cpu_bitmap)))
return false;
if (((ptime->start != 0) && (ptime->start > time)) ||
@ -2041,7 +2043,18 @@ static void top_calc_total_runtime(struct perf_kwork *kwork)
next = rb_first_cached(&class->work_root);
while (next) {
work = rb_entry(next, struct kwork_work, node);
BUG_ON(work->cpu >= MAX_NR_CPUS);
/*
* work->cpu comes from sample->cpu which is -1 when
* PERF_SAMPLE_CPU is absent. As int that's -1, but as
* unsigned it exceeds MAX_NR_CPUS skip to avoid OOB
* on cpus_runtime[].
*/
/* Counted and reported in perf_kwork__top_report() */
if ((unsigned int)work->cpu >= MAX_NR_CPUS) {
stat->nr_skipped_cpu++;
next = rb_next(next);
continue;
}
stat->cpus_runtime[work->cpu].total += work->total_runtime;
stat->cpus_runtime[MAX_NR_CPUS].total += work->total_runtime;
next = rb_next(next);
@ -2053,7 +2066,8 @@ static void top_calc_idle_time(struct perf_kwork *kwork,
{
struct kwork_top_stat *stat = &kwork->top_stat;
if (work->id == 0) {
/* See comment in top_calc_total_runtime() */
if (work->id == 0 && (unsigned int)work->cpu < MAX_NR_CPUS) {
stat->cpus_runtime[work->cpu].idle += work->total_runtime;
stat->cpus_runtime[MAX_NR_CPUS].idle += work->total_runtime;
}
@ -2065,6 +2079,10 @@ static void top_calc_irq_runtime(struct perf_kwork *kwork,
{
struct kwork_top_stat *stat = &kwork->top_stat;
/* See comment in top_calc_total_runtime() */
if ((unsigned int)work->cpu >= MAX_NR_CPUS)
return;
if (type == KWORK_CLASS_IRQ) {
stat->cpus_runtime[work->cpu].irq += work->total_runtime;
stat->cpus_runtime[MAX_NR_CPUS].irq += work->total_runtime;
@ -2117,12 +2135,19 @@ static void top_calc_cpu_usage(struct perf_kwork *kwork)
if (work->total_runtime == 0)
goto next;
/* See comment in top_calc_total_runtime() */
if ((unsigned int)work->cpu >= MAX_NR_CPUS)
goto next;
__set_bit(work->cpu, stat->all_cpus_bitmap);
top_subtract_irq_runtime(kwork, work);
work->cpu_usage = work->total_runtime * 10000 /
stat->cpus_runtime[work->cpu].total;
/* Guard against division by zero if no runtime was accumulated */
if (stat->cpus_runtime[work->cpu].total) {
work->cpu_usage = work->total_runtime * 10000 /
stat->cpus_runtime[work->cpu].total;
}
top_calc_idle_time(kwork, work);
next:
@ -2135,7 +2160,8 @@ static void top_calc_load_runtime(struct perf_kwork *kwork,
{
struct kwork_top_stat *stat = &kwork->top_stat;
if (work->id != 0) {
/* See comment in top_calc_total_runtime() */
if (work->id != 0 && (unsigned int)work->cpu < MAX_NR_CPUS) {
stat->cpus_runtime[work->cpu].load += work->total_runtime;
stat->cpus_runtime[MAX_NR_CPUS].load += work->total_runtime;
}
@ -2211,6 +2237,13 @@ static void perf_kwork__top_report(struct perf_kwork *kwork)
next = rb_next(next);
}
if (kwork->top_stat.nr_skipped_cpu) {
printf(" Warning: %u work entries with invalid CPU were excluded from totals.\n"
" Task runtimes may appear inflated (IRQ time not subtracted).\n"
" Consider re-recording with PERF_SAMPLE_CPU enabled.\n",
kwork->top_stat.nr_skipped_cpu);
}
printf("\n");
}

View File

@ -202,6 +202,7 @@ struct __top_cpus_runtime {
struct kwork_top_stat {
DECLARE_BITMAP(all_cpus_bitmap, MAX_NR_CPUS);
struct __top_cpus_runtime *cpus_runtime;
unsigned int nr_skipped_cpu;
};
struct perf_kwork {