* [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes
@ 2026-06-02 23:56 Arnaldo Carvalho de Melo
2026-06-02 23:57 ` [PATCH 1/8] perf sample: Add file_offset field to struct perf_sample Arnaldo Carvalho de Melo
` (8 more replies)
0 siblings, 9 replies; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-02 23:56 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo
When perf report, perf sched, or perf timechart skip a malformed or
unprocessable event, the warning message doesn't say where in the
perf.data file the problem occurred. This makes it hard to
cross-reference with 'perf report -D' output or to locate the
corrupted region with a hex editor.
This series adds a file_offset field to struct perf_sample, set in the
event delivery path (including the deferred callchain path), and
retrofits all skip/stop/error messages to include:
- The file offset where the event was found
- The event type name via perf_event__name() with the numeric
type value in parentheses
For example, instead of:
problem processing 10 event, skipping it.
a user now sees:
WARNING: at offset 0x1a3f0: MMAP2 (10) event size 24 too small (min 64), skipping
The peek_event() path, which validates events during initial file
scanning, also gains file offsets in its three warning messages
(misaligned size, unsupported type, undersized event).
Two pre-existing bugs found by sashiko-bot are fixed:
- builtin-timechart.c cat_backtrace(): use-after-free and
double-free when an invalid callchain context triggers zfree()
before fclose() on an open_memstream buffer. The open_memstream
contract requires fclose() before the buffer can be freed — see
open_memstream(3).
- builtin-sched.c: three BUG_ON(cpu >= MAX_CPUS || cpu < 0)
that abort perf sched when PERF_SAMPLE_CPU is absent from the
sample type and the CPU sentinel (u32)-1 is cast to signed -1.
perf.data is untrusted input — a corrupted or truncated file
should produce a warning, not an abort.
Arnaldo Carvalho de Melo (8):
perf sample: Add file_offset field to struct perf_sample
perf session: Include file offset in event skip/stop messages
perf sched: Include file offset in event skip messages
perf timechart: Include file offset in CPU bounds check messages
perf tools: Include file offset and event type name in skip messages
perf timechart: Fix cat_backtrace() use-after-free on corrupted callchain
perf sched: Replace BUG_ON on invalid CPU with graceful skip
perf test: Add file offset diagnostic test for corrupted perf.data
15 files changed, 261 insertions(+), 101 deletions(-)
Developed with AI assistance (Claude/sashiko), tagged in commits.
Best regards,
- Arnaldo
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 1/8] perf sample: Add file_offset field to struct perf_sample
2026-06-02 23:56 [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Arnaldo Carvalho de Melo
@ 2026-06-02 23:57 ` Arnaldo Carvalho de Melo
2026-06-03 15:11 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 2/8] perf session: Include file offset in event skip/stop messages Arnaldo Carvalho de Melo
` (7 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-02 23:57 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo, Claude Opus 4.6
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Add a file_offset field to struct perf_sample so that event processing
callbacks can report the byte offset of the problematic event in
perf.data, letting users cross-reference with 'perf report -D' output.
Set sample.file_offset in perf_session__deliver_event(), which is the
common entry point for both file mode (mmap'd offset) and pipe mode
(running byte counter from __perf_session__process_pipe_events).
The assignment is placed after evsel__parse_sample(), which zeroes
the struct via memset.
Preserve file_offset through the deferred callchain delivery path by
storing it in struct deferred_event and restoring it after
evlist__parse_sample() in both evlist__deliver_deferred_callchain()
and session__flush_deferred_samples().
Subsequent patches will use this field in skip/stop warning messages.
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/sample.h | 2 ++
tools/perf/util/session.c | 5 +++++
2 files changed, 7 insertions(+)
diff --git a/tools/perf/util/sample.h b/tools/perf/util/sample.h
index e556c9b656ea9cd6..c4eae8b2fd06035a 100644
--- a/tools/perf/util/sample.h
+++ b/tools/perf/util/sample.h
@@ -158,6 +158,8 @@ struct perf_sample {
u64 code_page_size;
/** @cgroup: The sample event PERF_SAMPLE_CGROUP value. */
u64 cgroup;
+ /** @file_offset: Byte offset of this event in the perf.data file. */
+ u64 file_offset;
/** @flags: Extra flag data from auxiliary events like intel-pt. */
u32 flags;
/** @machine_pid: The guest machine pid derived from the sample id. */
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index e2e821b77766dbfc..7996787d742e32c6 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -1824,6 +1824,7 @@ static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool
struct deferred_event {
struct list_head list;
union perf_event *event;
+ u64 file_offset;
};
/*
@@ -1858,6 +1859,7 @@ static int evlist__deliver_deferred_callchain(struct evlist *evlist,
perf_sample__exit(&orig_sample);
break;
}
+ orig_sample.file_offset = de->file_offset;
if (sample->tid != orig_sample.tid) {
perf_sample__exit(&orig_sample);
@@ -1906,6 +1908,7 @@ static int session__flush_deferred_samples(struct perf_session *session,
perf_sample__exit(&sample);
break;
}
+ sample.file_offset = de->file_offset;
sample.evsel = evlist__id2evsel(evlist, sample.id);
ret = evlist__deliver_sample(evlist, tool, de->event,
@@ -1984,6 +1987,7 @@ static int machines__deliver_event(struct machines *machines,
return -ENOMEM;
}
memcpy(de->event, event, sz);
+ de->file_offset = sample->file_offset;
list_add_tail(&de->list, &evlist->deferred_samples);
return 0;
}
@@ -2126,6 +2130,7 @@ static int perf_session__deliver_event(struct perf_session *session,
pr_err("Can't parse sample, err = %d\n", ret);
goto out;
}
+ sample.file_offset = file_offset;
/*
* evsel__parse_sample() doesn't populate machine_pid/vcpu,
* which are needed by machines__find_for_cpumode() to
--
2.54.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 2/8] perf session: Include file offset in event skip/stop messages
2026-06-02 23:56 [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Arnaldo Carvalho de Melo
2026-06-02 23:57 ` [PATCH 1/8] perf sample: Add file_offset field to struct perf_sample Arnaldo Carvalho de Melo
@ 2026-06-02 23:57 ` Arnaldo Carvalho de Melo
2026-06-03 15:12 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 3/8] perf sched: Include file offset in event skip messages Arnaldo Carvalho de Melo
` (6 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-02 23:57 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo, Claude Opus 4.6
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Add 'at offset %#<hex>' to all warning and error messages in session.c
that fire when events are skipped or processing stops due to validation
failures. This lets users cross-reference with 'perf report -D' output
to inspect the surrounding records and understand the corruption context.
Covers messages in perf_session__process_event() (alignment, min size,
swap failure), perf_session__deliver_event() (no evsel, parse failure,
CPU clamping), machines__deliver_event() (NAMESPACES, TEXT_POKE,
null-terminated string checks for MMAP/MMAP2/COMM/CGROUP/KSYMBOL), and
perf_session__process_user_event() (THREAD_MAP, CPU_MAP, STAT_CONFIG,
BPF_METADATA, HEADER_BUILD_ID).
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/session.c | 112 ++++++++++++++++++++------------------
1 file changed, 60 insertions(+), 52 deletions(-)
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index 7996787d742e32c6..e4efb75509278a4e 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -1931,13 +1931,14 @@ static int session__flush_deferred_samples(struct perf_session *session,
* 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)
+static bool perf_event__check_nul(const char *str, const void *end,
+ const char *event_name, u64 file_offset)
{
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);
+ pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_%s: string not null-terminated, skipping event\n",
+ file_offset, event_name);
return false;
}
@@ -1995,7 +1996,7 @@ static int machines__deliver_event(struct machines *machines,
case PERF_RECORD_MMAP:
if (!perf_event__check_nul(event->mmap.filename,
(void *)event + event->header.size,
- "MMAP"))
+ "MMAP", file_offset))
return 0;
return tool->mmap(tool, event, sample, machine);
case PERF_RECORD_MMAP2:
@@ -2003,13 +2004,13 @@ static int machines__deliver_event(struct machines *machines,
++evlist->stats.nr_proc_map_timeout;
if (!perf_event__check_nul(event->mmap2.filename,
(void *)event + event->header.size,
- "MMAP2"))
+ "MMAP2", file_offset))
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"))
+ "COMM", file_offset))
return 0;
return tool->comm(tool, event, sample, machine);
case PERF_RECORD_NAMESPACES: {
@@ -2027,8 +2028,8 @@ static int machines__deliver_event(struct machines *machines,
* cross-endian path.
*/
if (event->namespaces.nr_namespaces > max_nr) {
- pr_warning("WARNING: PERF_RECORD_NAMESPACES: nr_namespaces %" PRIu64 " exceeds payload (max %" PRIu64 "), skipping\n",
- (u64)event->namespaces.nr_namespaces, max_nr);
+ pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_NAMESPACES: nr_namespaces %" PRIu64 " exceeds payload (max %" PRIu64 "), skipping\n",
+ file_offset, (u64)event->namespaces.nr_namespaces, max_nr);
return 0;
}
return tool->namespaces(tool, event, sample, machine);
@@ -2036,7 +2037,7 @@ static int machines__deliver_event(struct machines *machines,
case PERF_RECORD_CGROUP:
if (!perf_event__check_nul(event->cgroup.path,
(void *)event + event->header.size,
- "CGROUP"))
+ "CGROUP", file_offset))
return 0;
return tool->cgroup(tool, event, sample, machine);
case PERF_RECORD_FORK:
@@ -2078,7 +2079,7 @@ static int machines__deliver_event(struct machines *machines,
case PERF_RECORD_KSYMBOL:
if (!perf_event__check_nul(event->ksymbol.name,
(void *)event + event->header.size,
- "KSYMBOL"))
+ "KSYMBOL", file_offset))
return 0;
return tool->ksymbol(tool, event, sample, machine);
case PERF_RECORD_BPF_EVENT:
@@ -2090,7 +2091,8 @@ static int machines__deliver_event(struct machines *machines,
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");
+ pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_TEXT_POKE: old_len+new_len exceeds event, skipping\n",
+ file_offset);
return 0;
}
return tool->text_poke(tool, event, sample, machine);
@@ -2120,14 +2122,17 @@ static int perf_session__deliver_event(struct perf_session *session,
perf_sample__init(&sample, /*all=*/false);
evsel = evlist__event2evsel(session->evlist, event);
if (!evsel) {
- pr_err("No evsel found for event type %u\n",
+ pr_err("ERROR: at offset %#" PRIx64 ": no evsel found for %s (%u) event\n",
+ file_offset, perf_event__name(event->header.type),
event->header.type);
ret = -EFAULT;
goto out;
}
ret = evsel__parse_sample(evsel, event, &sample);
if (ret) {
- pr_err("Can't parse sample, err = %d\n", ret);
+ pr_err("ERROR: at offset %#" PRIx64 ": can't parse %s (%u) sample, err = %d\n",
+ file_offset, perf_event__name(event->header.type),
+ event->header.type, ret);
goto out;
}
sample.file_offset = file_offset;
@@ -2204,8 +2209,8 @@ static int perf_session__deliver_event(struct perf_session *session,
* Downstream array users (timechart, kwork) have
* their own per-callback bounds checks.
*/
- pr_warning_once("WARNING: sample CPU %u >= nr_cpus_avail %u, clamping to 0\n",
- sample.cpu, nr_cpus_avail);
+ pr_warning_once("WARNING: at offset %#" PRIx64 ": sample CPU %u >= nr_cpus_avail %u, clamping to 0\n",
+ file_offset, sample.cpu, nr_cpus_avail);
sample.cpu = 0;
}
}
@@ -2278,7 +2283,7 @@ static s64 perf_session__process_user_event(struct perf_session *session,
case PERF_RECORD_HEADER_BUILD_ID:
if (!perf_event__check_nul(event->build_id.filename,
(void *)event + event_size,
- "HEADER_BUILD_ID")) {
+ "HEADER_BUILD_ID", file_offset)) {
err = 0;
break;
}
@@ -2311,8 +2316,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
u64 max_nr;
if (event_size < sizeof(event->thread_map)) {
- pr_err("PERF_RECORD_THREAD_MAP: header.size (%u) too small\n",
- event_size);
+ pr_err("ERROR: at offset %#" PRIx64 ": PERF_RECORD_THREAD_MAP: header.size (%u) too small\n",
+ file_offset, event_size);
err = -EINVAL;
break;
}
@@ -2320,8 +2325,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
max_nr = (event_size - sizeof(event->thread_map)) /
sizeof(event->thread_map.entries[0]);
if (event->thread_map.nr > max_nr) {
- pr_err("PERF_RECORD_THREAD_MAP: nr %" PRIu64 " exceeds max %" PRIu64 "\n",
- (u64)event->thread_map.nr, max_nr);
+ pr_err("ERROR: at offset %#" PRIx64 ": PERF_RECORD_THREAD_MAP: nr %" PRIu64 " exceeds max %" PRIu64 "\n",
+ file_offset, (u64)event->thread_map.nr, max_nr);
err = -EINVAL;
break;
}
@@ -2345,8 +2350,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
sizeof(data->cpus_data.cpu[0]);
if (data->cpus_data.nr > max_nr) {
- pr_warning("WARNING: PERF_RECORD_CPU_MAP: nr %u exceeds payload (max %u), skipping\n",
- data->cpus_data.nr, max_nr);
+ pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_CPU_MAP: nr %u exceeds payload (max %u), skipping\n",
+ file_offset, data->cpus_data.nr, max_nr);
err = 0;
goto out;
}
@@ -2359,8 +2364,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
sizeof(data->mask32_data.mask[0]);
if (data->mask32_data.nr > max_nr) {
- pr_warning("WARNING: PERF_RECORD_CPU_MAP mask32: nr %u exceeds payload (max %u), skipping\n",
- data->mask32_data.nr, max_nr);
+ pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_CPU_MAP mask32: nr %u exceeds payload (max %u), skipping\n",
+ file_offset, data->mask32_data.nr, max_nr);
err = 0;
goto out;
}
@@ -2375,14 +2380,14 @@ static s64 perf_session__process_user_event(struct perf_session *session,
mask64_data.mask)) /
sizeof(data->mask64_data.mask[0]);
if (data->mask64_data.nr > max_nr) {
- pr_warning("WARNING: PERF_RECORD_CPU_MAP mask64: nr %u exceeds payload (max %u), skipping\n",
- data->mask64_data.nr, max_nr);
+ pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_CPU_MAP mask64: nr %u exceeds payload (max %u), skipping\n",
+ file_offset, data->mask64_data.nr, max_nr);
err = 0;
goto out;
}
} else {
- pr_warning("WARNING: PERF_RECORD_CPU_MAP: unsupported long_size %u, skipping\n",
- data->mask32_data.long_size);
+ pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_CPU_MAP: unsupported long_size %u, skipping\n",
+ file_offset, data->mask32_data.long_size);
err = 0;
goto out;
}
@@ -2404,8 +2409,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
* cannot clamp nr in place. Skip the event instead.
*/
if (event->stat_config.nr > max_nr) {
- pr_warning("WARNING: PERF_RECORD_STAT_CONFIG: nr %" PRIu64 " exceeds payload (max %" PRIu64 "), skipping\n",
- (u64)event->stat_config.nr, max_nr);
+ pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_STAT_CONFIG: nr %" PRIu64 " exceeds payload (max %" PRIu64 "), skipping\n",
+ file_offset, (u64)event->stat_config.nr, max_nr);
err = 0;
goto out;
}
@@ -2446,8 +2451,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
u64 nr_entries, max_entries;
if (event_size < sizeof(event->bpf_metadata)) {
- pr_warning("WARNING: PERF_RECORD_BPF_METADATA: header.size (%u) too small, skipping\n",
- event_size);
+ pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_BPF_METADATA: header.size (%u) too small, skipping\n",
+ file_offset, event_size);
err = 0;
break;
}
@@ -2458,7 +2463,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
*/
if (strnlen(event->bpf_metadata.prog_name,
BPF_PROG_NAME_LEN) == BPF_PROG_NAME_LEN) {
- pr_warning("WARNING: PERF_RECORD_BPF_METADATA: prog_name not null-terminated, skipping\n");
+ pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_BPF_METADATA: prog_name not null-terminated, skipping\n",
+ file_offset);
err = 0;
break;
}
@@ -2467,8 +2473,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
max_entries = (event_size - sizeof(event->bpf_metadata)) /
sizeof(event->bpf_metadata.entries[0]);
if (nr_entries > max_entries) {
- pr_warning("WARNING: PERF_RECORD_BPF_METADATA: nr_entries %" PRIu64 " exceeds max %" PRIu64 ", skipping\n",
- nr_entries, max_entries);
+ pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_BPF_METADATA: nr_entries %" PRIu64 " exceeds max %" PRIu64 ", skipping\n",
+ file_offset, nr_entries, max_entries);
err = 0;
break;
}
@@ -2478,7 +2484,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
BPF_METADATA_KEY_LEN) == BPF_METADATA_KEY_LEN ||
strnlen(event->bpf_metadata.entries[i].value,
BPF_METADATA_VALUE_LEN) == BPF_METADATA_VALUE_LEN) {
- pr_warning("WARNING: PERF_RECORD_BPF_METADATA: entry %" PRIu64 " key/value not null-terminated, skipping\n", i);
+ pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_BPF_METADATA: entry %" PRIu64 " key/value not null-terminated, skipping\n",
+ file_offset, i);
err = 0;
goto out;
}
@@ -2752,22 +2759,22 @@ int perf_session__peek_event(struct perf_session *session, off_t file_offset,
event->header.type != PERF_RECORD_HEADER_TRACING_DATA &&
event->header.type != PERF_RECORD_COMPRESSED &&
event->header.type != PERF_RECORD_HEADER_FEATURE) {
- pr_warning("WARNING: peek_event: event type %u size %u not aligned to %zu\n",
- event->header.type,
- event->header.size, sizeof(u64));
+ pr_warning("WARNING: at offset %#" PRIx64 ": %s (%u) event size %u not aligned to %zu\n",
+ (u64)file_offset, perf_event__name(event->header.type),
+ event->header.type, event->header.size, sizeof(u64));
return -1;
}
if (event->header.type >= PERF_RECORD_HEADER_MAX) {
- pr_warning("WARNING: peek_event: unsupported event type %u, skipping\n",
- event->header.type);
+ pr_warning("WARNING: at offset %#" PRIx64 ": unsupported event type %u, skipping\n",
+ (u64)file_offset, event->header.type);
return 0;
}
if (perf_event__too_small(event, &min_sz)) {
- pr_warning("WARNING: peek_event: %s event size %u too small (min %u)\n",
- perf_event__name(event->header.type),
- event->header.size, min_sz);
+ pr_warning("WARNING: at offset %#" PRIx64 ": %s (%u) event size %u too small (min %u)\n",
+ (u64)file_offset, perf_event__name(event->header.type),
+ event->header.type, event->header.size, min_sz);
return -1;
}
@@ -2883,9 +2890,9 @@ static s64 perf_session__process_event(struct perf_session *session,
event->header.type != PERF_RECORD_HEADER_TRACING_DATA &&
event->header.type != PERF_RECORD_COMPRESSED &&
event->header.type != PERF_RECORD_HEADER_FEATURE) {
- pr_err("ERROR: %s event size %u is not 8-byte aligned, aborting\n",
- perf_event__name(event->header.type),
- event->header.size);
+ pr_err("ERROR: at offset %#" PRIx64 ": %s (%u) event size %u is not 8-byte aligned, aborting\n",
+ file_offset, perf_event__name(event->header.type),
+ event->header.type, event->header.size);
return -EINVAL;
}
@@ -2905,16 +2912,17 @@ static s64 perf_session__process_event(struct perf_session *session,
* can be safely stepped over without misaligning the stream.
*/
if (perf_event__too_small(event, &min_sz)) {
- pr_warning("WARNING: %s event size %u too small (min %u), skipping\n",
- perf_event__name(event->header.type),
- event->header.size, min_sz);
+ pr_warning("WARNING: at offset %#" PRIx64 ": %s (%u) event size %u too small (min %u), skipping\n",
+ file_offset, perf_event__name(event->header.type),
+ event->header.type, event->header.size, min_sz);
return 0;
}
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));
+ pr_warning("WARNING: at offset %#" PRIx64 ": swap failed for %s (%u) event, skipping\n",
+ file_offset, perf_event__name(event->header.type),
+ event->header.type);
return 0;
}
--
2.54.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 3/8] perf sched: Include file offset in event skip messages
2026-06-02 23:56 [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Arnaldo Carvalho de Melo
2026-06-02 23:57 ` [PATCH 1/8] perf sample: Add file_offset field to struct perf_sample Arnaldo Carvalho de Melo
2026-06-02 23:57 ` [PATCH 2/8] perf session: Include file offset in event skip/stop messages Arnaldo Carvalho de Melo
@ 2026-06-02 23:57 ` Arnaldo Carvalho de Melo
2026-06-03 15:13 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 4/8] perf timechart: Include file offset in CPU bounds check messages Arnaldo Carvalho de Melo
` (5 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-02 23:57 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo, Claude Opus 4.6
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Add the perf.data file offset to the CPU out-of-bounds and
machine__resolve failure messages emitted when samples are skipped in
process_sched_switch_event(), process_sched_runtime_event(), and
timehist_sched_change_event(). Also switch event type from raw integer
to perf_event__name() string for readability.
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/builtin-sched.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
index 812a1b0d56d6e5f3..9ec8e049e19b0038 100644
--- a/tools/perf/builtin-sched.c
+++ b/tools/perf/builtin-sched.c
@@ -1792,8 +1792,10 @@ static int process_sched_switch_event(const struct perf_tool *tool,
u32 prev_pid = perf_sample__intval(sample, "prev_pid"),
next_pid = perf_sample__intval(sample, "next_pid");
+ /* perf.data is untrusted input — CPU may be absent or corrupted */
if (this_cpu < 0 || this_cpu >= MAX_CPUS) {
- pr_warning("Out-of-bound sample CPU %d. Skipping sample\n", this_cpu);
+ pr_warning("WARNING: at offset %#" PRIx64 ": out-of-bound sample CPU %d, skipping sample\n",
+ sample->file_offset, this_cpu);
return 0;
}
@@ -1819,8 +1821,10 @@ static int process_sched_runtime_event(const struct perf_tool *tool,
{
struct perf_sched *sched = container_of(tool, struct perf_sched, tool);
+ /* perf.data is untrusted input — CPU may be absent or corrupted */
if (sample->cpu >= MAX_CPUS) {
- pr_warning("Out-of-bound sample CPU %u. Skipping sample\n", sample->cpu);
+ pr_warning("WARNING: at offset %#" PRIx64 ": out-of-bound sample CPU %u, skipping sample\n",
+ sample->file_offset, sample->cpu);
return 0;
}
@@ -2786,15 +2790,18 @@ static int timehist_sched_change_event(const struct perf_tool *tool,
int rc = 0;
const char state = perf_sample__taskstate(sample, "prev_state");
+ /* perf.data is untrusted input — CPU may be absent or corrupted */
if (sample->cpu >= MAX_CPUS) {
- pr_warning("Out-of-bound sample CPU %d. Skipping sample\n", sample->cpu);
+ pr_warning("WARNING: at offset %#" PRIx64 ": out-of-bound sample CPU %d, skipping sample\n",
+ sample->file_offset, sample->cpu);
return 0;
}
addr_location__init(&al);
if (machine__resolve(machine, &al, sample) < 0) {
- pr_err("problem processing %d event. skipping it\n",
- event->header.type);
+ pr_err("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it\n",
+ perf_event__name(event->header.type), event->header.type,
+ sample->file_offset);
rc = -1;
goto out;
}
--
2.54.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 4/8] perf timechart: Include file offset in CPU bounds check messages
2026-06-02 23:56 [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Arnaldo Carvalho de Melo
` (2 preceding siblings ...)
2026-06-02 23:57 ` [PATCH 3/8] perf sched: Include file offset in event skip messages Arnaldo Carvalho de Melo
@ 2026-06-02 23:57 ` Arnaldo Carvalho de Melo
2026-06-03 15:14 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 5/8] perf tools: Include file offset and event type name in skip messages Arnaldo Carvalho de Melo
` (4 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-02 23:57 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo, Claude Opus 4.6
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Add the perf.data file offset to the out-of-bounds CPU debug messages
in process_sample_cpu_idle(), process_sample_cpu_frequency(),
process_sample_sched_wakeup(), and process_sample_sched_switch().
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/builtin-timechart.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c
index 630756bebe3242dc..071987241a528ba4 100644
--- a/tools/perf/builtin-timechart.c
+++ b/tools/perf/builtin-timechart.c
@@ -605,8 +605,10 @@ process_sample_cpu_idle(struct timechart *tchart __maybe_unused,
u32 state = perf_sample__intval(sample, "state");
u32 cpu_id = perf_sample__intval(sample, "cpu_id");
+ /* perf.data is untrusted input — cpu_id may be corrupted */
if (cpu_id >= MAX_CPUS) {
- pr_debug("Out-of-bounds cpu_id %u\n", cpu_id);
+ pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu_id %u\n",
+ sample->file_offset, cpu_id);
return -1;
}
if (state == (u32)PWR_EVENT_EXIT)
@@ -624,8 +626,10 @@ process_sample_cpu_frequency(struct timechart *tchart,
u32 state = perf_sample__intval(sample, "state");
u32 cpu_id = perf_sample__intval(sample, "cpu_id");
+ /* perf.data is untrusted input — cpu_id may be corrupted */
if (cpu_id >= MAX_CPUS) {
- pr_debug("Out-of-bounds cpu_id %u\n", cpu_id);
+ pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu_id %u\n",
+ sample->file_offset, cpu_id);
return -1;
}
p_state_change(tchart, cpu_id, sample->time, state);
@@ -641,8 +645,10 @@ process_sample_sched_wakeup(struct timechart *tchart,
int waker = perf_sample__intval(sample, "common_pid");
int wakee = perf_sample__intval(sample, "pid");
+ /* perf.data is untrusted input — CPU may be absent or corrupted */
if (sample->cpu >= MAX_CPUS) {
- pr_debug("Out-of-bounds cpu %u\n", sample->cpu);
+ pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu %u\n",
+ sample->file_offset, sample->cpu);
return -1;
}
sched_wakeup(tchart, sample->cpu, sample->time, waker, wakee, flags, backtrace);
@@ -658,8 +664,10 @@ process_sample_sched_switch(struct timechart *tchart,
int next_pid = perf_sample__intval(sample, "next_pid");
u64 prev_state = perf_sample__intval(sample, "prev_state");
+ /* perf.data is untrusted input — CPU may be absent or corrupted */
if (sample->cpu >= MAX_CPUS) {
- pr_debug("Out-of-bounds cpu %u\n", sample->cpu);
+ pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu %u\n",
+ sample->file_offset, sample->cpu);
return -1;
}
sched_switch(tchart, sample->cpu, sample->time, prev_pid, next_pid,
@@ -676,6 +684,7 @@ process_sample_power_start(struct timechart *tchart __maybe_unused,
u64 cpu_id = perf_sample__intval(sample, "cpu_id");
u64 value = perf_sample__intval(sample, "value");
+ /* perf.data is untrusted input — cpu_id may be corrupted */
if (cpu_id >= MAX_CPUS) {
pr_debug("Out-of-bounds cpu_id %llu\n", (unsigned long long)cpu_id);
return -1;
@@ -689,6 +698,7 @@ process_sample_power_end(struct timechart *tchart,
struct perf_sample *sample,
const char *backtrace __maybe_unused)
{
+ /* perf.data is untrusted input — CPU may be absent or corrupted */
if (sample->cpu >= MAX_CPUS) {
pr_debug("Out-of-bounds cpu %u\n", sample->cpu);
return -1;
@@ -705,6 +715,7 @@ process_sample_power_frequency(struct timechart *tchart,
u64 cpu_id = perf_sample__intval(sample, "cpu_id");
u64 value = perf_sample__intval(sample, "value");
+ /* perf.data is untrusted input — cpu_id may be corrupted */
if (cpu_id >= MAX_CPUS) {
pr_debug("Out-of-bounds cpu_id %llu\n", (unsigned long long)cpu_id);
return -1;
--
2.54.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 5/8] perf tools: Include file offset and event type name in skip messages
2026-06-02 23:56 [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Arnaldo Carvalho de Melo
` (3 preceding siblings ...)
2026-06-02 23:57 ` [PATCH 4/8] perf timechart: Include file offset in CPU bounds check messages Arnaldo Carvalho de Melo
@ 2026-06-02 23:57 ` Arnaldo Carvalho de Melo
2026-06-03 15:14 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 6/8] perf timechart: Fix cat_backtrace() use-after-free on corrupted callchain Arnaldo Carvalho de Melo
` (3 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-02 23:57 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo, Claude Opus 4.6
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Add the perf.data file offset and use perf_event__name() instead of raw
event type integers in the 'problem processing event, skipping it'
messages emitted by process_sample_event() callbacks across annotate,
c2c, diff, kmem, kvm, kwork, lock, report, script, and build-id.
This lets users cross-reference skipped events with 'perf report -D'
output. Also add explicit #include "util/event.h" and <inttypes.h>
where needed to avoid depending on transitive includes.
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/builtin-annotate.c | 5 +++--
tools/perf/builtin-c2c.c | 5 +++--
tools/perf/builtin-diff.c | 8 +++++---
tools/perf/builtin-kmem.c | 6 ++++--
tools/perf/builtin-kvm.c | 9 ++++++---
tools/perf/builtin-kwork.c | 4 +++-
tools/perf/builtin-lock.c | 6 ++++--
tools/perf/builtin-report.c | 9 ++++++---
tools/perf/builtin-script.c | 10 ++++++----
tools/perf/util/build-id.c | 5 +++--
10 files changed, 43 insertions(+), 24 deletions(-)
diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
index 5f450c8093c09210..b918f9eed5fd2441 100644
--- a/tools/perf/builtin-annotate.c
+++ b/tools/perf/builtin-annotate.c
@@ -288,8 +288,9 @@ static int process_sample_event(const struct perf_tool *tool,
addr_location__init(&al);
if (machine__resolve(machine, &al, sample) < 0) {
- pr_warning("problem processing %d event, skipping it.\n",
- event->header.type);
+ pr_warning("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
+ perf_event__name(event->header.type), event->header.type,
+ sample->file_offset);
ret = -1;
goto out_put;
}
diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
index 36f38694992386ad..d3503be9350c03bb 100644
--- a/tools/perf/builtin-c2c.c
+++ b/tools/perf/builtin-c2c.c
@@ -328,8 +328,9 @@ static int process_sample_event(const struct perf_tool *tool __maybe_unused,
addr_location__init(&al);
if (machine__resolve(machine, &al, sample) < 0) {
- pr_debug("problem processing %d event, skipping it.\n",
- event->header.type);
+ pr_debug("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
+ perf_event__name(event->header.type), event->header.type,
+ sample->file_offset);
ret = -1;
goto out;
}
diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c
index b4ff863b304ca046..9592f44b6545bab6 100644
--- a/tools/perf/builtin-diff.c
+++ b/tools/perf/builtin-diff.c
@@ -409,8 +409,9 @@ static int diff__process_sample_event(const struct perf_tool *tool,
addr_location__init(&al);
if (machine__resolve(machine, &al, sample) < 0) {
- pr_warning("problem processing %d event, skipping it.\n",
- event->header.type);
+ pr_warning("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
+ perf_event__name(event->header.type), event->header.type,
+ sample->file_offset);
ret = -1;
goto out;
}
@@ -436,7 +437,8 @@ static int diff__process_sample_event(const struct perf_tool *tool,
case COMPUTE_STREAM:
if (hist_entry_iter__add(&iter, &al, PERF_MAX_STACK_DEPTH,
NULL)) {
- pr_debug("problem adding hist entry, skipping event\n");
+ pr_debug("problem adding hist entry at offset %#" PRIx64 ", skipping event\n",
+ sample->file_offset);
goto out;
}
break;
diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c
index 33585e353efe56cc..e1b2f5bc1ba8d887 100644
--- a/tools/perf/builtin-kmem.c
+++ b/tools/perf/builtin-kmem.c
@@ -22,6 +22,7 @@
#include "util/cpumap.h"
#include "util/debug.h"
+#include "util/event.h"
#include "util/string2.h"
#include "util/util.h"
@@ -987,8 +988,9 @@ static int process_sample_event(const struct perf_tool *tool __maybe_unused,
sample->tid);
if (thread == NULL) {
- pr_debug("problem processing %d event, skipping it.\n",
- event->header.type);
+ pr_debug("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
+ perf_event__name(event->header.type), event->header.type,
+ sample->file_offset);
return -1;
}
diff --git a/tools/perf/builtin-kvm.c b/tools/perf/builtin-kvm.c
index dd2ed21596aa59f9..394302ebdb161077 100644
--- a/tools/perf/builtin-kvm.c
+++ b/tools/perf/builtin-kvm.c
@@ -22,6 +22,7 @@
#include "util/synthetic-events.h"
#include "util/top.h"
#include "util/data.h"
+#include "util/event.h"
#include "util/ordered-events.h"
#include "util/kvm-stat.h"
#include "util/util.h"
@@ -1141,14 +1142,16 @@ static int process_sample_event(const struct perf_tool *tool,
return 0;
if (machine__resolve(machine, &kvm->al, sample) < 0) {
- pr_warning("Fail to resolve address location, skip sample.\n");
+ pr_warning("WARNING: at offset %#" PRIx64 ": fail to resolve address location, skipping sample\n",
+ sample->file_offset);
return 0;
}
thread = machine__findnew_thread(machine, sample->pid, sample->tid);
if (thread == NULL) {
- pr_debug("problem processing %d event, skipping it.\n",
- event->header.type);
+ pr_debug("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
+ perf_event__name(event->header.type), event->header.type,
+ sample->file_offset);
return -1;
}
diff --git a/tools/perf/builtin-kwork.c b/tools/perf/builtin-kwork.c
index 99dc293a0744726e..110de3507d48160c 100644
--- a/tools/perf/builtin-kwork.c
+++ b/tools/perf/builtin-kwork.c
@@ -9,6 +9,7 @@
#include "perf.h"
#include "util/data.h"
+#include "util/event.h"
#include "util/evlist.h"
#include "util/evsel.h"
#include "util/header.h"
@@ -897,7 +898,8 @@ static int timehist_exit_event(struct perf_kwork *kwork,
addr_location__init(&al);
if (machine__resolve(machine, &al, sample) < 0) {
- pr_debug("Problem processing event, skipping it\n");
+ pr_debug("problem processing event at offset %#" PRIx64 ", skipping it\n",
+ sample->file_offset);
ret = -1;
goto out;
}
diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c
index 94a8c35abb0bc991..5841d43be9718414 100644
--- a/tools/perf/builtin-lock.c
+++ b/tools/perf/builtin-lock.c
@@ -21,6 +21,7 @@
#include "util/tracepoint.h"
#include "util/debug.h"
+#include "util/event.h"
#include "util/session.h"
#include "util/tool.h"
#include "util/data.h"
@@ -1433,8 +1434,9 @@ static int process_sample_event(const struct perf_tool *tool __maybe_unused,
sample->tid);
if (thread == NULL) {
- pr_debug("problem processing %d event, skipping it.\n",
- event->header.type);
+ pr_debug("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
+ perf_event__name(event->header.type), event->header.type,
+ sample->file_offset);
return -1;
}
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index 973d97af85019e6e..cd052aa78132b65f 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -27,6 +27,7 @@
#include "perf.h"
#include "util/debug.h"
+#include "util/event.h"
#include "util/evlist.h"
#include "util/evsel.h"
#include "util/evswitch.h"
@@ -284,8 +285,9 @@ static int process_sample_event(const struct perf_tool *tool,
addr_location__init(&al);
if (machine__resolve(machine, &al, sample) < 0) {
- pr_debug("problem processing %d event, skipping it.\n",
- event->header.type);
+ pr_debug("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
+ perf_event__name(event->header.type), event->header.type,
+ sample->file_offset);
ret = -1;
goto out_put;
}
@@ -332,7 +334,8 @@ static int process_sample_event(const struct perf_tool *tool,
ret = hist_entry_iter__add(&iter, &al, rep->max_stack, rep);
if (ret < 0)
- pr_debug("problem adding hist entry, skipping event\n");
+ pr_debug("problem adding hist entry at offset %#" PRIx64 ", skipping event\n",
+ sample->file_offset);
out_put:
addr_location__exit(&al);
return ret;
diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index 5124edf2b7a692b2..f4aa255fc3297f90 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -2693,8 +2693,9 @@ static int process_sample_event(const struct perf_tool *tool,
goto out_put;
if (!al.thread && machine__resolve(machine, &al, sample) < 0) {
- pr_err("problem processing %d event, skipping it.\n",
- event->header.type);
+ pr_err("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
+ perf_event__name(event->header.type), event->header.type,
+ sample->file_offset);
ret = -1;
goto out_put;
}
@@ -2775,8 +2776,9 @@ static int process_deferred_sample_event(const struct perf_tool *tool,
goto out_put;
if (machine__resolve(machine, &al, sample) < 0) {
- pr_err("problem processing %d event, skipping it.\n",
- event->header.type);
+ pr_err("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
+ perf_event__name(event->header.type), event->header.type,
+ sample->file_offset);
ret = -1;
goto out_put;
}
diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c
index af4d874f13810ffe..8c0a9ae932aa5798 100644
--- a/tools/perf/util/build-id.c
+++ b/tools/perf/util/build-id.c
@@ -10,6 +10,7 @@
#include "util.h" // lsdir(), mkdir_p(), rm_rf()
#include <dirent.h>
#include <errno.h>
+#include <inttypes.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -62,8 +63,8 @@ int build_id__mark_dso_hit(const struct perf_tool *tool __maybe_unused,
sample->tid);
if (thread == NULL) {
- pr_err("problem processing %d event, skipping it.\n",
- event->header.type);
+ pr_err("problem processing %s event at offset %#" PRIx64 ", skipping it.\n",
+ perf_event__name(event->header.type), sample->file_offset);
return -1;
}
--
2.54.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 6/8] perf timechart: Fix cat_backtrace() use-after-free on corrupted callchain
2026-06-02 23:56 [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Arnaldo Carvalho de Melo
` (4 preceding siblings ...)
2026-06-02 23:57 ` [PATCH 5/8] perf tools: Include file offset and event type name in skip messages Arnaldo Carvalho de Melo
@ 2026-06-02 23:57 ` Arnaldo Carvalho de Melo
2026-06-03 15:16 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 7/8] perf sched: Replace BUG_ON on invalid CPU with graceful skip Arnaldo Carvalho de Melo
` (2 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-02 23:57 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo, sashiko-bot, Claude Opus 4.6
From: Arnaldo Carvalho de Melo <acme@redhat.com>
cat_backtrace() uses open_memstream() to build a backtrace string.
When an invalid callchain context is encountered, zfree(&p) frees
the memstream buffer, then the exit path calls fclose(f), which
flushes to the already-freed buffer — a use-after-free. The function
then returns a dangling pointer that the caller passes to a handler
and subsequently double-frees.
Fix by replacing the zfree(&p) with a 'corrupted' flag. At the exit
label, always fclose(f) first (which finalizes the buffer), then
conditionally free it when corrupted. This ensures the memstream
contract is honored: the buffer remains valid until fclose().
While here, update the machine__resolve failure message to include
file_offset and the event type name, matching the pattern from the
preceding series. Also update the three legacy power event handlers
under SUPPORT_OLD_POWER_EVENTS to include file_offset in their
out-of-bounds CPU messages for consistency.
Reported-by: sashiko-bot@kernel.org # Running on a local machine
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/builtin-timechart.c | 36 ++++++++++++++++++++++------------
1 file changed, 23 insertions(+), 13 deletions(-)
diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c
index 071987241a528ba4..85a9ad0455aecccd 100644
--- a/tools/perf/builtin-timechart.c
+++ b/tools/perf/builtin-timechart.c
@@ -489,6 +489,10 @@ static void sched_switch(struct timechart *tchart, int cpu, u64 timestamp,
}
}
+/*
+ * Returns a malloc'd backtrace string built via open_memstream, or NULL
+ * on error. Caller must free() the returned pointer.
+ */
static char *cat_backtrace(union perf_event *event,
struct perf_sample *sample,
struct machine *machine)
@@ -500,6 +504,7 @@ static char *cat_backtrace(union perf_event *event,
u8 cpumode = PERF_RECORD_MISC_USER;
struct ip_callchain *chain = sample->callchain;
FILE *f = open_memstream(&p, &p_len);
+ bool corrupted = false;
if (!f) {
perror("open_memstream error");
@@ -511,8 +516,9 @@ static char *cat_backtrace(union perf_event *event,
goto exit;
if (machine__resolve(machine, &al, sample) < 0) {
- fprintf(stderr, "problem processing %d event, skipping it.\n",
- event->header.type);
+ pr_err("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
+ perf_event__name(event->header.type), event->header.type,
+ sample->file_offset);
goto exit;
}
@@ -537,14 +543,8 @@ static char *cat_backtrace(union perf_event *event,
cpumode = PERF_RECORD_MISC_USER;
break;
default:
- pr_debug("invalid callchain context: "
- "%"PRId64"\n", (s64) ip);
-
- /*
- * It seems the callchain is corrupted.
- * Discard all.
- */
- zfree(&p);
+ pr_debug("invalid callchain context: %" PRId64 "\n", (s64) ip);
+ corrupted = true;
goto exit;
}
continue;
@@ -561,7 +561,14 @@ static char *cat_backtrace(union perf_event *event,
}
exit:
addr_location__exit(&al);
+ /*
+ * fclose() on an open_memstream always sets p to a valid buffer,
+ * even if nothing was written — see open_memstream(3). So p is
+ * never NULL after fclose and we need the flag to discard it.
+ */
fclose(f);
+ if (corrupted)
+ zfree(&p);
return p;
}
@@ -686,7 +693,8 @@ process_sample_power_start(struct timechart *tchart __maybe_unused,
/* perf.data is untrusted input — cpu_id may be corrupted */
if (cpu_id >= MAX_CPUS) {
- pr_debug("Out-of-bounds cpu_id %llu\n", (unsigned long long)cpu_id);
+ pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu_id %llu\n",
+ sample->file_offset, (unsigned long long)cpu_id);
return -1;
}
c_state_start(cpu_id, sample->time, value);
@@ -700,7 +708,8 @@ process_sample_power_end(struct timechart *tchart,
{
/* perf.data is untrusted input — CPU may be absent or corrupted */
if (sample->cpu >= MAX_CPUS) {
- pr_debug("Out-of-bounds cpu %u\n", sample->cpu);
+ pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu %u\n",
+ sample->file_offset, sample->cpu);
return -1;
}
c_state_end(tchart, sample->cpu, sample->time);
@@ -717,7 +726,8 @@ process_sample_power_frequency(struct timechart *tchart,
/* perf.data is untrusted input — cpu_id may be corrupted */
if (cpu_id >= MAX_CPUS) {
- pr_debug("Out-of-bounds cpu_id %llu\n", (unsigned long long)cpu_id);
+ pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu_id %llu\n",
+ sample->file_offset, (unsigned long long)cpu_id);
return -1;
}
p_state_change(tchart, cpu_id, sample->time, value);
--
2.54.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 7/8] perf sched: Replace BUG_ON on invalid CPU with graceful skip
2026-06-02 23:56 [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Arnaldo Carvalho de Melo
` (5 preceding siblings ...)
2026-06-02 23:57 ` [PATCH 6/8] perf timechart: Fix cat_backtrace() use-after-free on corrupted callchain Arnaldo Carvalho de Melo
@ 2026-06-02 23:57 ` Arnaldo Carvalho de Melo
2026-06-03 15:17 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 8/8] perf test: Add file offset diagnostic test for corrupted perf.data Arnaldo Carvalho de Melo
2026-06-03 15:06 ` [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Ian Rogers
8 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-02 23:57 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo, sashiko-bot, Claude Opus 4.6
From: Arnaldo Carvalho de Melo <acme@redhat.com>
latency_switch_event(), latency_runtime_event(), and map_switch_event()
use BUG_ON(cpu >= MAX_CPUS || cpu < 0) to validate the sample CPU.
When PERF_SAMPLE_CPU is absent from the sample type,
evsel__parse_sample() initializes sample->cpu to (u32)-1. Casting
this to int yields -1, which triggers the BUG_ON and aborts perf sched.
The central CPU validation in perf_session__deliver_event() intentionally
preserves the (u32)-1 sentinel for downstream tools like perf script
and perf inject, so leaf callbacks must handle it themselves.
Replace the three BUG_ON calls with graceful skips using pr_warning(),
matching the existing pattern in process_sched_switch_event() and
process_sched_runtime_event() earlier in the same file. Include the
file offset for cross-referencing with perf report -D.
Reported-by: sashiko-bot@kernel.org # Running on a local machine
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/builtin-sched.c | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
index 9ec8e049e19b0038..81833d169470582b 100644
--- a/tools/perf/builtin-sched.c
+++ b/tools/perf/builtin-sched.c
@@ -1145,7 +1145,12 @@ static int latency_switch_event(struct perf_sched *sched,
int cpu = sample->cpu, err = -1;
s64 delta;
- BUG_ON(cpu >= MAX_CPUS || cpu < 0);
+ /* perf.data is untrusted input — CPU may be absent or corrupted */
+ if (cpu >= MAX_CPUS || cpu < 0) {
+ pr_warning("WARNING: at offset %#" PRIx64 ": out-of-bound sample CPU %d, skipping sample\n",
+ sample->file_offset, cpu);
+ return 0;
+ }
timestamp0 = sched->cpu_last_switched[cpu];
sched->cpu_last_switched[cpu] = timestamp;
@@ -1215,7 +1220,13 @@ static int latency_runtime_event(struct perf_sched *sched,
if (thread == NULL)
return -1;
- BUG_ON(cpu >= MAX_CPUS || cpu < 0);
+ /* perf.data is untrusted input — CPU may be absent or corrupted */
+ if (cpu >= MAX_CPUS || cpu < 0) {
+ pr_warning("WARNING: at offset %#" PRIx64 ": out-of-bound sample CPU %d, skipping sample\n",
+ sample->file_offset, cpu);
+ err = 0;
+ goto out_put;
+ }
if (!atoms) {
if (thread_atoms_insert(sched, thread))
goto out_put;
@@ -1640,7 +1651,12 @@ static int map_switch_event(struct perf_sched *sched, struct perf_sample *sampl
const char *str;
int ret = -1;
- BUG_ON(this_cpu.cpu >= MAX_CPUS || this_cpu.cpu < 0);
+ /* perf.data is untrusted input — CPU may be absent or corrupted */
+ if (this_cpu.cpu >= MAX_CPUS || this_cpu.cpu < 0) {
+ pr_warning("WARNING: at offset %#" PRIx64 ": out-of-bound sample CPU %d, skipping sample\n",
+ sample->file_offset, this_cpu.cpu);
+ return 0;
+ }
if (this_cpu.cpu > sched->max_cpu.cpu)
sched->max_cpu = this_cpu;
--
2.54.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 8/8] perf test: Add file offset diagnostic test for corrupted perf.data
2026-06-02 23:56 [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Arnaldo Carvalho de Melo
` (6 preceding siblings ...)
2026-06-02 23:57 ` [PATCH 7/8] perf sched: Replace BUG_ON on invalid CPU with graceful skip Arnaldo Carvalho de Melo
@ 2026-06-02 23:57 ` Arnaldo Carvalho de Melo
2026-06-03 15:19 ` Ian Rogers
2026-06-03 15:06 ` [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Ian Rogers
8 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-02 23:57 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo, Claude Opus 4.6
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Add a shell test that verifies the file_offset diagnostic messages
work correctly when perf encounters corrupted events.
The test corrupts a MMAP2 event's size field in a recorded perf.data
file, then checks that perf report produces warning messages that
include both the file offset (e.g. "at offset 0x2738:") and the
event type name with numeric id (e.g. "MMAP2 (10)").
This exercises the diagnostic improvements from the file_offset
series, which retrofitted all skip/stop/error messages to include
the position and type of the problematic event.
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
.../shell/data_file_offset_diagnostics.sh | 82 +++++++++++++++++++
1 file changed, 82 insertions(+)
create mode 100755 tools/perf/tests/shell/data_file_offset_diagnostics.sh
diff --git a/tools/perf/tests/shell/data_file_offset_diagnostics.sh b/tools/perf/tests/shell/data_file_offset_diagnostics.sh
new file mode 100755
index 0000000000000000..031f49480d999d8c
--- /dev/null
+++ b/tools/perf/tests/shell/data_file_offset_diagnostics.sh
@@ -0,0 +1,82 @@
+#!/bin/bash
+# Test that perf report includes file offsets and event type names in diagnostic messages.
+# SPDX-License-Identifier: GPL-2.0
+#
+# The file_offset diagnostic series adds "at offset 0x...: TYPE (N)"
+# to all skip/stop/error messages. This test corrupts an event's size
+# field in a perf.data file, then verifies the resulting warning
+# includes the file offset and event type.
+
+err=0
+
+cleanup() {
+ [ -n "${perfdata}" ] && rm -f "${perfdata}" "${perfdata}.old"
+ rm -f "${corrupted}" "${stderrfile}"
+ trap - EXIT TERM INT
+}
+trap 'cleanup; exit 1' TERM INT
+trap cleanup EXIT
+
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) || exit 2
+corrupted=$(mktemp /tmp/__perf_test.perf.data.XXXXX) || exit 2
+stderrfile=$(mktemp /tmp/__perf_test.perf.data.XXXXX) || exit 2
+
+if ! perf record -o "${perfdata}" -- perf test -w noploop 2>/dev/null; then
+ echo "Skip: perf record failed"
+ cleanup
+ exit 2
+fi
+
+# Find the file offset of the first MMAP2 event via perf report -D.
+# Format: "timestamp 0xOFFSET [0xSIZE]: PERF_RECORD_MMAP2 ..."
+mmap2_line=$(perf report -D -i "${perfdata}" 2>/dev/null | grep "PERF_RECORD_MMAP2" | head -1)
+if [ -z "${mmap2_line}" ]; then
+ echo "Skip: no MMAP2 events found in perf.data"
+ cleanup
+ exit 2
+fi
+
+mmap2_offset=$(echo "${mmap2_line}" | awk '{print $2}')
+mmap2_offset_dec=$((mmap2_offset))
+
+# Copy the file and corrupt the MMAP2 event's size field.
+# perf_event_header layout: type(u32) misc(u16) size(u16)
+# Set size to 16 (0x10 0x00 little-endian) — below the MMAP2
+# minimum, which triggers the "event size too small" warning.
+cp "${perfdata}" "${corrupted}"
+printf '\x10\x00' | dd of="${corrupted}" bs=1 seek=$((mmap2_offset_dec + 6)) conv=notrunc 2>/dev/null
+
+perf report -i "${corrupted}" --stdio > /dev/null 2> "${stderrfile}"
+
+# Check that warnings include "at offset 0x..."
+if grep -q "at offset 0x" "${stderrfile}"; then
+ echo "File offset in diagnostics [Success]"
+else
+ echo "File offset in diagnostics [Failed: no 'at offset 0x...' found]"
+ echo " stderr was:"
+ head -5 "${stderrfile}"
+ err=1
+fi
+
+# Check that the event type name and numeric id appear: "MMAP2 (10)"
+if grep -q "MMAP2 (10)" "${stderrfile}"; then
+ echo "Event type name in diagnostics [Success]"
+else
+ echo "Event type name in diagnostics [Failed: no 'MMAP2 (10)' found]"
+ echo " stderr was:"
+ head -5 "${stderrfile}"
+ err=1
+fi
+
+# Check that the reported offset matches the actual corruption point
+if grep -q "at offset ${mmap2_offset}:" "${stderrfile}"; then
+ echo "Correct offset reported [Success]"
+else
+ echo "Correct offset reported [Failed: expected offset ${mmap2_offset}]"
+ echo " stderr was:"
+ head -5 "${stderrfile}"
+ err=1
+fi
+
+cleanup
+exit ${err}
--
2.54.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes
2026-06-02 23:56 [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Arnaldo Carvalho de Melo
` (7 preceding siblings ...)
2026-06-02 23:57 ` [PATCH 8/8] perf test: Add file offset diagnostic test for corrupted perf.data Arnaldo Carvalho de Melo
@ 2026-06-03 15:06 ` Ian Rogers
2026-06-03 19:27 ` Arnaldo Carvalho de Melo
8 siblings, 1 reply; 20+ messages in thread
From: Ian Rogers @ 2026-06-03 15:06 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: Namhyung Kim, Ingo Molnar, Thomas Gleixner, James Clark,
Jiri Olsa, Adrian Hunter, Clark Williams, linux-kernel,
linux-perf-users
On Tue, Jun 2, 2026 at 4:57 PM Arnaldo Carvalho de Melo <acme@kernel.org> wrote:
>
> When perf report, perf sched, or perf timechart skip a malformed or
> unprocessable event, the warning message doesn't say where in the
> perf.data file the problem occurred. This makes it hard to
> cross-reference with 'perf report -D' output or to locate the
> corrupted region with a hex editor.
>
> This series adds a file_offset field to struct perf_sample, set in the
> event delivery path (including the deferred callchain path), and
> retrofits all skip/stop/error messages to include:
>
> - The file offset where the event was found
> - The event type name via perf_event__name() with the numeric
> type value in parentheses
>
> For example, instead of:
>
> problem processing 10 event, skipping it.
>
> a user now sees:
>
> WARNING: at offset 0x1a3f0: MMAP2 (10) event size 24 too small (min 64), skipping
>
> The peek_event() path, which validates events during initial file
> scanning, also gains file offsets in its three warning messages
> (misaligned size, unsupported type, undersized event).
>
> Two pre-existing bugs found by sashiko-bot are fixed:
>
> - builtin-timechart.c cat_backtrace(): use-after-free and
> double-free when an invalid callchain context triggers zfree()
> before fclose() on an open_memstream buffer. The open_memstream
> contract requires fclose() before the buffer can be freed — see
> open_memstream(3).
Fwiw, I've also been around the timechart code prompted by AI review
and also trying to clean up tests with address sanitizer:
https://lore.kernel.org/linux-perf-users/agzWqrn6XPEwTAsb@google.com/
Thanks,
Ian
> - builtin-sched.c: three BUG_ON(cpu >= MAX_CPUS || cpu < 0)
> that abort perf sched when PERF_SAMPLE_CPU is absent from the
> sample type and the CPU sentinel (u32)-1 is cast to signed -1.
> perf.data is untrusted input — a corrupted or truncated file
> should produce a warning, not an abort.
>
> Arnaldo Carvalho de Melo (8):
> perf sample: Add file_offset field to struct perf_sample
> perf session: Include file offset in event skip/stop messages
> perf sched: Include file offset in event skip messages
> perf timechart: Include file offset in CPU bounds check messages
> perf tools: Include file offset and event type name in skip messages
> perf timechart: Fix cat_backtrace() use-after-free on corrupted callchain
> perf sched: Replace BUG_ON on invalid CPU with graceful skip
> perf test: Add file offset diagnostic test for corrupted perf.data
>
> 15 files changed, 261 insertions(+), 101 deletions(-)
>
> Developed with AI assistance (Claude/sashiko), tagged in commits.
>
> Best regards,
>
> - Arnaldo
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 1/8] perf sample: Add file_offset field to struct perf_sample
2026-06-02 23:57 ` [PATCH 1/8] perf sample: Add file_offset field to struct perf_sample Arnaldo Carvalho de Melo
@ 2026-06-03 15:11 ` Ian Rogers
0 siblings, 0 replies; 20+ messages in thread
From: Ian Rogers @ 2026-06-03 15:11 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: Namhyung Kim, Ingo Molnar, Thomas Gleixner, James Clark,
Jiri Olsa, Adrian Hunter, Clark Williams, linux-kernel,
linux-perf-users, Arnaldo Carvalho de Melo, Claude Opus 4.6
On Tue, Jun 2, 2026 at 4:57 PM Arnaldo Carvalho de Melo <acme@kernel.org> wrote:
>
> From: Arnaldo Carvalho de Melo <acme@redhat.com>
>
> Add a file_offset field to struct perf_sample so that event processing
> callbacks can report the byte offset of the problematic event in
> perf.data, letting users cross-reference with 'perf report -D' output.
>
> Set sample.file_offset in perf_session__deliver_event(), which is the
> common entry point for both file mode (mmap'd offset) and pipe mode
> (running byte counter from __perf_session__process_pipe_events).
>
> The assignment is placed after evsel__parse_sample(), which zeroes
> the struct via memset.
>
> Preserve file_offset through the deferred callchain delivery path by
> storing it in struct deferred_event and restoring it after
> evlist__parse_sample() in both evlist__deliver_deferred_callchain()
> and session__flush_deferred_samples().
>
> Subsequent patches will use this field in skip/stop warning messages.
>
> Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/util/sample.h | 2 ++
> tools/perf/util/session.c | 5 +++++
> 2 files changed, 7 insertions(+)
>
> diff --git a/tools/perf/util/sample.h b/tools/perf/util/sample.h
> index e556c9b656ea9cd6..c4eae8b2fd06035a 100644
> --- a/tools/perf/util/sample.h
> +++ b/tools/perf/util/sample.h
> @@ -158,6 +158,8 @@ struct perf_sample {
> u64 code_page_size;
> /** @cgroup: The sample event PERF_SAMPLE_CGROUP value. */
> u64 cgroup;
> + /** @file_offset: Byte offset of this event in the perf.data file. */
> + u64 file_offset;
> /** @flags: Extra flag data from auxiliary events like intel-pt. */
> u32 flags;
> /** @machine_pid: The guest machine pid derived from the sample id. */
> diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
> index e2e821b77766dbfc..7996787d742e32c6 100644
> --- a/tools/perf/util/session.c
> +++ b/tools/perf/util/session.c
> @@ -1824,6 +1824,7 @@ static int evlist__deliver_sample(struct evlist *evlist, const struct perf_tool
> struct deferred_event {
> struct list_head list;
> union perf_event *event;
> + u64 file_offset;
> };
>
> /*
> @@ -1858,6 +1859,7 @@ static int evlist__deliver_deferred_callchain(struct evlist *evlist,
> perf_sample__exit(&orig_sample);
> break;
> }
> + orig_sample.file_offset = de->file_offset;
>
> if (sample->tid != orig_sample.tid) {
> perf_sample__exit(&orig_sample);
> @@ -1906,6 +1908,7 @@ static int session__flush_deferred_samples(struct perf_session *session,
> perf_sample__exit(&sample);
> break;
> }
> + sample.file_offset = de->file_offset;
>
> sample.evsel = evlist__id2evsel(evlist, sample.id);
> ret = evlist__deliver_sample(evlist, tool, de->event,
> @@ -1984,6 +1987,7 @@ static int machines__deliver_event(struct machines *machines,
> return -ENOMEM;
> }
> memcpy(de->event, event, sz);
> + de->file_offset = sample->file_offset;
> list_add_tail(&de->list, &evlist->deferred_samples);
> return 0;
> }
> @@ -2126,6 +2130,7 @@ static int perf_session__deliver_event(struct perf_session *session,
> pr_err("Can't parse sample, err = %d\n", ret);
> goto out;
> }
> + sample.file_offset = file_offset;
> /*
> * evsel__parse_sample() doesn't populate machine_pid/vcpu,
> * which are needed by machines__find_for_cpumode() to
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 2/8] perf session: Include file offset in event skip/stop messages
2026-06-02 23:57 ` [PATCH 2/8] perf session: Include file offset in event skip/stop messages Arnaldo Carvalho de Melo
@ 2026-06-03 15:12 ` Ian Rogers
0 siblings, 0 replies; 20+ messages in thread
From: Ian Rogers @ 2026-06-03 15:12 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: Namhyung Kim, Ingo Molnar, Thomas Gleixner, James Clark,
Jiri Olsa, Adrian Hunter, Clark Williams, linux-kernel,
linux-perf-users, Arnaldo Carvalho de Melo, Claude Opus 4.6
On Tue, Jun 2, 2026 at 4:57 PM Arnaldo Carvalho de Melo <acme@kernel.org> wrote:
>
> From: Arnaldo Carvalho de Melo <acme@redhat.com>
>
> Add 'at offset %#<hex>' to all warning and error messages in session.c
> that fire when events are skipped or processing stops due to validation
> failures. This lets users cross-reference with 'perf report -D' output
> to inspect the surrounding records and understand the corruption context.
>
> Covers messages in perf_session__process_event() (alignment, min size,
> swap failure), perf_session__deliver_event() (no evsel, parse failure,
> CPU clamping), machines__deliver_event() (NAMESPACES, TEXT_POKE,
> null-terminated string checks for MMAP/MMAP2/COMM/CGROUP/KSYMBOL), and
> perf_session__process_user_event() (THREAD_MAP, CPU_MAP, STAT_CONFIG,
> BPF_METADATA, HEADER_BUILD_ID).
>
> Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/util/session.c | 112 ++++++++++++++++++++------------------
> 1 file changed, 60 insertions(+), 52 deletions(-)
>
> diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
> index 7996787d742e32c6..e4efb75509278a4e 100644
> --- a/tools/perf/util/session.c
> +++ b/tools/perf/util/session.c
> @@ -1931,13 +1931,14 @@ static int session__flush_deferred_samples(struct perf_session *session,
> * 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)
> +static bool perf_event__check_nul(const char *str, const void *end,
> + const char *event_name, u64 file_offset)
> {
> 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);
> + pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_%s: string not null-terminated, skipping event\n",
> + file_offset, event_name);
> return false;
> }
>
> @@ -1995,7 +1996,7 @@ static int machines__deliver_event(struct machines *machines,
> case PERF_RECORD_MMAP:
> if (!perf_event__check_nul(event->mmap.filename,
> (void *)event + event->header.size,
> - "MMAP"))
> + "MMAP", file_offset))
> return 0;
> return tool->mmap(tool, event, sample, machine);
> case PERF_RECORD_MMAP2:
> @@ -2003,13 +2004,13 @@ static int machines__deliver_event(struct machines *machines,
> ++evlist->stats.nr_proc_map_timeout;
> if (!perf_event__check_nul(event->mmap2.filename,
> (void *)event + event->header.size,
> - "MMAP2"))
> + "MMAP2", file_offset))
> 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"))
> + "COMM", file_offset))
> return 0;
> return tool->comm(tool, event, sample, machine);
> case PERF_RECORD_NAMESPACES: {
> @@ -2027,8 +2028,8 @@ static int machines__deliver_event(struct machines *machines,
> * cross-endian path.
> */
> if (event->namespaces.nr_namespaces > max_nr) {
> - pr_warning("WARNING: PERF_RECORD_NAMESPACES: nr_namespaces %" PRIu64 " exceeds payload (max %" PRIu64 "), skipping\n",
> - (u64)event->namespaces.nr_namespaces, max_nr);
> + pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_NAMESPACES: nr_namespaces %" PRIu64 " exceeds payload (max %" PRIu64 "), skipping\n",
> + file_offset, (u64)event->namespaces.nr_namespaces, max_nr);
> return 0;
> }
> return tool->namespaces(tool, event, sample, machine);
> @@ -2036,7 +2037,7 @@ static int machines__deliver_event(struct machines *machines,
> case PERF_RECORD_CGROUP:
> if (!perf_event__check_nul(event->cgroup.path,
> (void *)event + event->header.size,
> - "CGROUP"))
> + "CGROUP", file_offset))
> return 0;
> return tool->cgroup(tool, event, sample, machine);
> case PERF_RECORD_FORK:
> @@ -2078,7 +2079,7 @@ static int machines__deliver_event(struct machines *machines,
> case PERF_RECORD_KSYMBOL:
> if (!perf_event__check_nul(event->ksymbol.name,
> (void *)event + event->header.size,
> - "KSYMBOL"))
> + "KSYMBOL", file_offset))
> return 0;
> return tool->ksymbol(tool, event, sample, machine);
> case PERF_RECORD_BPF_EVENT:
> @@ -2090,7 +2091,8 @@ static int machines__deliver_event(struct machines *machines,
> 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");
> + pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_TEXT_POKE: old_len+new_len exceeds event, skipping\n",
> + file_offset);
> return 0;
> }
> return tool->text_poke(tool, event, sample, machine);
> @@ -2120,14 +2122,17 @@ static int perf_session__deliver_event(struct perf_session *session,
> perf_sample__init(&sample, /*all=*/false);
> evsel = evlist__event2evsel(session->evlist, event);
> if (!evsel) {
> - pr_err("No evsel found for event type %u\n",
> + pr_err("ERROR: at offset %#" PRIx64 ": no evsel found for %s (%u) event\n",
> + file_offset, perf_event__name(event->header.type),
> event->header.type);
> ret = -EFAULT;
> goto out;
> }
> ret = evsel__parse_sample(evsel, event, &sample);
> if (ret) {
> - pr_err("Can't parse sample, err = %d\n", ret);
> + pr_err("ERROR: at offset %#" PRIx64 ": can't parse %s (%u) sample, err = %d\n",
> + file_offset, perf_event__name(event->header.type),
> + event->header.type, ret);
> goto out;
> }
> sample.file_offset = file_offset;
> @@ -2204,8 +2209,8 @@ static int perf_session__deliver_event(struct perf_session *session,
> * Downstream array users (timechart, kwork) have
> * their own per-callback bounds checks.
> */
> - pr_warning_once("WARNING: sample CPU %u >= nr_cpus_avail %u, clamping to 0\n",
> - sample.cpu, nr_cpus_avail);
> + pr_warning_once("WARNING: at offset %#" PRIx64 ": sample CPU %u >= nr_cpus_avail %u, clamping to 0\n",
> + file_offset, sample.cpu, nr_cpus_avail);
> sample.cpu = 0;
> }
> }
> @@ -2278,7 +2283,7 @@ static s64 perf_session__process_user_event(struct perf_session *session,
> case PERF_RECORD_HEADER_BUILD_ID:
> if (!perf_event__check_nul(event->build_id.filename,
> (void *)event + event_size,
> - "HEADER_BUILD_ID")) {
> + "HEADER_BUILD_ID", file_offset)) {
> err = 0;
> break;
> }
> @@ -2311,8 +2316,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
> u64 max_nr;
>
> if (event_size < sizeof(event->thread_map)) {
> - pr_err("PERF_RECORD_THREAD_MAP: header.size (%u) too small\n",
> - event_size);
> + pr_err("ERROR: at offset %#" PRIx64 ": PERF_RECORD_THREAD_MAP: header.size (%u) too small\n",
> + file_offset, event_size);
> err = -EINVAL;
> break;
> }
> @@ -2320,8 +2325,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
> max_nr = (event_size - sizeof(event->thread_map)) /
> sizeof(event->thread_map.entries[0]);
> if (event->thread_map.nr > max_nr) {
> - pr_err("PERF_RECORD_THREAD_MAP: nr %" PRIu64 " exceeds max %" PRIu64 "\n",
> - (u64)event->thread_map.nr, max_nr);
> + pr_err("ERROR: at offset %#" PRIx64 ": PERF_RECORD_THREAD_MAP: nr %" PRIu64 " exceeds max %" PRIu64 "\n",
> + file_offset, (u64)event->thread_map.nr, max_nr);
> err = -EINVAL;
> break;
> }
> @@ -2345,8 +2350,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
> sizeof(data->cpus_data.cpu[0]);
>
> if (data->cpus_data.nr > max_nr) {
> - pr_warning("WARNING: PERF_RECORD_CPU_MAP: nr %u exceeds payload (max %u), skipping\n",
> - data->cpus_data.nr, max_nr);
> + pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_CPU_MAP: nr %u exceeds payload (max %u), skipping\n",
> + file_offset, data->cpus_data.nr, max_nr);
> err = 0;
> goto out;
> }
> @@ -2359,8 +2364,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
> sizeof(data->mask32_data.mask[0]);
>
> if (data->mask32_data.nr > max_nr) {
> - pr_warning("WARNING: PERF_RECORD_CPU_MAP mask32: nr %u exceeds payload (max %u), skipping\n",
> - data->mask32_data.nr, max_nr);
> + pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_CPU_MAP mask32: nr %u exceeds payload (max %u), skipping\n",
> + file_offset, data->mask32_data.nr, max_nr);
> err = 0;
> goto out;
> }
> @@ -2375,14 +2380,14 @@ static s64 perf_session__process_user_event(struct perf_session *session,
> mask64_data.mask)) /
> sizeof(data->mask64_data.mask[0]);
> if (data->mask64_data.nr > max_nr) {
> - pr_warning("WARNING: PERF_RECORD_CPU_MAP mask64: nr %u exceeds payload (max %u), skipping\n",
> - data->mask64_data.nr, max_nr);
> + pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_CPU_MAP mask64: nr %u exceeds payload (max %u), skipping\n",
> + file_offset, data->mask64_data.nr, max_nr);
> err = 0;
> goto out;
> }
> } else {
> - pr_warning("WARNING: PERF_RECORD_CPU_MAP: unsupported long_size %u, skipping\n",
> - data->mask32_data.long_size);
> + pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_CPU_MAP: unsupported long_size %u, skipping\n",
> + file_offset, data->mask32_data.long_size);
> err = 0;
> goto out;
> }
> @@ -2404,8 +2409,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
> * cannot clamp nr in place. Skip the event instead.
> */
> if (event->stat_config.nr > max_nr) {
> - pr_warning("WARNING: PERF_RECORD_STAT_CONFIG: nr %" PRIu64 " exceeds payload (max %" PRIu64 "), skipping\n",
> - (u64)event->stat_config.nr, max_nr);
> + pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_STAT_CONFIG: nr %" PRIu64 " exceeds payload (max %" PRIu64 "), skipping\n",
> + file_offset, (u64)event->stat_config.nr, max_nr);
> err = 0;
> goto out;
> }
> @@ -2446,8 +2451,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
> u64 nr_entries, max_entries;
>
> if (event_size < sizeof(event->bpf_metadata)) {
> - pr_warning("WARNING: PERF_RECORD_BPF_METADATA: header.size (%u) too small, skipping\n",
> - event_size);
> + pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_BPF_METADATA: header.size (%u) too small, skipping\n",
> + file_offset, event_size);
> err = 0;
> break;
> }
> @@ -2458,7 +2463,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
> */
> if (strnlen(event->bpf_metadata.prog_name,
> BPF_PROG_NAME_LEN) == BPF_PROG_NAME_LEN) {
> - pr_warning("WARNING: PERF_RECORD_BPF_METADATA: prog_name not null-terminated, skipping\n");
> + pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_BPF_METADATA: prog_name not null-terminated, skipping\n",
> + file_offset);
> err = 0;
> break;
> }
> @@ -2467,8 +2473,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
> max_entries = (event_size - sizeof(event->bpf_metadata)) /
> sizeof(event->bpf_metadata.entries[0]);
> if (nr_entries > max_entries) {
> - pr_warning("WARNING: PERF_RECORD_BPF_METADATA: nr_entries %" PRIu64 " exceeds max %" PRIu64 ", skipping\n",
> - nr_entries, max_entries);
> + pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_BPF_METADATA: nr_entries %" PRIu64 " exceeds max %" PRIu64 ", skipping\n",
> + file_offset, nr_entries, max_entries);
> err = 0;
> break;
> }
> @@ -2478,7 +2484,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
> BPF_METADATA_KEY_LEN) == BPF_METADATA_KEY_LEN ||
> strnlen(event->bpf_metadata.entries[i].value,
> BPF_METADATA_VALUE_LEN) == BPF_METADATA_VALUE_LEN) {
> - pr_warning("WARNING: PERF_RECORD_BPF_METADATA: entry %" PRIu64 " key/value not null-terminated, skipping\n", i);
> + pr_warning("WARNING: at offset %#" PRIx64 ": PERF_RECORD_BPF_METADATA: entry %" PRIu64 " key/value not null-terminated, skipping\n",
> + file_offset, i);
> err = 0;
> goto out;
> }
> @@ -2752,22 +2759,22 @@ int perf_session__peek_event(struct perf_session *session, off_t file_offset,
> event->header.type != PERF_RECORD_HEADER_TRACING_DATA &&
> event->header.type != PERF_RECORD_COMPRESSED &&
> event->header.type != PERF_RECORD_HEADER_FEATURE) {
> - pr_warning("WARNING: peek_event: event type %u size %u not aligned to %zu\n",
> - event->header.type,
> - event->header.size, sizeof(u64));
> + pr_warning("WARNING: at offset %#" PRIx64 ": %s (%u) event size %u not aligned to %zu\n",
> + (u64)file_offset, perf_event__name(event->header.type),
> + event->header.type, event->header.size, sizeof(u64));
> return -1;
> }
>
> if (event->header.type >= PERF_RECORD_HEADER_MAX) {
> - pr_warning("WARNING: peek_event: unsupported event type %u, skipping\n",
> - event->header.type);
> + pr_warning("WARNING: at offset %#" PRIx64 ": unsupported event type %u, skipping\n",
> + (u64)file_offset, event->header.type);
> return 0;
> }
>
> if (perf_event__too_small(event, &min_sz)) {
> - pr_warning("WARNING: peek_event: %s event size %u too small (min %u)\n",
> - perf_event__name(event->header.type),
> - event->header.size, min_sz);
> + pr_warning("WARNING: at offset %#" PRIx64 ": %s (%u) event size %u too small (min %u)\n",
> + (u64)file_offset, perf_event__name(event->header.type),
> + event->header.type, event->header.size, min_sz);
> return -1;
> }
>
> @@ -2883,9 +2890,9 @@ static s64 perf_session__process_event(struct perf_session *session,
> event->header.type != PERF_RECORD_HEADER_TRACING_DATA &&
> event->header.type != PERF_RECORD_COMPRESSED &&
> event->header.type != PERF_RECORD_HEADER_FEATURE) {
> - pr_err("ERROR: %s event size %u is not 8-byte aligned, aborting\n",
> - perf_event__name(event->header.type),
> - event->header.size);
> + pr_err("ERROR: at offset %#" PRIx64 ": %s (%u) event size %u is not 8-byte aligned, aborting\n",
> + file_offset, perf_event__name(event->header.type),
> + event->header.type, event->header.size);
> return -EINVAL;
> }
>
> @@ -2905,16 +2912,17 @@ static s64 perf_session__process_event(struct perf_session *session,
> * can be safely stepped over without misaligning the stream.
> */
> if (perf_event__too_small(event, &min_sz)) {
> - pr_warning("WARNING: %s event size %u too small (min %u), skipping\n",
> - perf_event__name(event->header.type),
> - event->header.size, min_sz);
> + pr_warning("WARNING: at offset %#" PRIx64 ": %s (%u) event size %u too small (min %u), skipping\n",
> + file_offset, perf_event__name(event->header.type),
> + event->header.type, event->header.size, min_sz);
> return 0;
> }
>
> 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));
> + pr_warning("WARNING: at offset %#" PRIx64 ": swap failed for %s (%u) event, skipping\n",
> + file_offset, perf_event__name(event->header.type),
> + event->header.type);
> return 0;
> }
>
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/8] perf sched: Include file offset in event skip messages
2026-06-02 23:57 ` [PATCH 3/8] perf sched: Include file offset in event skip messages Arnaldo Carvalho de Melo
@ 2026-06-03 15:13 ` Ian Rogers
0 siblings, 0 replies; 20+ messages in thread
From: Ian Rogers @ 2026-06-03 15:13 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: Namhyung Kim, Ingo Molnar, Thomas Gleixner, James Clark,
Jiri Olsa, Adrian Hunter, Clark Williams, linux-kernel,
linux-perf-users, Arnaldo Carvalho de Melo, Claude Opus 4.6
On Tue, Jun 2, 2026 at 4:57 PM Arnaldo Carvalho de Melo <acme@kernel.org> wrote:
>
> From: Arnaldo Carvalho de Melo <acme@redhat.com>
>
> Add the perf.data file offset to the CPU out-of-bounds and
> machine__resolve failure messages emitted when samples are skipped in
> process_sched_switch_event(), process_sched_runtime_event(), and
> timehist_sched_change_event(). Also switch event type from raw integer
> to perf_event__name() string for readability.
>
> Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/builtin-sched.c | 17 ++++++++++++-----
> 1 file changed, 12 insertions(+), 5 deletions(-)
>
> diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
> index 812a1b0d56d6e5f3..9ec8e049e19b0038 100644
> --- a/tools/perf/builtin-sched.c
> +++ b/tools/perf/builtin-sched.c
> @@ -1792,8 +1792,10 @@ static int process_sched_switch_event(const struct perf_tool *tool,
> u32 prev_pid = perf_sample__intval(sample, "prev_pid"),
> next_pid = perf_sample__intval(sample, "next_pid");
>
> + /* perf.data is untrusted input — CPU may be absent or corrupted */
> if (this_cpu < 0 || this_cpu >= MAX_CPUS) {
> - pr_warning("Out-of-bound sample CPU %d. Skipping sample\n", this_cpu);
> + pr_warning("WARNING: at offset %#" PRIx64 ": out-of-bound sample CPU %d, skipping sample\n",
> + sample->file_offset, this_cpu);
> return 0;
> }
>
> @@ -1819,8 +1821,10 @@ static int process_sched_runtime_event(const struct perf_tool *tool,
> {
> struct perf_sched *sched = container_of(tool, struct perf_sched, tool);
>
> + /* perf.data is untrusted input — CPU may be absent or corrupted */
> if (sample->cpu >= MAX_CPUS) {
> - pr_warning("Out-of-bound sample CPU %u. Skipping sample\n", sample->cpu);
> + pr_warning("WARNING: at offset %#" PRIx64 ": out-of-bound sample CPU %u, skipping sample\n",
> + sample->file_offset, sample->cpu);
> return 0;
> }
>
> @@ -2786,15 +2790,18 @@ static int timehist_sched_change_event(const struct perf_tool *tool,
> int rc = 0;
> const char state = perf_sample__taskstate(sample, "prev_state");
>
> + /* perf.data is untrusted input — CPU may be absent or corrupted */
> if (sample->cpu >= MAX_CPUS) {
> - pr_warning("Out-of-bound sample CPU %d. Skipping sample\n", sample->cpu);
> + pr_warning("WARNING: at offset %#" PRIx64 ": out-of-bound sample CPU %d, skipping sample\n",
> + sample->file_offset, sample->cpu);
> return 0;
> }
>
> addr_location__init(&al);
> if (machine__resolve(machine, &al, sample) < 0) {
> - pr_err("problem processing %d event. skipping it\n",
> - event->header.type);
> + pr_err("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it\n",
> + perf_event__name(event->header.type), event->header.type,
> + sample->file_offset);
> rc = -1;
> goto out;
> }
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 4/8] perf timechart: Include file offset in CPU bounds check messages
2026-06-02 23:57 ` [PATCH 4/8] perf timechart: Include file offset in CPU bounds check messages Arnaldo Carvalho de Melo
@ 2026-06-03 15:14 ` Ian Rogers
0 siblings, 0 replies; 20+ messages in thread
From: Ian Rogers @ 2026-06-03 15:14 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: Namhyung Kim, Ingo Molnar, Thomas Gleixner, James Clark,
Jiri Olsa, Adrian Hunter, Clark Williams, linux-kernel,
linux-perf-users, Arnaldo Carvalho de Melo, Claude Opus 4.6
On Tue, Jun 2, 2026 at 4:57 PM Arnaldo Carvalho de Melo <acme@kernel.org> wrote:
>
> From: Arnaldo Carvalho de Melo <acme@redhat.com>
>
> Add the perf.data file offset to the out-of-bounds CPU debug messages
> in process_sample_cpu_idle(), process_sample_cpu_frequency(),
> process_sample_sched_wakeup(), and process_sample_sched_switch().
>
> Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/builtin-timechart.c | 19 +++++++++++++++----
> 1 file changed, 15 insertions(+), 4 deletions(-)
>
> diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c
> index 630756bebe3242dc..071987241a528ba4 100644
> --- a/tools/perf/builtin-timechart.c
> +++ b/tools/perf/builtin-timechart.c
> @@ -605,8 +605,10 @@ process_sample_cpu_idle(struct timechart *tchart __maybe_unused,
> u32 state = perf_sample__intval(sample, "state");
> u32 cpu_id = perf_sample__intval(sample, "cpu_id");
>
> + /* perf.data is untrusted input — cpu_id may be corrupted */
> if (cpu_id >= MAX_CPUS) {
> - pr_debug("Out-of-bounds cpu_id %u\n", cpu_id);
> + pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu_id %u\n",
> + sample->file_offset, cpu_id);
> return -1;
> }
> if (state == (u32)PWR_EVENT_EXIT)
> @@ -624,8 +626,10 @@ process_sample_cpu_frequency(struct timechart *tchart,
> u32 state = perf_sample__intval(sample, "state");
> u32 cpu_id = perf_sample__intval(sample, "cpu_id");
>
> + /* perf.data is untrusted input — cpu_id may be corrupted */
> if (cpu_id >= MAX_CPUS) {
> - pr_debug("Out-of-bounds cpu_id %u\n", cpu_id);
> + pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu_id %u\n",
> + sample->file_offset, cpu_id);
> return -1;
> }
> p_state_change(tchart, cpu_id, sample->time, state);
> @@ -641,8 +645,10 @@ process_sample_sched_wakeup(struct timechart *tchart,
> int waker = perf_sample__intval(sample, "common_pid");
> int wakee = perf_sample__intval(sample, "pid");
>
> + /* perf.data is untrusted input — CPU may be absent or corrupted */
> if (sample->cpu >= MAX_CPUS) {
> - pr_debug("Out-of-bounds cpu %u\n", sample->cpu);
> + pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu %u\n",
> + sample->file_offset, sample->cpu);
> return -1;
> }
> sched_wakeup(tchart, sample->cpu, sample->time, waker, wakee, flags, backtrace);
> @@ -658,8 +664,10 @@ process_sample_sched_switch(struct timechart *tchart,
> int next_pid = perf_sample__intval(sample, "next_pid");
> u64 prev_state = perf_sample__intval(sample, "prev_state");
>
> + /* perf.data is untrusted input — CPU may be absent or corrupted */
> if (sample->cpu >= MAX_CPUS) {
> - pr_debug("Out-of-bounds cpu %u\n", sample->cpu);
> + pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu %u\n",
> + sample->file_offset, sample->cpu);
> return -1;
> }
> sched_switch(tchart, sample->cpu, sample->time, prev_pid, next_pid,
> @@ -676,6 +684,7 @@ process_sample_power_start(struct timechart *tchart __maybe_unused,
> u64 cpu_id = perf_sample__intval(sample, "cpu_id");
> u64 value = perf_sample__intval(sample, "value");
>
> + /* perf.data is untrusted input — cpu_id may be corrupted */
> if (cpu_id >= MAX_CPUS) {
> pr_debug("Out-of-bounds cpu_id %llu\n", (unsigned long long)cpu_id);
> return -1;
> @@ -689,6 +698,7 @@ process_sample_power_end(struct timechart *tchart,
> struct perf_sample *sample,
> const char *backtrace __maybe_unused)
> {
> + /* perf.data is untrusted input — CPU may be absent or corrupted */
> if (sample->cpu >= MAX_CPUS) {
> pr_debug("Out-of-bounds cpu %u\n", sample->cpu);
> return -1;
> @@ -705,6 +715,7 @@ process_sample_power_frequency(struct timechart *tchart,
> u64 cpu_id = perf_sample__intval(sample, "cpu_id");
> u64 value = perf_sample__intval(sample, "value");
>
> + /* perf.data is untrusted input — cpu_id may be corrupted */
> if (cpu_id >= MAX_CPUS) {
> pr_debug("Out-of-bounds cpu_id %llu\n", (unsigned long long)cpu_id);
> return -1;
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 5/8] perf tools: Include file offset and event type name in skip messages
2026-06-02 23:57 ` [PATCH 5/8] perf tools: Include file offset and event type name in skip messages Arnaldo Carvalho de Melo
@ 2026-06-03 15:14 ` Ian Rogers
0 siblings, 0 replies; 20+ messages in thread
From: Ian Rogers @ 2026-06-03 15:14 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: Namhyung Kim, Ingo Molnar, Thomas Gleixner, James Clark,
Jiri Olsa, Adrian Hunter, Clark Williams, linux-kernel,
linux-perf-users, Arnaldo Carvalho de Melo, Claude Opus 4.6
On Tue, Jun 2, 2026 at 4:57 PM Arnaldo Carvalho de Melo <acme@kernel.org> wrote:
>
> From: Arnaldo Carvalho de Melo <acme@redhat.com>
>
> Add the perf.data file offset and use perf_event__name() instead of raw
> event type integers in the 'problem processing event, skipping it'
> messages emitted by process_sample_event() callbacks across annotate,
> c2c, diff, kmem, kvm, kwork, lock, report, script, and build-id.
>
> This lets users cross-reference skipped events with 'perf report -D'
> output. Also add explicit #include "util/event.h" and <inttypes.h>
> where needed to avoid depending on transitive includes.
>
> Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/builtin-annotate.c | 5 +++--
> tools/perf/builtin-c2c.c | 5 +++--
> tools/perf/builtin-diff.c | 8 +++++---
> tools/perf/builtin-kmem.c | 6 ++++--
> tools/perf/builtin-kvm.c | 9 ++++++---
> tools/perf/builtin-kwork.c | 4 +++-
> tools/perf/builtin-lock.c | 6 ++++--
> tools/perf/builtin-report.c | 9 ++++++---
> tools/perf/builtin-script.c | 10 ++++++----
> tools/perf/util/build-id.c | 5 +++--
> 10 files changed, 43 insertions(+), 24 deletions(-)
>
> diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
> index 5f450c8093c09210..b918f9eed5fd2441 100644
> --- a/tools/perf/builtin-annotate.c
> +++ b/tools/perf/builtin-annotate.c
> @@ -288,8 +288,9 @@ static int process_sample_event(const struct perf_tool *tool,
>
> addr_location__init(&al);
> if (machine__resolve(machine, &al, sample) < 0) {
> - pr_warning("problem processing %d event, skipping it.\n",
> - event->header.type);
> + pr_warning("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
> + perf_event__name(event->header.type), event->header.type,
> + sample->file_offset);
> ret = -1;
> goto out_put;
> }
> diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
> index 36f38694992386ad..d3503be9350c03bb 100644
> --- a/tools/perf/builtin-c2c.c
> +++ b/tools/perf/builtin-c2c.c
> @@ -328,8 +328,9 @@ static int process_sample_event(const struct perf_tool *tool __maybe_unused,
>
> addr_location__init(&al);
> if (machine__resolve(machine, &al, sample) < 0) {
> - pr_debug("problem processing %d event, skipping it.\n",
> - event->header.type);
> + pr_debug("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
> + perf_event__name(event->header.type), event->header.type,
> + sample->file_offset);
> ret = -1;
> goto out;
> }
> diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c
> index b4ff863b304ca046..9592f44b6545bab6 100644
> --- a/tools/perf/builtin-diff.c
> +++ b/tools/perf/builtin-diff.c
> @@ -409,8 +409,9 @@ static int diff__process_sample_event(const struct perf_tool *tool,
>
> addr_location__init(&al);
> if (machine__resolve(machine, &al, sample) < 0) {
> - pr_warning("problem processing %d event, skipping it.\n",
> - event->header.type);
> + pr_warning("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
> + perf_event__name(event->header.type), event->header.type,
> + sample->file_offset);
> ret = -1;
> goto out;
> }
> @@ -436,7 +437,8 @@ static int diff__process_sample_event(const struct perf_tool *tool,
> case COMPUTE_STREAM:
> if (hist_entry_iter__add(&iter, &al, PERF_MAX_STACK_DEPTH,
> NULL)) {
> - pr_debug("problem adding hist entry, skipping event\n");
> + pr_debug("problem adding hist entry at offset %#" PRIx64 ", skipping event\n",
> + sample->file_offset);
> goto out;
> }
> break;
> diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c
> index 33585e353efe56cc..e1b2f5bc1ba8d887 100644
> --- a/tools/perf/builtin-kmem.c
> +++ b/tools/perf/builtin-kmem.c
> @@ -22,6 +22,7 @@
> #include "util/cpumap.h"
>
> #include "util/debug.h"
> +#include "util/event.h"
> #include "util/string2.h"
> #include "util/util.h"
>
> @@ -987,8 +988,9 @@ static int process_sample_event(const struct perf_tool *tool __maybe_unused,
> sample->tid);
>
> if (thread == NULL) {
> - pr_debug("problem processing %d event, skipping it.\n",
> - event->header.type);
> + pr_debug("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
> + perf_event__name(event->header.type), event->header.type,
> + sample->file_offset);
> return -1;
> }
>
> diff --git a/tools/perf/builtin-kvm.c b/tools/perf/builtin-kvm.c
> index dd2ed21596aa59f9..394302ebdb161077 100644
> --- a/tools/perf/builtin-kvm.c
> +++ b/tools/perf/builtin-kvm.c
> @@ -22,6 +22,7 @@
> #include "util/synthetic-events.h"
> #include "util/top.h"
> #include "util/data.h"
> +#include "util/event.h"
> #include "util/ordered-events.h"
> #include "util/kvm-stat.h"
> #include "util/util.h"
> @@ -1141,14 +1142,16 @@ static int process_sample_event(const struct perf_tool *tool,
> return 0;
>
> if (machine__resolve(machine, &kvm->al, sample) < 0) {
> - pr_warning("Fail to resolve address location, skip sample.\n");
> + pr_warning("WARNING: at offset %#" PRIx64 ": fail to resolve address location, skipping sample\n",
> + sample->file_offset);
> return 0;
> }
>
> thread = machine__findnew_thread(machine, sample->pid, sample->tid);
> if (thread == NULL) {
> - pr_debug("problem processing %d event, skipping it.\n",
> - event->header.type);
> + pr_debug("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
> + perf_event__name(event->header.type), event->header.type,
> + sample->file_offset);
> return -1;
> }
>
> diff --git a/tools/perf/builtin-kwork.c b/tools/perf/builtin-kwork.c
> index 99dc293a0744726e..110de3507d48160c 100644
> --- a/tools/perf/builtin-kwork.c
> +++ b/tools/perf/builtin-kwork.c
> @@ -9,6 +9,7 @@
> #include "perf.h"
>
> #include "util/data.h"
> +#include "util/event.h"
> #include "util/evlist.h"
> #include "util/evsel.h"
> #include "util/header.h"
> @@ -897,7 +898,8 @@ static int timehist_exit_event(struct perf_kwork *kwork,
>
> addr_location__init(&al);
> if (machine__resolve(machine, &al, sample) < 0) {
> - pr_debug("Problem processing event, skipping it\n");
> + pr_debug("problem processing event at offset %#" PRIx64 ", skipping it\n",
> + sample->file_offset);
> ret = -1;
> goto out;
> }
> diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c
> index 94a8c35abb0bc991..5841d43be9718414 100644
> --- a/tools/perf/builtin-lock.c
> +++ b/tools/perf/builtin-lock.c
> @@ -21,6 +21,7 @@
> #include "util/tracepoint.h"
>
> #include "util/debug.h"
> +#include "util/event.h"
> #include "util/session.h"
> #include "util/tool.h"
> #include "util/data.h"
> @@ -1433,8 +1434,9 @@ static int process_sample_event(const struct perf_tool *tool __maybe_unused,
> sample->tid);
>
> if (thread == NULL) {
> - pr_debug("problem processing %d event, skipping it.\n",
> - event->header.type);
> + pr_debug("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
> + perf_event__name(event->header.type), event->header.type,
> + sample->file_offset);
> return -1;
> }
>
> diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
> index 973d97af85019e6e..cd052aa78132b65f 100644
> --- a/tools/perf/builtin-report.c
> +++ b/tools/perf/builtin-report.c
> @@ -27,6 +27,7 @@
>
> #include "perf.h"
> #include "util/debug.h"
> +#include "util/event.h"
> #include "util/evlist.h"
> #include "util/evsel.h"
> #include "util/evswitch.h"
> @@ -284,8 +285,9 @@ static int process_sample_event(const struct perf_tool *tool,
>
> addr_location__init(&al);
> if (machine__resolve(machine, &al, sample) < 0) {
> - pr_debug("problem processing %d event, skipping it.\n",
> - event->header.type);
> + pr_debug("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
> + perf_event__name(event->header.type), event->header.type,
> + sample->file_offset);
> ret = -1;
> goto out_put;
> }
> @@ -332,7 +334,8 @@ static int process_sample_event(const struct perf_tool *tool,
>
> ret = hist_entry_iter__add(&iter, &al, rep->max_stack, rep);
> if (ret < 0)
> - pr_debug("problem adding hist entry, skipping event\n");
> + pr_debug("problem adding hist entry at offset %#" PRIx64 ", skipping event\n",
> + sample->file_offset);
> out_put:
> addr_location__exit(&al);
> return ret;
> diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
> index 5124edf2b7a692b2..f4aa255fc3297f90 100644
> --- a/tools/perf/builtin-script.c
> +++ b/tools/perf/builtin-script.c
> @@ -2693,8 +2693,9 @@ static int process_sample_event(const struct perf_tool *tool,
> goto out_put;
>
> if (!al.thread && machine__resolve(machine, &al, sample) < 0) {
> - pr_err("problem processing %d event, skipping it.\n",
> - event->header.type);
> + pr_err("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
> + perf_event__name(event->header.type), event->header.type,
> + sample->file_offset);
> ret = -1;
> goto out_put;
> }
> @@ -2775,8 +2776,9 @@ static int process_deferred_sample_event(const struct perf_tool *tool,
> goto out_put;
>
> if (machine__resolve(machine, &al, sample) < 0) {
> - pr_err("problem processing %d event, skipping it.\n",
> - event->header.type);
> + pr_err("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
> + perf_event__name(event->header.type), event->header.type,
> + sample->file_offset);
> ret = -1;
> goto out_put;
> }
> diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c
> index af4d874f13810ffe..8c0a9ae932aa5798 100644
> --- a/tools/perf/util/build-id.c
> +++ b/tools/perf/util/build-id.c
> @@ -10,6 +10,7 @@
> #include "util.h" // lsdir(), mkdir_p(), rm_rf()
> #include <dirent.h>
> #include <errno.h>
> +#include <inttypes.h>
> #include <stdio.h>
> #include <sys/stat.h>
> #include <sys/types.h>
> @@ -62,8 +63,8 @@ int build_id__mark_dso_hit(const struct perf_tool *tool __maybe_unused,
> sample->tid);
>
> if (thread == NULL) {
> - pr_err("problem processing %d event, skipping it.\n",
> - event->header.type);
> + pr_err("problem processing %s event at offset %#" PRIx64 ", skipping it.\n",
> + perf_event__name(event->header.type), sample->file_offset);
> return -1;
> }
>
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 6/8] perf timechart: Fix cat_backtrace() use-after-free on corrupted callchain
2026-06-02 23:57 ` [PATCH 6/8] perf timechart: Fix cat_backtrace() use-after-free on corrupted callchain Arnaldo Carvalho de Melo
@ 2026-06-03 15:16 ` Ian Rogers
0 siblings, 0 replies; 20+ messages in thread
From: Ian Rogers @ 2026-06-03 15:16 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: Namhyung Kim, Ingo Molnar, Thomas Gleixner, James Clark,
Jiri Olsa, Adrian Hunter, Clark Williams, linux-kernel,
linux-perf-users, Arnaldo Carvalho de Melo, sashiko-bot,
Claude Opus 4.6
On Tue, Jun 2, 2026 at 4:57 PM Arnaldo Carvalho de Melo <acme@kernel.org> wrote:
>
> From: Arnaldo Carvalho de Melo <acme@redhat.com>
>
> cat_backtrace() uses open_memstream() to build a backtrace string.
> When an invalid callchain context is encountered, zfree(&p) frees
> the memstream buffer, then the exit path calls fclose(f), which
> flushes to the already-freed buffer — a use-after-free. The function
> then returns a dangling pointer that the caller passes to a handler
> and subsequently double-frees.
>
> Fix by replacing the zfree(&p) with a 'corrupted' flag. At the exit
> label, always fclose(f) first (which finalizes the buffer), then
> conditionally free it when corrupted. This ensures the memstream
> contract is honored: the buffer remains valid until fclose().
>
> While here, update the machine__resolve failure message to include
> file_offset and the event type name, matching the pattern from the
> preceding series. Also update the three legacy power event handlers
> under SUPPORT_OLD_POWER_EVENTS to include file_offset in their
> out-of-bounds CPU messages for consistency.
>
> Reported-by: sashiko-bot@kernel.org # Running on a local machine
> Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/builtin-timechart.c | 36 ++++++++++++++++++++++------------
> 1 file changed, 23 insertions(+), 13 deletions(-)
>
> diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c
> index 071987241a528ba4..85a9ad0455aecccd 100644
> --- a/tools/perf/builtin-timechart.c
> +++ b/tools/perf/builtin-timechart.c
> @@ -489,6 +489,10 @@ static void sched_switch(struct timechart *tchart, int cpu, u64 timestamp,
> }
> }
>
> +/*
> + * Returns a malloc'd backtrace string built via open_memstream, or NULL
> + * on error. Caller must free() the returned pointer.
> + */
> static char *cat_backtrace(union perf_event *event,
> struct perf_sample *sample,
> struct machine *machine)
> @@ -500,6 +504,7 @@ static char *cat_backtrace(union perf_event *event,
> u8 cpumode = PERF_RECORD_MISC_USER;
> struct ip_callchain *chain = sample->callchain;
> FILE *f = open_memstream(&p, &p_len);
> + bool corrupted = false;
>
> if (!f) {
> perror("open_memstream error");
> @@ -511,8 +516,9 @@ static char *cat_backtrace(union perf_event *event,
> goto exit;
>
> if (machine__resolve(machine, &al, sample) < 0) {
> - fprintf(stderr, "problem processing %d event, skipping it.\n",
> - event->header.type);
> + pr_err("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n",
> + perf_event__name(event->header.type), event->header.type,
> + sample->file_offset);
> goto exit;
> }
>
> @@ -537,14 +543,8 @@ static char *cat_backtrace(union perf_event *event,
> cpumode = PERF_RECORD_MISC_USER;
> break;
> default:
> - pr_debug("invalid callchain context: "
> - "%"PRId64"\n", (s64) ip);
> -
> - /*
> - * It seems the callchain is corrupted.
> - * Discard all.
> - */
> - zfree(&p);
> + pr_debug("invalid callchain context: %" PRId64 "\n", (s64) ip);
> + corrupted = true;
> goto exit;
> }
> continue;
> @@ -561,7 +561,14 @@ static char *cat_backtrace(union perf_event *event,
> }
> exit:
> addr_location__exit(&al);
> + /*
> + * fclose() on an open_memstream always sets p to a valid buffer,
> + * even if nothing was written — see open_memstream(3). So p is
> + * never NULL after fclose and we need the flag to discard it.
> + */
> fclose(f);
> + if (corrupted)
> + zfree(&p);
>
> return p;
> }
> @@ -686,7 +693,8 @@ process_sample_power_start(struct timechart *tchart __maybe_unused,
>
> /* perf.data is untrusted input — cpu_id may be corrupted */
> if (cpu_id >= MAX_CPUS) {
> - pr_debug("Out-of-bounds cpu_id %llu\n", (unsigned long long)cpu_id);
> + pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu_id %llu\n",
> + sample->file_offset, (unsigned long long)cpu_id);
> return -1;
> }
> c_state_start(cpu_id, sample->time, value);
> @@ -700,7 +708,8 @@ process_sample_power_end(struct timechart *tchart,
> {
> /* perf.data is untrusted input — CPU may be absent or corrupted */
> if (sample->cpu >= MAX_CPUS) {
> - pr_debug("Out-of-bounds cpu %u\n", sample->cpu);
> + pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu %u\n",
> + sample->file_offset, sample->cpu);
> return -1;
> }
> c_state_end(tchart, sample->cpu, sample->time);
> @@ -717,7 +726,8 @@ process_sample_power_frequency(struct timechart *tchart,
>
> /* perf.data is untrusted input — cpu_id may be corrupted */
> if (cpu_id >= MAX_CPUS) {
> - pr_debug("Out-of-bounds cpu_id %llu\n", (unsigned long long)cpu_id);
> + pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu_id %llu\n",
> + sample->file_offset, (unsigned long long)cpu_id);
> return -1;
> }
> p_state_change(tchart, cpu_id, sample->time, value);
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 7/8] perf sched: Replace BUG_ON on invalid CPU with graceful skip
2026-06-02 23:57 ` [PATCH 7/8] perf sched: Replace BUG_ON on invalid CPU with graceful skip Arnaldo Carvalho de Melo
@ 2026-06-03 15:17 ` Ian Rogers
0 siblings, 0 replies; 20+ messages in thread
From: Ian Rogers @ 2026-06-03 15:17 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: Namhyung Kim, Ingo Molnar, Thomas Gleixner, James Clark,
Jiri Olsa, Adrian Hunter, Clark Williams, linux-kernel,
linux-perf-users, Arnaldo Carvalho de Melo, sashiko-bot,
Claude Opus 4.6
On Tue, Jun 2, 2026 at 4:57 PM Arnaldo Carvalho de Melo <acme@kernel.org> wrote:
>
> From: Arnaldo Carvalho de Melo <acme@redhat.com>
>
> latency_switch_event(), latency_runtime_event(), and map_switch_event()
> use BUG_ON(cpu >= MAX_CPUS || cpu < 0) to validate the sample CPU.
> When PERF_SAMPLE_CPU is absent from the sample type,
> evsel__parse_sample() initializes sample->cpu to (u32)-1. Casting
> this to int yields -1, which triggers the BUG_ON and aborts perf sched.
>
> The central CPU validation in perf_session__deliver_event() intentionally
> preserves the (u32)-1 sentinel for downstream tools like perf script
> and perf inject, so leaf callbacks must handle it themselves.
>
> Replace the three BUG_ON calls with graceful skips using pr_warning(),
> matching the existing pattern in process_sched_switch_event() and
> process_sched_runtime_event() earlier in the same file. Include the
> file offset for cross-referencing with perf report -D.
>
> Reported-by: sashiko-bot@kernel.org # Running on a local machine
> Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/builtin-sched.c | 22 +++++++++++++++++++---
> 1 file changed, 19 insertions(+), 3 deletions(-)
>
> diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
> index 9ec8e049e19b0038..81833d169470582b 100644
> --- a/tools/perf/builtin-sched.c
> +++ b/tools/perf/builtin-sched.c
> @@ -1145,7 +1145,12 @@ static int latency_switch_event(struct perf_sched *sched,
> int cpu = sample->cpu, err = -1;
> s64 delta;
>
> - BUG_ON(cpu >= MAX_CPUS || cpu < 0);
> + /* perf.data is untrusted input — CPU may be absent or corrupted */
> + if (cpu >= MAX_CPUS || cpu < 0) {
> + pr_warning("WARNING: at offset %#" PRIx64 ": out-of-bound sample CPU %d, skipping sample\n",
> + sample->file_offset, cpu);
> + return 0;
> + }
>
> timestamp0 = sched->cpu_last_switched[cpu];
> sched->cpu_last_switched[cpu] = timestamp;
> @@ -1215,7 +1220,13 @@ static int latency_runtime_event(struct perf_sched *sched,
> if (thread == NULL)
> return -1;
>
> - BUG_ON(cpu >= MAX_CPUS || cpu < 0);
> + /* perf.data is untrusted input — CPU may be absent or corrupted */
> + if (cpu >= MAX_CPUS || cpu < 0) {
> + pr_warning("WARNING: at offset %#" PRIx64 ": out-of-bound sample CPU %d, skipping sample\n",
> + sample->file_offset, cpu);
> + err = 0;
> + goto out_put;
> + }
> if (!atoms) {
> if (thread_atoms_insert(sched, thread))
> goto out_put;
> @@ -1640,7 +1651,12 @@ static int map_switch_event(struct perf_sched *sched, struct perf_sample *sampl
> const char *str;
> int ret = -1;
>
> - BUG_ON(this_cpu.cpu >= MAX_CPUS || this_cpu.cpu < 0);
> + /* perf.data is untrusted input — CPU may be absent or corrupted */
> + if (this_cpu.cpu >= MAX_CPUS || this_cpu.cpu < 0) {
> + pr_warning("WARNING: at offset %#" PRIx64 ": out-of-bound sample CPU %d, skipping sample\n",
> + sample->file_offset, this_cpu.cpu);
> + return 0;
> + }
>
> if (this_cpu.cpu > sched->max_cpu.cpu)
> sched->max_cpu = this_cpu;
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 8/8] perf test: Add file offset diagnostic test for corrupted perf.data
2026-06-02 23:57 ` [PATCH 8/8] perf test: Add file offset diagnostic test for corrupted perf.data Arnaldo Carvalho de Melo
@ 2026-06-03 15:19 ` Ian Rogers
0 siblings, 0 replies; 20+ messages in thread
From: Ian Rogers @ 2026-06-03 15:19 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: Namhyung Kim, Ingo Molnar, Thomas Gleixner, James Clark,
Jiri Olsa, Adrian Hunter, Clark Williams, linux-kernel,
linux-perf-users, Arnaldo Carvalho de Melo, Claude Opus 4.6
On Tue, Jun 2, 2026 at 4:58 PM Arnaldo Carvalho de Melo <acme@kernel.org> wrote:
>
> From: Arnaldo Carvalho de Melo <acme@redhat.com>
>
> Add a shell test that verifies the file_offset diagnostic messages
> work correctly when perf encounters corrupted events.
>
> The test corrupts a MMAP2 event's size field in a recorded perf.data
> file, then checks that perf report produces warning messages that
> include both the file offset (e.g. "at offset 0x2738:") and the
> event type name with numeric id (e.g. "MMAP2 (10)").
>
> This exercises the diagnostic improvements from the file_offset
> series, which retrofitted all skip/stop/error messages to include
> the position and type of the problematic event.
>
> Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> .../shell/data_file_offset_diagnostics.sh | 82 +++++++++++++++++++
> 1 file changed, 82 insertions(+)
> create mode 100755 tools/perf/tests/shell/data_file_offset_diagnostics.sh
>
> diff --git a/tools/perf/tests/shell/data_file_offset_diagnostics.sh b/tools/perf/tests/shell/data_file_offset_diagnostics.sh
> new file mode 100755
> index 0000000000000000..031f49480d999d8c
> --- /dev/null
> +++ b/tools/perf/tests/shell/data_file_offset_diagnostics.sh
> @@ -0,0 +1,82 @@
> +#!/bin/bash
> +# Test that perf report includes file offsets and event type names in diagnostic messages.
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# The file_offset diagnostic series adds "at offset 0x...: TYPE (N)"
> +# to all skip/stop/error messages. This test corrupts an event's size
> +# field in a perf.data file, then verifies the resulting warning
> +# includes the file offset and event type.
> +
> +err=0
> +
> +cleanup() {
> + [ -n "${perfdata}" ] && rm -f "${perfdata}" "${perfdata}.old"
> + rm -f "${corrupted}" "${stderrfile}"
> + trap - EXIT TERM INT
> +}
> +trap 'cleanup; exit 1' TERM INT
> +trap cleanup EXIT
> +
> +perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) || exit 2
> +corrupted=$(mktemp /tmp/__perf_test.perf.data.XXXXX) || exit 2
> +stderrfile=$(mktemp /tmp/__perf_test.perf.data.XXXXX) || exit 2
> +
> +if ! perf record -o "${perfdata}" -- perf test -w noploop 2>/dev/null; then
> + echo "Skip: perf record failed"
> + cleanup
> + exit 2
> +fi
> +
> +# Find the file offset of the first MMAP2 event via perf report -D.
> +# Format: "timestamp 0xOFFSET [0xSIZE]: PERF_RECORD_MMAP2 ..."
> +mmap2_line=$(perf report -D -i "${perfdata}" 2>/dev/null | grep "PERF_RECORD_MMAP2" | head -1)
> +if [ -z "${mmap2_line}" ]; then
> + echo "Skip: no MMAP2 events found in perf.data"
> + cleanup
> + exit 2
> +fi
> +
> +mmap2_offset=$(echo "${mmap2_line}" | awk '{print $2}')
> +mmap2_offset_dec=$((mmap2_offset))
> +
> +# Copy the file and corrupt the MMAP2 event's size field.
> +# perf_event_header layout: type(u32) misc(u16) size(u16)
> +# Set size to 16 (0x10 0x00 little-endian) — below the MMAP2
> +# minimum, which triggers the "event size too small" warning.
> +cp "${perfdata}" "${corrupted}"
> +printf '\x10\x00' | dd of="${corrupted}" bs=1 seek=$((mmap2_offset_dec + 6)) conv=notrunc 2>/dev/null
> +
> +perf report -i "${corrupted}" --stdio > /dev/null 2> "${stderrfile}"
> +
> +# Check that warnings include "at offset 0x..."
> +if grep -q "at offset 0x" "${stderrfile}"; then
> + echo "File offset in diagnostics [Success]"
> +else
> + echo "File offset in diagnostics [Failed: no 'at offset 0x...' found]"
> + echo " stderr was:"
> + head -5 "${stderrfile}"
> + err=1
> +fi
> +
> +# Check that the event type name and numeric id appear: "MMAP2 (10)"
> +if grep -q "MMAP2 (10)" "${stderrfile}"; then
> + echo "Event type name in diagnostics [Success]"
> +else
> + echo "Event type name in diagnostics [Failed: no 'MMAP2 (10)' found]"
> + echo " stderr was:"
> + head -5 "${stderrfile}"
> + err=1
> +fi
> +
> +# Check that the reported offset matches the actual corruption point
> +if grep -q "at offset ${mmap2_offset}:" "${stderrfile}"; then
> + echo "Correct offset reported [Success]"
> +else
> + echo "Correct offset reported [Failed: expected offset ${mmap2_offset}]"
> + echo " stderr was:"
> + head -5 "${stderrfile}"
> + err=1
> +fi
> +
> +cleanup
> +exit ${err}
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes
2026-06-03 15:06 ` [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Ian Rogers
@ 2026-06-03 19:27 ` Arnaldo Carvalho de Melo
2026-06-03 19:44 ` Ian Rogers
0 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-03 19:27 UTC (permalink / raw)
To: Ian Rogers
Cc: Namhyung Kim, Ingo Molnar, Thomas Gleixner, James Clark,
Jiri Olsa, Adrian Hunter, Clark Williams, linux-kernel,
linux-perf-users
On Wed, Jun 03, 2026 at 08:06:48AM -0700, Ian Rogers wrote:
> On Tue, Jun 2, 2026 at 4:57 PM Arnaldo Carvalho de Melo <acme@kernel.org> wrote:
> >
> > When perf report, perf sched, or perf timechart skip a malformed or
> > unprocessable event, the warning message doesn't say where in the
> > perf.data file the problem occurred. This makes it hard to
> > cross-reference with 'perf report -D' output or to locate the
> > corrupted region with a hex editor.
> >
> > This series adds a file_offset field to struct perf_sample, set in the
> > event delivery path (including the deferred callchain path), and
> > retrofits all skip/stop/error messages to include:
> >
> > - The file offset where the event was found
> > - The event type name via perf_event__name() with the numeric
> > type value in parentheses
> >
> > For example, instead of:
> >
> > problem processing 10 event, skipping it.
> >
> > a user now sees:
> >
> > WARNING: at offset 0x1a3f0: MMAP2 (10) event size 24 too small (min 64), skipping
> >
> > The peek_event() path, which validates events during initial file
> > scanning, also gains file offsets in its three warning messages
> > (misaligned size, unsupported type, undersized event).
> >
> > Two pre-existing bugs found by sashiko-bot are fixed:
> >
> > - builtin-timechart.c cat_backtrace(): use-after-free and
> > double-free when an invalid callchain context triggers zfree()
> > before fclose() on an open_memstream buffer. The open_memstream
> > contract requires fclose() before the buffer can be freed — see
> > open_memstream(3).
>
> Fwiw, I've also been around the timechart code prompted by AI review
> and also trying to clean up tests with address sanitizer:
> https://lore.kernel.org/linux-perf-users/agzWqrn6XPEwTAsb@google.com/
Thanks for all the reviews, I'll merge this series since sashiko found
just one endianess issue with the new 'perf test' entry and the other
comments are for pre-existing problems that we've added to TODO lists,
then you can rebase that timechart leaks on top of it, ok?
- Arnaldo
> Thanks,
> Ian
>
> > - builtin-sched.c: three BUG_ON(cpu >= MAX_CPUS || cpu < 0)
> > that abort perf sched when PERF_SAMPLE_CPU is absent from the
> > sample type and the CPU sentinel (u32)-1 is cast to signed -1.
> > perf.data is untrusted input — a corrupted or truncated file
> > should produce a warning, not an abort.
> >
> > Arnaldo Carvalho de Melo (8):
> > perf sample: Add file_offset field to struct perf_sample
> > perf session: Include file offset in event skip/stop messages
> > perf sched: Include file offset in event skip messages
> > perf timechart: Include file offset in CPU bounds check messages
> > perf tools: Include file offset and event type name in skip messages
> > perf timechart: Fix cat_backtrace() use-after-free on corrupted callchain
> > perf sched: Replace BUG_ON on invalid CPU with graceful skip
> > perf test: Add file offset diagnostic test for corrupted perf.data
> >
> > 15 files changed, 261 insertions(+), 101 deletions(-)
> >
> > Developed with AI assistance (Claude/sashiko), tagged in commits.
> >
> > Best regards,
> >
> > - Arnaldo
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes
2026-06-03 19:27 ` Arnaldo Carvalho de Melo
@ 2026-06-03 19:44 ` Ian Rogers
0 siblings, 0 replies; 20+ messages in thread
From: Ian Rogers @ 2026-06-03 19:44 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: Namhyung Kim, Ingo Molnar, Thomas Gleixner, James Clark,
Jiri Olsa, Adrian Hunter, Clark Williams, linux-kernel,
linux-perf-users
On Wed, Jun 3, 2026 at 12:27 PM Arnaldo Carvalho de Melo
<acme@kernel.org> wrote:
>
> On Wed, Jun 03, 2026 at 08:06:48AM -0700, Ian Rogers wrote:
> > On Tue, Jun 2, 2026 at 4:57 PM Arnaldo Carvalho de Melo <acme@kernel.org> wrote:
> > >
> > > When perf report, perf sched, or perf timechart skip a malformed or
> > > unprocessable event, the warning message doesn't say where in the
> > > perf.data file the problem occurred. This makes it hard to
> > > cross-reference with 'perf report -D' output or to locate the
> > > corrupted region with a hex editor.
> > >
> > > This series adds a file_offset field to struct perf_sample, set in the
> > > event delivery path (including the deferred callchain path), and
> > > retrofits all skip/stop/error messages to include:
> > >
> > > - The file offset where the event was found
> > > - The event type name via perf_event__name() with the numeric
> > > type value in parentheses
> > >
> > > For example, instead of:
> > >
> > > problem processing 10 event, skipping it.
> > >
> > > a user now sees:
> > >
> > > WARNING: at offset 0x1a3f0: MMAP2 (10) event size 24 too small (min 64), skipping
> > >
> > > The peek_event() path, which validates events during initial file
> > > scanning, also gains file offsets in its three warning messages
> > > (misaligned size, unsupported type, undersized event).
> > >
> > > Two pre-existing bugs found by sashiko-bot are fixed:
> > >
> > > - builtin-timechart.c cat_backtrace(): use-after-free and
> > > double-free when an invalid callchain context triggers zfree()
> > > before fclose() on an open_memstream buffer. The open_memstream
> > > contract requires fclose() before the buffer can be freed — see
> > > open_memstream(3).
> >
> > Fwiw, I've also been around the timechart code prompted by AI review
> > and also trying to clean up tests with address sanitizer:
> > https://lore.kernel.org/linux-perf-users/agzWqrn6XPEwTAsb@google.com/
>
> Thanks for all the reviews, I'll merge this series since sashiko found
> just one endianess issue with the new 'perf test' entry and the other
> comments are for pre-existing problems that we've added to TODO lists,
> then you can rebase that timechart leaks on top of it, ok?
Sure. I thought we merged the timechart issue fix with this:
https://web.git.kernel.org/pub/scm/linux/kernel/git/perf/perf-tools-next.git/commit/tools/perf/builtin-timechart.c?h=perf-tools-next&id=00b36b394c15f625fa166ba3b399cad5bd5065f9
So you may well be fixing other issues I introduced while trying to
implement that fix.
Thanks,
Ian
> - Arnaldo
>
> > Thanks,
> > Ian
> >
> > > - builtin-sched.c: three BUG_ON(cpu >= MAX_CPUS || cpu < 0)
> > > that abort perf sched when PERF_SAMPLE_CPU is absent from the
> > > sample type and the CPU sentinel (u32)-1 is cast to signed -1.
> > > perf.data is untrusted input — a corrupted or truncated file
> > > should produce a warning, not an abort.
> > >
> > > Arnaldo Carvalho de Melo (8):
> > > perf sample: Add file_offset field to struct perf_sample
> > > perf session: Include file offset in event skip/stop messages
> > > perf sched: Include file offset in event skip messages
> > > perf timechart: Include file offset in CPU bounds check messages
> > > perf tools: Include file offset and event type name in skip messages
> > > perf timechart: Fix cat_backtrace() use-after-free on corrupted callchain
> > > perf sched: Replace BUG_ON on invalid CPU with graceful skip
> > > perf test: Add file offset diagnostic test for corrupted perf.data
> > >
> > > 15 files changed, 261 insertions(+), 101 deletions(-)
> > >
> > > Developed with AI assistance (Claude/sashiko), tagged in commits.
> > >
> > > Best regards,
> > >
> > > - Arnaldo
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-06-03 19:44 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-02 23:56 [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Arnaldo Carvalho de Melo
2026-06-02 23:57 ` [PATCH 1/8] perf sample: Add file_offset field to struct perf_sample Arnaldo Carvalho de Melo
2026-06-03 15:11 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 2/8] perf session: Include file offset in event skip/stop messages Arnaldo Carvalho de Melo
2026-06-03 15:12 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 3/8] perf sched: Include file offset in event skip messages Arnaldo Carvalho de Melo
2026-06-03 15:13 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 4/8] perf timechart: Include file offset in CPU bounds check messages Arnaldo Carvalho de Melo
2026-06-03 15:14 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 5/8] perf tools: Include file offset and event type name in skip messages Arnaldo Carvalho de Melo
2026-06-03 15:14 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 6/8] perf timechart: Fix cat_backtrace() use-after-free on corrupted callchain Arnaldo Carvalho de Melo
2026-06-03 15:16 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 7/8] perf sched: Replace BUG_ON on invalid CPU with graceful skip Arnaldo Carvalho de Melo
2026-06-03 15:17 ` Ian Rogers
2026-06-02 23:57 ` [PATCH 8/8] perf test: Add file offset diagnostic test for corrupted perf.data Arnaldo Carvalho de Melo
2026-06-03 15:19 ` Ian Rogers
2026-06-03 15:06 ` [PATCHES 0/8] perf tools: Diagnostic offsets in skip messages + two hardening fixes Ian Rogers
2026-06-03 19:27 ` Arnaldo Carvalho de Melo
2026-06-03 19:44 ` Ian Rogers
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Powered by JetHome