[PATCH v11 31/33] perf evsel: Add bounds checking to trace point raw data accessors

Ian Rogers
2026-04-13T01:23:41Z

Avoid a tracepoint field accidentally reading out of bounds by
checking the size of read fits. This was prompted by Sashiko review
feedback about the potential. Properly compute the size for dynamic
fields using the high 16-bits.

Signed-off-by: Ian Rogers <irogers@google.com>
---
I suspect the int field handling should also incorporate these
changes, but I've stopped with just rawptr's (which also includes
strings) as those are the only current fields that support dynamic and
relative.
---
tools/perf/util/evsel.c | 50 ++++++++++++++++++++++++++++++++++++-----
1 file changed, 45 insertions(+), 5 deletions(-)

diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index bb48568b8101..a23bd28579b5 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3700,22 +3700,59 @@ struct tep_format_field *evsel__common_field(struct evsel *evsel, const char *na
return tp_format ? tep_find_common_field(tp_format, name) : NULL;
}

+static bool out_of_bounds(const struct tep_format_field *field, int offset, int size, u32 raw_size)
+{
+ if (offset < 0) {
+ pr_warning("Negative trace point field offset %d in %s\n",
+ offset, field->name);
+ return true;
+ }
+ if (size < 0) {
+ pr_warning("Negative trace point field size %d in %s\n",
+ size, field->name);
+ return true;
+ }
+ if ((u32)offset + (u32)size > raw_size) {
+ pr_warning("Out of bound tracepoint field (%s) offset %d size %d in %u\n",
+ field->name, offset, size, raw_size);
+ return true;
+ }
+ return false;
+}
+
void *perf_sample__rawptr(struct perf_sample *sample, const char *name)
{
struct tep_format_field *field = evsel__field(sample->evsel, name);
- int offset;
+ int offset, size;

if (!field)
return NULL;

offset = field->offset;
-
+ size = field->size;
if (field->flags & TEP_FIELD_IS_DYNAMIC) {
- offset = *(int *)(sample->raw_data + field->offset);
- offset &= 0xffff;
- if (tep_field_is_relative(field->flags))
+ int dynamic_data;
+
+ if (out_of_bounds(field, offset, 4, sample->raw_size))
+ return NULL;
+
+ dynamic_data = *(int *)(sample->raw_data + field->offset);
+ offset = dynamic_data & 0xffff;
+ size = (dynamic_data >> 16) & 0xffff;
+
+ if (tep_field_is_relative(field->flags)) {
+ /*
+ * Newer kernel feature: Relative offsets (__rel_loc).
+ * If the relative flag is set, the parsed offset is not
+ * absolute from the start of the record. Instead, it is
+ * relative to the *end* of the dynamic field descriptor
+ * itself.
+ */
offset += field->offset + field->size;
+ }
}
+ if (out_of_bounds(field, offset, size, sample->raw_size))
+ return NULL;

return sample->raw_data + offset;
}
@@ -3726,6 +3763,9 @@ u64 format_field__intval(struct tep_format_field *field, struct perf_sample *sam
u64 value;
void *ptr = sample->raw_data + field->offset;

+ if (out_of_bounds(field, field->offset, field->size, sample->raw_size))
+ return 0;
+
switch (field->size) {
case 1:
return *(u8 *)ptr;
--
2.53.0.1213.gd9a14994de-goog