* [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation
2026-06-15 21:36 [PATCHES v1 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
@ 2026-06-15 21:36 ` Arnaldo Carvalho de Melo
0 siblings, 0 replies; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-15 21:36 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, James Clark, Leo Yan,
Tor Jeremiassen, Claude
From: Arnaldo Carvalho de Melo <acme@redhat.com>
cs_etm__process_auxtrace_info_full() reads num_cpu from untrusted
perf.data and uses it to allocate the metadata pointer array:
metadata = zalloc(sizeof(*metadata) * num_cpu);
On 32-bit, sizeof(*metadata) is 4, so num_cpu = 0x40000000 overflows
the multiplication to 0, causing zalloc(0) to return a valid zero-sized
allocation followed by out-of-bounds writes in the population loop.
Fix by computing priv_size early and using it to bound num_cpu: each
CPU needs at least one u64 metadata entry, so num_cpu cannot exceed
the total number of u64 entries in the event's private data area.
Fixes: cd8bfd8c973eaff8 ("perf tools: Add processing of coresight metadata")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: James Clark <james.clark@arm.com>
Cc: Leo Yan <leo.yan@linaro.org>
Cc: Tor Jeremiassen <tor@ti.com>
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/cs-etm.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index 0927b0b9c06b1504..d121c8f22028d5ba 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -3431,6 +3431,18 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event,
/* First the global part */
ptr = (u64 *) auxtrace_info->priv;
num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff;
+
+ /*
+ * Bound num_cpu by the event size: the global header consumes
+ * CS_ETM_HEADER_SIZE bytes, and each CPU needs at least one u64
+ * metadata entry after that.
+ */
+ priv_size = total_size - event_header_size - INFO_HEADER_SIZE -
+ CS_ETM_HEADER_SIZE;
+ if (num_cpu <= 0 || priv_size <= 0 ||
+ num_cpu > priv_size / (int)sizeof(u64))
+ return -EINVAL;
+
metadata = zalloc(sizeof(*metadata) * num_cpu);
if (!metadata)
return -ENOMEM;
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation
2026-06-15 22:32 [PATCHES v2 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
@ 2026-06-15 22:32 ` Arnaldo Carvalho de Melo
2026-06-15 22:48 ` sashiko-bot
0 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-15 22:32 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, James Clark, Leo Yan,
Tor Jeremiassen, Claude
From: Arnaldo Carvalho de Melo <acme@redhat.com>
cs_etm__process_auxtrace_info_full() reads num_cpu from untrusted
perf.data and uses it to allocate the metadata pointer array:
metadata = zalloc(sizeof(*metadata) * num_cpu);
On 32-bit, sizeof(*metadata) is 4, so num_cpu = 0x40000000 overflows
the multiplication to 0, causing zalloc(0) to return a valid zero-sized
allocation followed by out-of-bounds writes in the population loop.
Fix by computing priv_size early and using it to bound num_cpu: each
CPU needs at least one u64 metadata entry, so num_cpu cannot exceed
the total number of u64 entries in the event's private data area.
Fixes: cd8bfd8c973eaff8 ("perf tools: Add processing of coresight metadata")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: James Clark <james.clark@arm.com>
Cc: Leo Yan <leo.yan@linaro.org>
Cc: Tor Jeremiassen <tor@ti.com>
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/cs-etm.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index 0927b0b9c06b1504..d121c8f22028d5ba 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -3431,6 +3431,18 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event,
/* First the global part */
ptr = (u64 *) auxtrace_info->priv;
num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff;
+
+ /*
+ * Bound num_cpu by the event size: the global header consumes
+ * CS_ETM_HEADER_SIZE bytes, and each CPU needs at least one u64
+ * metadata entry after that.
+ */
+ priv_size = total_size - event_header_size - INFO_HEADER_SIZE -
+ CS_ETM_HEADER_SIZE;
+ if (num_cpu <= 0 || priv_size <= 0 ||
+ num_cpu > priv_size / (int)sizeof(u64))
+ return -EINVAL;
+
metadata = zalloc(sizeof(*metadata) * num_cpu);
if (!metadata)
return -ENOMEM;
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation
2026-06-15 22:32 ` [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation Arnaldo Carvalho de Melo
@ 2026-06-15 22:48 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-06-15 22:48 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [Medium] Insufficient bounds check for the CPU metadata parsing loop leading to out-of-bounds memory reads from untrusted data.
- [Medium] Missing bounds validation on `num_cpu` before printing trace metadata causes out-of-bounds memory reads.
- [Medium] Memory leak of `cs_etm_queue` structures and RB trees on initialization error paths.
--
commit d0500b50aa50740d3a89612a4e4d97ba8f0c3129
Author: Arnaldo Carvalho de Melo <acme@redhat.com>
perf cs-etm: Validate num_cpu before metadata allocation
This commit bounds num_cpu by checking it against the total available private
data size to prevent an integer overflow and subsequent out-of-bounds writes
during metadata allocation.
> diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
> index 0927b0b9c06b1..d121c8f22028d 100644
> --- a/tools/perf/util/cs-etm.c
> +++ b/tools/perf/util/cs-etm.c
> @@ -3431,6 +3431,18 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event,
> /* First the global part */
> ptr = (u64 *) auxtrace_info->priv;
> num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff;
[Severity: Medium]
This is a pre-existing issue, but does dump_trace in
cs_etm__process_auxtrace_info() extract and use num_cpu for
cs_etm__print_auxtrace_info() before this new validation occurs?
tools/perf/util/cs-etm-base.c:cs_etm__process_auxtrace_info() {
...
if (dump_trace) {
num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff;
cs_etm__print_auxtrace_info(ptr, num_cpu);
...
If so, an attacker could still trigger out-of-bounds reads by specifying an
excessively large num_cpu when perf report -D is run.
> +
> + /*
> + * Bound num_cpu by the event size: the global header consumes
> + * CS_ETM_HEADER_SIZE bytes, and each CPU needs at least one u64
> + * metadata entry after that.
> + */
> + priv_size = total_size - event_header_size - INFO_HEADER_SIZE -
> + CS_ETM_HEADER_SIZE;
> + if (num_cpu <= 0 || priv_size <= 0 ||
> + num_cpu > priv_size / (int)sizeof(u64))
> + return -EINVAL;
> +
[Severity: Medium]
This is a pre-existing issue, but does the metadata parsing loop later in this
function lack a bounds check for the buffer size?
While num_cpu is now bounded, the loop iterates j < num_cpu and calls
cs_etm__create_meta_blk() which advances the index i:
tools/perf/util/cs-etm.c:cs_etm__create_meta_blk() {
...
nr_in_params = buff_in[i + CS_ETM_NR_TRC_PARAMS];
...
i += nr_in_params + nr_cmn_params;
*buff_in_offset = i;
...
Can a crafted nr_in_params bypass the new bounds check and cause an
out-of-bounds read on the next iteration?
> metadata = zalloc(sizeof(*metadata) * num_cpu);
> if (!metadata)
> return -ENOMEM;
[Severity: Medium]
This is a pre-existing issue, but does the err_free_queues path in this
function leak the queue->priv allocations?
If an error occurs later in cs_etm__process_auxtrace_info_full(), it cleans
up via:
tools/perf/util/cs-etm.c:cs_etm__process_auxtrace_info_full() {
...
err_free_queues:
auxtrace_queues__free(&etm->queues);
...
auxtrace_queues__free() frees the arrays but doesn't free the dynamically
allocated cs_etm_queue structures stored in queue->priv from
cs_etm__setup_queue(). Is an additional cs_etm__free_events() style cleanup
needed to prevent memory leaks?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260615223249.36598-1-acme@kernel.org?part=3
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCHES v3 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso
@ 2026-06-16 1:08 Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 1/9] perf machine: Propagate machine__init() error to callers Arnaldo Carvalho de Melo
` (8 more replies)
0 siblings, 9 replies; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-16 1:08 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
Hi,
Nine more pre-existing bugs found by sashiko-bot during AI-assisted
code review. All are independent of the perf-data-validation hardening
series — they are latent bugs in surrounding code exposed during review.
The fixes are grouped by subsystem:
machine__init() error propagation (patches 1-2):
machine__init() always returns 0 on allocation failure because the
error code is never propagated through the return statement. Callers
(including machines__init() and __machine__new_host()) proceed with a
partially initialized machine struct. The error cleanup also uses
zfree() on refcounted kmaps instead of maps__zput(). Additionally,
machines__findnew() and get_kernel_version() use sprintf() with
unsanitized guestmount paths that can overflow PATH_MAX stack buffers.
CoreSight ETM metadata validation (patches 3-5):
cs_etm__process_auxtrace_info_full() reads num_cpu from untrusted
perf.data and uses it directly in a multiplication that can overflow
to zero on 32-bit, producing a zero-sized allocation followed by OOB
writes. The minimum size check in cs_etm__process_auxtrace_info()
doesn't cover the global header fields actually accessed.
cs_etm__get_queue() indexes queue_array[] without bounds checking
the CPU value from untrusted trace payload, and several queue
iteration loops dereference .priv without NULL checks after array
growth zero-initializes new entries.
c2c hist entry leaks (patches 6-7):
When c2c_hists__init() fails, dynamically allocated format structures
are leaked because the error path frees the container without
unregistering them. During resort merges, c2c_he_free() only walks
the output-sorted tree (empty before resort), leaking all inner
hist_entry objects from entries_in_array[] and entries_collapsed.
BPF prog info pointer validation (patch 8):
Several functions cast bpf_prog_info u64 fields to pointers without
checking whether bpil_offs_to_addr() actually converted the file
offsets. A crafted perf.data with PERF_BPIL_* bits unset but non-zero
counts causes raw file offsets to be dereferenced as pointers.
DSO decompression errno (patch 9):
dso__get_filename() sets errno to a negative custom DSO_LOAD_ERRNO
value on decompression failure. __open_dso() computes fd = -errno,
producing a large positive value that looks like a valid fd, causing
close_data_fd() to close an unrelated file descriptor.
Build-tested with gcc and clang. Passes perf test on x86_64.
Changes in v3 (patch 1 only):
- Move perf_env__init() before machines__init() in
__perf_session__new() so the goto out_delete error path doesn't
call perf_env__exit() on uninitialized mutexes/rwlocks
(sashiko-bot).
Changes in v2 (patch 1 only):
- Move dsos__init()/threads__init() before maps__new() so that
machine__exit() is safe to call when machine__init() fails at the
first allocation (sashiko-bot).
- Propagate machines__init() error in aslr_tool__init(), which was
added by the ASLR patches after v1 was written (sashiko-bot).
Arnaldo Carvalho de Melo (9):
perf machine: Propagate machine__init() error to callers
perf machine: Use snprintf() for guestmount path construction
perf cs-etm: Validate num_cpu before metadata allocation
perf cs-etm: Require full global header in auxtrace_info size check
perf cs-etm: Bounds-check CPU in cs_etm__get_queue()
perf c2c: Free format list entries when c2c_hists__init() fails
perf c2c: Fix hist entry and format list leaks in c2c_he_free()
perf bpf: Validate array presence before casting BPF prog info pointers
perf dso: Set standard errno on decompression failure
tools/perf/builtin-c2c.c | 3 ++-
tools/perf/tests/hists_cumulate.c | 3 ++-
tools/perf/tests/hists_filter.c | 3 ++-
tools/perf/tests/hists_link.c | 3 ++-
tools/perf/tests/hists_output.c | 3 ++-
tools/perf/tests/thread-maps-share.c | 2 +-
tools/perf/util/aslr.c | 12 +++++++++---
tools/perf/util/bpf-event.c | 20 ++++++++++++++++---
tools/perf/util/bpf-event.h | 4 ++--
tools/perf/util/cs-etm-base.c | 4 +++-
tools/perf/util/cs-etm.c | 37 ++++++++++++++++++++++++++++++++++--
tools/perf/util/dso.c | 18 +++++++++++++++++-
tools/perf/util/header.c | 3 +--
tools/perf/util/hist.c | 2 +-
tools/perf/util/hist.h | 1 +
tools/perf/util/machine.c | 32 +++++++++++++++++--------------
tools/perf/util/machine.h | 2 +-
tools/perf/util/session.c | 7 ++++---
18 files changed, 120 insertions(+), 39 deletions(-)
Developed with AI assistance (Claude/sashiko), tagged in commits.
Thanks,
- Arnaldo
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 1/9] perf machine: Propagate machine__init() error to callers
2026-06-16 1:08 [PATCHES v3 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
@ 2026-06-16 1:08 ` Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 2/9] perf machine: Use snprintf() for guestmount path construction Arnaldo Carvalho de Melo
` (7 subsequent siblings)
8 siblings, 0 replies; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-16 1:08 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
From: Arnaldo Carvalho de Melo <acme@redhat.com>
machine__init() always returns 0 even when memory allocation fails,
because commit 81f981d7ec43ed93 ("perf machine: Free root_dir in
machine__init() error path") introduced 'int err = -ENOMEM' and an
error cleanup path but left the final 'return 0' instead of
'return err'.
Fix by returning err, check the return value in __machine__new_host()
which was ignoring it, and change machines__init() from void to int so
it too can propagate the error to perf_session__new(), aslr_tool__init()
and test callers.
The error cleanup also used zfree(&machine->kmaps), but kmaps is a
refcounted maps structure — use maps__zput() to properly drop the
reference, matching machine__exit().
Move dsos__init() and threads__init() before the first fallible
allocation (maps__new) so that machine__exit() is safe to call on
any machine struct that machine__init() touched, even on early failure.
Fixes: 81f981d7ec43ed93 ("perf machine: Free root_dir in machine__init() error path")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: Ian Rogers <irogers@google.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/tests/hists_cumulate.c | 3 ++-
tools/perf/tests/hists_filter.c | 3 ++-
tools/perf/tests/hists_link.c | 3 ++-
tools/perf/tests/hists_output.c | 3 ++-
tools/perf/tests/thread-maps-share.c | 2 +-
tools/perf/util/aslr.c | 12 +++++++++---
tools/perf/util/machine.c | 24 ++++++++++++++----------
tools/perf/util/machine.h | 2 +-
tools/perf/util/session.c | 7 ++++---
9 files changed, 37 insertions(+), 22 deletions(-)
diff --git a/tools/perf/tests/hists_cumulate.c b/tools/perf/tests/hists_cumulate.c
index 267cbc24691acd77..9f4d4e281e11edaa 100644
--- a/tools/perf/tests/hists_cumulate.c
+++ b/tools/perf/tests/hists_cumulate.c
@@ -723,7 +723,8 @@ static int test__hists_cumulate(struct test_suite *test __maybe_unused, int subt
goto out;
err = TEST_FAIL;
- machines__init(&machines);
+ if (machines__init(&machines))
+ goto out;
/* setup threads/dso/map/symbols also */
machine = setup_fake_machine(&machines);
diff --git a/tools/perf/tests/hists_filter.c b/tools/perf/tests/hists_filter.c
index 002e3a4c1ca59b9d..fa47e995de82d427 100644
--- a/tools/perf/tests/hists_filter.c
+++ b/tools/perf/tests/hists_filter.c
@@ -131,7 +131,8 @@ static int test__hists_filter(struct test_suite *test __maybe_unused, int subtes
goto out;
err = TEST_FAIL;
- machines__init(&machines);
+ if (machines__init(&machines))
+ goto out;
/* setup threads/dso/map/symbols also */
machine = setup_fake_machine(&machines);
diff --git a/tools/perf/tests/hists_link.c b/tools/perf/tests/hists_link.c
index 996f5f0b3bd17fe5..05078dac93c4ec6b 100644
--- a/tools/perf/tests/hists_link.c
+++ b/tools/perf/tests/hists_link.c
@@ -303,7 +303,8 @@ static int test__hists_link(struct test_suite *test __maybe_unused, int subtest
goto out;
err = TEST_FAIL;
- machines__init(&machines);
+ if (machines__init(&machines))
+ goto out;
/* setup threads/dso/map/symbols also */
machine = setup_fake_machine(&machines);
diff --git a/tools/perf/tests/hists_output.c b/tools/perf/tests/hists_output.c
index fa683fd7b1e5ebb2..1ec51c15b1046eb4 100644
--- a/tools/perf/tests/hists_output.c
+++ b/tools/perf/tests/hists_output.c
@@ -610,7 +610,8 @@ static int test__hists_output(struct test_suite *test __maybe_unused, int subtes
goto out;
err = TEST_FAIL;
- machines__init(&machines);
+ if (machines__init(&machines))
+ goto out;
/* setup threads/dso/map/symbols also */
machine = setup_fake_machine(&machines);
diff --git a/tools/perf/tests/thread-maps-share.c b/tools/perf/tests/thread-maps-share.c
index e9ecd30a5c058076..0431bff31b3a18c3 100644
--- a/tools/perf/tests/thread-maps-share.c
+++ b/tools/perf/tests/thread-maps-share.c
@@ -27,7 +27,7 @@ static int test__thread_maps_share(struct test_suite *test __maybe_unused, int s
* other group (pid: 4, tids: 4, 5)
*/
- machines__init(&machines);
+ TEST_ASSERT_VAL("failed to init machines", machines__init(&machines) == 0);
machine = &machines.host;
/* create process with 4 threads */
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index a946fff2ac4dd4b4..6a7542e7db827d1b 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -1237,12 +1237,13 @@ void aslr_tool__strip_attr_event(union perf_event *event, struct evlist *evlist)
}
}
-static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
+static int aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
{
delegate_tool__init(&aslr->tool, delegate);
aslr->tool.tool.ordered_events = true;
- machines__init(&aslr->machines);
+ if (machines__init(&aslr->machines))
+ return -ENOMEM;
hashmap__init(&aslr->remap_addresses,
remap_addresses__hash, remap_addresses__equal,
@@ -1276,6 +1277,8 @@ static void aslr_tool__init(struct aslr_tool *aslr, struct perf_tool *delegate)
aslr->tool.tool.auxtrace = aslr_tool__process_auxtrace;
aslr->tool.tool.auxtrace_info = aslr_tool__process_auxtrace_info;
aslr->tool.tool.auxtrace_error = aslr_tool__process_auxtrace_error;
+
+ return 0;
}
struct perf_tool *aslr_tool__new(struct perf_tool *delegate)
@@ -1285,7 +1288,10 @@ struct perf_tool *aslr_tool__new(struct perf_tool *delegate)
if (!aslr)
return NULL;
- aslr_tool__init(aslr, delegate);
+ if (aslr_tool__init(aslr, delegate)) {
+ free(aslr);
+ return NULL;
+ }
return &aslr->tool.tool;
}
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index 31715366e29ff704..9329d319bd033699 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -79,15 +79,14 @@ int machine__init(struct machine *machine, const char *root_dir, pid_t pid)
int err = -ENOMEM;
memset(machine, 0, sizeof(*machine));
- machine->kmaps = maps__new(machine);
- if (machine->kmaps == NULL)
- return -ENOMEM;
-
RB_CLEAR_NODE(&machine->rb_node);
dsos__init(&machine->dsos);
-
threads__init(&machine->threads);
+ machine->kmaps = maps__new(machine);
+ if (machine->kmaps == NULL)
+ goto out;
+
machine->vdso_info = NULL;
machine->env = NULL;
@@ -124,11 +123,11 @@ int machine__init(struct machine *machine, const char *root_dir, pid_t pid)
out:
if (err) {
- zfree(&machine->kmaps);
+ maps__zput(machine->kmaps);
zfree(&machine->root_dir);
zfree(&machine->mmap_name);
}
- return 0;
+ return err;
}
static struct machine *__machine__new_host(struct perf_env *host_env, bool kernel_maps)
@@ -138,7 +137,10 @@ static struct machine *__machine__new_host(struct perf_env *host_env, bool kerne
if (!machine)
return NULL;
- machine__init(machine, "", HOST_KERNEL_ID);
+ if (machine__init(machine, "", HOST_KERNEL_ID) != 0) {
+ free(machine);
+ return NULL;
+ }
if (kernel_maps && machine__create_kernel_maps(machine) < 0) {
free(machine);
@@ -231,10 +233,12 @@ void machine__delete(struct machine *machine)
}
}
-void machines__init(struct machines *machines)
+int machines__init(struct machines *machines)
{
- machine__init(&machines->host, "", HOST_KERNEL_ID);
+ int err = machine__init(&machines->host, "", HOST_KERNEL_ID);
+
machines->guests = RB_ROOT_CACHED;
+ return err;
}
void machines__exit(struct machines *machines)
diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h
index aaddfb70ea665452..26f9827062f5eb5b 100644
--- a/tools/perf/util/machine.h
+++ b/tools/perf/util/machine.h
@@ -152,7 +152,7 @@ struct machines {
struct rb_root_cached guests;
};
-void machines__init(struct machines *machines);
+int machines__init(struct machines *machines);
void machines__exit(struct machines *machines);
void machines__process_guests(struct machines *machines,
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index 1a9a008ddda35120..f391a822480db001 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -160,11 +160,12 @@ struct perf_session *__perf_session__new(struct perf_data *data,
session->decomp_data.zstd_decomp = &session->zstd_data;
session->active_decomp = &session->decomp_data;
INIT_LIST_HEAD(&session->auxtrace_index);
- machines__init(&session->machines);
+ perf_env__init(&session->header.env);
+ if (machines__init(&session->machines))
+ goto out_delete;
+
ordered_events__init(&session->ordered_events,
ordered_events__deliver_event, NULL);
-
- perf_env__init(&session->header.env);
if (data) {
ret = perf_data__open(data);
if (ret < 0)
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 2/9] perf machine: Use snprintf() for guestmount path construction
2026-06-16 1:08 [PATCHES v3 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 1/9] perf machine: Propagate machine__init() error to callers Arnaldo Carvalho de Melo
@ 2026-06-16 1:08 ` Arnaldo Carvalho de Melo
2026-06-16 1:23 ` sashiko-bot
2026-06-16 1:08 ` [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation Arnaldo Carvalho de Melo
` (6 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-16 1:08 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, Zhang, Yanmin, Claude
From: Arnaldo Carvalho de Melo <acme@redhat.com>
machines__findnew(), machines__create_guest_kernel_maps(), and
get_kernel_version() use sprintf() to build paths by prepending
symbol_conf.guestmount or root_dir. All write into PATH_MAX stack
buffers, but guestmount comes from user configuration and is not
length-checked. A guestmount path at or near PATH_MAX causes a
stack buffer overflow — and a truncated root_dir propagated to
get_kernel_version() would overflow its own version[PATH_MAX] buffer
when "/proc/version" is appended.
Switch to snprintf() with sizeof() to prevent overflow. The
subsequent access()/fopen() calls will fail on a truncated path.
Fixes: a1645ce12adb6c9c ("perf: 'perf kvm' tool for monitoring guest performance from host")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: Zhang, Yanmin <yanmin_zhang@linux.intel.com>
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/machine.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index 9329d319bd033699..0d2ebf6a84bcf880 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -333,7 +333,7 @@ struct machine *machines__findnew(struct machines *machines, pid_t pid)
if ((pid != HOST_KERNEL_ID) &&
(pid != DEFAULT_GUEST_KERNEL_ID) &&
(symbol_conf.guestmount)) {
- sprintf(path, "%s/%d", symbol_conf.guestmount, pid);
+ snprintf(path, sizeof(path), "%s/%d", symbol_conf.guestmount, pid);
if (access(path, R_OK)) {
static struct strlist *seen;
@@ -1260,9 +1260,9 @@ int machines__create_guest_kernel_maps(struct machines *machines)
namelist[i]->d_name);
continue;
}
- sprintf(path, "%s/%s/proc/kallsyms",
- symbol_conf.guestmount,
- namelist[i]->d_name);
+ snprintf(path, sizeof(path), "%s/%s/proc/kallsyms",
+ symbol_conf.guestmount,
+ namelist[i]->d_name);
ret = access(path, R_OK);
if (ret) {
pr_debug("Can't access file %s\n", path);
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation
2026-06-16 1:08 [PATCHES v3 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 1/9] perf machine: Propagate machine__init() error to callers Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 2/9] perf machine: Use snprintf() for guestmount path construction Arnaldo Carvalho de Melo
@ 2026-06-16 1:08 ` Arnaldo Carvalho de Melo
2026-06-16 1:21 ` sashiko-bot
2026-06-16 1:08 ` [PATCH 4/9] perf cs-etm: Require full global header in auxtrace_info size check Arnaldo Carvalho de Melo
` (5 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-16 1:08 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, James Clark, Leo Yan,
Tor Jeremiassen, Claude
From: Arnaldo Carvalho de Melo <acme@redhat.com>
cs_etm__process_auxtrace_info_full() reads num_cpu from untrusted
perf.data and uses it to allocate the metadata pointer array:
metadata = zalloc(sizeof(*metadata) * num_cpu);
On 32-bit, sizeof(*metadata) is 4, so num_cpu = 0x40000000 overflows
the multiplication to 0, causing zalloc(0) to return a valid zero-sized
allocation followed by out-of-bounds writes in the population loop.
Fix by computing priv_size early and using it to bound num_cpu: each
CPU needs at least one u64 metadata entry, so num_cpu cannot exceed
the total number of u64 entries in the event's private data area.
Fixes: cd8bfd8c973eaff8 ("perf tools: Add processing of coresight metadata")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: James Clark <james.clark@arm.com>
Cc: Leo Yan <leo.yan@linaro.org>
Cc: Tor Jeremiassen <tor@ti.com>
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/cs-etm.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index 0927b0b9c06b1504..d121c8f22028d5ba 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -3431,6 +3431,18 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event,
/* First the global part */
ptr = (u64 *) auxtrace_info->priv;
num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff;
+
+ /*
+ * Bound num_cpu by the event size: the global header consumes
+ * CS_ETM_HEADER_SIZE bytes, and each CPU needs at least one u64
+ * metadata entry after that.
+ */
+ priv_size = total_size - event_header_size - INFO_HEADER_SIZE -
+ CS_ETM_HEADER_SIZE;
+ if (num_cpu <= 0 || priv_size <= 0 ||
+ num_cpu > priv_size / (int)sizeof(u64))
+ return -EINVAL;
+
metadata = zalloc(sizeof(*metadata) * num_cpu);
if (!metadata)
return -ENOMEM;
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 4/9] perf cs-etm: Require full global header in auxtrace_info size check
2026-06-16 1:08 [PATCHES v3 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
` (2 preceding siblings ...)
2026-06-16 1:08 ` [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation Arnaldo Carvalho de Melo
@ 2026-06-16 1:08 ` Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 5/9] perf cs-etm: Bounds-check CPU in cs_etm__get_queue() Arnaldo Carvalho de Melo
` (4 subsequent siblings)
8 siblings, 0 replies; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-16 1:08 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, James Clark, Leo Yan,
Claude
From: Arnaldo Carvalho de Melo <acme@redhat.com>
cs_etm__process_auxtrace_info() checks that header.size covers
event_header_size + INFO_HEADER_SIZE (16 bytes total), but then
accesses ptr[CS_PMU_TYPE_CPUS] at offset 24 from the start of the
event. A crafted 16-byte auxtrace_info event passes the size check
but reads out-of-bounds.
Include CS_ETM_HEADER_SIZE in the minimum size check so that the
global header entries (version, pmu_type_cpus, snapshot) are
guaranteed to fit within the event.
Fixes: 55c1de9973d66516 ("perf cs-etm: Print auxtrace info even if OpenCSD isn't linked")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: James Clark <james.clark@arm.com>
Cc: Leo Yan <leo.yan@linaro.org>
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/cs-etm-base.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tools/perf/util/cs-etm-base.c b/tools/perf/util/cs-etm-base.c
index 4abe416e3febd2cc..aebef71d3a0a1d7b 100644
--- a/tools/perf/util/cs-etm-base.c
+++ b/tools/perf/util/cs-etm-base.c
@@ -170,7 +170,9 @@ int cs_etm__process_auxtrace_info(union perf_event *event,
u64 *ptr = NULL;
u64 hdr_version;
- if (auxtrace_info->header.size < (event_header_size + INFO_HEADER_SIZE))
+ /* Ensure priv[] is large enough for the global header entries */
+ if (auxtrace_info->header.size < (event_header_size + INFO_HEADER_SIZE +
+ CS_ETM_HEADER_SIZE))
return -EINVAL;
/* First the global part */
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 5/9] perf cs-etm: Bounds-check CPU in cs_etm__get_queue()
2026-06-16 1:08 [PATCHES v3 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
` (3 preceding siblings ...)
2026-06-16 1:08 ` [PATCH 4/9] perf cs-etm: Require full global header in auxtrace_info size check Arnaldo Carvalho de Melo
@ 2026-06-16 1:08 ` Arnaldo Carvalho de Melo
2026-06-16 1:28 ` sashiko-bot
2026-06-16 1:08 ` [PATCH 6/9] perf c2c: Free format list entries when c2c_hists__init() fails Arnaldo Carvalho de Melo
` (3 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-16 1:08 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, James Clark, Leo Yan,
Claude
From: Arnaldo Carvalho de Melo <acme@redhat.com>
cs_etm__get_queue() indexes etm->queues.queue_array[cpu] without
validating that cpu is within nr_queues. When processing
AUX_OUTPUT_HW_ID events, the cpu value comes from untrusted perf.data
trace payload and flows through cs_etm__process_trace_id_v0_1() and
cs_etm__queue_aux_fragment() without bounds checking, allowing an
out-of-bounds read with a crafted file.
Add a bounds check in cs_etm__get_queue() and NULL checks in all
callers.
Also add NULL checks for queue_array[i].priv in the queue iteration
loops in cs_etm__map_trace_id_v0() and cs_etm__process_trace_id_v0_1()
— after auxtrace_queues__grow() new entries are zero-initialized so
.priv can be NULL. Add a get_cpu_data() NULL check in
cs_etm__process_trace_id_v0_1(), matching the existing check in
cs_etm__process_trace_id_v0().
Fixes: 77c123f53e97ad4b ("perf: cs-etm: Move traceid_list to each queue")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: James Clark <james.clark@arm.com>
Cc: Leo Yan <leo.yan@linaro.org>
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/cs-etm.c | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index d121c8f22028d5ba..5d0664ff73b79122 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -292,8 +292,11 @@ static struct cs_etm_queue *cs_etm__get_queue(struct cs_etm_auxtrace *etm, int c
{
if (etm->per_thread_decoding)
return etm->queues.queue_array[0].priv;
- else
- return etm->queues.queue_array[cpu].priv;
+
+ if (cpu < 0 || cpu >= (int)etm->queues.nr_queues)
+ return NULL;
+
+ return etm->queues.queue_array[cpu].priv;
}
static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id,
@@ -306,6 +309,9 @@ static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id
* queue associated with that CPU so only one decoder is made.
*/
etmq = cs_etm__get_queue(etm, cpu_metadata[CS_ETM_CPU]);
+ if (!etmq)
+ return -EINVAL;
+
if (etmq->format == UNFORMATTED)
return cs_etm__insert_trace_id_node(etmq, trace_chan_id,
cpu_metadata);
@@ -318,6 +324,9 @@ static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id
int ret;
etmq = etm->queues.queue_array[i].priv;
+ if (!etmq)
+ continue;
+
ret = cs_etm__insert_trace_id_node(etmq, trace_chan_id,
cpu_metadata);
if (ret)
@@ -358,6 +367,9 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu,
u32 sink_id = FIELD_GET(CS_AUX_HW_ID_SINK_ID_MASK, hw_id);
u8 trace_id = FIELD_GET(CS_AUX_HW_ID_TRACE_ID_MASK, hw_id);
+ if (!etmq)
+ return -EINVAL;
+
/*
* Check sink id hasn't changed in per-cpu mode. In per-thread mode,
* let it pass for now until an actual overlapping trace ID is hit. In
@@ -375,6 +387,9 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu,
for (unsigned int i = 0; i < etm->queues.nr_queues; ++i) {
struct cs_etm_queue *other_etmq = etm->queues.queue_array[i].priv;
+ if (!other_etmq)
+ continue;
+
/* Different sinks, skip */
if (other_etmq->sink_id != etmq->sink_id)
continue;
@@ -396,6 +411,9 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu,
}
cpu_data = get_cpu_data(etm, cpu);
+ if (!cpu_data)
+ return -EINVAL;
+
ret = cs_etm__insert_trace_id_node(etmq, trace_id, cpu_data);
if (ret)
return ret;
@@ -3144,6 +3162,9 @@ static int cs_etm__queue_aux_fragment(struct perf_session *session, off_t file_o
aux_offset + aux_size <= auxtrace_event->offset + auxtrace_event->size) {
struct cs_etm_queue *etmq = cs_etm__get_queue(etm, auxtrace_event->cpu);
+ if (!etmq)
+ return -EINVAL;
+
/*
* If this AUX event was inside this buffer somewhere, create a new auxtrace event
* based on the sizes of the aux event, and queue that fragment.
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 6/9] perf c2c: Free format list entries when c2c_hists__init() fails
2026-06-16 1:08 [PATCHES v3 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
` (4 preceding siblings ...)
2026-06-16 1:08 ` [PATCH 5/9] perf cs-etm: Bounds-check CPU in cs_etm__get_queue() Arnaldo Carvalho de Melo
@ 2026-06-16 1:08 ` Arnaldo Carvalho de Melo
2026-06-16 1:23 ` sashiko-bot
2026-06-16 1:08 ` [PATCH 7/9] perf c2c: Fix hist entry and format list leaks in c2c_he_free() Arnaldo Carvalho de Melo
` (2 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-16 1:08 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
From: Arnaldo Carvalho de Melo <acme@redhat.com>
When c2c_hists__init() fails partway through hpp_list__parse(),
dynamically allocated format structures that were already added to
hists->list are leaked because he__get_c2c_hists() frees the hists
container without first unregistering the format entries.
Call perf_hpp__reset_output_field() before freeing the hists container
on the error path, matching what c2c_he_free() already does on the
normal destruction path.
Fixes: 17a7c5946d79a12c ("perf c2c report: Decode c2c_stats for hist entries")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/builtin-c2c.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
index 07c7e8fb315e6cf3..eabb922ef295ef86 100644
--- a/tools/perf/builtin-c2c.c
+++ b/tools/perf/builtin-c2c.c
@@ -226,6 +226,7 @@ he__get_c2c_hists(struct hist_entry *he,
ret = c2c_hists__init(hists, sort, nr_header_lines, env);
if (ret) {
+ perf_hpp__reset_output_field(&hists->list);
c2c_he->hists = NULL;
free(hists);
return NULL;
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 7/9] perf c2c: Fix hist entry and format list leaks in c2c_he_free()
2026-06-16 1:08 [PATCHES v3 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
` (5 preceding siblings ...)
2026-06-16 1:08 ` [PATCH 6/9] perf c2c: Free format list entries when c2c_hists__init() fails Arnaldo Carvalho de Melo
@ 2026-06-16 1:08 ` Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 8/9] perf bpf: Validate array presence before casting BPF prog info pointers Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 9/9] perf dso: Set standard errno on decompression failure Arnaldo Carvalho de Melo
8 siblings, 0 replies; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-16 1:08 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
From: Arnaldo Carvalho de Melo <acme@redhat.com>
c2c_he_free() calls hists__delete_entries() which only walks the
output-sorted entries tree. During c2c resort, when cacheline entries
are merged and the redundant entry is freed, the inner hists have not
been output-resorted yet, so hists->entries is empty. The actual inner
hist_entry objects live in entries_in_array[] and entries_collapsed,
which are never walked, leaking all inner hist_entry objects for every
merged cacheline.
Additionally, the dynamically allocated format entries on hists->list
are never unregistered or freed.
Fix both issues by switching to hists__delete_all_entries() which walks
all rb_root trees, and calling perf_hpp__reset_output_field() to clean
up format entries.
Fixes: bf0e0d407ea09ce5 ("perf c2c report: Add sample processing")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/builtin-c2c.c | 2 +-
tools/perf/util/hist.c | 2 +-
tools/perf/util/hist.h | 1 +
3 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
index eabb922ef295ef86..c9584dbedf77afe8 100644
--- a/tools/perf/builtin-c2c.c
+++ b/tools/perf/builtin-c2c.c
@@ -184,7 +184,7 @@ static void c2c_he_free(void *he)
c2c_he = container_of(he, struct c2c_hist_entry, he);
if (c2c_he->hists) {
- hists__delete_entries(&c2c_he->hists->hists);
+ hists__delete_all_entries(&c2c_he->hists->hists);
perf_hpp__reset_output_field(&c2c_he->hists->list);
zfree(&c2c_he->hists);
}
diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c
index df978c996b6c2262..c93915625ee75de1 100644
--- a/tools/perf/util/hist.c
+++ b/tools/perf/util/hist.c
@@ -3041,7 +3041,7 @@ static void hists__delete_remaining_entries(struct rb_root_cached *root)
}
}
-static void hists__delete_all_entries(struct hists *hists)
+void hists__delete_all_entries(struct hists *hists)
{
hists__delete_entries(hists);
hists__delete_remaining_entries(&hists->entries_in_array[0]);
diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h
index 8fb89d81ef069d95..b830cbe7f95bf597 100644
--- a/tools/perf/util/hist.h
+++ b/tools/perf/util/hist.h
@@ -391,6 +391,7 @@ int hists__collapse_resort(struct hists *hists, struct ui_progress *prog);
void hists__decay_entries(struct hists *hists, bool zap_user, bool zap_kernel);
void hists__delete_entries(struct hists *hists);
+void hists__delete_all_entries(struct hists *hists);
void hists__output_recalc_col_len(struct hists *hists, int max_rows);
struct hist_entry *hists__get_entry(struct hists *hists, int idx);
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 8/9] perf bpf: Validate array presence before casting BPF prog info pointers
2026-06-16 1:08 [PATCHES v3 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
` (6 preceding siblings ...)
2026-06-16 1:08 ` [PATCH 7/9] perf c2c: Fix hist entry and format list leaks in c2c_he_free() Arnaldo Carvalho de Melo
@ 2026-06-16 1:08 ` Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 9/9] perf dso: Set standard errno on decompression failure Arnaldo Carvalho de Melo
8 siblings, 0 replies; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-16 1:08 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, Song Liu, Claude
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Several functions cast bpf_prog_info fields (jited_ksyms,
jited_func_lens, jited_prog_insns) from u64 to pointers and
dereference them. These fields are only valid pointers if
bpil_offs_to_addr() converted their file offsets to addresses, which
only happens when the corresponding PERF_BPIL_* bits are set in
info_linear->arrays.
A crafted perf.data can leave these bits unset while setting non-zero
counts and offset values, causing the functions to dereference raw file
offsets as pointers.
Add array bitmask validation to all perf.data processing paths:
- __bpf_event__print_bpf_prog_info(): check JITED_KSYMS and
JITED_FUNC_LENS (changed to take struct perf_bpil *)
- machine__process_bpf_event_load(): check JITED_KSYMS
- bpf_read(): check JITED_INSNS before memcpy from jited_prog_insns
- dso__disassemble_filename(): check JITED_INSNS before returning
jited_prog_insns pointer
Fixes: f8dfeae009effc0b ("perf bpf: Show more BPF program info in print_bpf_prog_info()")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: Song Liu <songliubraving@fb.com>
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/bpf-event.c | 20 +++++++++++++++++---
tools/perf/util/bpf-event.h | 4 ++--
tools/perf/util/dso.c | 10 ++++++++++
| 3 +--
4 files changed, 30 insertions(+), 7 deletions(-)
diff --git a/tools/perf/util/bpf-event.c b/tools/perf/util/bpf-event.c
index 57d53ba848359e12..fa3ebc8ea7f09cdd 100644
--- a/tools/perf/util/bpf-event.c
+++ b/tools/perf/util/bpf-event.c
@@ -59,6 +59,10 @@ static int machine__process_bpf_event_load(struct machine *machine,
return 0;
info_linear = info_node->info_linear;
+ /* jited_ksyms is only valid if bpil_offs_to_addr() converted it */
+ if (!(info_linear->arrays & (1UL << PERF_BPIL_JITED_KSYMS)))
+ return 0;
+
for (i = 0; i < info_linear->info.nr_jited_ksyms; i++) {
u64 *addrs = (u64 *)(uintptr_t)(info_linear->info.jited_ksyms);
u64 addr = addrs[i];
@@ -959,12 +963,15 @@ int evlist__add_bpf_sb_event(struct evlist *evlist, struct perf_env *env)
return evlist__add_sb_event(evlist, &attr, bpf_event__sb_cb, env);
}
-void __bpf_event__print_bpf_prog_info(struct bpf_prog_info *info,
+void __bpf_event__print_bpf_prog_info(struct perf_bpil *info_linear,
struct perf_env *env,
FILE *fp)
{
- __u32 *prog_lens = (__u32 *)(uintptr_t)(info->jited_func_lens);
- __u64 *prog_addrs = (__u64 *)(uintptr_t)(info->jited_ksyms);
+ struct bpf_prog_info *info = &info_linear->info;
+ __u64 required_arrays = (1UL << PERF_BPIL_JITED_KSYMS) |
+ (1UL << PERF_BPIL_JITED_FUNC_LENS);
+ __u32 *prog_lens;
+ __u64 *prog_addrs;
char name[KSYM_NAME_LEN];
struct btf *btf = NULL;
u32 sub_prog_cnt, i;
@@ -974,6 +981,13 @@ void __bpf_event__print_bpf_prog_info(struct bpf_prog_info *info,
sub_prog_cnt != info->nr_jited_func_lens)
return;
+ /* Ensure the arrays were present and converted by bpil_offs_to_addr() */
+ if ((info_linear->arrays & required_arrays) != required_arrays)
+ return;
+
+ prog_lens = (__u32 *)(uintptr_t)(info->jited_func_lens);
+ prog_addrs = (__u64 *)(uintptr_t)(info->jited_ksyms);
+
if (info->btf_id) {
struct btf_node *node;
diff --git a/tools/perf/util/bpf-event.h b/tools/perf/util/bpf-event.h
index 60d2c6637af5d6eb..da4eeb4a1a73208c 100644
--- a/tools/perf/util/bpf-event.h
+++ b/tools/perf/util/bpf-event.h
@@ -40,7 +40,7 @@ struct btf_node {
int machine__process_bpf(struct machine *machine, union perf_event *event,
struct perf_sample *sample);
int evlist__add_bpf_sb_event(struct evlist *evlist, struct perf_env *env);
-void __bpf_event__print_bpf_prog_info(struct bpf_prog_info *info,
+void __bpf_event__print_bpf_prog_info(struct perf_bpil *info_linear,
struct perf_env *env,
FILE *fp);
void bpf_metadata_free(struct bpf_metadata *metadata);
@@ -58,7 +58,7 @@ static inline int evlist__add_bpf_sb_event(struct evlist *evlist __maybe_unused,
return 0;
}
-static inline void __bpf_event__print_bpf_prog_info(struct bpf_prog_info *info __maybe_unused,
+static inline void __bpf_event__print_bpf_prog_info(struct perf_bpil *info_linear __maybe_unused,
struct perf_env *env __maybe_unused,
FILE *fp __maybe_unused)
{
diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
index 1a2fc6d18da74d6c..79f1a30f3683d6b3 100644
--- a/tools/perf/util/dso.c
+++ b/tools/perf/util/dso.c
@@ -880,6 +880,12 @@ static ssize_t bpf_read(struct dso *dso, u64 offset, char *data)
return -1;
}
+ /* jited_prog_insns is only valid if bpil_offs_to_addr() converted it */
+ if (!(node->info_linear->arrays & (1UL << PERF_BPIL_JITED_INSNS))) {
+ dso__data(dso)->status = DSO_DATA_STATUS_ERROR;
+ return -1;
+ }
+
len = node->info_linear->info.jited_prog_len;
buf = (u8 *)(uintptr_t)node->info_linear->info.jited_prog_insns;
@@ -1995,6 +2001,10 @@ const u8 *dso__read_symbol(struct dso *dso, const char *symfs_filename,
return NULL;
}
info_linear = info_node->info_linear;
+ if (!(info_linear->arrays & (1UL << PERF_BPIL_JITED_INSNS))) {
+ errno = SYMBOL_ANNOTATE_ERRNO__BPF_MISSING_BTF;
+ return NULL;
+ }
assert(len <= info_linear->info.jited_prog_len);
*out_buf_len = len;
return (const u8 *)(uintptr_t)(info_linear->info.jited_prog_insns);
--git a/tools/perf/util/header.c b/tools/perf/util/header.c
index d7f41db7322cbcb4..091d8f7f6bd2c9d5 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -2107,8 +2107,7 @@ static void print_bpf_prog_info(struct feat_fd *ff __maybe_unused, FILE *fp)
node = rb_entry(next, struct bpf_prog_info_node, rb_node);
next = rb_next(&node->rb_node);
- __bpf_event__print_bpf_prog_info(&node->info_linear->info,
- env, fp);
+ __bpf_event__print_bpf_prog_info(node->info_linear, env, fp);
}
up_read(&env->bpf_progs.lock);
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 9/9] perf dso: Set standard errno on decompression failure
2026-06-16 1:08 [PATCHES v3 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
` (7 preceding siblings ...)
2026-06-16 1:08 ` [PATCH 8/9] perf bpf: Validate array presence before casting BPF prog info pointers Arnaldo Carvalho de Melo
@ 2026-06-16 1:08 ` Arnaldo Carvalho de Melo
2026-06-16 1:38 ` sashiko-bot
8 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-16 1:08 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
From: Arnaldo Carvalho de Melo <acme@redhat.com>
dso__get_filename() sets errno to a negative custom DSO_LOAD_ERRNO
value when kernel module decompression fails:
errno = *dso__load_errno(dso); /* e.g. -9996 */
The caller __open_dso() then computes fd = -errno, producing a large
positive value (9996) that looks like a valid file descriptor. This
can cause close_data_fd() to close an unrelated fd used by another
subsystem.
Set errno to EIO instead. The detailed error code is already stored
in dso__load_errno(dso) for diagnostic messages.
Fixes: 1d6b3c9ba756a513 ("perf tools: Decompress kernel module when reading DSO data")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/dso.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
index 79f1a30f3683d6b3..2309196d8df3111c 100644
--- a/tools/perf/util/dso.c
+++ b/tools/perf/util/dso.c
@@ -600,7 +600,13 @@ static char *dso__get_filename(struct dso *dso, const char *root_dir,
size_t len = sizeof(newpath);
if (dso__decompress_kmodule_path(dso, name, newpath, len) < 0) {
- errno = *dso__load_errno(dso);
+ /*
+ * Use a standard errno value, not the negative custom
+ * DSO_LOAD_ERRNO stored in dso__load_errno(dso):
+ * __open_dso() computes fd = -errno, so a negative
+ * errno produces a positive fd that looks valid.
+ */
+ errno = EIO;
goto out;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation
2026-06-16 1:08 ` [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation Arnaldo Carvalho de Melo
@ 2026-06-16 1:21 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-06-16 1:21 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Medium] Insufficient bounds checking during metadata parsing allows out-of-bounds reads, arbitrary index manipulation, and potential memory disclosure inside the `num_cpu` loop.
--
commit 08062652d1b7507748a2450df846239247c38cd7
Author: Arnaldo Carvalho de Melo <acme@redhat.com>
perf cs-etm: Validate num_cpu before metadata allocation
cs_etm__process_auxtrace_info_full() reads num_cpu from untrusted
perf.data and uses it to allocate the metadata pointer array.
This patch adds bounds checking to prevent an integer overflow
when allocating the metadata pointer array.
> diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
> index 0927b0b9c06b1..d121c8f22028d 100644
> --- a/tools/perf/util/cs-etm.c
> +++ b/tools/perf/util/cs-etm.c
> @@ -3431,6 +3431,18 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event,
> /* First the global part */
> ptr = (u64 *) auxtrace_info->priv;
> num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff;
> +
> + /*
> + * Bound num_cpu by the event size: the global header consumes
> + * CS_ETM_HEADER_SIZE bytes, and each CPU needs at least one u64
> + * metadata entry after that.
> + */
> + priv_size = total_size - event_header_size - INFO_HEADER_SIZE -
> + CS_ETM_HEADER_SIZE;
> + if (num_cpu <= 0 || priv_size <= 0 ||
> + num_cpu > priv_size / (int)sizeof(u64))
> + return -EINVAL;
[Severity: Medium]
This is a pre-existing issue, but does this bounds check fully protect the
metadata parsing loop that follows?
While this check ensures there is at least one 8-byte element per CPU, inside
the parsing loop, cs_etm__create_meta_blk() immediately accesses elements
beyond this guaranteed size:
nr_in_params = buff_in[i + CS_ETM_NR_TRC_PARAMS]
This could trigger an out-of-bounds read if priv_size is small.
Additionally, because nr_in_params is read without dynamic bounds checking, it
can arbitrarily alter the read index i. On the next iteration, the code blindly
reads ptr[i], and on the error path, it could leak memory:
tools/perf/util/cs-etm.c:cs_etm__process_auxtrace_info_full() {
...
for (int j = 0; j < num_cpu; j++) {
if (ptr[i] == __perf_cs_etmv3_magic) {
...
} else {
ui__error("CS ETM Trace: Unrecognised magic number %#"PRIx64". ...\n",
ptr[i]);
err = -EINVAL;
goto err_free_metadata;
}
...
}
The strict bounds check (if (i * 8 != priv_size)) only occurs after the
loop completes.
> +
> metadata = zalloc(sizeof(*metadata) * num_cpu);
> if (!metadata)
> return -ENOMEM;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260616010834.37618-1-acme@kernel.org?part=3
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 2/9] perf machine: Use snprintf() for guestmount path construction
2026-06-16 1:08 ` [PATCH 2/9] perf machine: Use snprintf() for guestmount path construction Arnaldo Carvalho de Melo
@ 2026-06-16 1:23 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-06-16 1:23 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Silent path truncation due to unchecked snprintf return value can cause data corruption.
- [Medium] The patch claims to fix get_kernel_version() by switching it to snprintf(), but the provided code changes do not modify this function.
Pre-existing issues:
- [High] Unchecked failure of strlist__new() leads to a NULL pointer dereference in strlist__has_entry().
- [High] Unchecked failure of machine__findnew_thread() leads to a NULL pointer dereference.
--
commit 95810f32b8a216c1bb3b9a1565b24c27cfec9f8d
Author: Arnaldo Carvalho de Melo <acme@redhat.com>
perf machine: Use snprintf() for guestmount path construction
This commit updates machines__findnew() and
machines__create_guest_kernel_maps() to use snprintf() instead of sprintf()
for constructing guestmount paths. This change prevents stack buffer
overflows caused by unchecked user configuration strings.
> machines__findnew(), machines__create_guest_kernel_maps(), and
> get_kernel_version() use sprintf() to build paths by prepending
> symbol_conf.guestmount or root_dir. All write into PATH_MAX stack
[Severity: Medium]
Is the commit message description for get_kernel_version() accurate?
The commit message states that get_kernel_version() uses sprintf() and is
being switched to snprintf() to prevent overflow. However, the provided code
changes do not modify this function. It appears get_kernel_version() already
uses snprintf() in the baseline.
[ ... ]
> diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
> --- a/tools/perf/util/machine.c
> +++ b/tools/perf/util/machine.c
> @@ -333,7 +333,7 @@ struct machine *machines__findnew(struct machines *machines, pid_t pid)
> if ((pid != HOST_KERNEL_ID) &&
> (pid != DEFAULT_GUEST_KERNEL_ID) &&
> (symbol_conf.guestmount)) {
> - sprintf(path, "%s/%d", symbol_conf.guestmount, pid);
> + snprintf(path, sizeof(path), "%s/%d", symbol_conf.guestmount, pid);
[Severity: High]
Could this unchecked snprintf() lead to silent data corruption?
If symbol_conf.guestmount is long enough, snprintf() could silently truncate
the trailing digits of the guest PID. If the resulting shorter PID
coincidentally exists (e.g., guestmount/12 instead of guestmount/1234),
access() will falsely succeed. This would assign the wrong root_dir to the
guest, silently corrupting profiling data by attributing maps to the wrong
guest.
> if (access(path, R_OK)) {
> static struct strlist *seen;
[Severity: High]
This is a pre-existing issue, but does this code handle a failed allocation
correctly?
If access() fails, a static seen list is initialized:
tools/perf/util/machine.c:machines__findnew() {
...
if (!seen)
seen = strlist__new(NULL, NULL);
if (!strlist__has_entry(seen, path)) {
...
}
If strlist__new() fails due to out-of-memory, it returns a null pointer. The
immediate subsequent check !strlist__has_entry(seen, path) unconditionally
passes this null pointer, which causes an immediate segmentation fault when
accessing underlying structures.
[ ... ]
> @@ -1260,9 +1260,9 @@ int machines__create_guest_kernel_maps(struct machines *machines)
> namelist[i]->d_name);
> continue;
> }
> - sprintf(path, "%s/%s/proc/kallsyms",
> - symbol_conf.guestmount,
> - namelist[i]->d_name);
> + snprintf(path, sizeof(path), "%s/%s/proc/kallsyms",
> + symbol_conf.guestmount,
> + namelist[i]->d_name);
[Severity: High]
Could an unchecked snprintf() here also result in a falsely successful
access() check?
Similar to the issue above, if the path is truncated, it might inadvertently
match an existing shorter path instead of properly failing the access() test.
[ ... ]
While reviewing this file, I noticed another pre-existing issue unrelated to
the patch diff:
[Severity: High]
This isn't a bug introduced by this patch, but can machine__findnew_thread()
return a null pointer here?
tools/perf/util/machine.c:machine__process_fork_event() {
struct thread *parent = machine__findnew_thread(machine,
event->fork.ppid,
event->fork.ptid);
...
if (thread__pid(parent) != (pid_t)event->fork.ppid) {
...
}
If memory allocation fails within thread creation, machine__findnew_thread()
returns a null pointer. Unconditionally evaluating thread__pid(parent) accesses
fields of the null object, which results in a segmentation fault.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260616010834.37618-1-acme@kernel.org?part=2
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 6/9] perf c2c: Free format list entries when c2c_hists__init() fails
2026-06-16 1:08 ` [PATCH 6/9] perf c2c: Free format list entries when c2c_hists__init() fails Arnaldo Carvalho de Melo
@ 2026-06-16 1:23 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-06-16 1:23 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] perf_hpp__setup_output_field() unconditionally appends missing sort keys to the global perf_hpp_list instead of the provided local list.
- [Medium] Error return from PARSE_LIST(output, ...) is silently overwritten by PARSE_LIST(sort, ...) in hpp_list__parse().
- [Medium] Out-of-memory failures during string duplication are silently masked as empty inputs in hpp_list__parse() and kvm_hpp_list__parse().
--
commit 82a61a9eafaa1d6dd28dc01f46106f2ec1acc81e
Author: Arnaldo Carvalho de Melo <acme@redhat.com>
perf c2c: Free format list entries when c2c_hists__init() fails
This commit fixes a memory leak in the error path of he__get_c2c_hists(). It
ensures that dynamically allocated format structures are correctly cleaned up by
calling perf_hpp__reset_output_field() before freeing the hists container.
> diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
> index 07c7e8fb315e6..eabb922ef295e 100644
> --- a/tools/perf/builtin-c2c.c
> +++ b/tools/perf/builtin-c2c.c
> @@ -226,6 +226,7 @@ he__get_c2c_hists(struct hist_entry *he,
>
> ret = c2c_hists__init(hists, sort, nr_header_lines, env);
[Severity: Medium]
This is a pre-existing issue, but I noticed something when looking at
c2c_hists__init() -> hpp_list__parse().
When an invalid --fields key is provided, the first PARSE_LIST(output, ...)
macro correctly sets ret to an error code and breaks its internal loop.
However, the subsequent PARSE_LIST(sort, ...) macro invocation immediately
resets ret = 0:
tools/perf/builtin-c2c.c:hpp_list__parse() {
...
PARSE_LIST(output, c2c_hists__init_output);
PARSE_LIST(sort, c2c_hists__init_sort);
...
}
Does this silently swallow the error from the output parsing, returning
success and allowing execution to proceed with an invalid configuration?
[Severity: Medium]
This isn't a bug introduced by this patch, but while reviewing the parsing
logic in hpp_list__parse() and kvm_hpp_list__parse(), I noticed how strdup()
failures are handled.
If strdup() fails due to memory exhaustion, the resulting variables become NULL:
tools/perf/builtin-c2c.c:hpp_list__parse() {
char *output = output_ ? strdup(output_) : NULL;
char *sort = sort_ ? strdup(sort_) : NULL;
...
PARSE_LIST(output, c2c_hists__init_output);
}
Since PARSE_LIST short-circuits a NULL list to success (0), does this mask
out-of-memory failures as empty inputs, skipping configuration entirely
instead of properly aborting?
[Severity: High]
This is a pre-existing issue, but looking deeper into the initialization
path, perf_hpp__setup_output_field() seems to append missing sort keys
to the wrong list.
The function iterates over the local list->sorts, but then calls
perf_hpp__column_register(fmt):
tools/perf/ui/hist.c:perf_hpp__setup_output_field() {
perf_hpp_list__for_each_sort_list(list, fmt) {
...
perf_hpp__column_register(fmt);
next:
continue;
}
}
This inline wrapper explicitly targets the global perf_hpp_list:
tools/perf/util/hist.h:perf_hpp__column_register() {
perf_hpp_list__column_register(&perf_hpp_list, format);
}
Could this pollute the global list with pointers to dynamically allocated
format structures owned by the local c2c_hists or kvm_hists?
If so, does this lead to use-after-free vulnerabilities for concurrent
global list accessors when the local list is destroyed?
> if (ret) {
> + perf_hpp__reset_output_field(&hists->list);
> c2c_he->hists = NULL;
> free(hists);
> return NULL;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260616010834.37618-1-acme@kernel.org?part=6
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 5/9] perf cs-etm: Bounds-check CPU in cs_etm__get_queue()
2026-06-16 1:08 ` [PATCH 5/9] perf cs-etm: Bounds-check CPU in cs_etm__get_queue() Arnaldo Carvalho de Melo
@ 2026-06-16 1:28 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-06-16 1:28 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [High] Integer overflow in auxtrace queue growth logic allows massive out-of-bounds heap corruption.
- [High] Implicit signedness cast of `event->auxtrace.idx` causes a negative index out-of-bounds access.
- [Medium] cs_etm__dump_event() lacks a NULL check for `etmq`, leading to a NULL pointer dereference when processing malformed PERF_RECORD_AUXTRACE events with the dump_trace (-D) option.
- [Medium] The `auxtrace_queues__grow` function leaks the old `queues->queue_array` memory when growing the array.
- [Medium] The error path in `cs_etm__process_auxtrace_info_full` permanently leaks all allocated `etmq` structures.
--
commit d0ad74eaa00ab2c6c0f68cce04f6849c506a5134
Author: Arnaldo Carvalho de Melo <acme@redhat.com>
perf cs-etm: Bounds-check CPU in cs_etm__get_queue()
This commit adds a bounds check for the cpu parameter in cs_etm__get_queue()
to prevent out-of-bounds reads when processing untrusted trace payloads.
It also adds NULL checks for the returned etmq pointer in callers to handle
uninitialized entries correctly.
> diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
> index d121c8f22028d..5d0664ff73b79 100644
> --- a/tools/perf/util/cs-etm.c
> +++ b/tools/perf/util/cs-etm.c
[ ... ]
[Severity: High]
This is a pre-existing issue, but does assigning the untrusted u32 index to a
signed int lead to an out-of-bounds access?
In tools/perf/util/cs-etm.c:cs_etm__process_auxtrace_event():
int idx = event->auxtrace.idx;
...
cs_etm__dump_event(etm->queues.queue_array[idx].priv, buffer);
If event->auxtrace.idx is 0xffffffff, it casts to -1. If dump_trace is
enabled, this uses a negative idx as an array index, reading an out-of-bounds
pointer from the heap before passing it to cs_etm__dump_event().
[Severity: High]
This is a pre-existing issue, but is there an integer overflow in the auxtrace
queue growth logic that could lead to out-of-bounds heap corruption?
In tools/perf/util/auxtrace.c:auxtrace_queues__queue_buffer():
queue = &queues->queue_array[idx];
...
list_add_tail(&buffer->list, &queue->head);
If idx is read as 0xffffffff from the untrusted perf.data,
auxtrace_queues__grow(queues, idx + 1) is called. The value idx + 1 overflows
to 0. Inside auxtrace_queues__grow():
if (nr_queues < queues->nr_queues || nr_queues < new_nr_queues)
return -EINVAL;
This evaluates to false, the array remains at its original size, and
success is returned. Execution continues with
queue = &queues->queue_array[0xffffffff], which writes linked-list pointers
massively out of bounds.
[Severity: Medium]
This is a pre-existing issue, but does cs_etm__dump_event() need a NULL check
for etmq?
In tools/perf/util/cs-etm.c:cs_etm__process_auxtrace_event():
cs_etm__dump_event(etm->queues.queue_array[idx].priv, buffer);
If idx is larger than the currently allocated queues, auxtrace_queues__grow()
dynamically expands the array, leaving the new .priv pointers
zero-initialized (NULL). Because cs_etm__dump_event() unconditionally
dereferences etmq->decoder, could a malformed PERF_RECORD_AUXTRACE event
with the dump_trace (-D) option cause a segmentation fault?
[Severity: Medium]
This is a pre-existing issue, but does auxtrace_queues__grow() leak the old
queues->queue_array memory?
In tools/perf/util/auxtrace.c:auxtrace_queues__grow():
queues->queue_array = queue_array;
When the array is expanded, a new array is allocated and the old state is
copied, but it doesn't appear that free() is ever called on the old
queues->queue_array pointer.
[Severity: Medium]
This is a pre-existing issue, but does the error path in
cs_etm__process_auxtrace_info_full() permanently leak all allocated etmq
structures?
In tools/perf/util/cs-etm.c:cs_etm__process_auxtrace_info_full():
err_free_queues:
auxtrace_queues__free(&etm->queues);
session->auxtrace = NULL;
If a setup step fails, auxtrace_queues__free() is called, which only frees
the queue array and buffers, but not the decoder-specific .priv pointers.
Because session->auxtrace is then set to NULL, the generic session teardown
will also fail to call cs_etm__free_events(), leaking the etmq structures
and their internal red-black trees.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260616010834.37618-1-acme@kernel.org?part=5
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 9/9] perf dso: Set standard errno on decompression failure
2026-06-16 1:08 ` [PATCH 9/9] perf dso: Set standard errno on decompression failure Arnaldo Carvalho de Melo
@ 2026-06-16 1:38 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-06-16 1:38 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Error paths in `dso__get_filename()` and its callees fail to initialize `errno` before returning `NULL`, which causes `__open_dso()` to evaluate `fd = -errno` as `0`. This mistakenly hijacks standard input (fd 0) as a valid DSO data file descriptor.
- [High] An integer underflow in `dso_cache__memcpy()` can occur when reading after a short read, causing an out-of-bounds heap memory access.
--
commit edef5ff69168425f269de2888a6e30ebd8c6891a
Author: Arnaldo Carvalho de Melo <acme@redhat.com>
perf dso: Set standard errno on decompression failure
dso__get_filename() sets errno to a negative custom DSO_LOAD_ERRNO
value when kernel module decompression fails. This commit changes it
to set errno to EIO instead to avoid producing a valid-looking file
descriptor.
> diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
> --- a/tools/perf/util/dso.c
> +++ b/tools/perf/util/dso.c
[ ... ]
> @@ -600,7 +600,13 @@ static char *dso__get_filename(struct dso *dso, const char *root_dir,
> size_t len = sizeof(newpath);
>
> if (dso__decompress_kmodule_path(dso, name, newpath, len) < 0) {
> - errno = *dso__load_errno(dso);
> + /*
> + * Use a standard errno value, not the negative custom
> + * DSO_LOAD_ERRNO stored in dso__load_errno(dso):
> + * __open_dso() computes fd = -errno, so a negative
> + * errno produces a positive fd that looks valid.
> + */
> + errno = EIO;
> goto out;
[Severity: High]
This isn't a bug introduced by this patch, but while reviewing how errno
is handled here, I noticed other error paths might return NULL without
initializing errno.
For example, earlier in dso__get_filename():
if (dso__read_binary_type_filename(dso, dso__binary_type(dso),
root_dir, name, PATH_MAX))
goto out;
If dso__get_filename() returns NULL and errno happens to be 0 from prior
execution, could this cause __open_dso() to accidentally hijack standard
input?
__open_dso() computes the file descriptor as:
else
fd = -errno;
And then open_dso() treats fd >= 0 as successful:
if (fd >= 0) {
dso__list_add(dso);
Would this result in fd 0 being registered in the global dso__data_open
list, causing subsequent pread() calls to read from stdin and eventually
incorrectly close(0)?
[Severity: High]
This is a pre-existing issue, but I also noticed a potential bounds
checking problem in dso_cache__memcpy().
Is it possible for cache_offset to underflow in this sequence?
tools/perf/util/dso.c:dso_cache__memcpy() {
...
u64 cache_offset = offset - cache->offset;
u64 cache_size = min(cache->size - cache_offset, size);
if (out)
memcpy(data, cache->data + cache_offset, cache_size);
else
memcpy(cache->data + cache_offset, data, cache_size);
...
}
If a previous read of the file chunk returned a short read, cache->size
will be smaller than DSO__DATA_CACHE_SIZE. If a subsequent read accesses
an offset past cache->size but within the same chunk, cache->size <
cache_offset becomes true.
Could this cause cache->size - cache_offset to underflow to a massive
positive value, causing min() to select the caller-provided size, and
leading to a heap buffer overflow during memcpy()?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260616010834.37618-1-acme@kernel.org?part=9
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation
2026-06-16 2:27 [PATCHES v4 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
@ 2026-06-16 2:27 ` Arnaldo Carvalho de Melo
2026-06-16 2:40 ` sashiko-bot
0 siblings, 1 reply; 20+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-06-16 2:27 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, James Clark, Leo Yan,
Tor Jeremiassen, Claude
From: Arnaldo Carvalho de Melo <acme@redhat.com>
cs_etm__process_auxtrace_info_full() reads num_cpu from untrusted
perf.data and uses it to allocate the metadata pointer array:
metadata = zalloc(sizeof(*metadata) * num_cpu);
On 32-bit, sizeof(*metadata) is 4, so num_cpu = 0x40000000 overflows
the multiplication to 0, causing zalloc(0) to return a valid zero-sized
allocation followed by out-of-bounds writes in the population loop.
Fix by computing priv_size early and using it to bound num_cpu: each
CPU needs at least one u64 metadata entry, so num_cpu cannot exceed
the total number of u64 entries in the event's private data area.
Fixes: cd8bfd8c973eaff8 ("perf tools: Add processing of coresight metadata")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: James Clark <james.clark@arm.com>
Cc: Leo Yan <leo.yan@linaro.org>
Cc: Tor Jeremiassen <tor@ti.com>
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/util/cs-etm.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index 0927b0b9c06b1504..d121c8f22028d5ba 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -3431,6 +3431,18 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event,
/* First the global part */
ptr = (u64 *) auxtrace_info->priv;
num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff;
+
+ /*
+ * Bound num_cpu by the event size: the global header consumes
+ * CS_ETM_HEADER_SIZE bytes, and each CPU needs at least one u64
+ * metadata entry after that.
+ */
+ priv_size = total_size - event_header_size - INFO_HEADER_SIZE -
+ CS_ETM_HEADER_SIZE;
+ if (num_cpu <= 0 || priv_size <= 0 ||
+ num_cpu > priv_size / (int)sizeof(u64))
+ return -EINVAL;
+
metadata = zalloc(sizeof(*metadata) * num_cpu);
if (!metadata)
return -ENOMEM;
--
2.54.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation
2026-06-16 2:27 ` [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation Arnaldo Carvalho de Melo
@ 2026-06-16 2:40 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-06-16 2:40 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Insufficient bounds checking in the per-CPU metadata parsing loop allows arbitrary out-of-bounds reads based on untrusted event data.
- [Medium] Memory allocated for `etm->queues` array elements (`etmq`) is leaked when initialization fails on the error path.
--
commit 3997f8cbed57aaf438aeb6594523932b36c0d9d5
Author: Arnaldo Carvalho de Melo <acme@redhat.com>
perf cs-etm: Validate num_cpu before metadata allocation
This commit computes priv_size early to bound num_cpu against the private
data area size. This prevents a 32-bit multiplication overflow and
subsequent out-of-bounds writes when allocating the metadata array.
> diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
> index 0927b0b9c06b1..d121c8f22028d 100644
> --- a/tools/perf/util/cs-etm.c
> +++ b/tools/perf/util/cs-etm.c
> @@ -3431,6 +3431,18 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event,
> /* First the global part */
> ptr = (u64 *) auxtrace_info->priv;
> num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff;
> +
> + /*
> + * Bound num_cpu by the event size: the global header consumes
> + * CS_ETM_HEADER_SIZE bytes, and each CPU needs at least one u64
> + * metadata entry after that.
> + */
> + priv_size = total_size - event_header_size - INFO_HEADER_SIZE -
> + CS_ETM_HEADER_SIZE;
> + if (num_cpu <= 0 || priv_size <= 0 ||
> + num_cpu > priv_size / (int)sizeof(u64))
> + return -EINVAL;
> +
[Severity: High]
This is a pre-existing issue, but does the new validation fully prevent
out-of-bounds reads during metadata parsing?
While the check ensures a minimum of one 8-byte entry per CPU, it doesn't
protect the inner parsing loop. When cs_etm__create_meta_blk() is called,
it reads nr_in_params directly from the untrusted perf.data file payload:
tools/perf/util/cs-etm.c:cs_etm__create_meta_blk()
...
nr_in_params = buff_in[i + CS_ETM_NR_TRC_PARAMS];
...
for (k = 0; k < nr_in_params; k++)
metadata[k] = buff_in[i + k];
...
Could a maliciously crafted perf.data file provide an arbitrarily large
nr_in_params, causing an out-of-bounds read before the index i is finally
validated against priv_size at the end of the function?
> metadata = zalloc(sizeof(*metadata) * num_cpu);
> if (!metadata)
> return -ENOMEM;
[Severity: Medium]
This is a pre-existing issue, but does the error handling in this function
leak memory?
During initialization, cs_etm__setup_queue() allocates a struct
cs_etm_queue and assigns it to the .priv pointer. If initialization fails
later, the code branches here:
tools/perf/util/cs-etm.c:cs_etm__process_auxtrace_info_full()
...
err_free_queues:
auxtrace_queues__free(&etm->queues);
Since auxtrace_queues__free() is a generic helper that doesn't clean up
subsystem-specific .priv contexts, will these dynamically allocated
queue structures be leaked when the error path is taken?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260616022715.5739-1-acme@kernel.org?part=3
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-06-16 2:40 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-16 1:08 [PATCHES v3 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 1/9] perf machine: Propagate machine__init() error to callers Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 2/9] perf machine: Use snprintf() for guestmount path construction Arnaldo Carvalho de Melo
2026-06-16 1:23 ` sashiko-bot
2026-06-16 1:08 ` [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation Arnaldo Carvalho de Melo
2026-06-16 1:21 ` sashiko-bot
2026-06-16 1:08 ` [PATCH 4/9] perf cs-etm: Require full global header in auxtrace_info size check Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 5/9] perf cs-etm: Bounds-check CPU in cs_etm__get_queue() Arnaldo Carvalho de Melo
2026-06-16 1:28 ` sashiko-bot
2026-06-16 1:08 ` [PATCH 6/9] perf c2c: Free format list entries when c2c_hists__init() fails Arnaldo Carvalho de Melo
2026-06-16 1:23 ` sashiko-bot
2026-06-16 1:08 ` [PATCH 7/9] perf c2c: Fix hist entry and format list leaks in c2c_he_free() Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 8/9] perf bpf: Validate array presence before casting BPF prog info pointers Arnaldo Carvalho de Melo
2026-06-16 1:08 ` [PATCH 9/9] perf dso: Set standard errno on decompression failure Arnaldo Carvalho de Melo
2026-06-16 1:38 ` sashiko-bot
-- strict thread matches above, loose matches on Subject: below --
2026-06-16 2:27 [PATCHES v4 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
2026-06-16 2:27 ` [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation Arnaldo Carvalho de Melo
2026-06-16 2:40 ` sashiko-bot
2026-06-15 22:32 [PATCHES v2 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
2026-06-15 22:32 ` [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation Arnaldo Carvalho de Melo
2026-06-15 22:48 ` sashiko-bot
2026-06-15 21:36 [PATCHES v1 0/9] perf tools: Fix pre-existing bugs in machine, cs-etm, c2c, bpf, and dso Arnaldo Carvalho de Melo
2026-06-15 21:36 ` [PATCH 3/9] perf cs-etm: Validate num_cpu before metadata allocation Arnaldo Carvalho de Melo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox