perf session: Add validated swap infrastructure with null-termination checks

Change swap callbacks from void to int return so handlers can
propagate errors.  All 28 existing handlers are converted to
return 0 on success, -1 on error.  Three new handlers (KSYMBOL,
BPF_EVENT, HEADER_FEATURE) are added returning int from the
start, with sample_id_all handling for the kernel event types.

event_swap() propagates the return to its callers (process_event
and peek_event), which skip events that fail to swap.

Add perf_event__check_nul() for null-termination enforcement
on the common event delivery path for MMAP, MMAP2, COMM,
CGROUP, and KSYMBOL events.  Events with
unterminated strings are skipped — native-endian files are
mapped read-only, so writing a NUL byte in place would segfault.

Swap handler hardening:

 - Use strnlen bounded by event size (instead of strlen) in
   COMM/MMAP/MMAP2/CGROUP swap handlers, returning -1 on
   unterminated strings.

 - Bounds check text_poke old_len+new_len before computing the
   sample_id offset, returning -1 on overflow.  Use offsetof()
   for the native-path check in machines__deliver_event() since
   sizeof() includes struct padding past the flexible array.

 - Fix PERF_RECORD_SWITCH sample_id_all: non-CPU_WIDE SWITCH
   events have sample_id immediately after the 8-byte header,
   not at sizeof(struct perf_record_switch) which is the
   CPU_WIDE variant size.

 - Fix perf_event__time_conv_swap(): decouple time_cycles and
   time_mask into independent per-field event_contains() checks,
   so each field is only swapped when the event is large enough
   to contain it.  The original code guarded both fields under
   a single time_cycles check, which would swap time_mask on a
   short event that contains time_cycles but not time_mask.

 - Handle ABI0 (attr.size == 0) in perf_event__attr_swap()
   by substituting PERF_ATTR_SIZE_VER0, so bswap_safe()
   correctly swaps VER0 fields instead of skipping everything.

 - peek_events: on swap failure, advance past the malformed
   entry instead of aborting the loop.

