[PATCH 11/28] perf header: Byte-swap build ID event pid and bounds check section entries
From: Arnaldo Carvalho de Melo
Date: Sat May 09 2026 - 23:39:25 EST
From: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>
perf_header__read_build_ids() swaps the event header fields for cross-endian
perf.data files but not bev.pid. This causes perf_session__findnew_machine()
to look up the wrong machine for guest VM build IDs, misattributing them.
Swap bev.pid alongside the header fields.
Also add a build_id_swap callback for stream-mode build ID events.
Harden perf_header__read_build_ids() against crafted perf.data files:
- Add overflow check on offset + size to prevent wrap past ULLONG_MAX.
- Reject bev.header.size == 0 which would loop forever.
- Reject bev.header.size > remaining section to prevent reading past
the section boundary.
- Guard memcmp(filename, "nel.kallsyms]", 13) with len >= 13 to avoid
reading uninitialized stack memory on short filenames.
- Force NUL-termination of filename before passing it to functions
like machine__findnew_dso() that use strlen/strcmp.
Reported-by: sashiko-bot@xxxxxxxxxx # Running on a local machine
Cc: Adrian Hunter <adrian.hunter@xxxxxxxxx>
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/header.c | 50 +++++++++++++++++++++++++++++++++++----
tools/perf/util/session.c | 16 ++++++++++++-
2 files changed, 61 insertions(+), 5 deletions(-)
diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
index b263f83601842736..f2198ab0defd5804 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include <errno.h>
#include <inttypes.h>
+#include <limits.h>
#include "string2.h"
#include <sys/param.h>
#include <sys/types.h>
@@ -2578,7 +2579,13 @@ static int perf_header__read_build_ids_abi_quirk(struct perf_header *header,
} old_bev;
struct perf_record_header_build_id bev;
char filename[PATH_MAX];
- u64 limit = offset + size;
+ u64 limit;
+
+ /* Prevent offset + size from wrapping past ULLONG_MAX */
+ if (size > ULLONG_MAX - offset)
+ return -1;
+
+ limit = offset + size;
while (offset < limit) {
ssize_t len;
@@ -2589,6 +2596,10 @@ static int perf_header__read_build_ids_abi_quirk(struct perf_header *header,
if (header->needs_swap)
perf_event_header__bswap(&old_bev.header);
+ /* size == 0 loops forever; size > remaining reads past section */
+ if (old_bev.header.size == 0 || old_bev.header.size > limit - offset)
+ return -1;
+
len = old_bev.header.size - sizeof(old_bev);
if (len < 0 || len >= PATH_MAX) {
pr_warning("invalid build_id filename length %zd\n", len);
@@ -2597,6 +2608,13 @@ static int perf_header__read_build_ids_abi_quirk(struct perf_header *header,
if (readn(input, filename, len) != len)
return -1;
+ /*
+ * The file data may lack a null terminator, which could
+ * indicate a corrupt or crafted perf.data file. Ensure
+ * filename is always a valid C string before passing it
+ * to functions like machine__findnew_dso().
+ */
+ filename[len] = '\0';
bev.header = old_bev.header;
@@ -2624,17 +2642,32 @@ static int perf_header__read_build_ids(struct perf_header *header,
struct perf_session *session = container_of(header, struct perf_session, header);
struct perf_record_header_build_id bev;
char filename[PATH_MAX];
- u64 limit = offset + size, orig_offset = offset;
+ u64 limit, orig_offset = offset;
int err = -1;
+ /* Prevent offset + size from wrapping past ULLONG_MAX */
+ if (size > ULLONG_MAX - offset)
+ return -1;
+
+ limit = offset + size;
+
while (offset < limit) {
ssize_t len;
if (readn(input, &bev, sizeof(bev)) != sizeof(bev))
goto out;
- if (header->needs_swap)
+ if (header->needs_swap) {
perf_event_header__bswap(&bev.header);
+ bev.pid = bswap_32(bev.pid);
+ }
+
+ /*
+ * size == 0 would loop forever (offset never advances);
+ * size > remaining would read past the section boundary.
+ */
+ if (bev.header.size == 0 || bev.header.size > limit - offset)
+ goto out;
len = bev.header.size - sizeof(bev);
if (len < 0 || len >= PATH_MAX) {
@@ -2644,6 +2677,13 @@ static int perf_header__read_build_ids(struct perf_header *header,
if (readn(input, filename, len) != len)
goto out;
+ /*
+ * The file data may lack a null terminator, which could
+ * indicate a corrupt or crafted perf.data file. Ensure
+ * filename is always a valid C string before passing it
+ * to functions like machine__findnew_dso().
+ */
+ filename[len] = '\0';
/*
* The a1645ce1 changeset:
*
@@ -2657,7 +2697,9 @@ static int perf_header__read_build_ids(struct perf_header *header,
* '[kernel.kallsyms]' string for the kernel build-id has the
* first 4 characters chopped off (where the pid_t sits).
*/
- if (memcmp(filename, "nel.kallsyms]", 13) == 0) {
+ /* Guard short filenames against memcmp reading past the buffer */
+ if (len >= (ssize_t)sizeof("nel.kallsyms]") - 1 &&
+ memcmp(filename, "nel.kallsyms]", sizeof("nel.kallsyms]") - 1) == 0) {
if (lseek(input, orig_offset, SEEK_SET) == (off_t)-1)
return -1;
return perf_header__read_build_ids_abi_quirk(header, input, offset, size);
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index fbffa61762cae801..c23899c42ef7af34 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -676,6 +676,14 @@ static int perf_event__hdr_attr_swap(union perf_event *event,
return 0;
}
+static int perf_event__build_id_swap(union perf_event *event,
+ bool sample_id_all __maybe_unused)
+{
+ /* Only pid needs swapping — build_id[] is a raw byte array */
+ event->build_id.pid = bswap_32(event->build_id.pid);
+ return 0;
+}
+
static int perf_event__event_update_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
@@ -1006,7 +1014,7 @@ static perf_event__swap_op perf_event__swap_ops[] = {
[PERF_RECORD_HEADER_ATTR] = perf_event__hdr_attr_swap,
[PERF_RECORD_HEADER_EVENT_TYPE] = perf_event__event_type_swap,
[PERF_RECORD_HEADER_TRACING_DATA] = perf_event__tracing_data_swap,
- [PERF_RECORD_HEADER_BUILD_ID] = NULL,
+ [PERF_RECORD_HEADER_BUILD_ID] = perf_event__build_id_swap,
[PERF_RECORD_HEADER_FEATURE] = perf_event__header_feature_swap,
[PERF_RECORD_ID_INDEX] = perf_event__all64_swap,
[PERF_RECORD_AUXTRACE_INFO] = perf_event__auxtrace_info_swap,
@@ -1993,6 +2001,12 @@ static s64 perf_session__process_user_event(struct perf_session *session,
err = tool->tracing_data(tool, session, event);
break;
case PERF_RECORD_HEADER_BUILD_ID:
+ if (!perf_event__check_nul(event->build_id.filename,
+ (void *)event + event->header.size,
+ "HEADER_BUILD_ID")) {
+ err = 0;
+ break;
+ }
err = tool->build_id(tool, session, event);
break;
case PERF_RECORD_FINISHED_ROUND:
--
2.54.0