From: Arnaldo Carvalho de Melo <acme@kernel.org>
To: Namhyung Kim <namhyung@kernel.org>
Cc: Ingo Molnar <mingo@kernel.org>,
Thomas Gleixner <tglx@linutronix.de>,
James Clark <james.clark@linaro.org>,
Jiri Olsa <jolsa@kernel.org>, Ian Rogers <irogers@google.com>,
Adrian Hunter <adrian.hunter@intel.com>,
Kan Liang <kan.liang@linux.intel.com>,
Clark Williams <williams@redhat.com>,
linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org,
Arnaldo Carvalho de Melo <acme@redhat.com>,
sashiko-bot@kernel.org,
"Claude Opus 4.6 (1M context)" <noreply@anthropic.com>
Subject: [PATCH 25/28] perf session: Bound nr_cpus_avail and validate sample CPU
Date: Sun, 10 May 2026 00:34:16 -0300 [thread overview]
Message-ID: <20260510033424.255812-26-acme@kernel.org> (raw)
In-Reply-To: <20260510033424.255812-1-acme@kernel.org>
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Several downstream consumers (timechart, kwork, sched) use fixed-size
arrays indexed by CPU. A crafted perf.data can supply arbitrary CPU
values that index past these arrays, causing out-of-bounds access.
Clamp nr_cpus_avail to MAX_NR_CPUS when reading HEADER_NRCPUS, and
fall back to MAX_NR_CPUS when the header is missing (truncated files,
pipe mode, pre-2017 perf). Then validate sample.cpu against
nr_cpus_avail in perf_session__deliver_event() before any tool
callback runs.
Only validate when PERF_SAMPLE_CPU is set in sample_type — when
absent, evsel__parse_sample() leaves sample.cpu as (u32)-1, a
sentinel that downstream tools (script, inject) check to identify
events without CPU info. Clamping it to 0 would break those checks.
Also refactor the sample parsing in perf_session__deliver_event()
to call evsel__parse_sample() directly (via evlist__event2evsel()
for evsel lookup), with explicit guest VM SID resolution for
machine_pid and vcpu fields.
Fix an off-by-one in end_sample_processing(): change the loop bound
from cpu <= numcpus to cpu < numcpus to prevent accessing one
element past the array.
For pipe-mode streams where HEADER_NRCPUS may arrive late or not at
all, the MAX_NR_CPUS fallback ensures the bounds check is still
effective against the fixed-size downstream arrays.
Reported-by: sashiko-bot@kernel.org # Running on a local machine
Assisted-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/builtin-timechart.c | 2 +-
| 43 +++++++++++++++++++
tools/perf/util/session.c | 75 +++++++++++++++++++++++++++++++++-
3 files changed, 118 insertions(+), 2 deletions(-)
diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c
index 28f33e39895d362d..40297f2dcd0353cc 100644
--- a/tools/perf/builtin-timechart.c
+++ b/tools/perf/builtin-timechart.c
@@ -700,7 +700,7 @@ static void end_sample_processing(struct timechart *tchart)
u64 cpu;
struct power_event *pwr;
- for (cpu = 0; cpu <= tchart->numcpus; cpu++) {
+ for (cpu = 0; cpu < tchart->numcpus; cpu++) {
/* C state */
#if 0
pwr = zalloc(sizeof(*pwr));
--git a/tools/perf/util/header.c b/tools/perf/util/header.c
index 994e54167ea3196b..30b65c58784b596f 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -48,6 +48,7 @@
#include <api/io_dir.h>
#include "asm/bug.h"
#include "tool.h"
+#include "../perf.h"
#include "time-utils.h"
#include "units.h"
#include "util/util.h" // perf_exe()
@@ -2884,12 +2885,36 @@ static int process_nrcpus(struct feat_fd *ff, void *data __maybe_unused)
if (ret)
return ret;
+ /* Validate raw values before clamping */
if (nr_cpus_online > nr_cpus_avail) {
pr_err("Invalid HEADER_NRCPUS: nr_cpus_online (%u) > nr_cpus_avail (%u)\n",
nr_cpus_online, nr_cpus_avail);
return -1;
}
+ /*
+ * FIXME: Several downstream consumers use fixed-size arrays
+ * indexed by CPU (timechart MAX_CPUS, kwork/sched/annotate
+ * DECLARE_BITMAP(MAX_NR_CPUS)). Until these are converted
+ * to dynamic allocation, clamp nr_cpus_avail so per-event
+ * CPU bounds checks reject samples above the array limit.
+ * Data from CPUs beyond MAX_NR_CPUS will be lost.
+ *
+ * Pipe-mode streams from pre-2017 perf or third-party tools
+ * that lack HEADER_NRCPUS will hit the MAX_NR_CPUS fallback
+ * in perf_session__deliver_event() instead.
+ */
+ if (nr_cpus_avail > MAX_NR_CPUS) {
+ pr_warning("WARNING: perf.data recorded on a %u-CPU machine but perf is compiled with MAX_NR_CPUS=%d.\n"
+ " Samples from CPUs >= %d will be clamped to CPU 0. Consider rebuilding\n"
+ " perf with a larger MAX_NR_CPUS, or help convert fixed-size CPU arrays to\n"
+ " dynamic allocation.\n",
+ nr_cpus_avail, MAX_NR_CPUS, MAX_NR_CPUS);
+ nr_cpus_avail = MAX_NR_CPUS;
+ if (nr_cpus_online > nr_cpus_avail)
+ nr_cpus_online = nr_cpus_avail;
+ }
+
env->nr_cpus_avail = (int)nr_cpus_avail;
env->nr_cpus_online = (int)nr_cpus_online;
return 0;
@@ -5239,6 +5264,24 @@ int perf_session__read_header(struct perf_session *session)
#endif
}
+ /*
+ * Without nr_cpus_avail the sample CPU bounds check in
+ * perf_session__deliver_event() is bypassed, allowing crafted
+ * CPU IDs to reach downstream consumers that index fixed-size
+ * arrays (timechart, kwork, sched — all sized MAX_NR_CPUS).
+ *
+ * This can happen with truncated files (interrupted recording
+ * loses all feature sections), very old files that predate
+ * HEADER_NRCPUS, or crafted files that omit it. Fall back to
+ * MAX_NR_CPUS so the bounds check is still effective — any
+ * CPU ID below that limit is safe for all downstream arrays.
+ */
+ if (header->env.nr_cpus_avail == 0) {
+ header->env.nr_cpus_avail = MAX_NR_CPUS;
+ pr_warning("WARNING: perf.data is missing HEADER_NRCPUS, using MAX_NR_CPUS (%d) as CPU bound\n",
+ MAX_NR_CPUS);
+ }
+
return 0;
out_errno:
return -errno;
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index 80cb03d150cecc0b..dd84b3cd017a5073 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -2085,14 +2085,87 @@ static int perf_session__deliver_event(struct perf_session *session,
const char *file_path)
{
struct perf_sample sample;
+ struct evsel *evsel;
int ret;
perf_sample__init(&sample, /*all=*/false);
- ret = evlist__parse_sample(session->evlist, event, &sample);
+ evsel = evlist__event2evsel(session->evlist, event);
+ if (!evsel) {
+ ret = -EFAULT;
+ goto out;
+ }
+ ret = evsel__parse_sample(evsel, event, &sample);
if (ret) {
pr_err("Can't parse sample, err = %d\n", ret);
goto out;
}
+ /*
+ * evsel__parse_sample() doesn't populate machine_pid/vcpu,
+ * which are needed by machines__find_for_cpumode() to
+ * attribute samples to guest VMs. The SID table maps
+ * sample IDs to the guest that owns the event.
+ */
+ if (perf_guest && sample.id) {
+ struct perf_sample_id *sid = evlist__id2sid(session->evlist, sample.id);
+
+ if (sid) {
+ sample.machine_pid = sid->machine_pid;
+ sample.vcpu = sid->vcpu.cpu;
+ }
+ }
+
+ /*
+ * Validate sample.cpu before any callback can use it as an
+ * array index (kwork cpus_runtime, timechart cpus_cstate_*,
+ * sched cpu_last_switched).
+ *
+ * When PERF_SAMPLE_CPU is absent, evsel__parse_sample() leaves
+ * sample.cpu as (u32)-1 — a sentinel that downstream tools
+ * (script, inject) check to identify events without CPU info.
+ * Only check when sample.cpu was actually populated from event
+ * data: PERF_RECORD_SAMPLE always has it when PERF_SAMPLE_CPU
+ * is set; non-sample events only have it when sample_id_all is
+ * enabled. Otherwise sample.cpu is the (u32)-1 sentinel from
+ * evsel__parse_sample() and must not be validated or clamped.
+ */
+ if ((evsel->core.attr.sample_type & PERF_SAMPLE_CPU) &&
+ (event->header.type == PERF_RECORD_SAMPLE ||
+ evsel->core.attr.sample_id_all)) {
+ int nr_cpus_avail = perf_session__env(session)->nr_cpus_avail;
+
+ /*
+ * For perf.data files the MAX_NR_CPUS fallback in
+ * perf_session__read_header() guarantees this is set.
+ * For pipe mode, HEADER_NRCPUS may arrive late or not
+ * at all (pre-2017 perf, third-party tools). Fall
+ * back to MAX_NR_CPUS so the bounds check still works
+ * against fixed-size downstream arrays.
+ */
+ if (nr_cpus_avail <= 0) {
+ nr_cpus_avail = MAX_NR_CPUS;
+ perf_session__env(session)->nr_cpus_avail = nr_cpus_avail;
+ pr_warning_once("WARNING: HEADER_NRCPUS not set, using MAX_NR_CPUS (%d) as CPU bound\n",
+ MAX_NR_CPUS);
+ }
+ if (sample.cpu >= (u32)nr_cpus_avail &&
+ sample.cpu != (u32)-1) {
+ /*
+ * Warn rather than abort: synthesized events
+ * (MMAP, COMM) lack sample_id_all data, so
+ * parse_id_sample reads garbage from the event
+ * payload. Clamping to 0 protects downstream
+ * array indexing while keeping the session alive.
+ *
+ * Preserve (u32)-1: perf script and perf inject
+ * use it as a sentinel for "CPU not applicable."
+ * Downstream array users (timechart, kwork) have
+ * their own per-callback bounds checks.
+ */
+ pr_warning_once("WARNING: sample CPU %u >= nr_cpus_avail %u, clamping to 0\n",
+ sample.cpu, nr_cpus_avail);
+ sample.cpu = 0;
+ }
+ }
ret = auxtrace__process_event(session, event, &sample, tool);
if (ret < 0)
--
2.54.0
next prev parent reply other threads:[~2026-05-10 3:36 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-10 3:33 [PATCH 00/28] perf: Harden perf.data parsing against crafted/corrupted files Arnaldo Carvalho de Melo
2026-05-10 3:33 ` [PATCH 01/28] perf session: Add minimum event size validation table Arnaldo Carvalho de Melo
2026-05-11 19:01 ` Ian Rogers
2026-05-10 3:33 ` [PATCH 02/28] perf tools: Fix event_contains() macro to verify full field extent Arnaldo Carvalho de Melo
2026-05-10 3:33 ` [PATCH 03/28] perf zstd: Fix compression error path in zstd_compress_stream_to_records() Arnaldo Carvalho de Melo
2026-05-10 3:33 ` [PATCH 04/28] perf zstd: Fix multi-iteration decompression and error handling Arnaldo Carvalho de Melo
2026-05-10 3:33 ` [PATCH 05/28] perf session: Fix PERF_RECORD_READ swap and dump for variable-length events Arnaldo Carvalho de Melo
2026-05-10 3:33 ` [PATCH 06/28] perf session: Align auxtrace_info priv size before byte-swapping Arnaldo Carvalho de Melo
2026-05-10 3:33 ` [PATCH 07/28] perf session: Add validated swap infrastructure with null-termination checks Arnaldo Carvalho de Melo
2026-05-10 3:33 ` [PATCH 08/28] perf session: Use bounded copy for PERF_RECORD_TIME_CONV Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 09/28] perf session: Validate HEADER_ATTR alignment and attr.size before swapping Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 10/28] perf session: Validate nr fields against event size on both swap and common paths Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 11/28] perf header: Byte-swap build ID event pid and bounds check section entries Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 12/28] perf cpumap: Reject RANGE_CPUS with start_cpu > end_cpu Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 13/28] perf auxtrace: Harden auxtrace_error event handling Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 14/28] perf session: Add byte-swap and bounds check for PERF_RECORD_BPF_METADATA events Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 15/28] perf header: Validate null-termination in PERF_RECORD_EVENT_UPDATE string fields Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 16/28] perf tools: Bounds check perf_event_attr fields against attr.size before printing Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 17/28] perf header: Propagate feature section processing errors Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 18/28] perf header: Validate f_attr.ids section before use in perf_session__read_header() Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 19/28] perf header: Validate feature section size and add read path bounds checking Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 20/28] perf header: Sanity check HEADER_EVENT_DESC attr.size before swap Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 21/28] perf header: Validate bitmap size before allocating in do_read_bitmap() Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 22/28] perf session: Add byte-swap for PERF_RECORD_COMPRESSED2 events Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 23/28] perf tools: Harden compressed event processing Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 24/28] perf session: Check for decompression buffer size overflow Arnaldo Carvalho de Melo
2026-05-10 3:34 ` Arnaldo Carvalho de Melo [this message]
2026-05-10 3:34 ` [PATCH 26/28] perf timechart: Bounds check cpu_id and fix topology_map allocation Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 27/28] perf kwork: Bounds check work->cpu before indexing cpus_runtime[] Arnaldo Carvalho de Melo
2026-05-10 3:34 ` [PATCH 28/28] perf test: Add truncated perf.data robustness test Arnaldo Carvalho de Melo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260510033424.255812-26-acme@kernel.org \
--to=acme@kernel.org \
--cc=acme@redhat.com \
--cc=adrian.hunter@intel.com \
--cc=irogers@google.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=kan.liang@linux.intel.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mingo@kernel.org \
--cc=namhyung@kernel.org \
--cc=noreply@anthropic.com \
--cc=sashiko-bot@kernel.org \
--cc=tglx@linutronix.de \
--cc=williams@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox