[PATCH v2 1/2] perf evsel: Validate branch stack before byte swapping
From: Mark Amirkan via B4 Relay
Date: Thu Sep 03 2026 - 07:12:20 EST
From: Mark Amirkan <markdamirkan@xxxxxxxxx>
When perf reads an opposite-endian branch stack, __evsel__parse_sample()
swaps each entry before checking whether all entries fit in the event. A
truncated sample can therefore make the swap loop read and write past the
event boundary.
A truncated perf.data file makes perf report crash with SIGSEGV. ASan
reports an out-of-bounds read. A regression test puts an entry just past
the declared end and shows that its flags are changed before the parser
returns -EFAULT.
Move the bounds check before the byte-swap loop. Valid samples are handled
as before.
Fixes: 63c12ae2f246 ("perf evsel: Add bitfield_swap() to handle branch_stack endian issue")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Symbolic
Signed-off-by: Mark Amirkan <markdamirkan@xxxxxxxxx>
---
tools/perf/tests/sample-parsing.c | 48 +++++++++++++++++++++++++++++++++++++++
tools/perf/util/evsel.c | 3 ++-
2 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/tools/perf/tests/sample-parsing.c b/tools/perf/tests/sample-parsing.c
index 32dbc484487a..583951534937 100644
--- a/tools/perf/tests/sample-parsing.c
+++ b/tools/perf/tests/sample-parsing.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdbool.h>
+#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
@@ -417,6 +418,49 @@ static int do_test(u64 sample_type, u64 sample_regs, u64 read_format)
return ret;
}
+static int test_truncated_branch_stack(void)
+{
+ struct perf_event_attr attr = {
+ .sample_type = PERF_SAMPLE_BRANCH_STACK,
+ };
+ struct {
+ struct perf_event_header header;
+ u64 nr;
+ struct branch_entry entry;
+ } input = {
+ .header = {
+ .type = PERF_RECORD_SAMPLE,
+ .size = sizeof(input.header) + sizeof(input.nr),
+ },
+ .nr = 1,
+ };
+ struct perf_sample sample;
+ struct evsel *evsel;
+ u64 flags = 1;
+ int err;
+
+ input.entry.flags.value = flags;
+ evsel = evsel__new(&attr);
+ if (!evsel)
+ return -1;
+
+ evsel->sample_size = __evsel__sample_size(attr.sample_type);
+ err = __evsel__parse_sample(evsel, (union perf_event *)&input,
+ &sample, /*needs_swap=*/true);
+ perf_sample__exit(&sample);
+ evsel__put(evsel);
+
+ if (err != -EFAULT) {
+ pr_debug("truncated branch stack returned %d, expected -EFAULT\n", err);
+ return -1;
+ }
+ if (input.entry.flags.value != flags) {
+ pr_debug("truncated branch stack modified data past the event\n");
+ return -1;
+ }
+ return 0;
+}
+
/**
* test__sample_parsing - test sample parsing.
*
@@ -433,6 +477,10 @@ static int test__sample_parsing(struct test_suite *test __maybe_unused, int subt
size_t i;
int err;
+ err = test_truncated_branch_stack();
+ if (err)
+ return err;
+
/*
* Fail the test if it has not been updated when new sample format bits
* were added. Please actually update the test rather than just change
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index d4cb455f4a7d..cc0bc0857754 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3639,6 +3639,8 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
e = (struct branch_entry *)&data->branch_stack->hw_idx;
}
+ OVERFLOW_CHECK(array, sz, max_size);
+
if (swapped) {
/*
* struct branch_flag does not have endian
@@ -3654,7 +3656,6 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
e->flags.value = evsel__bitfield_swap_branch_flags(e->flags.value);
}
- OVERFLOW_CHECK(array, sz, max_size);
array = (void *)array + sz;
if (evsel__has_branch_counters(evsel)) {
--
Git-146)