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 18/28] perf header: Validate f_attr.ids section before use in perf_session__read_header()
Date: Sun, 10 May 2026 00:34:09 -0300 [thread overview]
Message-ID: <20260510033424.255812-19-acme@kernel.org> (raw)
In-Reply-To: <20260510033424.255812-1-acme@kernel.org>
From: Arnaldo Carvalho de Melo <acme@redhat.com>
perf_session__read_header() reads f_attr.ids.size from the perf.data
file and divides it by sizeof(u64) to compute nr_ids, which is
declared as int. No validation is performed on the value before it
is used to allocate arrays and drive a read loop.
On 32-bit architectures, a crafted f_attr.ids.size of 0x100000000
(4 GB) produces nr_ids = 0x20000000, but the allocation size
1 * 0x20000000 * 8 overflows size_t to 0, so zalloc(0) returns a
valid pointer. The subsequent loop writes 0x20000000 IDs into that
zero-length buffer, corrupting the heap.
On 64-bit, the u64-to-int truncation silently drops high bits,
processing fewer IDs than the file claims. While not exploitable,
this is a data integrity issue.
Add validation before using f_attr.ids:
- Cap nr_attrs (attrs.size / attr_size) to MAX_NR_ATTRS (1 << 16)
with overflow-safe u64 comparison before assigning to int
- Reject ids.size not aligned to sizeof(u64)
- Cap ids.size / sizeof(u64) to MAX_IDS_PER_ATTR (1 << 24) to
prevent int truncation and size_t overflow on 32-bit
- Reject ids sections that extend past the end of the file,
guarded by S_ISREG() so non-regular files (block devices,
pipes) are not falsely rejected
Also fix perf_header__getbuffer64() to set errno = EIO when
readn() returns 0 (EOF). Without this, the out_errno path in
perf_session__read_header() returns -errno which is 0 (success)
on truncated files, causing downstream NULL dereferences.
Reported-by: sashiko-bot@kernel.org # Running on a local machine
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Ian Rogers <irogers@google.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Assisted-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
| 78 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 77 insertions(+), 1 deletion(-)
--git a/tools/perf/util/header.c b/tools/perf/util/header.c
index 5cbeda0335f1140c..f4008878bd7eda04 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -64,6 +64,25 @@
#include <event-parse.h>
#endif
+/*
+ * nr_ids * sizeof(struct perf_sample_id) must not overflow
+ * size_t on 32-bit; the struct is ~104 bytes (32-bit) or
+ * ~184 bytes (64-bit), so 1<<24 (16M) keeps the product
+ * under 2 GB on 32-bit.
+ *
+ * This is a per-attribute cap only — the total across all
+ * attributes is not capped because legitimate high-core-count
+ * workloads (e.g. 5000 tracepoints × 4096 CPUs) can exceed
+ * a single-attribute limit.
+ */
+#define MAX_IDS_PER_ATTR (1 << 24)
+/*
+ * Cap nr_attrs to prevent resource exhaustion from crafted
+ * files. 65536 is well beyond any real workload (perf stat
+ * typically uses < 100 events) but prevents u64-to-int
+ * truncation on the attr count.
+ */
+#define MAX_NR_ATTRS (1 << 16)
#define MAX_BPF_DATA_LEN (256 * 1024 * 1024)
#define MAX_BPF_PROGS 131072
#define MAX_CACHE_ENTRIES 32768
@@ -4468,8 +4487,13 @@ int perf_session__inject_header(struct perf_session *session,
static int perf_header__getbuffer64(struct perf_header *header,
int fd, void *buf, size_t size)
{
- if (readn(fd, buf, size) <= 0)
+ ssize_t n = readn(fd, buf, size);
+
+ if (n <= 0) {
+ if (n == 0)
+ errno = EIO;
return -1;
+ }
if (header->needs_swap)
mem_bswap_64(buf, size);
@@ -4803,6 +4827,8 @@ static int read_attr(int fd, struct perf_header *ph,
if (ret <= 0) {
pr_debug("cannot read %d bytes of header attr\n",
PERF_ATTR_SIZE_VER0);
+ if (ret == 0)
+ errno = EIO;
return -1;
}
@@ -4903,6 +4929,7 @@ int perf_session__read_header(struct perf_session *session)
struct perf_file_header f_header;
struct perf_file_attr f_attr;
u64 f_id;
+ struct stat input_stat;
int nr_attrs, nr_ids, i, j, err = -ENOMEM;
int fd = perf_data__fd(data);
@@ -4952,6 +4979,15 @@ int perf_session__read_header(struct perf_session *session)
return -EINVAL;
}
+ if (fstat(fd, &input_stat) < 0)
+ return -errno;
+
+ /* Check before assigning to int to avoid u64-to-int truncation */
+ if (f_header.attrs.size / f_header.attr_size > MAX_NR_ATTRS) {
+ pr_err("Too many attributes: %" PRIu64 " (max %d)\n",
+ f_header.attrs.size / f_header.attr_size, MAX_NR_ATTRS);
+ return -EINVAL;
+ }
nr_attrs = f_header.attrs.size / f_header.attr_size;
lseek(fd, f_header.attrs.offset, SEEK_SET);
@@ -4968,6 +5004,45 @@ int perf_session__read_header(struct perf_session *session)
perf_event__attr_swap(&f_attr.attr);
}
+ /*
+ * Validate ids section: must be aligned to u64, and
+ * the count must fit in an int to avoid truncation in
+ * nr_ids and size_t overflow in perf_evsel__alloc_id()
+ * on 32-bit architectures.
+ */
+ if (f_attr.ids.size % sizeof(u64)) {
+ pr_err("Invalid ids section size %" PRIu64 " for attr %d, not aligned to u64\n",
+ f_attr.ids.size, i);
+ err = -EINVAL;
+ goto out_delete_evlist;
+ }
+
+ /*
+ * Cap the ID count to avoid int truncation of nr_ids
+ * on 64-bit and size_t overflow in the allocation
+ * paths (nr_ids * sizeof(u64), nr_ids *
+ * sizeof(struct perf_sample_id)) on 32-bit.
+ */
+ if (f_attr.ids.size / sizeof(u64) > MAX_IDS_PER_ATTR) {
+ pr_err("Invalid ids section size %" PRIu64 " for attr %d, too many IDs\n",
+ f_attr.ids.size, i);
+ err = -EINVAL;
+ goto out_delete_evlist;
+ }
+
+ /*
+ * FIXME: see perf_header__process_sections() — block
+ * devices bypass this check because st_size is 0.
+ */
+ if (S_ISREG(input_stat.st_mode) &&
+ (f_attr.ids.offset > (u64)input_stat.st_size ||
+ f_attr.ids.size > (u64)input_stat.st_size - f_attr.ids.offset)) {
+ pr_err("Invalid ids section for attr %d: offset=%" PRIu64 " size=%" PRIu64 " exceeds file size %" PRIu64 "\n",
+ i, f_attr.ids.offset, f_attr.ids.size, (u64)input_stat.st_size);
+ err = -EINVAL;
+ goto out_delete_evlist;
+ }
+
tmp = lseek(fd, 0, SEEK_CUR);
evsel = evsel__new(&f_attr.attr);
@@ -4982,6 +5057,7 @@ int perf_session__read_header(struct perf_session *session)
evlist__add(session->evlist, evsel);
nr_ids = f_attr.ids.size / sizeof(u64);
+
/*
* We don't have the cpu and thread maps on the header, so
* for allocating the perf_sample_id table we fake 1 cpu and
--
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 ` Arnaldo Carvalho de Melo [this message]
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 ` [PATCH 25/28] perf session: Bound nr_cpus_avail and validate sample CPU Arnaldo Carvalho de Melo
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-19-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