Note: the nr-field bounds checks for namespaces, thread_map,
cpu_map, and stat_config arrays are added by a subsequent
patch ("perf session: Validate nr fields against event size
on both swap and common paths").  The HEADER_ATTR attr.size
validation is added by ("perf session: Validate HEADER_ATTR
attr.size before swapping").

By establishing the int-returning swap infrastructure first,
all subsequent hardening patches can use direct error returns
from day one — no poison values, no workarounds for void return.

Changes in v2:
- peek_events: abort instead of skip for AUXTRACE events on
  validation failure — skipping only header.size would land
  inside the raw trace payload, causing subsequent iterations
  to misparse data as events (Reported-by: sashiko-bot@kernel.org)

Fixes: 9aa0bfa370 ("perf tools: Handle PERF_RECORD_KSYMBOL")
Fixes: 45178a928a ("perf tools: Handle PERF_RECORD_BPF_EVENT")
Fixes: e9def1b2e7 ("perf tools: Add feature header record to pipe-mode")
Reported-by: sashiko-bot@kernel.org # Running on a local machine
Reviewed-by: Ian Rogers <irogers@google.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: David Carrillo-Cisneros <davidcc@google.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Song Liu <songliubraving@fb.com>
Assisted-by: Claude:claude-opus-4.6-1m
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Arnaldo Carvalho de Melo 2026-05-02 12:47:46 -03:00
parent 315c81b4b9
commit 981f13ae62

View File

@ -290,28 +290,44 @@ static void swap_sample_id_all(union perf_event *event, void *data)
mem_bswap_64(data, size);
}
static void perf_event__all64_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__all64_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
struct perf_event_header *hdr = &event->header;
mem_bswap_64(hdr + 1, event->header.size - sizeof(*hdr));
size_t size = event->header.size - sizeof(*hdr);
/* mem_bswap_64 rounds up to 8-byte chunks — unaligned size overruns the buffer */
if (size % sizeof(u64))
return -1;
mem_bswap_64(hdr + 1, size);
return 0;
}
static void perf_event__comm_swap(union perf_event *event, bool sample_id_all)
static int perf_event__comm_swap(union perf_event *event, bool sample_id_all)
{
event->comm.pid = bswap_32(event->comm.pid);
event->comm.tid = bswap_32(event->comm.tid);
if (sample_id_all) {
void *data = &event->comm.comm;
void *end = (void *)event + event->header.size;
size_t len = strnlen(data, end - data);
data += PERF_ALIGN(strlen(data) + 1, sizeof(u64));
/*
* No NUL within the event boundary can't locate where
* sample_id_all starts. Reject so the event is skipped
* rather than swapping garbage.
*/
if (len == (size_t)(end - data))
return -1;
data += PERF_ALIGN(len + 1, sizeof(u64));
swap_sample_id_all(event, data);
}
return 0;
}
static void perf_event__mmap_swap(union perf_event *event,
bool sample_id_all)
static int perf_event__mmap_swap(union perf_event *event,
bool sample_id_all)
{
event->mmap.pid = bswap_32(event->mmap.pid);
event->mmap.tid = bswap_32(event->mmap.tid);
@ -321,13 +337,19 @@ static void perf_event__mmap_swap(union perf_event *event,
if (sample_id_all) {
void *data = &event->mmap.filename;
void *end = (void *)event + event->header.size;
size_t len = strnlen(data, end - data);
data += PERF_ALIGN(strlen(data) + 1, sizeof(u64));
/* See comment in perf_event__comm_swap() */
if (len == (size_t)(end - data))
return -1;
data += PERF_ALIGN(len + 1, sizeof(u64));
swap_sample_id_all(event, data);
}
return 0;
}
static void perf_event__mmap2_swap(union perf_event *event,
static int perf_event__mmap2_swap(union perf_event *event,
bool sample_id_all)
{
event->mmap2.pid = bswap_32(event->mmap2.pid);
@ -345,12 +367,19 @@ static void perf_event__mmap2_swap(union perf_event *event,
if (sample_id_all) {
void *data = &event->mmap2.filename;
void *end = (void *)event + event->header.size;
size_t len = strnlen(data, end - data);
data += PERF_ALIGN(strlen(data) + 1, sizeof(u64));
/* See comment in perf_event__comm_swap() */
if (len == (size_t)(end - data))
return -1;
data += PERF_ALIGN(len + 1, sizeof(u64));
swap_sample_id_all(event, data);
}
return 0;
}
static void perf_event__task_swap(union perf_event *event, bool sample_id_all)
static int perf_event__task_swap(union perf_event *event, bool sample_id_all)
{
event->fork.pid = bswap_32(event->fork.pid);
event->fork.tid = bswap_32(event->fork.tid);
@ -360,10 +389,11 @@ static void perf_event__task_swap(union perf_event *event, bool sample_id_all)
if (sample_id_all)
swap_sample_id_all(event, &event->fork + 1);
return 0;
}
static void perf_event__read_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__read_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
size_t tail;
@ -378,11 +408,12 @@ static void perf_event__read_swap(union perf_event *event,
tail = event->header.size - offsetof(struct perf_record_read, value);
/* mem_bswap_64 rounds up to 8-byte chunks — unaligned tail overruns the buffer */
if (tail % sizeof(u64))
return;
return -1;
mem_bswap_64(&event->read.value, tail);
return 0;
}
static void perf_event__aux_swap(union perf_event *event, bool sample_id_all)
static int perf_event__aux_swap(union perf_event *event, bool sample_id_all)
{
event->aux.aux_offset = bswap_64(event->aux.aux_offset);
event->aux.aux_size = bswap_64(event->aux.aux_size);
@ -390,19 +421,21 @@ static void perf_event__aux_swap(union perf_event *event, bool sample_id_all)
if (sample_id_all)
swap_sample_id_all(event, &event->aux + 1);
return 0;
}
static void perf_event__itrace_start_swap(union perf_event *event,
bool sample_id_all)
static int perf_event__itrace_start_swap(union perf_event *event,
bool sample_id_all)
{
event->itrace_start.pid = bswap_32(event->itrace_start.pid);
event->itrace_start.tid = bswap_32(event->itrace_start.tid);
if (sample_id_all)
swap_sample_id_all(event, &event->itrace_start + 1);
return 0;
}
static void perf_event__switch_swap(union perf_event *event, bool sample_id_all)
static int perf_event__switch_swap(union perf_event *event, bool sample_id_all)
{
if (event->header.type == PERF_RECORD_SWITCH_CPU_WIDE) {
event->context_switch.next_prev_pid =
@ -411,30 +444,45 @@ static void perf_event__switch_swap(union perf_event *event, bool sample_id_all)
bswap_32(event->context_switch.next_prev_tid);
}
if (sample_id_all)
swap_sample_id_all(event, &event->context_switch + 1);
if (sample_id_all) {
/*
* PERF_RECORD_SWITCH has no fields beyond the header;
* SWITCH_CPU_WIDE adds pid/tid. Use the right offset
* so sample_id starts at the correct position.
*/
if (event->header.type == PERF_RECORD_SWITCH)
swap_sample_id_all(event, (void *)event + sizeof(event->header));
else
swap_sample_id_all(event, &event->context_switch + 1);
}
return 0;
}
static void perf_event__text_poke_swap(union perf_event *event, bool sample_id_all)
static int perf_event__text_poke_swap(union perf_event *event, bool sample_id_all)
{
event->text_poke.addr = bswap_64(event->text_poke.addr);
event->text_poke.old_len = bswap_16(event->text_poke.old_len);
event->text_poke.new_len = bswap_16(event->text_poke.new_len);
if (sample_id_all) {
void *data = &event->text_poke.old_len;
void *end = (void *)event + event->header.size;
size_t len = sizeof(event->text_poke.old_len) +
sizeof(event->text_poke.new_len) +
event->text_poke.old_len +
event->text_poke.new_len;
void *data = &event->text_poke.old_len;
/* old_len + new_len exceeds event — can't find sample_id_all */
if (data + len > end)
return -1;
data += PERF_ALIGN(len, sizeof(u64));
swap_sample_id_all(event, data);
}
return 0;
}
static void perf_event__throttle_swap(union perf_event *event,
bool sample_id_all)
static int perf_event__throttle_swap(union perf_event *event,
bool sample_id_all)
{
event->throttle.time = bswap_64(event->throttle.time);
event->throttle.id = bswap_64(event->throttle.id);
@ -442,10 +490,11 @@ static void perf_event__throttle_swap(union perf_event *event,
if (sample_id_all)
swap_sample_id_all(event, &event->throttle + 1);
return 0;
}
static void perf_event__namespaces_swap(union perf_event *event,
bool sample_id_all)
static int perf_event__namespaces_swap(union perf_event *event,
bool sample_id_all)
{
u64 i;
@ -462,18 +511,25 @@ static void perf_event__namespaces_swap(union perf_event *event,
if (sample_id_all)
swap_sample_id_all(event, &event->namespaces.link_info[i]);
return 0;
}
static void perf_event__cgroup_swap(union perf_event *event, bool sample_id_all)
static int perf_event__cgroup_swap(union perf_event *event, bool sample_id_all)
{
event->cgroup.id = bswap_64(event->cgroup.id);
if (sample_id_all) {
void *data = &event->cgroup.path;
void *end = (void *)event + event->header.size;
size_t len = strnlen(data, end - data);
data += PERF_ALIGN(strlen(data) + 1, sizeof(u64));
/* See comment in perf_event__comm_swap() */
if (len == (size_t)(end - data))
return -1;
data += PERF_ALIGN(len + 1, sizeof(u64));
swap_sample_id_all(event, data);
}
return 0;
}
static u8 revbyte(u8 b)
@ -514,9 +570,19 @@ void perf_event__attr_swap(struct perf_event_attr *attr)
attr->type = bswap_32(attr->type);
attr->size = bswap_32(attr->size);
#define bswap_safe(f, n) \
(attr->size > (offsetof(struct perf_event_attr, f) + \
sizeof(attr->f) * (n)))
/*
* ABI0: size == 0 means the producer didn't set it.
* Assume PERF_ATTR_SIZE_VER0 so bswap_safe() below
* correctly swaps the VER0 fields instead of skipping
* everything. Same convention as read_attr().
*/
if (!attr->size)
attr->size = PERF_ATTR_SIZE_VER0;
/* Verify the full field extent fits, not just its start offset */
#define bswap_safe(f, n) \
(attr->size >= (offsetof(struct perf_event_attr, f) + \
sizeof(attr->f) * ((n) + 1)))
#define bswap_field(f, sz) \
do { \
if (bswap_safe(f, 0)) \
@ -554,8 +620,8 @@ do { \
#undef bswap_safe
}
static void perf_event__hdr_attr_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__hdr_attr_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
size_t size;
@ -564,30 +630,34 @@ static void perf_event__hdr_attr_swap(union perf_event *event,
size = event->header.size;
size -= perf_record_header_attr_id(event) - (void *)event;
mem_bswap_64(perf_record_header_attr_id(event), size);
return 0;
}
static void perf_event__event_update_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__event_update_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
event->event_update.type = bswap_64(event->event_update.type);
event->event_update.id = bswap_64(event->event_update.id);
return 0;
}
static void perf_event__event_type_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__event_type_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
event->event_type.event_type.event_id =
bswap_64(event->event_type.event_type.event_id);
return 0;
}
static void perf_event__tracing_data_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__tracing_data_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
event->tracing_data.size = bswap_32(event->tracing_data.size);
return 0;
}
static void perf_event__auxtrace_info_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__auxtrace_info_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
size_t size;
@ -596,10 +666,11 @@ static void perf_event__auxtrace_info_swap(union perf_event *event,
size = event->header.size;
size -= (void *)&event->auxtrace_info.priv - (void *)event;
mem_bswap_64(event->auxtrace_info.priv, size);
return 0;
}
static void perf_event__auxtrace_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__auxtrace_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
event->auxtrace.size = bswap_64(event->auxtrace.size);
event->auxtrace.offset = bswap_64(event->auxtrace.offset);
@ -607,10 +678,11 @@ static void perf_event__auxtrace_swap(union perf_event *event,
event->auxtrace.idx = bswap_32(event->auxtrace.idx);
event->auxtrace.tid = bswap_32(event->auxtrace.tid);
event->auxtrace.cpu = bswap_32(event->auxtrace.cpu);
return 0;
}
static void perf_event__auxtrace_error_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__auxtrace_error_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
event->auxtrace_error.type = bswap_32(event->auxtrace_error.type);
event->auxtrace_error.code = bswap_32(event->auxtrace_error.code);
@ -625,10 +697,11 @@ static void perf_event__auxtrace_error_swap(union perf_event *event,
event->auxtrace_error.machine_pid = bswap_32(event->auxtrace_error.machine_pid);
event->auxtrace_error.vcpu = bswap_32(event->auxtrace_error.vcpu);
}
return 0;
}
static void perf_event__thread_map_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__thread_map_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
unsigned i;
@ -636,10 +709,11 @@ static void perf_event__thread_map_swap(union perf_event *event,
for (i = 0; i < event->thread_map.nr; i++)
event->thread_map.entries[i].pid = bswap_64(event->thread_map.entries[i].pid);
return 0;
}
static void perf_event__cpu_map_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__cpu_map_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
struct perf_record_cpu_map_data *data = &event->cpu_map.data;
@ -677,20 +751,22 @@ static void perf_event__cpu_map_swap(union perf_event *event,
default:
break;
}
return 0;
}
static void perf_event__stat_config_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__stat_config_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
u64 size;
size = bswap_64(event->stat_config.nr) * sizeof(event->stat_config.data[0]);
size += 1; /* nr item itself */
mem_bswap_64(&event->stat_config.nr, size);
return 0;
}
static void perf_event__stat_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__stat_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
event->stat.id = bswap_64(event->stat.id);
event->stat.thread = bswap_32(event->stat.thread);
@ -698,44 +774,90 @@ static void perf_event__stat_swap(union perf_event *event,
event->stat.val = bswap_64(event->stat.val);
event->stat.ena = bswap_64(event->stat.ena);
event->stat.run = bswap_64(event->stat.run);
return 0;
}
static void perf_event__stat_round_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__stat_round_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
event->stat_round.type = bswap_64(event->stat_round.type);
event->stat_round.time = bswap_64(event->stat_round.time);
return 0;
}
static void perf_event__time_conv_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
static int perf_event__time_conv_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
event->time_conv.time_shift = bswap_64(event->time_conv.time_shift);
event->time_conv.time_mult = bswap_64(event->time_conv.time_mult);
event->time_conv.time_zero = bswap_64(event->time_conv.time_zero);
if (event_contains(event->time_conv, time_cycles)) {
if (event_contains(event->time_conv, time_cycles))
event->time_conv.time_cycles = bswap_64(event->time_conv.time_cycles);
if (event_contains(event->time_conv, time_mask))
event->time_conv.time_mask = bswap_64(event->time_conv.time_mask);
}
return 0;
}
static void
static int
perf_event__schedstat_cpu_swap(union perf_event *event __maybe_unused,
bool sample_id_all __maybe_unused)
{
/* FIXME */
return 0;
}
static void
static int
perf_event__schedstat_domain_swap(union perf_event *event __maybe_unused,
bool sample_id_all __maybe_unused)
{
/* FIXME */
return 0;
}
typedef void (*perf_event__swap_op)(union perf_event *event,
bool sample_id_all);
static int perf_event__ksymbol_swap(union perf_event *event,
bool sample_id_all)
{
event->ksymbol.addr = bswap_64(event->ksymbol.addr);
event->ksymbol.len = bswap_32(event->ksymbol.len);
event->ksymbol.ksym_type = bswap_16(event->ksymbol.ksym_type);
event->ksymbol.flags = bswap_16(event->ksymbol.flags);
if (sample_id_all) {
void *data = &event->ksymbol.name;
void *end = (void *)event + event->header.size;
size_t len = strnlen(data, end - data);
/* See comment in perf_event__comm_swap() */
if (len == (size_t)(end - data))
return -1;
data += PERF_ALIGN(len + 1, sizeof(u64));
swap_sample_id_all(event, data);
}
return 0;
}
static int perf_event__bpf_event_swap(union perf_event *event,
bool sample_id_all)
{
event->bpf.type = bswap_16(event->bpf.type);
event->bpf.flags = bswap_16(event->bpf.flags);
event->bpf.id = bswap_32(event->bpf.id);
if (sample_id_all)
swap_sample_id_all(event, &event->bpf + 1);
return 0;
}
static int perf_event__header_feature_swap(union perf_event *event,
bool sample_id_all __maybe_unused)
{
event->feat.feat_id = bswap_64(event->feat.feat_id);
return 0;
}
typedef int (*perf_event__swap_op)(union perf_event *event,
bool sample_id_all);
static perf_event__swap_op perf_event__swap_ops[] = {
[PERF_RECORD_MMAP] = perf_event__mmap_swap,
@ -755,6 +877,8 @@ static perf_event__swap_op perf_event__swap_ops[] = {
[PERF_RECORD_SWITCH_CPU_WIDE] = perf_event__switch_swap,
[PERF_RECORD_NAMESPACES] = perf_event__namespaces_swap,
[PERF_RECORD_CGROUP] = perf_event__cgroup_swap,
[PERF_RECORD_KSYMBOL] = perf_event__ksymbol_swap,
[PERF_RECORD_BPF_EVENT] = perf_event__bpf_event_swap,
[PERF_RECORD_TEXT_POKE] = perf_event__text_poke_swap,
[PERF_RECORD_AUX_OUTPUT_HW_ID] = perf_event__all64_swap,
[PERF_RECORD_CALLCHAIN_DEFERRED] = perf_event__all64_swap,
@ -762,6 +886,7 @@ static perf_event__swap_op perf_event__swap_ops[] = {
[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_FEATURE] = perf_event__header_feature_swap,
[PERF_RECORD_ID_INDEX] = perf_event__all64_swap,
[PERF_RECORD_AUXTRACE_INFO] = perf_event__auxtrace_info_swap,
[PERF_RECORD_AUXTRACE] = perf_event__auxtrace_swap,
@ -1488,6 +1613,25 @@ static int session__flush_deferred_samples(struct perf_session *session,
return ret;
}
/*
* Return true if the string field is properly null-terminated
* within the event boundary. Native-endian files are mapped
* read-only (MAP_SHARED + PROT_READ) so we cannot write a
* null byte in place; skip the event instead.
*/
static bool perf_event__check_nul(const char *str, const void *end, const char *event_name)
{
size_t max_len = (const char *)end - str;
if (max_len == 0 || strnlen(str, max_len) == max_len) {
pr_warning("WARNING: PERF_RECORD_%s: string not null-terminated, skipping event\n",
event_name);
return false;
}
return true;
}
static int machines__deliver_event(struct machines *machines,
struct evlist *evlist,
union perf_event *event,
@ -1536,16 +1680,32 @@ static int machines__deliver_event(struct machines *machines,
}
return evlist__deliver_sample(evlist, tool, event, sample, machine);
case PERF_RECORD_MMAP:
if (!perf_event__check_nul(event->mmap.filename,
(void *)event + event->header.size,
"MMAP"))
return 0;
return tool->mmap(tool, event, sample, machine);
case PERF_RECORD_MMAP2:
if (event->header.misc & PERF_RECORD_MISC_PROC_MAP_PARSE_TIMEOUT)
++evlist->stats.nr_proc_map_timeout;
if (!perf_event__check_nul(event->mmap2.filename,
(void *)event + event->header.size,
"MMAP2"))
return 0;
return tool->mmap2(tool, event, sample, machine);
case PERF_RECORD_COMM:
if (!perf_event__check_nul(event->comm.comm,
(void *)event + event->header.size,
"COMM"))
return 0;
return tool->comm(tool, event, sample, machine);
case PERF_RECORD_NAMESPACES:
return tool->namespaces(tool, event, sample, machine);
case PERF_RECORD_CGROUP:
if (!perf_event__check_nul(event->cgroup.path,
(void *)event + event->header.size,
"CGROUP"))
return 0;
return tool->cgroup(tool, event, sample, machine);
case PERF_RECORD_FORK:
return tool->fork(tool, event, sample, machine);
@ -1584,11 +1744,25 @@ static int machines__deliver_event(struct machines *machines,
case PERF_RECORD_SWITCH_CPU_WIDE:
return tool->context_switch(tool, event, sample, machine);
case PERF_RECORD_KSYMBOL:
if (!perf_event__check_nul(event->ksymbol.name,
(void *)event + event->header.size,
"KSYMBOL"))
return 0;
return tool->ksymbol(tool, event, sample, machine);
case PERF_RECORD_BPF_EVENT:
return tool->bpf(tool, event, sample, machine);
case PERF_RECORD_TEXT_POKE:
case PERF_RECORD_TEXT_POKE: {
/* offsetof(bytes), not sizeof — sizeof includes padding past the flexible array */
size_t text_poke_len = offsetof(struct perf_record_text_poke_event, bytes) +
event->text_poke.old_len +
event->text_poke.new_len;
if (event->header.size < text_poke_len) {
pr_warning("WARNING: PERF_RECORD_TEXT_POKE: old_len+new_len exceeds event, skipping\n");
return 0;
}
return tool->text_poke(tool, event, sample, machine);
}
case PERF_RECORD_AUX_OUTPUT_HW_ID:
return tool->aux_output_hw_id(tool, event, sample, machine);
case PERF_RECORD_CALLCHAIN_DEFERRED:
@ -1794,12 +1968,28 @@ int perf_session__deliver_synth_attr_event(struct perf_session *session,
return perf_session__deliver_synth_event(session, &ev.ev, NULL);
}
/* Caller must ensure event->header.type < PERF_RECORD_HEADER_MAX */
static int event_swap(union perf_event *event, bool sample_id_all)
{
perf_event__swap_op swap = perf_event__swap_ops[event->header.type];
if (swap)
return swap(event, sample_id_all);
return 0;
}
/*
* Minimum event sizes indexed by type. Checked before swap and
* processing so that both cross-endian and native-endian paths
* are protected from accessing fields past the event boundary.
* Zero means no minimum beyond the 8-byte header (already
* enforced by the reader).
*
* These values represent the smallest event the kernel has ever
* emitted for each type, so they do not reject legitimate legacy
* perf.data files from older kernels. Variable-length events
* use offsetof() to the first variable field; the variable
* content is validated separately (e.g., perf_event__check_nul).
*/
static const u32 perf_event__min_size[PERF_RECORD_HEADER_MAX] = {
/*
@ -1821,7 +2011,9 @@ static const u32 perf_event__min_size[PERF_RECORD_HEADER_MAX] = {
[PERF_RECORD_FORK] = sizeof(struct perf_record_fork),
/*
* The kernel dynamically sizes PERF_RECORD_READ based on
* attr.read_format the minimum has just pid + tid + value.
* attr.read_format only the enabled fields are emitted,
* packed with no gaps. The minimum valid event has just
* pid + tid + one u64 value (no optional fields).
*/
[PERF_RECORD_READ] = offsetof(struct perf_record_read, time_enabled),
[PERF_RECORD_MMAP2] = offsetof(struct perf_record_mmap2, filename) + 1,
@ -1844,14 +2036,25 @@ static const u32 perf_event__min_size[PERF_RECORD_HEADER_MAX] = {
[PERF_RECORD_AUXTRACE] = sizeof(struct perf_record_auxtrace),
[PERF_RECORD_AUXTRACE_ERROR] = offsetof(struct perf_record_auxtrace_error, msg) + 1,
[PERF_RECORD_THREAD_MAP] = sizeof(struct perf_record_thread_map),
/* Smallest valid variant is RANGE_CPUS: header(8) + type(2) + range(6) */
/*
* sizeof(perf_record_cpu_map) is 20 because the outer struct
* isn't packed and GCC adds 2 bytes of trailing padding.
* The smallest valid variant (RANGE_CPUS) is only 16 bytes:
* header(8) + type(2) + range_cpu_data(6). Per-variant
* bounds are checked in the swap handler via payload.
*/
[PERF_RECORD_CPU_MAP] = sizeof(struct perf_event_header) +
sizeof(__u16) +
sizeof(struct perf_record_range_cpu_map),
[PERF_RECORD_STAT_CONFIG] = sizeof(struct perf_record_stat_config),
[PERF_RECORD_STAT] = sizeof(struct perf_record_stat),
[PERF_RECORD_STAT_ROUND] = sizeof(struct perf_record_stat_round),
/* Union inflates sizeof; use fixed header fields as minimum */
/*
* EVENT_UPDATE has a union whose largest member (cpus)
* inflates sizeof to 40, but SCALE events are only 32
* and UNIT/NAME events can be even smaller. Use the
* fixed header fields (header + type + id) as minimum.
*/
[PERF_RECORD_EVENT_UPDATE] = offsetof(struct perf_record_event_update, scale),
[PERF_RECORD_TIME_CONV] = offsetof(struct perf_record_time_conv, time_cycles),
[PERF_RECORD_ID_INDEX] = sizeof(struct perf_record_id_index),
@ -1887,14 +2090,6 @@ static bool perf_event__too_small(const union perf_event *event, u32 *min)
return false;
}
/* Caller must ensure event->header.type < PERF_RECORD_HEADER_MAX */
static void event_swap(union perf_event *event, bool sample_id_all)
{
perf_event__swap_op swap = perf_event__swap_ops[event->header.type];
if (swap)
swap(event, sample_id_all);
}
/*
* Read and validate the event at @file_offset.
*
@ -2003,8 +2198,16 @@ int perf_session__peek_event(struct perf_session *session, off_t file_offset,
return -1;
}
if (session->header.needs_swap)
event_swap(event, evlist__sample_id_all(session->evlist));
if (session->header.needs_swap &&
event_swap(event, evlist__sample_id_all(session->evlist))) {
/*
* The header was already swapped so header.size is
* valid expose the event so callers can advance
* past this malformed entry instead of aborting.
*/
*event_ptr = event;
return -1;
}
if (sample && event->header.type < PERF_RECORD_USER_TYPE_START &&
evlist__parse_sample(session->evlist, event, sample))
@ -2022,11 +2225,37 @@ int perf_session__peek_events(struct perf_session *session, u64 offset,
int err;
do {
event = NULL;
err = perf_session__peek_event(session, offset, buf,
PERF_SAMPLE_MAX_SIZE, &event,
NULL);
if (err)
return err;
if (err) {
/*
* Recoverable error: peek_event returns -1 but
* sets event_ptr when the header was read
* successfully but the event is malformed (too
* small or swap failed). Skip past it using
* header.size don't invoke the callback since
* type-specific fields may be truncated.
*
* Must abort if: event_ptr is NULL (I/O error),
* size is 0 (can't advance), type is AUXTRACE
* (payload extends beyond header.size), or size
* is unaligned (would misalign all subsequent reads).
*
* Direct callers (auxtrace, cs-etm) treat any
* non-zero return as fatal only this loop skips.
*/
if (event && event->header.size &&
event->header.type != PERF_RECORD_AUXTRACE &&
event->header.size % sizeof(u64) == 0) {
offset += event->header.size;
err = 0;
} else {
return err;
}
continue;
}
err = cb(session, event, offset, data);
if (err)
@ -2109,8 +2338,12 @@ static s64 perf_session__process_event(struct perf_session *session,
return 0;
}
if (session->header.needs_swap)
event_swap(event, evlist__sample_id_all(evlist));
if (session->header.needs_swap &&
event_swap(event, evlist__sample_id_all(evlist))) {
pr_warning("WARNING: swap failed for %s event, skipping\n",
perf_event__name(event->header.type));
return 0;
}
events_stats__inc(&evlist->stats, event->header.type);
@ -2579,6 +2812,17 @@ reader__mmap(struct reader *rd, struct perf_session *session)
char *buf, **mmaps = rd->mmaps;
u64 page_offset;
/*
* Native-endian: MAP_SHARED + PROT_READ the kernel
* guarantees page-level coherence but a concurrent writer
* could modify the file between validation and use. This
* is a theoretical TOCTOU that affects the entire perf.data
* processing pipeline; fixing it would require copying each
* event to a private buffer before processing.
*
* Cross-endian: MAP_PRIVATE + PROT_WRITE swap handlers
* get a copy-on-write snapshot immune to concurrent writes.
*/
mmap_prot = PROT_READ;
mmap_flags = MAP_SHARED;