[PATCH 12/28] perf cpumap: Reject RANGE_CPUS with start_cpu > end_cpu
From: Arnaldo Carvalho de Melo
Date: Sat May 09 2026 - 23:40:15 EST
From: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>
cpu_map__from_range() computes nr_cpus as end_cpu - start_cpu + 1.
When a crafted perf.data has start_cpu > end_cpu, this wraps to a
huge value, causing perf_cpu_map__empty_new() to attempt a massive
allocation.
Return NULL when the range is inverted.
Also clamp any_cpu to boolean (0 or 1) since it is added to the
allocation count — a crafted value > 1 would inflate the map size.
Harden cpu_map__from_mask() to reject unsupported long_size values
(anything other than 4 or 8), preventing misinterpretation of the
mask data layout.
Reported-by: sashiko-bot@xxxxxxxxxx # Running on a local machine
Cc: Ian Rogers <irogers@xxxxxxxxxx>
Cc: Jiri Olsa <jolsa@xxxxxxxxxx>
Cc: Namhyung Kim <namhyung@xxxxxxxxxx>
Assisted-by: Claude Opus 4.6 (1M context) <noreply@xxxxxxxxxxxxx>
Signed-off-by: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>
---
tools/perf/util/cpumap.c | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/tools/perf/util/cpumap.c b/tools/perf/util/cpumap.c
index 11922e1ded844a03..c32db7b307d7d959 100644
--- a/tools/perf/util/cpumap.c
+++ b/tools/perf/util/cpumap.c
@@ -93,9 +93,18 @@ static struct perf_cpu_map *cpu_map__from_entries(const struct perf_record_cpu_m
static struct perf_cpu_map *cpu_map__from_mask(const struct perf_record_cpu_map_data *data)
{
DECLARE_BITMAP(local_copy, 64);
- int weight = 0, mask_nr = data->mask32_data.nr;
+ int weight = 0, mask_nr;
+ /* Cache validated long_size — data is mmap'd and could change */
+ u16 long_size;
struct perf_cpu_map *map;
+ /* long_size must be 4 or 8; other values overflow cpus_per_i below */
+ if (data->mask32_data.long_size != 4 && data->mask32_data.long_size != 8)
+ return NULL;
+
+ long_size = data->mask32_data.long_size;
+ mask_nr = data->mask32_data.nr;
+
for (int i = 0; i < mask_nr; i++) {
perf_record_cpu_map_data__read_one_mask(data, i, local_copy);
weight += bitmap_weight(local_copy, 64);
@@ -106,11 +115,14 @@ static struct perf_cpu_map *cpu_map__from_mask(const struct perf_record_cpu_map_
return NULL;
for (int i = 0, j = 0; i < mask_nr; i++) {
- int cpus_per_i = (i * data->mask32_data.long_size * BITS_PER_BYTE);
+ int cpus_per_i = (i * long_size * BITS_PER_BYTE);
int cpu;
perf_record_cpu_map_data__read_one_mask(data, i, local_copy);
for_each_set_bit(cpu, local_copy, 64) {
+ /* Guard against more set bits than the first pass counted */
+ if (j >= weight)
+ break;
if (cpu + cpus_per_i < INT16_MAX) {
RC_CHK_ACCESS(map)->map[j++].cpu = cpu + cpus_per_i;
} else {
@@ -129,8 +141,12 @@ static struct perf_cpu_map *cpu_map__from_range(const struct perf_record_cpu_map
struct perf_cpu_map *map;
unsigned int i = 0;
+ if (data->range_cpu_data.end_cpu < data->range_cpu_data.start_cpu)
+ return NULL;
+
+ /* any_cpu is boolean (0 or 1), not a count — clamp to avoid inflated nr */
map = perf_cpu_map__empty_new(data->range_cpu_data.end_cpu -
- data->range_cpu_data.start_cpu + 1 + data->range_cpu_data.any_cpu);
+ data->range_cpu_data.start_cpu + 1 + !!data->range_cpu_data.any_cpu);
if (!map)
return NULL;
--
2.54.0