Add checked decoding for variable-layout PERF_RECORD_SAMPLE payloads. The
helpers centralize sample_type handling so BRBE, LBR, and SPE readers share
the same bounds validation.
afdo_tools/ChangeLog:
* src/perf_event_sample_util.cc: New file.
* src/perf_event_sample_util.h: New file.
Signed-off-by: Kugan Vivekanandarajah <[email protected]>
Co-authored-by: Dhruv Chawla <[email protected]>
Co-authored-by: Rahul Shinde <[email protected]>
---
afdo_tools/src/perf_event_sample_util.cc | 381 +++++++++++++++++++++++
afdo_tools/src/perf_event_sample_util.h | 59 ++++
2 files changed, 440 insertions(+)
create mode 100644 afdo_tools/src/perf_event_sample_util.cc
create mode 100644 afdo_tools/src/perf_event_sample_util.h
diff --git a/afdo_tools/src/perf_event_sample_util.cc
b/afdo_tools/src/perf_event_sample_util.cc
new file mode 100644
index 00000000000..4848a2f3476
--- /dev/null
+++ b/afdo_tools/src/perf_event_sample_util.cc
@@ -0,0 +1,381 @@
+/* Copyright The GNU Toolchain Authors
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "perf_event_sample_util.h"
+
+namespace perf_data {
+namespace perf_event_sample_util {
+namespace {
+
+int
+popcount64 (uint64_t x)
+{
+ return __builtin_popcountll (x);
+}
+
+bool
+bytes_available (const uint8_t *ptr, const uint8_t *end,
+ uint64_t bytes) noexcept
+{
+ return ptr <= end && bytes <= static_cast<uint64_t> (end - ptr);
+}
+
+/* Consume PERF_SAMPLE_READ, whose size depends on read format RF. */
+bool
+skip_sample_read (const uint8_t *&ptr, const uint8_t *end, uint64_t rf)
+{
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ const uint64_t first = read_u64 (ptr);
+ ptr += sizeof (uint64_t);
+ if (rf & perf_read_format::TOTAL_TIME_ENABLED)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (rf & perf_read_format::TOTAL_TIME_RUNNING)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (rf & perf_read_format::GROUP)
+ {
+ const uint64_t nr = first;
+ size_t words_per_event = 1;
+ if (rf & perf_read_format::ID)
+ ++words_per_event;
+ if (rf & perf_read_format::LOST)
+ ++words_per_event;
+ if (nr > SIZE_MAX / sizeof (uint64_t) / words_per_event)
+ return false;
+ const size_t sz
+ = static_cast<size_t> (nr) * words_per_event * sizeof (uint64_t);
+ if (!bytes_available (ptr, end, sz))
+ return false;
+ ptr += sz;
+ }
+ else
+ {
+ /* FIRST was the non-group value. ID and LOST are optional. */
+ if (rf & perf_read_format::ID)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (rf & perf_read_format::LOST)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ }
+ return true;
+}
+
+} /* anonymous namespace. */
+
+bool
+walk_sample_before_branch (const uint8_t *&ptr, const uint8_t *end,
+ uint64_t sample_ty, uint64_t read_format,
+ uint64_t *out_ip, bool *out_have_ip,
+ uint32_t *out_pid, uint32_t *out_tid,
+ uint64_t *out_period, uint64_t *out_time)
+{
+ uint64_t ip = 0;
+ uint32_t pid = 0;
+ uint32_t tid = 0;
+ uint64_t period = 0;
+ uint64_t time = 0;
+ bool have_ip = false;
+ if (out_ip)
+ *out_ip = 0;
+ if (out_have_ip)
+ *out_have_ip = false;
+ if (out_pid)
+ *out_pid = 0;
+ if (out_tid)
+ *out_tid = 0;
+ if (out_period)
+ *out_period = 0;
+ if (out_time)
+ *out_time = 0;
+
+ if (sample_ty & perf_sample_type::IDENTIFIER)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::IP)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ip = read_u64 (ptr);
+ have_ip = true;
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::TID)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ pid = read_u32 (ptr);
+ tid = read_u32 (ptr + sizeof (uint32_t));
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::TIME)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ time = read_u64 (ptr);
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::ADDR)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::ID)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::STREAM_ID)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::CPU)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::PERIOD)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ period = read_u64 (ptr);
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::READ)
+ {
+ if (!skip_sample_read (ptr, end, read_format))
+ return false;
+ }
+ if (sample_ty & perf_sample_type::CALLCHAIN)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ uint64_t nr = read_u64 (ptr);
+ ptr += sizeof (uint64_t);
+ if (nr > SIZE_MAX / sizeof (uint64_t))
+ return false;
+ const size_t sz = static_cast<size_t> (nr) * sizeof (uint64_t);
+ if (!bytes_available (ptr, end, sz))
+ return false;
+ ptr += sz;
+ }
+ if (sample_ty & perf_sample_type::RAW)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint32_t)))
+ return false;
+ uint32_t raw_size = read_u32 (ptr);
+ ptr += sizeof (uint32_t);
+ if (!bytes_available (ptr, end, raw_size))
+ return false;
+ ptr += raw_size;
+ }
+
+ if (out_ip)
+ *out_ip = ip;
+ if (out_have_ip)
+ *out_have_ip = have_ip;
+ if (out_pid)
+ *out_pid = pid;
+ if (out_tid)
+ *out_tid = tid;
+ if (out_period)
+ *out_period = period;
+ if (out_time)
+ *out_time = time;
+ return true;
+}
+
+bool
+walk_sample_branch_stack (const uint8_t *&ptr, const uint8_t *end,
+ uint64_t branch_sample_type, uint64_t sample_ty,
+ uint64_t *out_nr,
+ const perf_branch_stack_entry **out_entries)
+{
+ if (!(sample_ty & perf_sample_type::BRANCH_STACK))
+ {
+ if (out_nr)
+ *out_nr = 0;
+ if (out_entries)
+ *out_entries = nullptr;
+ return true;
+ }
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ uint64_t nr = read_u64 (ptr);
+ ptr += sizeof (uint64_t);
+ if (branch_sample_type & perf_branch_stack_format::HW_INDEX)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (nr > SIZE_MAX / BRANCH_ENTRY_BYTES)
+ return false;
+ const size_t esz = static_cast<size_t> (nr) * BRANCH_ENTRY_BYTES;
+ if (!bytes_available (ptr, end, esz))
+ return false;
+ const perf_branch_stack_entry *entries
+ = nr > 0 ? reinterpret_cast<const perf_branch_stack_entry *> (ptr)
+ : nullptr;
+ ptr += esz;
+ if (branch_sample_type & perf_branch_stack_format::COUNTERS)
+ {
+ if (nr > SIZE_MAX / sizeof (uint64_t))
+ return false;
+ const size_t csz = static_cast<size_t> (nr) * sizeof (uint64_t);
+ if (!bytes_available (ptr, end, csz))
+ return false;
+ ptr += csz;
+ }
+ if (out_nr)
+ *out_nr = nr;
+ if (out_entries)
+ *out_entries = entries;
+ return true;
+}
+
+bool
+walk_sample_after_branch (const uint8_t *&ptr, const uint8_t *end,
+ uint64_t sample_ty, uint64_t sample_regs_user,
+ uint64_t sample_regs_intr)
+{
+ if (sample_ty & perf_sample_type::REGS_USER)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ uint64_t abi = read_u64 (ptr);
+ ptr += sizeof (uint64_t);
+ if (abi != 0)
+ {
+ const size_t rsz = static_cast<size_t> (popcount64 (sample_regs_user))
+ * sizeof (uint64_t);
+ if (!bytes_available (ptr, end, rsz))
+ return false;
+ ptr += rsz;
+ }
+ }
+ if (sample_ty & perf_sample_type::STACK_USER)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ uint64_t stack_sz = read_u64 (ptr);
+ ptr += sizeof (uint64_t);
+ if (stack_sz != 0)
+ {
+ if (!bytes_available (ptr, end, stack_sz))
+ return false;
+ ptr += static_cast<size_t> (stack_sz);
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ }
+ if (sample_ty & perf_sample_type::WEIGHT_TYPE)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::DATA_SRC)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::TRANSACTION)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::REGS_INTR)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ uint64_t abi = read_u64 (ptr);
+ ptr += sizeof (uint64_t);
+ if (abi != 0)
+ {
+ const size_t rsz = static_cast<size_t> (popcount64 (sample_regs_intr))
+ * sizeof (uint64_t);
+ if (!bytes_available (ptr, end, rsz))
+ return false;
+ ptr += rsz;
+ }
+ }
+ if (sample_ty & perf_sample_type::PHYS_ADDR)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::CGROUP)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::DATA_PAGE_SIZE)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::CODE_PAGE_SIZE)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ ptr += sizeof (uint64_t);
+ }
+ if (sample_ty & perf_sample_type::AUX)
+ {
+ if (!bytes_available (ptr, end, sizeof (uint64_t)))
+ return false;
+ uint64_t aux_sz = read_u64 (ptr);
+ ptr += sizeof (uint64_t);
+ if (!bytes_available (ptr, end, aux_sz))
+ return false;
+ ptr += static_cast<size_t> (aux_sz);
+ }
+ return true;
+}
+
+} /* namespace perf_event_sample_util. */
+} /* namespace perf_data. */
diff --git a/afdo_tools/src/perf_event_sample_util.h
b/afdo_tools/src/perf_event_sample_util.h
new file mode 100644
index 00000000000..e549046b9d3
--- /dev/null
+++ b/afdo_tools/src/perf_event_sample_util.h
@@ -0,0 +1,59 @@
+/* Copyright The GNU Toolchain Authors
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_PERF_EVENT_SAMPLE_UTIL_H
+#define GCC_PERF_EVENT_SAMPLE_UTIL_H
+
+#include "perf.h"
+
+#include <cstddef>
+#include <cstdint>
+
+namespace perf_data {
+namespace perf_event_sample_util {
+
+/* Sample payload walkers in sample_type field order; false on truncation. */
+
+constexpr unsigned BRANCH_STACK_U64_PER_ENTRY = 3;
+constexpr size_t BRANCH_ENTRY_BYTES
+ = static_cast<size_t> (BRANCH_STACK_U64_PER_ENTRY) * sizeof (uint64_t);
+
+/* Consume fields before PERF_SAMPLE_BRANCH_STACK and return selected
+ values. */
+bool walk_sample_before_branch (const uint8_t *&ptr, const uint8_t *end,
+ uint64_t sample_ty, uint64_t read_format,
+ uint64_t *out_ip, bool *out_have_ip,
+ uint32_t *out_pid, uint32_t *out_tid,
+ uint64_t *out_period, uint64_t *out_time);
+
+/* Consume PERF_SAMPLE_BRANCH_STACK and return its entries. Leave PTR
+ unchanged when the field is absent. */
+bool walk_sample_branch_stack (const uint8_t *&ptr, const uint8_t *end,
+ uint64_t branch_sample_type, uint64_t sample_ty,
+ uint64_t *out_nr,
+ const perf_branch_stack_entry **out_entries);
+
+/* Consume fields after PERF_SAMPLE_BRANCH_STACK. */
+bool walk_sample_after_branch (const uint8_t *&ptr, const uint8_t *end,
+ uint64_t sample_ty, uint64_t sample_regs_user,
+ uint64_t sample_regs_intr);
+
+} /* namespace perf_event_sample_util. */
+} /* namespace perf_data. */
+
+#endif /* GCC_PERF_EVENT_SAMPLE_UTIL_H. */
--
2.34.1