[PATCH 28/29] perf session: Snapshot event->header.size in process_user_event()
From: Arnaldo Carvalho de Melo <acme@kernel.org>
Date: 2026-05-24 03:30:08
Also in:
lkml
Subsystem:
performance events subsystem, the rest · Maintainers:
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Linus Torvalds
From: Arnaldo Carvalho de Melo <redacted> On native-endian files, events are read from MAP_SHARED memory. Multiple reads of event->header.size can return different values if the file is concurrently modified, allowing an attacker to bypass bounds checks performed on an earlier read. Snapshot header.size into a local variable at function entry using READ_ONCE() to prevent compiler rematerialization, and use it for all size-dependent arithmetic within the function. This ensures every bounds calculation uses the same value that was validated by the reader. Reported-by: sashiko-bot@kernel.org # Running on a local machine Cc: Ian Rogers <irogers@google.com> Cc: Jiri Olsa <jolsa@kernel.org> Cc: Namhyung Kim <namhyung@kernel.org> Assisted-by: Claude Opus 4.6 (1M context) [off-list ref] Signed-off-by: Arnaldo Carvalho de Melo <redacted> --- tools/perf/util/session.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-)
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index 69eb404f805d87d1..d62cb21920e042f5 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c@@ -2219,6 +2219,7 @@ static s64 perf_session__process_user_event(struct perf_session *session, { struct ordered_events *oe = &session->ordered_events; const struct perf_tool *tool = session->tool; + const u32 event_size = READ_ONCE(event->header.size); struct perf_sample sample; int fd = perf_data__fd(session->data); s64 err;
@@ -2260,7 +2261,7 @@ static s64 perf_session__process_user_event(struct perf_session *session, break; case PERF_RECORD_HEADER_BUILD_ID: if (!perf_event__check_nul(event->build_id.filename, - (void *)event + event->header.size, + (void *)event + event_size, "HEADER_BUILD_ID")) { err = 0; break;
@@ -2283,7 +2284,7 @@ static s64 perf_session__process_user_event(struct perf_session *session, * place already. */ if (!perf_data__is_pipe(session->data)) - lseek(fd, file_offset + event->header.size, SEEK_SET); + lseek(fd, file_offset + event_size, SEEK_SET); err = tool->auxtrace(tool, session, event); break; case PERF_RECORD_AUXTRACE_ERROR:
@@ -2293,14 +2294,14 @@ static s64 perf_session__process_user_event(struct perf_session *session, case PERF_RECORD_THREAD_MAP: { u64 max_nr; - if (event->header.size < sizeof(event->thread_map)) { + if (event_size < sizeof(event->thread_map)) { pr_err("PERF_RECORD_THREAD_MAP: header.size (%u) too small\n", - event->header.size); + event_size); err = -EINVAL; break; } - max_nr = (event->header.size - sizeof(event->thread_map)) / + max_nr = (event_size - sizeof(event->thread_map)) / sizeof(event->thread_map.entries[0]); if (event->thread_map.nr > max_nr) { pr_err("PERF_RECORD_THREAD_MAP: nr %" PRIu64 " exceeds max %" PRIu64 "\n",
@@ -2314,7 +2315,7 @@ static s64 perf_session__process_user_event(struct perf_session *session, } case PERF_RECORD_CPU_MAP: { struct perf_record_cpu_map_data *data = &event->cpu_map.data; - u32 payload = event->header.size - sizeof(event->header); + u32 payload = event_size - sizeof(event->header); /* * Native-endian events are mmap'd read-only, so we
@@ -2378,8 +2379,8 @@ static s64 perf_session__process_user_event(struct perf_session *session, break; } case PERF_RECORD_STAT_CONFIG: { - /* Cannot underflow: perf_event__min_size[] guarantees header.size >= sizeof */ - u64 max_nr = (event->header.size - sizeof(event->stat_config)) / + /* Cannot underflow: perf_event__min_size[] guarantees event_size >= sizeof */ + u64 max_nr = (event_size - sizeof(event->stat_config)) / sizeof(event->stat_config.data[0]); /*
@@ -2410,7 +2411,7 @@ static s64 perf_session__process_user_event(struct perf_session *session, */ memset(&session->time_conv, 0, sizeof(session->time_conv)); memcpy(&session->time_conv, &event->time_conv, - min((size_t)event->header.size, sizeof(session->time_conv))); + min((size_t)event_size, sizeof(session->time_conv))); err = tool->time_conv(tool, session, event); break; case PERF_RECORD_HEADER_FEATURE:
@@ -2427,11 +2428,10 @@ static s64 perf_session__process_user_event(struct perf_session *session, break; case PERF_RECORD_BPF_METADATA: { u64 nr_entries, max_entries; - u32 hdr_size = READ_ONCE(event->header.size); - if (hdr_size < sizeof(event->bpf_metadata)) { + if (event_size < sizeof(event->bpf_metadata)) { pr_warning("WARNING: PERF_RECORD_BPF_METADATA: header.size (%u) too small, skipping\n", - hdr_size); + event_size); err = 0; break; }
@@ -2447,9 +2447,8 @@ static s64 perf_session__process_user_event(struct perf_session *session, break; } - /* Snapshot — event is mmap'd and could change between reads */ nr_entries = READ_ONCE(event->bpf_metadata.nr_entries); - max_entries = (hdr_size - sizeof(event->bpf_metadata)) / + max_entries = (event_size - sizeof(event->bpf_metadata)) / sizeof(event->bpf_metadata.entries[0]); if (nr_entries > max_entries) { pr_warning("WARNING: PERF_RECORD_BPF_METADATA: nr_entries %" PRIu64 " exceeds max %" PRIu64 ", skipping\n",
--
2.54.0