[PATCH 02/29] perf session: Bounds-check one_mmap event pointer in peek_event

From: Arnaldo Carvalho de Melo

Date: Sun May 24 2026 - 21:08:08 EST


From: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>

perf_session__peek_event() computes an event pointer directly from
file_offset when one_mmap is active, without verifying that file_offset
and the subsequent event->header.size fall within the mapped region.
A corrupted perf.data file could cause out-of-bounds memory reads.

Add one_mmap_size to the session struct and validate both the header
and full event fit within the mmap before dereferencing.

Reported-by: sashiko-bot@xxxxxxxxxx # Running on a local machine
Cc: Ian Rogers <irogers@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/session.c | 29 ++++++++++++++++++++++++++---
tools/perf/util/session.h | 2 ++
2 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index 0523fd243e02c09b..c4cd8ad6d810a74c 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -1887,12 +1887,27 @@ int perf_session__peek_event(struct perf_session *session, off_t file_offset,
*event_ptr = NULL;

if (session->one_mmap && !session->header.needs_swap) {
- event = file_offset - session->one_mmap_offset +
- session->one_mmap_addr;
+ u64 offset_in_mmap;
+
+ /* Validate offset with integer arithmetic to avoid pointer UB */
+ if ((u64)file_offset < session->one_mmap_offset)
+ return -1;
+
+ offset_in_mmap = (u64)file_offset - session->one_mmap_offset;
+
+ /* Use subtraction to avoid addition overflow */
+ if (offset_in_mmap >= session->one_mmap_size ||
+ session->one_mmap_size - offset_in_mmap < sizeof(struct perf_event_header))
+ return -1;
+
+ event = session->one_mmap_addr + offset_in_mmap;

- /* Every event must at least contain its own header */
if (event->header.size < sizeof(struct perf_event_header))
return -1;
+
+ /* Ensure full event is within the mmap region */
+ if (session->one_mmap_size - offset_in_mmap < event->header.size)
+ return -1;
} else {
if (perf_data__is_pipe(session->data))
return -1;
@@ -2560,6 +2575,14 @@ reader__mmap(struct reader *rd, struct perf_session *session)
if (session->one_mmap) {
session->one_mmap_addr = buf;
session->one_mmap_offset = rd->file_offset;
+ /*
+ * mmap_size was set to the full file extent (data_offset +
+ * data_size) but file_offset was shifted forward by
+ * page_offset for page alignment. Reduce by page_offset
+ * so the bounds check reflects the file-backed portion
+ * of the mapping — pages beyond the file cause SIGBUS.
+ */
+ session->one_mmap_size = rd->mmap_size - page_offset;
}

return 0;
diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h
index f05f0d4a6c238dc8..d554e2a1a50ed304 100644
--- a/tools/perf/util/session.h
+++ b/tools/perf/util/session.h
@@ -71,6 +71,8 @@ struct perf_session {
void *one_mmap_addr;
/** @one_mmap_offset: File offset in perf.data file when mapped. */
u64 one_mmap_offset;
+ /** @one_mmap_size: Size of the single mmap in bytes. */
+ u64 one_mmap_size;
/** @ordered_events: Used to turn unordered events into ordered ones. */
struct ordered_events ordered_events;
/** @data: Optional perf data file being read from. */
--
2.54.0