mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
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:
parent
315c81b4b9
commit
981f13ae62
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user