]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
perf kwork: Bounds check work->cpu before indexing cpus_runtime[]
authorArnaldo Carvalho de Melo <acme@redhat.com>
Sun, 3 May 2026 16:05:51 +0000 (13:05 -0300)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Fri, 29 May 2026 14:44:35 +0000 (11:44 -0300)
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>
tools/perf/builtin-kwork.c
tools/perf/util/kwork.h

index f793ea578515d08cf4058bb097656e32a6298e5a..99dc293a0744726ef0ba1b2acddefdd48d561646 100644 (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 @@ next:
                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");
 }
 
index 81d39a7f78c8b811e4220e0667511b94d8729049..6ec70dcc4157a1f2ea58e2db89994a8d074e86dd 100644 (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 {