+++ /dev/null
-From 6eb077ed90cb41ccf685ed405d72d9e5a7b8299e Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Tue, 4 Mar 2025 11:12:39 +0000
-Subject: perf arm-spe: Add branch stack
-
-From: Leo Yan <leo.yan@arm.com>
-
-[ Upstream commit 73cb57f56f749ac640b889f076d5db96264020f8 ]
-
-Although Arm SPE cannot generate continuous branch records, this commit
-creates a branch stack with only one branch entry. A single branch info
-can be used for performance optimization.
-
-A branch stack structure is dynamically allocated in the decode queue.
-The branch stack and stack flags are synthesized based on branch types
-and associated events.
-
-After:
-
- # perf script --itrace=bl1 -F flags,addr,brstack
-
- jcc ffffc0fad9c6b214 0xffffc0fad9c6b234/0xffffc0fad9c6b214/P/-/-/7/COND/-
- jcc/miss,not_taken/ ffffc0fadaaebb30 0xffffc0fadaaebb2c/0xffffc0fadaaebb30/MN/-/-/7/COND/-
- jmp ffffc0fadaaea358 0xffffc0fadaaea5ec/0xffffc0fadaaea358/P/-/-/5//-
- jcc/not_taken/ ffffc0fadaae6494 0xffffc0fadaae6490/0xffffc0fadaae6494/PN/-/-/11/COND/-
- jcc/not_taken/ ffff7f83ab54 0xffff7f83ab50/0xffff7f83ab54/PN/-/-/13/COND/-
- jcc/not_taken/ ffff7f83ab08 0xffff7f83ab04/0xffff7f83ab08/PN/-/-/8/COND/-
- jcc ffff7f83aa80 0xffff7f83aa58/0xffff7f83aa80/P/-/-/10/COND/-
- jcc ffff7f9a45d0 0xffff7f9a43f0/0xffff7f9a45d0/P/-/-/29/COND/-
- jcc/not_taken/ ffffc0fad9ba6db4 0xffffc0fad9ba6db0/0xffffc0fad9ba6db4/PN/-/-/44/COND/-
- jcc ffffc0fadaac2964 0xffffc0fadaac2970/0xffffc0fadaac2964/P/-/-/6/COND/-
- jcc ffffc0fad99ddc10 0xffffc0fad99ddc04/0xffffc0fad99ddc10/P/-/-/72/COND/-
- jcc/not_taken/ ffffc0fad9b3f21c 0xffffc0fad9b3f218/0xffffc0fad9b3f21c/PN/-/-/64/COND/-
- jcc ffffc0fad9c3b604 0xffffc0fad9c3b5f8/0xffffc0fad9c3b604/P/-/-/13/COND/-
- jcc ffffc0fadaad6048 0xffffc0fadaad5f8c/0xffffc0fadaad6048/P/-/-/5/COND/-
- return/miss/ ffff7f84e614 0xffffc0fad98a2274/0xffff7f84e614/M/-/-/13/RET/-
- jcc/not_taken/ ffffc0fadaac4eb4 0xffffc0fadaac4eb0/0xffffc0fadaac4eb4/PN/-/-/5/COND/-
- jmp ffff7f8e3130 0xffff7f87555c/0xffff7f8e3130/P/-/-/5//-
- jcc/not_taken/ ffffc0fad9b3d9b0 0xffffc0fad9b3d9ac/0xffffc0fad9b3d9b0/PN/-/-/14/COND/-
- return ffffc0fad9b91950 0xffffc0fad98c3e28/0xffffc0fad9b91950/P/-/-/12/RET/-
-
-Reviewed-by: Ian Rogers <irogers@google.com>
-Reviewed-by: James Clark <james.clark@linaro.org>
-Signed-off-by: Leo Yan <leo.yan@arm.com>
-Link: https://lore.kernel.org/r/20250304111240.3378214-12-leo.yan@arm.com
-Signed-off-by: Namhyung Kim <namhyung@kernel.org>
-Stable-dep-of: 059e9100d82a ("perf event: Fix size of synthesized sample with branch stacks")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/arm-spe.c | 99 +++++++++++++++++++++++++++++++++++++++
- 1 file changed, 99 insertions(+)
-
-diff --git a/tools/perf/util/arm-spe.c b/tools/perf/util/arm-spe.c
-index a004e1721a1ff1..80fcb8b10f52e4 100644
---- a/tools/perf/util/arm-spe.c
-+++ b/tools/perf/util/arm-spe.c
-@@ -99,6 +99,7 @@ struct arm_spe_queue {
- struct thread *thread;
- u64 period_instructions;
- u32 flags;
-+ struct branch_stack *last_branch;
- };
-
- static void arm_spe_dump(struct arm_spe *spe __maybe_unused,
-@@ -217,6 +218,16 @@ static struct arm_spe_queue *arm_spe__alloc_queue(struct arm_spe *spe,
- params.get_trace = arm_spe_get_trace;
- params.data = speq;
-
-+ if (spe->synth_opts.last_branch) {
-+ size_t sz = sizeof(struct branch_stack);
-+
-+ /* Allocate one entry for TGT */
-+ sz += sizeof(struct branch_entry);
-+ speq->last_branch = zalloc(sz);
-+ if (!speq->last_branch)
-+ goto out_free;
-+ }
-+
- /* create new decoder */
- speq->decoder = arm_spe_decoder_new(¶ms);
- if (!speq->decoder)
-@@ -226,6 +237,7 @@ static struct arm_spe_queue *arm_spe__alloc_queue(struct arm_spe *spe,
-
- out_free:
- zfree(&speq->event_buf);
-+ zfree(&speq->last_branch);
- free(speq);
-
- return NULL;
-@@ -332,6 +344,73 @@ static void arm_spe_prep_sample(struct arm_spe *spe,
- event->sample.header.size = sizeof(struct perf_event_header);
- }
-
-+static void arm_spe__prep_branch_stack(struct arm_spe_queue *speq)
-+{
-+ struct arm_spe_record *record = &speq->decoder->record;
-+ struct branch_stack *bstack = speq->last_branch;
-+ struct branch_flags *bs_flags;
-+ size_t sz = sizeof(struct branch_stack) +
-+ sizeof(struct branch_entry) /* TGT */;
-+
-+ /* Clean up branch stack */
-+ memset(bstack, 0x0, sz);
-+
-+ if (!(speq->flags & PERF_IP_FLAG_BRANCH))
-+ return;
-+
-+ bstack->entries[0].from = record->from_ip;
-+ bstack->entries[0].to = record->to_ip;
-+
-+ bs_flags = &bstack->entries[0].flags;
-+ bs_flags->value = 0;
-+
-+ if (record->op & ARM_SPE_OP_BR_CR_BL) {
-+ if (record->op & ARM_SPE_OP_BR_COND)
-+ bs_flags->type |= PERF_BR_COND_CALL;
-+ else
-+ bs_flags->type |= PERF_BR_CALL;
-+ /*
-+ * Indirect branch instruction without link (e.g. BR),
-+ * take this case as function return.
-+ */
-+ } else if (record->op & ARM_SPE_OP_BR_CR_RET ||
-+ record->op & ARM_SPE_OP_BR_INDIRECT) {
-+ if (record->op & ARM_SPE_OP_BR_COND)
-+ bs_flags->type |= PERF_BR_COND_RET;
-+ else
-+ bs_flags->type |= PERF_BR_RET;
-+ } else if (record->op & ARM_SPE_OP_BR_CR_NON_BL_RET) {
-+ if (record->op & ARM_SPE_OP_BR_COND)
-+ bs_flags->type |= PERF_BR_COND;
-+ else
-+ bs_flags->type |= PERF_BR_UNCOND;
-+ } else {
-+ if (record->op & ARM_SPE_OP_BR_COND)
-+ bs_flags->type |= PERF_BR_COND;
-+ else
-+ bs_flags->type |= PERF_BR_UNKNOWN;
-+ }
-+
-+ if (record->type & ARM_SPE_BRANCH_MISS) {
-+ bs_flags->mispred = 1;
-+ bs_flags->predicted = 0;
-+ } else {
-+ bs_flags->mispred = 0;
-+ bs_flags->predicted = 1;
-+ }
-+
-+ if (record->type & ARM_SPE_BRANCH_NOT_TAKEN)
-+ bs_flags->not_taken = 1;
-+
-+ if (record->type & ARM_SPE_IN_TXN)
-+ bs_flags->in_tx = 1;
-+
-+ bs_flags->cycles = min(record->latency, 0xFFFFU);
-+
-+ bstack->nr = 1;
-+ bstack->hw_idx = -1ULL;
-+}
-+
- static int arm_spe__inject_event(union perf_event *event, struct perf_sample *sample, u64 type)
- {
- event->header.size = perf_event__sample_event_size(sample, type, 0);
-@@ -394,6 +473,7 @@ static int arm_spe__synth_branch_sample(struct arm_spe_queue *speq,
- sample.addr = record->to_ip;
- sample.weight = record->latency;
- sample.flags = speq->flags;
-+ sample.branch_stack = speq->last_branch;
-
- return arm_spe_deliver_synth_event(spe, speq, event, &sample);
- }
-@@ -424,6 +504,7 @@ static int arm_spe__synth_instruction_sample(struct arm_spe_queue *speq,
- sample.period = spe->instructions_sample_period;
- sample.weight = record->latency;
- sample.flags = speq->flags;
-+ sample.branch_stack = speq->last_branch;
-
- return arm_spe_deliver_synth_event(spe, speq, event, &sample);
- }
-@@ -685,6 +766,10 @@ static int arm_spe_sample(struct arm_spe_queue *speq)
- }
- }
-
-+ if (spe->synth_opts.last_branch &&
-+ (spe->sample_branch || spe->sample_instructions))
-+ arm_spe__prep_branch_stack(speq);
-+
- if (spe->sample_branch && (record->op & ARM_SPE_OP_BRANCH_ERET)) {
- err = arm_spe__synth_branch_sample(speq, spe->branch_id);
- if (err)
-@@ -1109,6 +1194,7 @@ static void arm_spe_free_queue(void *priv)
- thread__zput(speq->thread);
- arm_spe_decoder_free(speq->decoder);
- zfree(&speq->event_buf);
-+ zfree(&speq->last_branch);
- free(speq);
- }
-
-@@ -1283,6 +1369,19 @@ arm_spe_synth_events(struct arm_spe *spe, struct perf_session *session)
- id += 1;
- }
-
-+ if (spe->synth_opts.last_branch) {
-+ if (spe->synth_opts.last_branch_sz > 1)
-+ pr_debug("Arm SPE supports only one bstack entry (TGT).\n");
-+
-+ attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
-+ /*
-+ * We don't use the hardware index, but the sample generation
-+ * code uses the new format branch_stack with this field,
-+ * so the event attributes must indicate that it's present.
-+ */
-+ attr.branch_sample_type |= PERF_SAMPLE_BRANCH_HW_INDEX;
-+ }
-+
- if (spe->synth_opts.branches) {
- spe->sample_branch = true;
-
---
-2.53.0
-
+++ /dev/null
-From f86107d0af421f668696ed5516ebc773614aa3b0 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Fri, 25 Oct 2024 15:30:08 +0100
-Subject: perf arm-spe: Correctly set sample flags
-
-From: Graham Woodward <graham.woodward@arm.com>
-
-[ Upstream commit edff8dad3f9a483259140fb814586b39da430a38 ]
-
-Set flags on all synthesized instruction and branch samples.
-
-Signed-off-by: Graham Woodward <graham.woodward@arm.com>
-Reviewed-by: James Clark <james.clark@linaro.org>
-Tested-by: Leo Yan <leo.yan@arm.com>
-Cc: nd@arm.com
-Cc: mike.leach@linaro.org
-Cc: linux-arm-kernel@lists.infradead.org
-Link: https://lore.kernel.org/r/20241025143009.25419-4-graham.woodward@arm.com
-Signed-off-by: Namhyung Kim <namhyung@kernel.org>
-Stable-dep-of: 059e9100d82a ("perf event: Fix size of synthesized sample with branch stacks")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-script.c | 1 +
- tools/perf/util/arm-spe.c | 17 +++++++++++++++++
- tools/perf/util/event.h | 1 +
- 3 files changed, 19 insertions(+)
-
-diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
-index a644787fa9e1dc..6f3db073786f96 100644
---- a/tools/perf/builtin-script.c
-+++ b/tools/perf/builtin-script.c
-@@ -1728,6 +1728,7 @@ static struct {
- {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_TRACE_END, "tr end"},
- {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | PERF_IP_FLAG_VMENTRY, "vmentry"},
- {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | PERF_IP_FLAG_VMEXIT, "vmexit"},
-+ {PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_BRANCH_MISS, "br miss"},
- {0, NULL}
- };
-
-diff --git a/tools/perf/util/arm-spe.c b/tools/perf/util/arm-spe.c
-index ce34cc5a2e7163..a004e1721a1ff1 100644
---- a/tools/perf/util/arm-spe.c
-+++ b/tools/perf/util/arm-spe.c
-@@ -98,6 +98,7 @@ struct arm_spe_queue {
- u64 timestamp;
- struct thread *thread;
- u64 period_instructions;
-+ u32 flags;
- };
-
- static void arm_spe_dump(struct arm_spe *spe __maybe_unused,
-@@ -392,6 +393,7 @@ static int arm_spe__synth_branch_sample(struct arm_spe_queue *speq,
- sample.stream_id = spe_events_id;
- sample.addr = record->to_ip;
- sample.weight = record->latency;
-+ sample.flags = speq->flags;
-
- return arm_spe_deliver_synth_event(spe, speq, event, &sample);
- }
-@@ -421,6 +423,7 @@ static int arm_spe__synth_instruction_sample(struct arm_spe_queue *speq,
- sample.data_src = data_src;
- sample.period = spe->instructions_sample_period;
- sample.weight = record->latency;
-+ sample.flags = speq->flags;
-
- return arm_spe_deliver_synth_event(spe, speq, event, &sample);
- }
-@@ -432,6 +435,19 @@ static const struct midr_range common_ds_encoding_cpus[] = {
- {},
- };
-
-+static void arm_spe__sample_flags(struct arm_spe_queue *speq)
-+{
-+ const struct arm_spe_record *record = &speq->decoder->record;
-+
-+ speq->flags = 0;
-+ if (record->op & ARM_SPE_OP_BRANCH_ERET) {
-+ speq->flags = PERF_IP_FLAG_BRANCH;
-+
-+ if (record->type & ARM_SPE_BRANCH_MISS)
-+ speq->flags |= PERF_IP_FLAG_BRANCH_MISS;
-+ }
-+}
-+
- static void arm_spe__synth_data_source_common(const struct arm_spe_record *record,
- union perf_mem_data_src *data_src)
- {
-@@ -618,6 +634,7 @@ static int arm_spe_sample(struct arm_spe_queue *speq)
- u64 data_src;
- int err;
-
-+ arm_spe__sample_flags(speq);
- data_src = arm_spe__synth_data_source(speq, record);
-
- if (spe->sample_flc) {
-diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h
-index f8742e6230a5ab..2744c54f404e7b 100644
---- a/tools/perf/util/event.h
-+++ b/tools/perf/util/event.h
-@@ -66,6 +66,7 @@ enum {
- PERF_IP_FLAG_VMEXIT = 1ULL << 12,
- PERF_IP_FLAG_INTR_DISABLE = 1ULL << 13,
- PERF_IP_FLAG_INTR_TOGGLE = 1ULL << 14,
-+ PERF_IP_FLAG_BRANCH_MISS = 1ULL << 15,
- };
-
- #define PERF_IP_FLAG_CHARS "bcrosyiABExghDt"
---
-2.53.0
-
+++ /dev/null
-From 979a3078c6ef80cfc382e67d88d4fc01e7c392dd Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Fri, 25 Oct 2024 15:30:07 +0100
-Subject: perf arm-spe: Use ARM_SPE_OP_BRANCH_ERET when synthesizing branches
-
-From: Graham Woodward <graham.woodward@arm.com>
-
-[ Upstream commit c1b67c85108f99af0a80aa9e59a2b94ad95428d7 ]
-
-Instead of checking the type for just branch misses, we can instead
-check for the OP_BRANCH_ERET and synthesise branches as well as
-branch misses.
-
-Signed-off-by: Graham Woodward <graham.woodward@arm.com>
-Reviewed-by: James Clark <james.clark@linaro.org>
-Tested-by: Leo Yan <leo.yan@arm.com>
-Cc: nd@arm.com
-Cc: mike.leach@linaro.org
-Cc: linux-arm-kernel@lists.infradead.org
-Link: https://lore.kernel.org/r/20241025143009.25419-3-graham.woodward@arm.com
-Signed-off-by: Namhyung Kim <namhyung@kernel.org>
-Stable-dep-of: 059e9100d82a ("perf event: Fix size of synthesized sample with branch stacks")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/arm-spe.c | 12 ++++++------
- 1 file changed, 6 insertions(+), 6 deletions(-)
-
-diff --git a/tools/perf/util/arm-spe.c b/tools/perf/util/arm-spe.c
-index fcacdd87c0d2b4..ce34cc5a2e7163 100644
---- a/tools/perf/util/arm-spe.c
-+++ b/tools/perf/util/arm-spe.c
-@@ -71,7 +71,7 @@ struct arm_spe {
- u64 llc_access_id;
- u64 tlb_miss_id;
- u64 tlb_access_id;
-- u64 branch_miss_id;
-+ u64 branch_id;
- u64 remote_access_id;
- u64 memory_id;
- u64 instructions_id;
-@@ -668,8 +668,8 @@ static int arm_spe_sample(struct arm_spe_queue *speq)
- }
- }
-
-- if (spe->sample_branch && (record->type & ARM_SPE_BRANCH_MISS)) {
-- err = arm_spe__synth_branch_sample(speq, spe->branch_miss_id);
-+ if (spe->sample_branch && (record->op & ARM_SPE_OP_BRANCH_ERET)) {
-+ err = arm_spe__synth_branch_sample(speq, spe->branch_id);
- if (err)
- return err;
- }
-@@ -1269,12 +1269,12 @@ arm_spe_synth_events(struct arm_spe *spe, struct perf_session *session)
- if (spe->synth_opts.branches) {
- spe->sample_branch = true;
-
-- /* Branch miss */
-+ /* Branch */
- err = perf_session__deliver_synth_attr_event(session, &attr, id);
- if (err)
- return err;
-- spe->branch_miss_id = id;
-- arm_spe_set_event_name(evlist, id, "branch-miss");
-+ spe->branch_id = id;
-+ arm_spe_set_event_name(evlist, id, "branch");
- id += 1;
- }
-
---
-2.53.0
-
+++ /dev/null
-From 475db7c808a106211ccfd69a597980a561daf49f Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Thu, 3 Oct 2024 19:53:19 +0100
-Subject: perf arm-spe: Use metadata to decide the data source feature
-
-From: Leo Yan <leo.yan@arm.com>
-
-[ Upstream commit ba5e7169e5483a61899497e23fa18f7ef33aa827 ]
-
-Use the info in the metadata to decide if the data source feature is
-supported. The CPU MIDR must be in the CPU list for the common data
-source encoding.
-
-For the metadata version 1, it doesn't include info for MIDR. In this
-case, due to absent info for making decision, print out warning to
-remind users to upgrade tool and returns false.
-
-Signed-off-by: Leo Yan <leo.yan@arm.com>
-Reviewed-by: James Clark <james.clark@linaro.org>
-Link: https://lore.kernel.org/r/20241003185322.192357-5-leo.yan@arm.com
-Signed-off-by: Namhyung Kim <namhyung@kernel.org>
-Stable-dep-of: 059e9100d82a ("perf event: Fix size of synthesized sample with branch stacks")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/arm-spe.c | 67 +++++++++++++++++++++++++++++++++++++--
- 1 file changed, 64 insertions(+), 3 deletions(-)
-
-diff --git a/tools/perf/util/arm-spe.c b/tools/perf/util/arm-spe.c
-index 9890b17241c34a..fcacdd87c0d2b4 100644
---- a/tools/perf/util/arm-spe.c
-+++ b/tools/perf/util/arm-spe.c
-@@ -275,6 +275,20 @@ static int arm_spe_set_tid(struct arm_spe_queue *speq, pid_t tid)
- return 0;
- }
-
-+static u64 *arm_spe__get_metadata_by_cpu(struct arm_spe *spe, u64 cpu)
-+{
-+ u64 i;
-+
-+ if (!spe->metadata)
-+ return NULL;
-+
-+ for (i = 0; i < spe->metadata_nr_cpu; i++)
-+ if (spe->metadata[i][ARM_SPE_CPU] == cpu)
-+ return spe->metadata[i];
-+
-+ return NULL;
-+}
-+
- static struct simd_flags arm_spe__synth_simd_flags(const struct arm_spe_record *record)
- {
- struct simd_flags simd_flags = {};
-@@ -517,10 +531,57 @@ static void arm_spe__synth_data_source_generic(const struct arm_spe_record *reco
- data_src->mem_remote = PERF_MEM_REMOTE_REMOTE;
- }
-
--static u64 arm_spe__synth_data_source(const struct arm_spe_record *record, u64 midr)
-+static bool arm_spe__is_common_ds_encoding(struct arm_spe_queue *speq)
-+{
-+ struct arm_spe *spe = speq->spe;
-+ bool is_in_cpu_list;
-+ u64 *metadata = NULL;
-+ u64 midr = 0;
-+
-+ /*
-+ * Metadata version 1 doesn't contain any info for MIDR.
-+ * Simply return false in this case.
-+ */
-+ if (spe->metadata_ver == 1) {
-+ pr_warning_once("The data file contains metadata version 1, "
-+ "which is absent the info for data source. "
-+ "Please upgrade the tool to record data.\n");
-+ return false;
-+ }
-+
-+ /* CPU ID is -1 for per-thread mode */
-+ if (speq->cpu < 0) {
-+ /*
-+ * On the heterogeneous system, due to CPU ID is -1,
-+ * cannot confirm the data source packet is supported.
-+ */
-+ if (!spe->is_homogeneous)
-+ return false;
-+
-+ /* In homogeneous system, simply use CPU0's metadata */
-+ if (spe->metadata)
-+ metadata = spe->metadata[0];
-+ } else {
-+ metadata = arm_spe__get_metadata_by_cpu(spe, speq->cpu);
-+ }
-+
-+ if (!metadata)
-+ return false;
-+
-+ midr = metadata[ARM_SPE_CPU_MIDR];
-+
-+ is_in_cpu_list = is_midr_in_range_list(midr, common_ds_encoding_cpus);
-+ if (is_in_cpu_list)
-+ return true;
-+ else
-+ return false;
-+}
-+
-+static u64 arm_spe__synth_data_source(struct arm_spe_queue *speq,
-+ const struct arm_spe_record *record)
- {
- union perf_mem_data_src data_src = { .mem_op = PERF_MEM_OP_NA };
-- bool is_common = is_midr_in_range_list(midr, common_ds_encoding_cpus);
-+ bool is_common = arm_spe__is_common_ds_encoding(speq);
-
- /* Only synthesize data source for LDST operations */
- if (!is_ldst_op(record->op))
-@@ -557,7 +618,7 @@ static int arm_spe_sample(struct arm_spe_queue *speq)
- u64 data_src;
- int err;
-
-- data_src = arm_spe__synth_data_source(record, spe->midr);
-+ data_src = arm_spe__synth_data_source(speq, record);
-
- if (spe->sample_flc) {
- if (record->type & ARM_SPE_L1D_MISS) {
---
-2.53.0
-
+++ /dev/null
-From ba993e5ada1ddce7a71140dc85ef65bc2cd981bc Mon Sep 17 00:00:00 2001
-From: James Clark <james.clark@linaro.org>
-Date: Tue, 29 Oct 2024 14:37:33 +0000
-Subject: perf arm-spe: Use old behavior when opening old SPE files
-
-From: James Clark <james.clark@linaro.org>
-
-commit ba993e5ada1ddce7a71140dc85ef65bc2cd981bc upstream.
-
-Since the linked commit, we stopped interpreting data source if the
-perf.data file doesn't have the new metadata version. This means that
-perf c2c will show no samples in this case.
-
-Keep the old behavior so old files can be opened, but also still show
-the new warning that updating might improve the decoding.
-
-Also re-write the warning to be more concise and specific to a user.
-
-Fixes: ba5e7169e548 ("perf arm-spe: Use metadata to decide the data source feature")
-Signed-off-by: James Clark <james.clark@linaro.org>
-Reviewed-by: Leo Yan <leo.yan@arm.com>
-Cc: Julio.Suarez@arm.com
-Cc: Kiel.Friedt@arm.com
-Cc: Ryan.Roberts@arm.com
-Cc: Will Deacon <will@kernel.org>
-Cc: Mike Leach <mike.leach@linaro.org>
-Cc: linux-arm-kernel@lists.infradead.org
-Cc: Besar Wicaksono <bwicaksono@nvidia.com>
-Cc: John Garry <john.g.garry@oracle.com>
-Link: https://lore.kernel.org/r/20241029143734.291638-1-james.clark@linaro.org
-Signed-off-by: Namhyung Kim <namhyung@kernel.org>
-Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
----
- tools/perf/util/arm-spe.c | 50 ++++++++++++++++++++++------------------------
- 1 file changed, 24 insertions(+), 26 deletions(-)
-
---- a/tools/perf/util/arm-spe.c
-+++ b/tools/perf/util/arm-spe.c
-@@ -635,37 +635,35 @@ static bool arm_spe__is_common_ds_encodi
- u64 *metadata = NULL;
- u64 midr = 0;
-
-- /*
-- * Metadata version 1 doesn't contain any info for MIDR.
-- * Simply return false in this case.
-- */
-+ /* Metadata version 1 assumes all CPUs are the same (old behavior) */
- if (spe->metadata_ver == 1) {
-- pr_warning_once("The data file contains metadata version 1, "
-- "which is absent the info for data source. "
-- "Please upgrade the tool to record data.\n");
-- return false;
-- }
--
-- /* CPU ID is -1 for per-thread mode */
-- if (speq->cpu < 0) {
-- /*
-- * On the heterogeneous system, due to CPU ID is -1,
-- * cannot confirm the data source packet is supported.
-- */
-- if (!spe->is_homogeneous)
-- return false;
-+ const char *cpuid;
-
-- /* In homogeneous system, simply use CPU0's metadata */
-- if (spe->metadata)
-- metadata = spe->metadata[0];
-+ pr_warning_once("Old SPE metadata, re-record to improve decode accuracy\n");
-+ cpuid = perf_env__cpuid(spe->session->evlist->env);
-+ midr = strtol(cpuid, NULL, 16);
- } else {
-- metadata = arm_spe__get_metadata_by_cpu(spe, speq->cpu);
-- }
-+ /* CPU ID is -1 for per-thread mode */
-+ if (speq->cpu < 0) {
-+ /*
-+ * On the heterogeneous system, due to CPU ID is -1,
-+ * cannot confirm the data source packet is supported.
-+ */
-+ if (!spe->is_homogeneous)
-+ return false;
-+
-+ /* In homogeneous system, simply use CPU0's metadata */
-+ if (spe->metadata)
-+ metadata = spe->metadata[0];
-+ } else {
-+ metadata = arm_spe__get_metadata_by_cpu(spe, speq->cpu);
-+ }
-
-- if (!metadata)
-- return false;
-+ if (!metadata)
-+ return false;
-
-- midr = metadata[ARM_SPE_CPU_MIDR];
-+ midr = metadata[ARM_SPE_CPU_MIDR];
-+ }
-
- is_in_cpu_list = is_midr_in_range_list(midr, common_ds_encoding_cpus);
- if (is_in_cpu_list)
+++ /dev/null
-From 04779f87f572c359f1d6f02092cb2332481df5eb Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Mon, 8 Jun 2026 08:10:43 -0300
-Subject: perf bpf: Add NULL check for btf__type_by_id() in
- synthesize_bpf_prog_name()
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 903b0526dcf86d030c5970b4b0a67f9c227368e2 ]
-
-synthesize_bpf_prog_name() calls btf__type_by_id() and immediately
-dereferences the result via t->name_off without checking for NULL.
-btf__type_by_id() returns NULL when the type_id is invalid or out
-of range. When processing perf.data files, finfo->type_id comes from
-untrusted input, so an invalid ID causes a NULL pointer dereference.
-
-Fix by checking t for NULL before dereferencing.
-
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Fixes: fc462ac75b36daaa ("perf bpf: Extract logic to create program names from perf_event__synthesize_one_bpf_prog()")
-Cc: Song Liu <songliubraving@fb.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/bpf-event.c | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/tools/perf/util/bpf-event.c b/tools/perf/util/bpf-event.c
-index 8919fc52eecd45..6c9a4a1b80a24c 100644
---- a/tools/perf/util/bpf-event.c
-+++ b/tools/perf/util/bpf-event.c
-@@ -138,7 +138,8 @@ static int synthesize_bpf_prog_name(char *buf, int size,
- if (btf) {
- finfo = func_infos + sub_id * info->func_info_rec_size;
- t = btf__type_by_id(btf, finfo->type_id);
-- short_name = btf__name_by_offset(btf, t->name_off);
-+ if (t)
-+ short_name = btf__name_by_offset(btf, t->name_off);
- } else if (sub_id == 0 && sub_prog_cnt == 1) {
- /* no subprog */
- if (info->name[0])
---
-2.53.0
-
+++ /dev/null
-From d728900c519019491aaf8d451d942733edcf3615 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Wed, 10 Jun 2026 21:03:16 -0300
-Subject: perf bpf: Bounds-check array offsets in bpil_offs_to_addr()
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 033e85edfbf271f92979d2a39aeaf40f8472a795 ]
-
-bpil_offs_to_addr() converts offsets stored in perf.data's
-bpf_prog_info_linear structure into heap pointers by adding the offset
-to the data allocation base. The offsets come from untrusted file input
-and are not validated against data_len.
-
-If an offset exceeds data_len, the computed address points outside the
-allocated data buffer. Callers like synthesize_bpf_prog_name() then
-dereference prog_tags[sub_id] or func_info pointers, reading arbitrary
-heap memory.
-
-Add a bounds check: when an offset exceeds data_len, zero the field
-and skip the conversion. This prevents out-of-bounds pointer
-construction from crafted perf.data files.
-
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Fixes: 6ac22d036f86c4e2 ("perf bpf: Pull in bpf_program__get_prog_info_linear()")
-Cc: Dave Marchevsky <davemarchevsky@fb.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/bpf-utils.c | 16 ++++++++++++++++
- 1 file changed, 16 insertions(+)
-
-diff --git a/tools/perf/util/bpf-utils.c b/tools/perf/util/bpf-utils.c
-index 5a66dc8594aa88..03a621a0c5dcd5 100644
---- a/tools/perf/util/bpf-utils.c
-+++ b/tools/perf/util/bpf-utils.c
-@@ -264,12 +264,28 @@ void bpil_offs_to_addr(struct perf_bpil *info_linear)
- for (i = PERF_BPIL_FIRST_ARRAY; i < PERF_BPIL_LAST_ARRAY; ++i) {
- const struct bpil_array_desc *desc = &bpil_array_desc[i];
- __u64 addr, offs;
-+ __u32 count, size;
-
- if ((info_linear->arrays & (1UL << i)) == 0)
- continue;
-
- offs = bpf_prog_info_read_offset_u64(&info_linear->info,
- desc->array_offset);
-+ count = bpf_prog_info_read_offset_u32(&info_linear->info,
-+ desc->count_offset);
-+ size = bpf_prog_info_read_offset_u32(&info_linear->info,
-+ desc->size_offset);
-+ /* offset and extent from perf.data are untrusted — keep within data[] */
-+ if (offs >= info_linear->data_len ||
-+ (u64)count * size > info_linear->data_len - offs) {
-+ bpf_prog_info_set_offset_u64(&info_linear->info,
-+ desc->array_offset, 0);
-+ bpf_prog_info_set_offset_u32(&info_linear->info,
-+ desc->count_offset, 0);
-+ /* clear the bit so bpil_addr_to_offs() won't reverse a zeroed address */
-+ info_linear->arrays &= ~(1UL << i);
-+ continue;
-+ }
- addr = offs + ptr_to_u64(info_linear->data);
- bpf_prog_info_set_offset_u64(&info_linear->info,
- desc->array_offset, addr);
---
-2.53.0
-
+++ /dev/null
-From 8733e9bbace794b8cd131440af75c81186fe1502 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sun, 7 Jun 2026 14:23:15 -0300
-Subject: perf bpf: Use scnprintf() in snprintf_hex() and
- synthesize_bpf_prog_name()
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit cab3a9331ed0b3f884dd61c8a25b3cf123705982 ]
-
-Both functions accumulate formatted output via ret += snprintf(buf + ret,
-size - ret, ...). If the buffer is too small and snprintf() returns more
-than the remaining space, ret exceeds size and the next 'size - ret'
-underflows, causing snprintf() to write past the buffer end.
-
-Switch to scnprintf() which returns the actual number of bytes written,
-making the accumulation safe.
-
-Fixes: 7b612e291a5affb1 ("perf tools: Synthesize PERF_RECORD_* for loaded BPF programs")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Reviewed-by: Ian Rogers <irogers@google.com>
-Cc: Song Liu <song@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/bpf-event.c | 11 ++++++-----
- 1 file changed, 6 insertions(+), 5 deletions(-)
-
-diff --git a/tools/perf/util/bpf-event.c b/tools/perf/util/bpf-event.c
-index d9123c9637baaf..8919fc52eecd45 100644
---- a/tools/perf/util/bpf-event.c
-+++ b/tools/perf/util/bpf-event.c
-@@ -28,7 +28,7 @@ static int snprintf_hex(char *buf, size_t size, unsigned char *data, size_t len)
- size_t i;
-
- for (i = 0; i < len; i++)
-- ret += snprintf(buf + ret, size - ret, "%02x", data[i]);
-+ ret += scnprintf(buf + ret, size - ret, "%02x", data[i]);
- return ret;
- }
-
-@@ -132,7 +132,7 @@ static int synthesize_bpf_prog_name(char *buf, int size,
- const struct btf_type *t;
- int name_len;
-
-- name_len = snprintf(buf, size, "bpf_prog_");
-+ name_len = scnprintf(buf, size, "bpf_prog_");
- name_len += snprintf_hex(buf + name_len, size - name_len,
- prog_tags[sub_id], BPF_TAG_SIZE);
- if (btf) {
-@@ -145,9 +145,10 @@ static int synthesize_bpf_prog_name(char *buf, int size,
- short_name = info->name;
- } else
- short_name = "F";
-- if (short_name)
-- name_len += snprintf(buf + name_len, size - name_len,
-- "_%s", short_name);
-+ if (short_name) {
-+ name_len += scnprintf(buf + name_len, size - name_len,
-+ "_%s", short_name);
-+ }
- return name_len;
- }
-
---
-2.53.0
-
+++ /dev/null
-From 8a9e06475407592b6ea1cfa70f569f373cdd6a39 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Wed, 10 Jun 2026 21:01:15 -0300
-Subject: perf bpf: Validate func_info_rec_size and sub_id in
- synthesize_bpf_prog_name()
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 10b3c3d63ecc17c6acb855bac5f40367f1115765 ]
-
-synthesize_bpf_prog_name() computes a pointer into the func_info array
-using sub_id * info->func_info_rec_size without validating either value.
-Both come from perf.data and are untrusted:
-
-- A func_info_rec_size smaller than sizeof(struct bpf_func_info) means
- the finfo pointer would reference a truncated entry, reading past it
- into adjacent data.
-
-- A sub_id >= nr_func_info computes an offset past the func_info buffer,
- causing an out-of-bounds read.
-
-Add bounds checks for both values before computing the pointer offset.
-When validation fails, fall through to the non-BTF name path instead
-of reading garbage.
-
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Fixes: 7b612e291a5affb1 ("perf tools: Synthesize PERF_RECORD_* for loaded BPF programs")
-Cc: Song Liu <songliubraving@fb.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/bpf-event.c | 4 +++-
- 1 file changed, 3 insertions(+), 1 deletion(-)
-
-diff --git a/tools/perf/util/bpf-event.c b/tools/perf/util/bpf-event.c
-index 6c9a4a1b80a24c..db89053ce96808 100644
---- a/tools/perf/util/bpf-event.c
-+++ b/tools/perf/util/bpf-event.c
-@@ -135,7 +135,9 @@ static int synthesize_bpf_prog_name(char *buf, int size,
- name_len = scnprintf(buf, size, "bpf_prog_");
- name_len += snprintf_hex(buf + name_len, size - name_len,
- prog_tags[sub_id], BPF_TAG_SIZE);
-- if (btf) {
-+ if (btf &&
-+ info->func_info_rec_size >= sizeof(*finfo) &&
-+ sub_id < info->nr_func_info) {
- finfo = func_infos + sub_id * info->func_info_rec_size;
- t = btf__type_by_id(btf, finfo->type_id);
- if (t)
---
-2.53.0
-
+++ /dev/null
-From b68d22b74b85ac8084e7b59d6a88b4542ec75792 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Thu, 24 Jul 2025 09:32:43 -0700
-Subject: perf build-id: Change sprintf functions to snprintf
-
-From: Ian Rogers <irogers@google.com>
-
-[ Upstream commit fccaaf6fbbc59910edcf276f97a5b2ef5778c55e ]
-
-Pass in a size argument rather than implying all build id strings must
-be SBUILD_ID_SIZE.
-
-Signed-off-by: Ian Rogers <irogers@google.com>
-Link: https://lore.kernel.org/r/20250724163302.596743-4-irogers@google.com
-[ fixed some build errors ]
-Signed-off-by: Namhyung Kim <namhyung@kernel.org>
-Stable-dep-of: 2ea64782a428 ("perf tools: Use snprintf() in dso__read_running_kernel_build_id()")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-buildid-cache.c | 12 +++----
- tools/perf/builtin-buildid-list.c | 6 ++--
- tools/perf/tests/sdt.c | 2 +-
- tools/perf/util/build-id.c | 33 ++++++++-----------
- tools/perf/util/build-id.h | 6 ++--
- tools/perf/util/disasm.c | 2 +-
- tools/perf/util/dso.c | 4 +--
- tools/perf/util/dsos.c | 2 +-
- tools/perf/util/event.c | 2 +-
- tools/perf/util/header.c | 2 +-
- tools/perf/util/map.c | 2 +-
- tools/perf/util/probe-event.c | 4 +--
- tools/perf/util/probe-file.c | 4 +--
- tools/perf/util/probe-finder.c | 2 +-
- .../scripting-engines/trace-event-python.c | 7 ++--
- tools/perf/util/symbol.c | 2 +-
- 16 files changed, 42 insertions(+), 50 deletions(-)
-
-diff --git a/tools/perf/builtin-buildid-cache.c b/tools/perf/builtin-buildid-cache.c
-index b0511d16aeb6c7..3f7739b21148bd 100644
---- a/tools/perf/builtin-buildid-cache.c
-+++ b/tools/perf/builtin-buildid-cache.c
-@@ -31,7 +31,7 @@
- #include <linux/string.h>
- #include <linux/err.h>
-
--static int build_id_cache__kcore_buildid(const char *proc_dir, char *sbuildid)
-+static int build_id_cache__kcore_buildid(const char *proc_dir, char *sbuildid, size_t sbuildid_size)
- {
- char root_dir[PATH_MAX];
- char *p;
-@@ -42,7 +42,7 @@ static int build_id_cache__kcore_buildid(const char *proc_dir, char *sbuildid)
- if (!p)
- return -1;
- *p = '\0';
-- return sysfs__sprintf_build_id(root_dir, sbuildid);
-+ return sysfs__snprintf_build_id(root_dir, sbuildid, sbuildid_size);
- }
-
- static int build_id_cache__kcore_dir(char *dir, size_t sz)
-@@ -128,7 +128,7 @@ static int build_id_cache__add_kcore(const char *filename, bool force)
- return -1;
- *p = '\0';
-
-- if (build_id_cache__kcore_buildid(from_dir, sbuildid) < 0)
-+ if (build_id_cache__kcore_buildid(from_dir, sbuildid, sizeof(sbuildid)) < 0)
- return -1;
-
- scnprintf(to_dir, sizeof(to_dir), "%s/%s/%s",
-@@ -187,7 +187,7 @@ static int build_id_cache__add_file(const char *filename, struct nsinfo *nsi)
- return -1;
- }
-
-- build_id__sprintf(&bid, sbuild_id);
-+ build_id__snprintf(&bid, sbuild_id, sizeof(sbuild_id));
- err = build_id_cache__add_s(sbuild_id, filename, nsi,
- false, false);
- pr_debug("Adding %s %s: %s\n", sbuild_id, filename,
-@@ -211,7 +211,7 @@ static int build_id_cache__remove_file(const char *filename, struct nsinfo *nsi)
- return -1;
- }
-
-- build_id__sprintf(&bid, sbuild_id);
-+ build_id__snprintf(&bid, sbuild_id, sizeof(sbuild_id));
- err = build_id_cache__remove_s(sbuild_id);
- pr_debug("Removing %s %s: %s\n", sbuild_id, filename,
- err ? "FAIL" : "Ok");
-@@ -317,7 +317,7 @@ static int build_id_cache__update_file(const char *filename, struct nsinfo *nsi)
- }
- err = 0;
-
-- build_id__sprintf(&bid, sbuild_id);
-+ build_id__snprintf(&bid, sbuild_id, sizeof(sbuild_id));
- if (build_id_cache__cached(sbuild_id))
- err = build_id_cache__remove_s(sbuild_id);
-
-diff --git a/tools/perf/builtin-buildid-list.c b/tools/perf/builtin-buildid-list.c
-index 52dfacaff8e39f..ba8ba0303920c5 100644
---- a/tools/perf/builtin-buildid-list.c
-+++ b/tools/perf/builtin-buildid-list.c
-@@ -31,7 +31,7 @@ static int buildid__map_cb(struct map *map, void *arg __maybe_unused)
-
- memset(bid_buf, 0, sizeof(bid_buf));
- if (dso__has_build_id(dso))
-- build_id__sprintf(dso__bid_const(dso), bid_buf);
-+ build_id__snprintf(dso__bid_const(dso), bid_buf, sizeof(bid_buf));
- printf("%s %16" PRIx64 " %16" PRIx64, bid_buf, map__start(map), map__end(map));
- if (dso_long_name != NULL)
- printf(" %s", dso_long_name);
-@@ -57,7 +57,7 @@ static int sysfs__fprintf_build_id(FILE *fp)
- char sbuild_id[SBUILD_ID_SIZE];
- int ret;
-
-- ret = sysfs__sprintf_build_id("/", sbuild_id);
-+ ret = sysfs__snprintf_build_id("/", sbuild_id, sizeof(sbuild_id));
- if (ret != sizeof(sbuild_id))
- return ret < 0 ? ret : -EINVAL;
-
-@@ -69,7 +69,7 @@ static int filename__fprintf_build_id(const char *name, FILE *fp)
- char sbuild_id[SBUILD_ID_SIZE];
- int ret;
-
-- ret = filename__sprintf_build_id(name, sbuild_id);
-+ ret = filename__snprintf_build_id(name, sbuild_id, sizeof(sbuild_id));
- if (ret != sizeof(sbuild_id))
- return ret < 0 ? ret : -EINVAL;
-
-diff --git a/tools/perf/tests/sdt.c b/tools/perf/tests/sdt.c
-index 91971289925102..663c8f700069d4 100644
---- a/tools/perf/tests/sdt.c
-+++ b/tools/perf/tests/sdt.c
-@@ -37,7 +37,7 @@ static int build_id_cache__add_file(const char *filename)
- return err;
- }
-
-- build_id__sprintf(&bid, sbuild_id);
-+ build_id__snprintf(&bid, sbuild_id, sizeof(sbuild_id));
- err = build_id_cache__add_s(sbuild_id, filename, NULL, false, false);
- if (err < 0)
- pr_debug("Failed to add build id cache of %s\n", filename);
-diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c
-index ee00313d5d7e2a..ac642c758d5a17 100644
---- a/tools/perf/util/build-id.c
-+++ b/tools/perf/util/build-id.c
-@@ -67,24 +67,17 @@ int build_id__mark_dso_hit(const struct perf_tool *tool __maybe_unused,
- return 0;
- }
-
--int build_id__sprintf(const struct build_id *build_id, char *bf)
-+int build_id__snprintf(const struct build_id *build_id, char *bf, size_t bf_size)
- {
-- char *bid = bf;
-- const u8 *raw = build_id->data;
-- size_t i;
--
-- bf[0] = 0x0;
-+ size_t offs = 0;
-
-- for (i = 0; i < build_id->size; ++i) {
-- sprintf(bid, "%02x", *raw);
-- ++raw;
-- bid += 2;
-- }
-+ for (size_t i = 0; i < build_id->size && offs < bf_size; ++i)
-+ offs += snprintf(bf + offs, bf_size - offs, "%02x", build_id->data[i]);
-
-- return (bid - bf) + 1;
-+ return offs;
- }
-
--int sysfs__sprintf_build_id(const char *root_dir, char *sbuild_id)
-+int sysfs__snprintf_build_id(const char *root_dir, char *sbuild_id, size_t sbuild_id_size)
- {
- char notes[PATH_MAX];
- struct build_id bid;
-@@ -99,10 +92,10 @@ int sysfs__sprintf_build_id(const char *root_dir, char *sbuild_id)
- if (ret < 0)
- return ret;
-
-- return build_id__sprintf(&bid, sbuild_id);
-+ return build_id__snprintf(&bid, sbuild_id, sbuild_id_size);
- }
-
--int filename__sprintf_build_id(const char *pathname, char *sbuild_id)
-+int filename__snprintf_build_id(const char *pathname, char *sbuild_id, size_t sbuild_id_size)
- {
- struct build_id bid;
- int ret;
-@@ -111,7 +104,7 @@ int filename__sprintf_build_id(const char *pathname, char *sbuild_id)
- if (ret < 0)
- return ret;
-
-- return build_id__sprintf(&bid, sbuild_id);
-+ return build_id__snprintf(&bid, sbuild_id, sbuild_id_size);
- }
-
- /* asnprintf consolidates asprintf and snprintf */
-@@ -212,9 +205,9 @@ static bool build_id_cache__valid_id(char *sbuild_id)
- return false;
-
- if (!strcmp(pathname, DSO__NAME_KALLSYMS))
-- ret = sysfs__sprintf_build_id("/", real_sbuild_id);
-+ ret = sysfs__snprintf_build_id("/", real_sbuild_id, sizeof(real_sbuild_id));
- else if (pathname[0] == '/')
-- ret = filename__sprintf_build_id(pathname, real_sbuild_id);
-+ ret = filename__snprintf_build_id(pathname, real_sbuild_id, sizeof(real_sbuild_id));
- else
- ret = -EINVAL; /* Should we support other special DSO cache? */
- if (ret >= 0)
-@@ -243,7 +236,7 @@ char *__dso__build_id_filename(const struct dso *dso, char *bf, size_t size,
- if (!dso__has_build_id(dso))
- return NULL;
-
-- build_id__sprintf(dso__bid_const(dso), sbuild_id);
-+ build_id__snprintf(dso__bid_const(dso), sbuild_id, sizeof(sbuild_id));
- linkname = build_id_cache__linkname(sbuild_id, NULL, 0);
- if (!linkname)
- return NULL;
-@@ -769,7 +762,7 @@ static int build_id_cache__add_b(const struct build_id *bid,
- {
- char sbuild_id[SBUILD_ID_SIZE];
-
-- build_id__sprintf(bid, sbuild_id);
-+ build_id__snprintf(bid, sbuild_id, sizeof(sbuild_id));
-
- return __build_id_cache__add_s(sbuild_id, name, nsi, is_kallsyms,
- is_vdso, proper_name, root_dir);
-diff --git a/tools/perf/util/build-id.h b/tools/perf/util/build-id.h
-index a212497bfdb0a5..37b8f00f8314c3 100644
---- a/tools/perf/util/build-id.h
-+++ b/tools/perf/util/build-id.h
-@@ -21,10 +21,10 @@ struct feat_fd;
- struct nsinfo;
-
- void build_id__init(struct build_id *bid, const u8 *data, size_t size);
--int build_id__sprintf(const struct build_id *build_id, char *bf);
-+int build_id__snprintf(const struct build_id *build_id, char *bf, size_t bf_size);
- bool build_id__is_defined(const struct build_id *bid);
--int sysfs__sprintf_build_id(const char *root_dir, char *sbuild_id);
--int filename__sprintf_build_id(const char *pathname, char *sbuild_id);
-+int sysfs__snprintf_build_id(const char *root_dir, char *sbuild_id, size_t sbuild_id_size);
-+int filename__snprintf_build_id(const char *pathname, char *sbuild_id, size_t sbuild_id_size);
- char *build_id_cache__kallsyms_path(const char *sbuild_id, char *bf,
- size_t size);
-
-diff --git a/tools/perf/util/disasm.c b/tools/perf/util/disasm.c
-index 8f35232f7f22c7..d8c27921332238 100644
---- a/tools/perf/util/disasm.c
-+++ b/tools/perf/util/disasm.c
-@@ -1220,7 +1220,7 @@ int symbol__strerror_disassemble(struct map_symbol *ms, int errnum, char *buf, s
- char *build_id_msg = NULL;
-
- if (dso__has_build_id(dso)) {
-- build_id__sprintf(dso__bid(dso), bf + 15);
-+ build_id__snprintf(dso__bid(dso), bf + 15, sizeof(bf) - 15);
- build_id_msg = bf;
- }
- scnprintf(buf, buflen,
-diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
-index 7c12d53656887d..aae754b1cc624a 100644
---- a/tools/perf/util/dso.c
-+++ b/tools/perf/util/dso.c
-@@ -215,7 +215,7 @@ int dso__read_binary_type_filename(const struct dso *dso,
- break;
- }
-
-- build_id__sprintf(dso__bid_const(dso), build_id_hex);
-+ build_id__snprintf(dso__bid_const(dso), build_id_hex, sizeof(build_id_hex));
- len = __symbol__join_symfs(filename, size, "/usr/lib/debug/.build-id/");
- snprintf(filename + len, size - len, "%.2s/%s.debug",
- build_id_hex, build_id_hex + 2);
-@@ -1694,7 +1694,7 @@ static size_t dso__fprintf_buildid(struct dso *dso, FILE *fp)
- {
- char sbuild_id[SBUILD_ID_SIZE];
-
-- build_id__sprintf(dso__bid(dso), sbuild_id);
-+ build_id__snprintf(dso__bid(dso), sbuild_id, sizeof(sbuild_id));
- return fprintf(fp, "%s", sbuild_id);
- }
-
-diff --git a/tools/perf/util/dsos.c b/tools/perf/util/dsos.c
-index e0998e2a7c4e75..b2172632b3cdd7 100644
---- a/tools/perf/util/dsos.c
-+++ b/tools/perf/util/dsos.c
-@@ -370,7 +370,7 @@ static int dsos__fprintf_buildid_cb(struct dso *dso, void *data)
-
- if (args->skip && args->skip(dso, args->parm))
- return 0;
-- build_id__sprintf(dso__bid(dso), sbuild_id);
-+ build_id__snprintf(dso__bid(dso), sbuild_id, sizeof(sbuild_id));
- args->ret += fprintf(args->fp, "%-40s %s\n", sbuild_id, dso__long_name(dso));
- return 0;
- }
-diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c
-index 1e5085817e8250..ce6c70c1d3ee53 100644
---- a/tools/perf/util/event.c
-+++ b/tools/perf/util/event.c
-@@ -330,7 +330,7 @@ size_t perf_event__fprintf_mmap2(union perf_event *event, FILE *fp)
-
- build_id__init(&bid, event->mmap2.build_id,
- event->mmap2.build_id_size);
-- build_id__sprintf(&bid, sbuild_id);
-+ build_id__snprintf(&bid, sbuild_id, sizeof(sbuild_id));
-
- return fprintf(fp, " %d/%d: [%#" PRI_lx64 "(%#" PRI_lx64 ") @ %#" PRI_lx64
- " <%s>]: %c%c%c%c %s\n",
-diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
-index 8c15ce90466997..2d8eda9b614ef1 100644
---- a/tools/perf/util/header.c
-+++ b/tools/perf/util/header.c
-@@ -2374,7 +2374,7 @@ static int __event_process_build_id(struct perf_record_header_build_id *bev,
- free(m.name);
- }
-
-- build_id__sprintf(dso__bid(dso), sbuild_id);
-+ build_id__snprintf(dso__bid(dso), sbuild_id, sizeof(sbuild_id));
- pr_debug("build id event received for %s: %s [%zu]\n",
- dso__long_name(dso), sbuild_id, size);
- dso__put(dso);
-diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c
-index d729438b7d6573..0f6b185f95896a 100644
---- a/tools/perf/util/map.c
-+++ b/tools/perf/util/map.c
-@@ -354,7 +354,7 @@ int map__load(struct map *map)
- if (dso__has_build_id(dso)) {
- char sbuild_id[SBUILD_ID_SIZE];
-
-- build_id__sprintf(dso__bid(dso), sbuild_id);
-+ build_id__snprintf(dso__bid(dso), sbuild_id, sizeof(sbuild_id));
- pr_debug("%s with build id %s not found", name, sbuild_id);
- } else
- pr_debug("Failed to open %s", name);
-diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c
-index a17c9b8a7a792d..72a71acf17c8c6 100644
---- a/tools/perf/util/probe-event.c
-+++ b/tools/perf/util/probe-event.c
-@@ -498,7 +498,7 @@ static struct debuginfo *open_from_debuginfod(struct dso *dso, struct nsinfo *ns
- if (!c)
- return NULL;
-
-- build_id__sprintf(dso__bid(dso), sbuild_id);
-+ build_id__snprintf(dso__bid(dso), sbuild_id, sizeof(sbuild_id));
- fd = debuginfod_find_debuginfo(c, (const unsigned char *)sbuild_id,
- 0, &path);
- if (fd >= 0)
-@@ -1071,7 +1071,7 @@ static int __show_line_range(struct line_range *lr, const char *module,
- }
- if (dinfo->build_id) {
- build_id__init(&bid, dinfo->build_id, BUILD_ID_SIZE);
-- build_id__sprintf(&bid, sbuild_id);
-+ build_id__snprintf(&bid, sbuild_id, sizeof(sbuild_id));
- }
- debuginfo__delete(dinfo);
- if (ret == 0 || ret == -ENOENT) {
-diff --git a/tools/perf/util/probe-file.c b/tools/perf/util/probe-file.c
-index 3d50de3217d50a..9ea0a838f59310 100644
---- a/tools/perf/util/probe-file.c
-+++ b/tools/perf/util/probe-file.c
-@@ -467,10 +467,10 @@ static int probe_cache__open(struct probe_cache *pcache, const char *target,
- if (!target || !strcmp(target, DSO__NAME_KALLSYMS)) {
- target = DSO__NAME_KALLSYMS;
- is_kallsyms = true;
-- ret = sysfs__sprintf_build_id("/", sbuildid);
-+ ret = sysfs__snprintf_build_id("/", sbuildid, sizeof(sbuildid));
- } else {
- nsinfo__mountns_enter(nsi, &nsc);
-- ret = filename__sprintf_build_id(target, sbuildid);
-+ ret = filename__snprintf_build_id(target, sbuildid, sizeof(sbuildid));
- nsinfo__mountns_exit(&nsc);
- }
-
-diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c
-index a30f88ed030044..cd6da777c16170 100644
---- a/tools/perf/util/probe-finder.c
-+++ b/tools/perf/util/probe-finder.c
-@@ -848,7 +848,7 @@ static int find_probe_point_lazy(Dwarf_Die *sp_die, struct probe_finder *pf)
- comp_dir = cu_get_comp_dir(&pf->cu_die);
- if (pf->dbg->build_id) {
- build_id__init(&bid, pf->dbg->build_id, BUILD_ID_SIZE);
-- build_id__sprintf(&bid, sbuild_id);
-+ build_id__snprintf(&bid, sbuild_id, sizeof(sbuild_id));
- }
- ret = find_source_path(pf->fname, sbuild_id, comp_dir, &fpath);
- if (ret < 0) {
-diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c
-index d7183134b66993..780f7e720849ea 100644
---- a/tools/perf/util/scripting-engines/trace-event-python.c
-+++ b/tools/perf/util/scripting-engines/trace-event-python.c
-@@ -795,14 +795,13 @@ static void set_sym_in_dict(PyObject *dict, struct addr_location *al,
- const char *dso_map_start, const char *dso_map_end,
- const char *sym_field, const char *symoff_field)
- {
-- char sbuild_id[SBUILD_ID_SIZE];
--
- if (al->map) {
-+ char sbuild_id[SBUILD_ID_SIZE];
- struct dso *dso = map__dso(al->map);
-
- pydict_set_item_string_decref(dict, dso_field,
- _PyUnicode_FromString(dso__name(dso)));
-- build_id__sprintf(dso__bid(dso), sbuild_id);
-+ build_id__snprintf(dso__bid(dso), sbuild_id, sizeof(sbuild_id));
- pydict_set_item_string_decref(dict, dso_bid_field,
- _PyUnicode_FromString(sbuild_id));
- pydict_set_item_string_decref(dict, dso_map_start,
-@@ -1250,7 +1249,7 @@ static int python_export_dso(struct db_export *dbe, struct dso *dso,
- char sbuild_id[SBUILD_ID_SIZE];
- PyObject *t;
-
-- build_id__sprintf(dso__bid(dso), sbuild_id);
-+ build_id__snprintf(dso__bid(dso), sbuild_id, sizeof(sbuild_id));
-
- t = tuple_new(5);
-
-diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
-index 249de806f8e0d3..b1b4a2d4b7e46c 100644
---- a/tools/perf/util/symbol.c
-+++ b/tools/perf/util/symbol.c
-@@ -2135,7 +2135,7 @@ static char *dso__find_kallsyms(struct dso *dso, struct map *map)
- goto proc_kallsyms;
- }
-
-- build_id__sprintf(dso__bid(dso), sbuild_id);
-+ build_id__snprintf(dso__bid(dso), sbuild_id, sizeof(sbuild_id));
-
- /* Find kallsyms in build-id cache with kcore */
- scnprintf(path, sizeof(path), "%s/%s/%s",
---
-2.53.0
-
+++ /dev/null
-From 0dc96cae063cbf9ebf6631b33b08e9ba02324248 Mon Sep 17 00:00:00 2001
-From: Ian Rogers <irogers@google.com>
-Date: Thu, 18 Sep 2025 10:24:16 -0700
-Subject: perf build-id: Ensure snprintf string is empty when size is 0
-
-From: Ian Rogers <irogers@google.com>
-
-commit 0dc96cae063cbf9ebf6631b33b08e9ba02324248 upstream.
-
-The string result of build_id__snprintf() is unconditionally used in
-places like dsos__fprintf_buildid_cb(). If the build id has size 0 then
-this creates a use of uninitialized memory. Add null termination for the
-size 0 case.
-
-A similar fix was written by Jiri Olsa in commit 6311951d4f8f28c4 ("perf
-tools: Initialize output buffer in build_id__sprintf") but lost in the
-transition to snprintf.
-
-Fixes: fccaaf6fbbc59910 ("perf build-id: Change sprintf functions to snprintf")
-Signed-off-by: Ian Rogers <irogers@google.com>
-Cc: Adrian Hunter <adrian.hunter@intel.com>
-Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
-Cc: Howard Chu <howardchu95@gmail.com>
-Cc: Ingo Molnar <mingo@redhat.com>
-Cc: James Clark <james.clark@linaro.org>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Kan Liang <kan.liang@linux.intel.com>
-Cc: Mark Rutland <mark.rutland@arm.com>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Cc: Peter Zijlstra <peterz@infradead.org>
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
----
- tools/perf/util/build-id.c | 7 +++++++
- 1 file changed, 7 insertions(+)
-
---- a/tools/perf/util/build-id.c
-+++ b/tools/perf/util/build-id.c
-@@ -71,6 +71,13 @@ int build_id__snprintf(const struct buil
- {
- size_t offs = 0;
-
-+ if (build_id->size == 0) {
-+ /* Ensure bf is always \0 terminated. */
-+ if (bf_size > 0)
-+ bf[0] = '\0';
-+ return 0;
-+ }
-+
- for (size_t i = 0; i < build_id->size && offs < bf_size; ++i)
- offs += snprintf(bf + offs, bf_size - offs, "%02x", build_id->data[i]);
-
+++ /dev/null
-From 017bca78e4d72b1ff027d368c20a1b2c654edaf7 Mon Sep 17 00:00:00 2001
-From: Michael Petlan <mpetlan@redhat.com>
-Date: Wed, 20 May 2026 00:38:55 +0200
-Subject: perf build-id: Fix off-by-one bug when printing kernel/module build-id
-
-From: Michael Petlan <mpetlan@redhat.com>
-
-commit 017bca78e4d72b1ff027d368c20a1b2c654edaf7 upstream.
-
-When changing sprintf functions to snprintf, one byte got lost. Since
-snprintf ones do not handle the '\0' terminating character, the number
-of printed characters is 40, while sizeof(sbuild_id) is 41, including
-the terminating '\0' character.
-
-This makes the later check fail so that nothing is printed.
-
-Fix that.
-
-Before:
-
- [Michael@Carbon ~]$ perf buildid-list -k
- [Michael@Carbon ~]$
-
-After:
-
- [Michael@Carbon ~]$ perf buildid-list -k
- a527806324d543c4bc3ff2f9c9519d494fed5f68
- [Michael@Carbon ~]$
-
-Fixes: fccaaf6fbbc59910 ("perf build-id: Change sprintf functions to snprintf")
-Signed-off-by: Michael Petlan <mpetlan@redhat.com>
-Tested-by: Ian Rogers <irogers@google.com>
-Cc: Ian Rogers <irogers@google.com>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
----
- tools/perf/builtin-buildid-list.c | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
---- a/tools/perf/builtin-buildid-list.c
-+++ b/tools/perf/builtin-buildid-list.c
-@@ -58,7 +58,7 @@ static int sysfs__fprintf_build_id(FILE
- int ret;
-
- ret = sysfs__snprintf_build_id("/", sbuild_id, sizeof(sbuild_id));
-- if (ret != sizeof(sbuild_id))
-+ if (ret + 1 != sizeof(sbuild_id))
- return ret < 0 ? ret : -EINVAL;
-
- return fprintf(fp, "%s\n", sbuild_id);
-@@ -70,7 +70,7 @@ static int filename__fprintf_build_id(co
- int ret;
-
- ret = filename__snprintf_build_id(name, sbuild_id, sizeof(sbuild_id));
-- if (ret != sizeof(sbuild_id))
-+ if (ret + 1 != sizeof(sbuild_id))
- return ret < 0 ? ret : -EINVAL;
-
- return fprintf(fp, "%s\n", sbuild_id);
+++ /dev/null
-From 7f9eb9343a7e7e5e0c62b1a3a1e3299d46cc8b55 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Fri, 5 Jun 2026 11:05:13 -0300
-Subject: perf c2c: Bounds-check CPU and node IDs before bitmap and array
- access
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 65117c3da50f749f4c22eb7a7effc53453dff57f ]
-
-c2c_he__set_cpu() passes sample->cpu directly to __set_bit(cpu, cpuset)
-after only checking for the (u32)-1 sentinel. The cpuset bitmap is
-allocated with c2c.cpus_cnt bits (from env->nr_cpus_avail), so a crafted
-perf.data with CPU IDs exceeding that count causes out-of-bounds heap
-writes.
-
-c2c_he__set_node() similarly passes the node ID from mem2node__node()
-to __set_bit(node, nodeset) after only checking for negative values.
-The nodeset bitmap is sized to c2c.nodes_cnt (from env->nr_numa_nodes),
-so a node ID exceeding that causes OOB writes.
-
-process_sample_event() indexes c2c.cpu2node[cpu] and
-c2c_he->node_stats[node] without bounds checking. Both arrays are
-sized to c2c.cpus_cnt and c2c.nodes_cnt respectively.
-
-Add bounds checks in all three paths:
- - c2c_he__set_cpu(): return if sample->cpu >= c2c.cpus_cnt
- - c2c_he__set_node(): return if node >= c2c.nodes_cnt
- - process_sample_event(): clamp cpu to 0 if >= cpus_cnt,
- guard node_stats access with bounds check
-
-Fixes: 1e181b92a2da ("perf c2c report: Add 'node' sort key")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-c2c.c | 19 +++++++++++++++++--
- 1 file changed, 17 insertions(+), 2 deletions(-)
-
-diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
-index 15e1fce71c7225..8dd371cb94ca5f 100644
---- a/tools/perf/builtin-c2c.c
-+++ b/tools/perf/builtin-c2c.c
-@@ -230,6 +230,10 @@ static void c2c_he__set_cpu(struct c2c_hist_entry *c2c_he,
- "WARNING: no sample cpu value"))
- return;
-
-+ /* cpuset bitmap has c2c.cpus_cnt bits from env->nr_cpus_avail */
-+ if (sample->cpu >= (unsigned int)c2c.cpus_cnt)
-+ return;
-+
- __set_bit(sample->cpu, c2c_he->cpuset);
- }
-
-@@ -247,6 +251,10 @@ static void c2c_he__set_node(struct c2c_hist_entry *c2c_he,
- if (WARN_ONCE(node < 0, "WARNING: failed to find node\n"))
- return;
-
-+ /* nodeset bitmap has c2c.nodes_cnt bits from env->nr_numa_nodes */
-+ if (node >= c2c.nodes_cnt)
-+ return;
-+
- __set_bit(node, c2c_he->nodeset);
-
- if (c2c_he->paddr != sample->phys_addr) {
-@@ -346,7 +354,12 @@ static int process_sample_event(const struct perf_tool *tool __maybe_unused,
- * Doing node stats only for single callchain data.
- */
- int cpu = sample->cpu == (unsigned int) -1 ? 0 : sample->cpu;
-- int node = c2c.cpu2node[cpu];
-+ int node;
-+
-+ /* cpu2node[] has c2c.cpus_cnt entries; large u32 wraps signed negative */
-+ if (cpu < 0 || cpu >= c2c.cpus_cnt)
-+ cpu = 0;
-+ node = c2c.cpu2node[cpu];
-
- mi = mi_dup;
-
-@@ -363,7 +376,9 @@ static int process_sample_event(const struct perf_tool *tool __maybe_unused,
- c2c_he = container_of(he, struct c2c_hist_entry, he);
- c2c_add_stats(&c2c_he->stats, &stats);
- c2c_add_stats(&c2c_hists->stats, &stats);
-- c2c_add_stats(&c2c_he->node_stats[node], &stats);
-+ /* node_stats[] has c2c.nodes_cnt entries */
-+ if (node >= 0 && node < c2c.nodes_cnt)
-+ c2c_add_stats(&c2c_he->node_stats[node], &stats);
-
- compute_stats(c2c_he, &stats, sample->weight);
-
---
-2.53.0
-
+++ /dev/null
-From 516cd92913df6b15f4920ae05f05b51f761786a0 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Fri, 5 Jun 2026 11:06:30 -0300
-Subject: perf c2c: Bounds-check CPU IDs in setup_nodes() topology loop
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 5fb2e6ad8c5d6b3b380f94c7456595511f3731be ]
-
-setup_nodes() iterates CPU maps from the perf.data topology header and
-uses cpu.cpu directly as an array index into cpu2node[] (allocated with
-c2c.cpus_cnt = env->nr_cpus_avail entries) and __set_bit(cpu.cpu, set)
-(bitmap also sized to c2c.cpus_cnt).
-
-A crafted perf.data with topology CPU IDs exceeding nr_cpus_avail causes
-out-of-bounds heap writes into both the cpu2node array and the per-node
-bitmap.
-
-Add a bounds check to skip CPU IDs that fall outside the valid range.
-
-Fixes: 1e181b92a2da ("perf c2c report: Add 'node' sort key")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-c2c.c | 4 ++++
- 1 file changed, 4 insertions(+)
-
-diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
-index 8dd371cb94ca5f..9b1482786d81cd 100644
---- a/tools/perf/builtin-c2c.c
-+++ b/tools/perf/builtin-c2c.c
-@@ -2318,6 +2318,10 @@ static int setup_nodes(struct perf_session *session)
- nodes[node] = set;
-
- perf_cpu_map__for_each_cpu_skip_any(cpu, idx, map) {
-+ /* topology CPU IDs from perf.data may exceed nr_cpus_avail */
-+ if (cpu.cpu < 0 || cpu.cpu >= c2c.cpus_cnt)
-+ continue;
-+
- __set_bit(cpu.cpu, set);
-
- if (WARN_ONCE(cpu2node[cpu.cpu] != -1, "node/cpu topology bug"))
---
-2.53.0
-
+++ /dev/null
-From 108a7a9016771f2dd391beece48df21d5761d3ec Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 6 Jun 2026 11:19:10 -0300
-Subject: perf c2c: Fix use-after-free in he__get_c2c_hists() error path
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 5e5e6196d737c5be03d20647428316b36621608d ]
-
-he__get_c2c_hists() assigns c2c_he->hists before calling
-c2c_hists__init(). If init fails, the error path calls free(hists)
-but leaves c2c_he->hists pointing to freed memory. On teardown,
-c2c_he_free() finds the non-NULL pointer and calls
-hists__delete_entries() on it, causing a use-after-free.
-
-Set c2c_he->hists to NULL before freeing so teardown skips the
-already-freed allocation.
-
-Fixes: b2252ae67b687d2b ("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:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- 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 9b1482786d81cd..b02b41059730f9 100644
---- a/tools/perf/builtin-c2c.c
-+++ b/tools/perf/builtin-c2c.c
-@@ -216,6 +216,7 @@ he__get_c2c_hists(struct hist_entry *he,
-
- ret = c2c_hists__init(hists, sort, nr_header_lines);
- if (ret) {
-+ c2c_he->hists = NULL;
- free(hists);
- return NULL;
- }
---
-2.53.0
-
+++ /dev/null
-From ae15db3e9b639491007cc1e9e99638e4b6091781 Mon Sep 17 00:00:00 2001
-From: Thomas Richter <tmricht@linux.ibm.com>
-Date: Tue, 14 Apr 2026 14:42:41 +0200
-Subject: perf callchain: Handle multiple address spaces
-
-From: Thomas Richter <tmricht@linux.ibm.com>
-
-commit ae15db3e9b639491007cc1e9e99638e4b6091781 upstream.
-
-perf test 'perf inject to convert DWARF callchains to regular ones'
-fails on s390. It was introduced with commit 92ea788d2af4e65a ("perf
-inject: Add --convert-callchain option")
-
-The failure comes the difference in output. Without the inject script to
-convert DWARF the callchains is:
-
- # perf record -F 999 --call-graph dwarf -- perf test -w noploop
- # perf report -i perf.data --stdio --no-children -q \
- --percent-limit=1 > /tmp/111
- # cat /tmp/111
- 99.30% perf-noploop perf [.] noploop
- |
- ---noploop
- run_workload (inlined)
- cmd_test
- run_builtin (inlined)
- handle_internal_command
- run_argv (inlined)
- main
- __libc_start_call_main
- __libc_start_main_impl (inlined)
- _start
- #
-
-With the inject script step the output is:
-
- # perf inject -i perf.data --convert-callchain -o /tmp/perf-inject-1.out
- # perf report -i /tmp/perf-inject-1.out --stdio --no-children -q \
- --percent-limit=1 > /tmp/222
- # cat /tmp/222
- 99.40% perf-noploop perf [.] noploop
- |
- ---noploop
- run_workload (inlined)
- cmd_test
- run_builtin (inlined)
- handle_internal_command
- run_argv (inlined)
- main
- _start
- # diff /tmp/111 /tmp/222
- 1c1
- < 99.30% perf-noploop perf [.] noploop
- ---
- > 99.40% perf-noploop perf [.] noploop
- 10,11d9
- < __libc_start_call_main
- < __libc_start_main_impl (inlined)
- #
-
-The difference are the symbols __libc_start_call_main and
-__libc_start_main_impl.
-
-On x86_64, kernel and user space share a single virtual address space,
-with the kernel mapped to the upper end of memory. The instruction
-pointer value alone is sufficient to distinguish between user space and
-kernel space addresses.
-
-This is not true for s390, which uses separate address spaces for user
-and kernel.
-
-The same virtual address can be valid in both address spaces, so the
-instruction pointer value alone cannot determine whether an address
-belongs to the kernel or user space.
-
-Instead, perf must rely on the cpumode metadata derived from the
-processor status word (PSW) at sample time.
-
-In function perf_event__convert_sample_callchain() the first part
-copies a kernel callchain and context entries, if any.
-
-It then appends additional entries ignoring the address space
-architecture. Taking that into account, the symbols at addresses
-
- 0x3ff970348cb __libc_start_call_main
- 0x3ff970349c5 __libc_start_main_impl
-
-(located after the kernel address space on s390) are now included.
-
-Output before:
-
- # perf test 83
- 83: perf inject to convert DWARF callchains to regular ones : FAILED!
-
-Output after:
- # perf test 83
- 83: perf inject to convert DWARF callchains to regular ones : Ok
-
-Question to Namhyung:
-
-In function perf_event__convert_sample_callchain() just before the
-for() loop this patch modifies, the kernel callchain is copied,
-see this comment and the next 5 lines:
-
- /* copy kernel callchain and context entries */
-
-Then why is machine__kernel_ip() needed in the for() loop, when
-the kernel entries have been copied just before the loop?
-
-Note: This patch was tested on x86_64 virtual machine and succeeded.
-
-Fixes: 92ea788d2af4e65a ("perf inject: Add --convert-callchain option")
-Signed-off-by: Thomas Richter <tmricht@linux.ibm.com>
-Acked-by: Namhyung Kim <namhyung@kernel.org>
-Cc: Alexander Gordeev <agordeev@linux.ibm.com>
-Cc: Heiko Carstens <hca@linux.ibm.com>
-Cc: Jan Polensky <japo@linux.ibm.com>
-Cc: linux-s390@vger.kernel.org
-Cc: Sumanth Korikkar <sumanthk@linux.ibm.com>
-Cc: Vasily Gorbik <gor@linux.ibm.com>
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
----
- tools/perf/arch/common.c | 4 +++-
- tools/perf/builtin-inject.c | 3 ++-
- 2 files changed, 5 insertions(+), 2 deletions(-)
-
---- a/tools/perf/arch/common.c
-+++ b/tools/perf/arch/common.c
-@@ -237,5 +237,7 @@ int perf_env__lookup_objdump(struct perf
- */
- bool perf_env__single_address_space(struct perf_env *env)
- {
-- return strcmp(perf_env__arch(env), "sparc");
-+ const char *arch = perf_env__arch(env);
-+
-+ return strcmp(arch, "s390") && strcmp(arch, "sparc");
- }
---- a/tools/perf/builtin-inject.c
-+++ b/tools/perf/builtin-inject.c
-@@ -452,7 +452,8 @@ static int perf_event__convert_sample_ca
-
- node = cursor->first;
- for (k = 0; k < cursor->nr && i < PERF_MAX_STACK_DEPTH; k++) {
-- if (machine__kernel_ip(machine, node->ip))
-+ if (machine->single_address_space &&
-+ machine__kernel_ip(machine, node->ip))
- /* kernel IPs were added already */;
- else if (node->ms.sym && node->ms.sym->inlined)
- /* we can't handle inlined callchains */;
+++ /dev/null
-From c2b8092feb71592b928681be3b68c7626c07a38e Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 13 Jun 2026 14:55:32 -0300
-Subject: perf cs-etm: Bounds-check CPU in cs_etm__get_queue()
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 9a989e60cc6e29d98aed2087425cba53bf4b392d ]
-
-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:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/cs-etm.c | 25 +++++++++++++++++++++++--
- 1 file changed, 23 insertions(+), 2 deletions(-)
-
---- 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_
- {
- 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(struc
- * 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(struc
- 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
- 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
- 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
- }
-
- 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;
-@@ -3128,6 +3146,9 @@ static int cs_etm__queue_aux_fragment(st
- aux_offset + aux_size <= auxtrace_event->offset + auxtrace_event->size) {
- struct cs_etm_queue *etmq = etm->queues.queue_array[auxtrace_event->idx].priv;
-
-+ 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.
+++ /dev/null
-From 05f90ec3fd0040a17a2967ff06eb10a9cc543b8f Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Tue, 9 Jun 2026 15:40:06 +0100
-Subject: perf cs-etm: Queue context packets for frontend
-
-From: James Clark <james.clark@linaro.org>
-
-[ Upstream commit 68ca50bc0fa64841cd73b8a1538df1d7f7eb4108 ]
-
-PE_CONTEXT elements update the context ID and exception level, but the
-decoder may still have prior packets cached for frontend processing.
-Updating the context immediately in the decoder backend can make those
-cached packets get consumed with the wrong thread or EL state.
-
-Add a CS_ETM_CONTEXT packet carrying the TID and EL to the frontend,
-this keeps context changes ordered with the rest of the packet stream
-and avoids mismatches when synthesizing samples from cached packets.
-
-Separate the memory access function into one for the frontend and one
-for decoding. The frontend also needs memory access to attach the
-instruction to samples. Because the frontend does memory access for
-both previous and current packets, change all the frontend memory access
-function signatures to take both a tidq and packet. But backend always
-uses the current backend EL and thread from the tidq.
-
-Treat context packets as a boundary for branch sample generation and
-remove tidq->prev_packet_thread because it's not possible to branch to a
-different thread, so only tracking the current thread is required for
-sample generation.
-
-Fixes: e573e978fb12e160 ("perf cs-etm: Inject capabilitity for CoreSight traces")
-Reported-by: Amir Ayupov <aaupov@meta.com>
-Closes: https://lore.kernel.org/linux-perf-users/20260515021135.1729028-1-aaupov@meta.com/
-Co-authored-by: James Clark <james.clark@linaro.org>
-Signed-off-by: Leo Yan <leo.yan@arm.com>
-Cc: Ian Rogers <irogers@google.com>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Jonathan Corbet <corbet@lwn.net>
-Cc: linux-doc@vger.kernel.org
-Cc: Mike Leach <mike.leach@arm.com>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Cc: Paschalis Mpeis <Paschalis.Mpeis@arm.com>
-Cc: Robert Walker <robert.walker@arm.com>
-Cc: Shuah Khan <skhan@linuxfoundation.org>
-Cc: Suzuki Poulouse <suzuki.poulose@arm.com>
-Signed-off-by: James Clark <james.clark@linaro.org>
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/cs-etm-decoder/cs-etm-decoder.c | 21 +-
- tools/perf/util/cs-etm.c | 236 ++++++++++++++----------
- tools/perf/util/cs-etm.h | 8
- 3 files changed, 163 insertions(+), 102 deletions(-)
-
---- a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
-+++ b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c
-@@ -399,6 +399,8 @@ cs_etm_decoder__buffer_packet(struct cs_
- packet_queue->packet_buffer[et].flags = 0;
- packet_queue->packet_buffer[et].exception_number = UINT32_MAX;
- packet_queue->packet_buffer[et].trace_chan_id = trace_chan_id;
-+ packet_queue->packet_buffer[et].el = ocsd_EL_unknown;
-+ packet_queue->packet_buffer[et].tid = -1;
-
- if (packet_queue->packet_count == CS_ETM_PACKET_MAX_BUFFER - 1)
- return OCSD_RESP_WAIT;
-@@ -446,6 +448,7 @@ cs_etm_decoder__buffer_range(struct cs_e
- packet->last_instr_type = elem->last_i_type;
- packet->last_instr_subtype = elem->last_i_subtype;
- packet->last_instr_cond = elem->last_instr_cond;
-+ packet->el = elem->context.exception_level;
-
- if (elem->last_i_type == OCSD_INSTR_BR || elem->last_i_type == OCSD_INSTR_BR_INDIRECT)
- packet->last_instr_taken_branch = elem->last_instr_exec;
-@@ -522,7 +525,9 @@ cs_etm_decoder__set_tid(struct cs_etm_qu
- const ocsd_generic_trace_elem *elem,
- const uint8_t trace_chan_id)
- {
-+ struct cs_etm_packet *packet;
- pid_t tid = -1;
-+ int ret;
-
- /*
- * Process the PE_CONTEXT packets if we have a valid contextID or VMID.
-@@ -543,12 +548,18 @@ cs_etm_decoder__set_tid(struct cs_etm_qu
- break;
- }
-
-- if (cs_etm__etmq_set_tid_el(etmq, tid, trace_chan_id,
-- elem->context.exception_level))
-+ if (cs_etm__etmq_update_decode_context(etmq, trace_chan_id,
-+ elem->context.exception_level, tid))
- return OCSD_RESP_FATAL_SYS_ERR;
-
-- if (tid == -1)
-- return OCSD_RESP_CONT;
-+ ret = cs_etm_decoder__buffer_packet(etmq, packet_queue, trace_chan_id,
-+ CS_ETM_CONTEXT);
-+ if (ret != OCSD_RESP_CONT && ret != OCSD_RESP_WAIT)
-+ return ret;
-+
-+ packet = &packet_queue->packet_buffer[packet_queue->tail];
-+ packet->tid = tid;
-+ packet->el = elem->context.exception_level;
-
- /*
- * A timestamp is generated after a PE_CONTEXT element so make sure
-@@ -556,7 +567,7 @@ cs_etm_decoder__set_tid(struct cs_etm_qu
- */
- cs_etm_decoder__reset_timestamp(packet_queue);
-
-- return OCSD_RESP_CONT;
-+ return ret;
- }
-
- static ocsd_datapath_resp_t cs_etm_decoder__gen_trace_elem_printer(
---- a/tools/perf/util/cs-etm.c
-+++ b/tools/perf/util/cs-etm.c
-@@ -86,15 +86,22 @@ struct cs_etm_traceid_queue {
- u64 period_instructions;
- size_t last_branch_pos;
- union perf_event *event_buf;
-- struct thread *thread;
-- struct thread *prev_packet_thread;
-- ocsd_ex_level prev_packet_el;
-- ocsd_ex_level el;
- struct branch_stack *last_branch;
- struct branch_stack *last_branch_rb;
- struct cs_etm_packet *prev_packet;
- struct cs_etm_packet *packet;
- struct cs_etm_packet_queue packet_queue;
-+
-+ struct thread *decode_thread;
-+ ocsd_ex_level decode_el;
-+
-+ /*
-+ * The frontend accesses the EL from '[prev_]packet' because it needs
-+ * previous EL for branch and current EL for instruction samples. It's
-+ * not possible to change thread in a single branch sample so no need to
-+ * store or access the thread through the packet.
-+ */
-+ struct thread *frontend_thread;
- };
-
- enum cs_etm_format {
-@@ -608,10 +615,11 @@ static int cs_etm__init_traceid_queue(st
-
- queue = &etmq->etm->queues.queue_array[etmq->queue_nr];
- tidq->trace_chan_id = trace_chan_id;
-- tidq->el = tidq->prev_packet_el = ocsd_EL_unknown;
-- tidq->thread = machine__findnew_thread(&etm->session->machines.host, -1,
-+ tidq->decode_el = ocsd_EL_unknown;
-+ tidq->frontend_thread = machine__findnew_thread(&etm->session->machines.host, -1,
-+ queue->tid);
-+ tidq->decode_thread = machine__findnew_thread(&etm->session->machines.host, -1,
- queue->tid);
-- tidq->prev_packet_thread = machine__idle_thread(&etm->session->machines.host);
-
- tidq->packet = zalloc(sizeof(struct cs_etm_packet));
- if (!tidq->packet)
-@@ -744,21 +752,10 @@ static void cs_etm__packet_swap(struct c
- /*
- * Swap PACKET with PREV_PACKET: PACKET becomes PREV_PACKET for
- * the next incoming packet.
-- *
-- * Threads and exception levels are also tracked for both the
-- * previous and current packets. This is because the previous
-- * packet is used for the 'from' IP for branch samples, so the
-- * thread at that time must also be assigned to that sample.
-- * Across discontinuity packets the thread can change, so by
-- * tracking the thread for the previous packet the branch sample
-- * will have the correct info.
- */
- tmp = tidq->packet;
- tidq->packet = tidq->prev_packet;
- tidq->prev_packet = tmp;
-- tidq->prev_packet_el = tidq->el;
-- thread__put(tidq->prev_packet_thread);
-- tidq->prev_packet_thread = thread__get(tidq->thread);
- }
- }
-
-@@ -931,8 +928,8 @@ static void cs_etm__free_traceid_queues(
-
- /* Free this traceid_queue from the array */
- tidq = etmq->traceid_queues[idx];
-- thread__zput(tidq->thread);
-- thread__zput(tidq->prev_packet_thread);
-+ thread__zput(tidq->frontend_thread);
-+ thread__zput(tidq->decode_thread);
- zfree(&tidq->event_buf);
- zfree(&tidq->last_branch);
- zfree(&tidq->last_branch_rb);
-@@ -1077,47 +1074,43 @@ static u8 cs_etm__cpu_mode(struct cs_etm
- }
- }
-
--static u32 cs_etm__mem_access(struct cs_etm_queue *etmq, u8 trace_chan_id,
-- u64 address, size_t size, u8 *buffer,
-- const ocsd_mem_space_acc_t mem_space)
-+static u32 __cs_etm__mem_access(struct cs_etm_queue *etmq,
-+ u64 address, size_t size, u8 *buffer,
-+ const ocsd_mem_space_acc_t mem_space,
-+ ocsd_ex_level el, struct thread *thread)
- {
- u8 cpumode;
- u64 offset;
- int len;
- struct addr_location al;
- struct dso *dso;
-- struct cs_etm_traceid_queue *tidq;
- int ret = 0;
-
- if (!etmq)
- return 0;
-
- addr_location__init(&al);
-- tidq = cs_etm__etmq_get_traceid_queue(etmq, trace_chan_id);
-- if (!tidq)
-- goto out;
-
- /*
-- * We've already tracked EL along side the PID in cs_etm__set_thread()
-- * so double check that it matches what OpenCSD thinks as well. It
-- * doesn't distinguish between EL0 and EL1 for this mem access callback
-- * so we had to do the extra tracking. Skip validation if it's any of
-- * the 'any' values.
-+ * We track EL for the frontend and the backend when receiving context
-+ * and range packets. OpenCSD doesn't distinguish between EL0 and EL1
-+ * for this mem access callback so we had to do the extra tracking. Skip
-+ * validation if it's any of the 'any' values.
- */
- if (!(mem_space == OCSD_MEM_SPACE_ANY ||
- mem_space == OCSD_MEM_SPACE_N || mem_space == OCSD_MEM_SPACE_S)) {
- if (mem_space & OCSD_MEM_SPACE_EL1N) {
- /* Includes both non secure EL1 and EL0 */
-- assert(tidq->el == ocsd_EL1 || tidq->el == ocsd_EL0);
-+ assert(el == ocsd_EL1 || el == ocsd_EL0);
- } else if (mem_space & OCSD_MEM_SPACE_EL2)
-- assert(tidq->el == ocsd_EL2);
-+ assert(el == ocsd_EL2);
- else if (mem_space & OCSD_MEM_SPACE_EL3)
-- assert(tidq->el == ocsd_EL3);
-+ assert(el == ocsd_EL3);
- }
-
-- cpumode = cs_etm__cpu_mode(etmq, address, tidq->el);
-+ cpumode = cs_etm__cpu_mode(etmq, address, el);
-
-- if (!thread__find_map(tidq->thread, cpumode, address, &al))
-+ if (!thread__find_map(thread, cpumode, address, &al))
- goto out;
-
- dso = map__dso(al.map);
-@@ -1132,7 +1125,7 @@ static u32 cs_etm__mem_access(struct cs_
-
- map__load(al.map);
-
-- len = dso__data_read_offset(dso, maps__machine(thread__maps(tidq->thread)),
-+ len = dso__data_read_offset(dso, maps__machine(thread__maps(thread)),
- offset, buffer, size);
-
- if (len <= 0) {
-@@ -1152,6 +1145,30 @@ out:
- return ret;
- }
-
-+static u32 cs_etm__frontend_mem_access(struct cs_etm_queue *etmq,
-+ struct cs_etm_traceid_queue *tidq,
-+ struct cs_etm_packet *packet,
-+ u64 address, size_t size, u8 *buffer)
-+{
-+ return __cs_etm__mem_access(etmq, address, size, buffer, 0, packet->el,
-+ tidq->frontend_thread);
-+}
-+
-+static u32 cs_etm__decoder_mem_access(struct cs_etm_queue *etmq, u8 trace_chan_id,
-+ u64 address, size_t size, u8 *buffer,
-+ const ocsd_mem_space_acc_t mem_space)
-+{
-+ struct cs_etm_traceid_queue *tidq;
-+
-+ tidq = cs_etm__etmq_get_traceid_queue(etmq, trace_chan_id);
-+ if (!tidq)
-+ return 0;
-+
-+ return __cs_etm__mem_access(etmq, address, size, buffer,
-+ mem_space, tidq->decode_el,
-+ tidq->decode_thread);
-+}
-+
- static struct cs_etm_queue *cs_etm__alloc_queue(void)
- {
- struct cs_etm_queue *etmq = zalloc(sizeof(*etmq));
-@@ -1327,12 +1344,13 @@ void cs_etm__reset_last_branch_rb(struct
- }
-
- static inline int cs_etm__t32_instr_size(struct cs_etm_queue *etmq,
-- u8 trace_chan_id, u64 addr)
-+ struct cs_etm_traceid_queue *tidq,
-+ struct cs_etm_packet *packet, u64 addr)
- {
- u8 instrBytes[2];
-
-- cs_etm__mem_access(etmq, trace_chan_id, addr, ARRAY_SIZE(instrBytes),
-- instrBytes, 0);
-+ cs_etm__frontend_mem_access(etmq, tidq, packet, addr,
-+ ARRAY_SIZE(instrBytes), instrBytes);
- /*
- * T32 instruction size is indicated by bits[15:11] of the first
- * 16-bit word of the instruction: 0b11101, 0b11110 and 0b11111
-@@ -1365,16 +1383,16 @@ u64 cs_etm__last_executed_instr(const st
- }
-
- static inline u64 cs_etm__instr_addr(struct cs_etm_queue *etmq,
-- u64 trace_chan_id,
-- const struct cs_etm_packet *packet,
-+ struct cs_etm_traceid_queue *tidq,
-+ struct cs_etm_packet *packet,
- u64 offset)
- {
- if (packet->isa == CS_ETM_ISA_T32) {
- u64 addr = packet->start_addr;
-
- while (offset) {
-- addr += cs_etm__t32_instr_size(etmq,
-- trace_chan_id, addr);
-+ addr += cs_etm__t32_instr_size(etmq, tidq, packet,
-+ addr);
- offset--;
- }
- return addr;
-@@ -1466,34 +1484,51 @@ cs_etm__get_trace(struct cs_etm_queue *e
- return etmq->buf_len;
- }
-
--static void cs_etm__set_thread(struct cs_etm_queue *etmq,
-- struct cs_etm_traceid_queue *tidq, pid_t tid,
-- ocsd_ex_level el)
-+/*
-+ * Convert a raw thread number to a thread struct and assign it to **thread.
-+ */
-+static int cs_etm__etmq_update_thread(struct cs_etm_queue *etmq,
-+ ocsd_ex_level el, pid_t tid,
-+ struct thread **thread)
- {
- struct machine *machine = cs_etm__get_machine(etmq, el);
-
-+ if (!machine || !*thread)
-+ return -EINVAL;
-+
- if (tid != -1) {
-- thread__zput(tidq->thread);
-- tidq->thread = machine__find_thread(machine, -1, tid);
-+ thread__zput(*thread);
-+ *thread = machine__find_thread(machine, -1, tid);
- }
-
- /* Couldn't find a known thread */
-- if (!tidq->thread)
-- tidq->thread = machine__idle_thread(machine);
-+ if (!*thread)
-+ *thread = machine__idle_thread(machine);
-
-- tidq->el = el;
-+ return 0;
- }
-
--int cs_etm__etmq_set_tid_el(struct cs_etm_queue *etmq, pid_t tid,
-- u8 trace_chan_id, ocsd_ex_level el)
-+/*
-+ * Set the thread and EL of the decode context which is ahead in time of the
-+ * frontend context.
-+ */
-+int cs_etm__etmq_update_decode_context(struct cs_etm_queue *etmq,
-+ u8 trace_chan_id,
-+ ocsd_ex_level el, pid_t tid)
- {
- struct cs_etm_traceid_queue *tidq;
-+ int ret;
-
- tidq = cs_etm__etmq_get_traceid_queue(etmq, trace_chan_id);
- if (!tidq)
- return -EINVAL;
-
-- cs_etm__set_thread(etmq, tidq, tid, el);
-+ ret = cs_etm__etmq_update_thread(etmq, el, tid,
-+ &tidq->decode_thread);
-+ if (ret)
-+ return ret;
-+
-+ tidq->decode_el = el;
- return 0;
- }
-
-@@ -1503,8 +1538,8 @@ bool cs_etm__etmq_is_timeless(struct cs_
- }
-
- static void cs_etm__copy_insn(struct cs_etm_queue *etmq,
-- u64 trace_chan_id,
-- const struct cs_etm_packet *packet,
-+ struct cs_etm_traceid_queue *tidq,
-+ struct cs_etm_packet *packet,
- struct perf_sample *sample)
- {
- /*
-@@ -1521,14 +1556,14 @@ static void cs_etm__copy_insn(struct cs_
- * cs_etm__t32_instr_size().
- */
- if (packet->isa == CS_ETM_ISA_T32)
-- sample->insn_len = cs_etm__t32_instr_size(etmq, trace_chan_id,
-+ sample->insn_len = cs_etm__t32_instr_size(etmq, tidq, packet,
- sample->ip);
- /* Otherwise, A64 and A32 instruction size are always 32-bit. */
- else
- sample->insn_len = 4;
-
-- cs_etm__mem_access(etmq, trace_chan_id, sample->ip, sample->insn_len,
-- (void *)sample->insn, 0);
-+ cs_etm__frontend_mem_access(etmq, tidq, packet, sample->ip,
-+ sample->insn_len, (void *)sample->insn);
- }
-
- u64 cs_etm__convert_sample_time(struct cs_etm_queue *etmq, u64 cs_timestamp)
-@@ -1555,6 +1590,7 @@ static inline u64 cs_etm__resolve_sample
-
- static int cs_etm__synth_instruction_sample(struct cs_etm_queue *etmq,
- struct cs_etm_traceid_queue *tidq,
-+ struct cs_etm_packet *packet,
- u64 addr, u64 period)
- {
- int ret = 0;
-@@ -1563,23 +1599,23 @@ static int cs_etm__synth_instruction_sam
- struct perf_sample sample = {.ip = 0,};
-
- event->sample.header.type = PERF_RECORD_SAMPLE;
-- event->sample.header.misc = cs_etm__cpu_mode(etmq, addr, tidq->el);
-+ event->sample.header.misc = cs_etm__cpu_mode(etmq, addr, packet->el);
- event->sample.header.size = sizeof(struct perf_event_header);
-
- /* Set time field based on etm auxtrace config. */
- sample.time = cs_etm__resolve_sample_time(etmq, tidq);
-
- sample.ip = addr;
-- sample.pid = thread__pid(tidq->thread);
-- sample.tid = thread__tid(tidq->thread);
-+ sample.pid = thread__pid(tidq->frontend_thread);
-+ sample.tid = thread__tid(tidq->frontend_thread);
- sample.id = etmq->etm->instructions_id;
- sample.stream_id = etmq->etm->instructions_id;
- sample.period = period;
-- sample.cpu = tidq->packet->cpu;
-+ sample.cpu = packet->cpu;
- sample.flags = tidq->prev_packet->flags;
- sample.cpumode = event->sample.header.misc;
-
-- cs_etm__copy_insn(etmq, tidq->trace_chan_id, tidq->packet, &sample);
-+ cs_etm__copy_insn(etmq, tidq, packet, &sample);
-
- if (etm->synth_opts.last_branch)
- sample.branch_stack = tidq->last_branch;
-@@ -1623,15 +1659,15 @@ static int cs_etm__synth_branch_sample(s
-
- event->sample.header.type = PERF_RECORD_SAMPLE;
- event->sample.header.misc = cs_etm__cpu_mode(etmq, ip,
-- tidq->prev_packet_el);
-+ tidq->prev_packet->el);
- event->sample.header.size = sizeof(struct perf_event_header);
-
- /* Set time field based on etm auxtrace config. */
- sample.time = cs_etm__resolve_sample_time(etmq, tidq);
-
- sample.ip = ip;
-- sample.pid = thread__pid(tidq->prev_packet_thread);
-- sample.tid = thread__tid(tidq->prev_packet_thread);
-+ sample.pid = thread__pid(tidq->frontend_thread);
-+ sample.tid = thread__tid(tidq->frontend_thread);
- sample.addr = cs_etm__first_executed_instr(tidq->packet);
- sample.id = etmq->etm->branches_id;
- sample.stream_id = etmq->etm->branches_id;
-@@ -1640,8 +1676,7 @@ static int cs_etm__synth_branch_sample(s
- sample.flags = tidq->prev_packet->flags;
- sample.cpumode = event->sample.header.misc;
-
-- cs_etm__copy_insn(etmq, tidq->trace_chan_id, tidq->prev_packet,
-- &sample);
-+ cs_etm__copy_insn(etmq, tidq, tidq->prev_packet, &sample);
-
- /*
- * perf report cannot handle events without a branch stack
-@@ -1765,7 +1800,6 @@ static int cs_etm__sample(struct cs_etm_
- {
- struct cs_etm_auxtrace *etm = etmq->etm;
- int ret;
-- u8 trace_chan_id = tidq->trace_chan_id;
- u64 instrs_prev;
-
- /* Get instructions remainder from previous packet */
-@@ -1851,10 +1885,10 @@ static int cs_etm__sample(struct cs_etm_
- * been executed, but PC has not advanced to next
- * instruction)
- */
-- addr = cs_etm__instr_addr(etmq, trace_chan_id,
-- tidq->packet, offset - 1);
-+ addr = cs_etm__instr_addr(etmq, tidq, tidq->packet,
-+ offset - 1);
- ret = cs_etm__synth_instruction_sample(
-- etmq, tidq, addr,
-+ etmq, tidq, tidq->packet, addr,
- etm->instructions_sample_period);
- if (ret)
- return ret;
-@@ -1936,7 +1970,7 @@ static int cs_etm__flush(struct cs_etm_q
- addr = cs_etm__last_executed_instr(tidq->prev_packet);
-
- err = cs_etm__synth_instruction_sample(
-- etmq, tidq, addr,
-+ etmq, tidq, tidq->prev_packet, addr,
- tidq->period_instructions);
- if (err)
- return err;
-@@ -1991,7 +2025,7 @@ static int cs_etm__end_block(struct cs_e
- addr = cs_etm__last_executed_instr(tidq->prev_packet);
-
- err = cs_etm__synth_instruction_sample(
-- etmq, tidq, addr,
-+ etmq, tidq, tidq->prev_packet, addr,
- tidq->period_instructions);
- if (err)
- return err;
-@@ -2028,9 +2062,9 @@ static int cs_etm__get_data_block(struct
- return etmq->buf_len;
- }
-
--static bool cs_etm__is_svc_instr(struct cs_etm_queue *etmq, u8 trace_chan_id,
-- struct cs_etm_packet *packet,
-- u64 end_addr)
-+static bool cs_etm__is_svc_instr(struct cs_etm_queue *etmq,
-+ struct cs_etm_traceid_queue *tidq,
-+ struct cs_etm_packet *packet, u64 end_addr)
- {
- /* Initialise to keep compiler happy */
- u16 instr16 = 0;
-@@ -2052,8 +2086,8 @@ static bool cs_etm__is_svc_instr(struct
- * so below only read 2 bytes as instruction size for T32.
- */
- addr = end_addr - 2;
-- cs_etm__mem_access(etmq, trace_chan_id, addr, sizeof(instr16),
-- (u8 *)&instr16, 0);
-+ cs_etm__frontend_mem_access(etmq, tidq, packet, addr,
-+ sizeof(instr16), (u8 *)&instr16);
- if ((instr16 & 0xFF00) == 0xDF00)
- return true;
-
-@@ -2068,8 +2102,8 @@ static bool cs_etm__is_svc_instr(struct
- * +---------+---------+-------------------------+
- */
- addr = end_addr - 4;
-- cs_etm__mem_access(etmq, trace_chan_id, addr, sizeof(instr32),
-- (u8 *)&instr32, 0);
-+ cs_etm__frontend_mem_access(etmq, tidq, packet, addr,
-+ sizeof(instr32), (u8 *)&instr32);
- if ((instr32 & 0x0F000000) == 0x0F000000 &&
- (instr32 & 0xF0000000) != 0xF0000000)
- return true;
-@@ -2085,8 +2119,8 @@ static bool cs_etm__is_svc_instr(struct
- * +-----------------------+---------+-----------+
- */
- addr = end_addr - 4;
-- cs_etm__mem_access(etmq, trace_chan_id, addr, sizeof(instr32),
-- (u8 *)&instr32, 0);
-+ cs_etm__frontend_mem_access(etmq, tidq, packet, addr,
-+ sizeof(instr32), (u8 *)&instr32);
- if ((instr32 & 0xFFE0001F) == 0xd4000001)
- return true;
-
-@@ -2102,7 +2136,6 @@ static bool cs_etm__is_svc_instr(struct
- static bool cs_etm__is_syscall(struct cs_etm_queue *etmq,
- struct cs_etm_traceid_queue *tidq, u64 magic)
- {
-- u8 trace_chan_id = tidq->trace_chan_id;
- struct cs_etm_packet *packet = tidq->packet;
- struct cs_etm_packet *prev_packet = tidq->prev_packet;
-
-@@ -2117,7 +2150,7 @@ static bool cs_etm__is_syscall(struct cs
- */
- if (magic == __perf_cs_etmv4_magic) {
- if (packet->exception_number == CS_ETMV4_EXC_CALL &&
-- cs_etm__is_svc_instr(etmq, trace_chan_id, prev_packet,
-+ cs_etm__is_svc_instr(etmq, tidq, prev_packet,
- prev_packet->end_addr))
- return true;
- }
-@@ -2155,7 +2188,6 @@ static bool cs_etm__is_sync_exception(st
- struct cs_etm_traceid_queue *tidq,
- u64 magic)
- {
-- u8 trace_chan_id = tidq->trace_chan_id;
- struct cs_etm_packet *packet = tidq->packet;
- struct cs_etm_packet *prev_packet = tidq->prev_packet;
-
-@@ -2181,7 +2213,7 @@ static bool cs_etm__is_sync_exception(st
- * (SMC, HVC) are taken as sync exceptions.
- */
- if (packet->exception_number == CS_ETMV4_EXC_CALL &&
-- !cs_etm__is_svc_instr(etmq, trace_chan_id, prev_packet,
-+ !cs_etm__is_svc_instr(etmq, tidq, prev_packet,
- prev_packet->end_addr))
- return true;
-
-@@ -2205,7 +2237,6 @@ static int cs_etm__set_sample_flags(stru
- {
- struct cs_etm_packet *packet = tidq->packet;
- struct cs_etm_packet *prev_packet = tidq->prev_packet;
-- u8 trace_chan_id = tidq->trace_chan_id;
- u64 magic;
- int ret;
-
-@@ -2286,11 +2317,11 @@ static int cs_etm__set_sample_flags(stru
- if (prev_packet->flags == (PERF_IP_FLAG_BRANCH |
- PERF_IP_FLAG_RETURN |
- PERF_IP_FLAG_INTERRUPT) &&
-- cs_etm__is_svc_instr(etmq, trace_chan_id,
-- packet, packet->start_addr))
-+ cs_etm__is_svc_instr(etmq, tidq, packet, packet->start_addr)) {
- prev_packet->flags = PERF_IP_FLAG_BRANCH |
- PERF_IP_FLAG_RETURN |
- PERF_IP_FLAG_SYSCALLRET;
-+ }
- break;
- case CS_ETM_DISCONTINUITY:
- /*
-@@ -2371,6 +2402,7 @@ static int cs_etm__set_sample_flags(stru
- PERF_IP_FLAG_RETURN |
- PERF_IP_FLAG_INTERRUPT;
- break;
-+ case CS_ETM_CONTEXT:
- case CS_ETM_EMPTY:
- default:
- break;
-@@ -2446,6 +2478,19 @@ static int cs_etm__process_traceid_queue
- */
- cs_etm__sample(etmq, tidq);
- break;
-+ case CS_ETM_CONTEXT:
-+ /*
-+ * Update context but don't swap packet. Keep the
-+ * previous one for branch source address info, if
-+ * tracing the kernel the context packet will be emitted
-+ * between two ranges.
-+ */
-+ ret = cs_etm__etmq_update_thread(etmq, tidq->packet->el,
-+ tidq->packet->tid,
-+ &tidq->frontend_thread);
-+ if (ret)
-+ goto out;
-+ break;
- case CS_ETM_EXCEPTION:
- case CS_ETM_EXCEPTION_RET:
- /*
-@@ -2474,6 +2519,7 @@ static int cs_etm__process_traceid_queue
- }
- }
-
-+out:
- return ret;
- }
-
-@@ -2597,7 +2643,7 @@ static int cs_etm__process_timeless_queu
- if (!tidq)
- continue;
-
-- if (tid == -1 || thread__tid(tidq->thread) == tid)
-+ if (tid == -1 || thread__tid(tidq->frontend_thread) == tid)
- cs_etm__run_per_thread_timeless_decoder(etmq);
- } else
- cs_etm__run_per_cpu_timeless_decoder(etmq);
-@@ -3308,7 +3354,7 @@ static int cs_etm__create_queue_decoders
- */
- if (cs_etm_decoder__add_mem_access_cb(etmq->decoder,
- 0x0L, ((u64) -1L),
-- cs_etm__mem_access))
-+ cs_etm__decoder_mem_access))
- goto out_free_decoder;
-
- zfree(&t_params);
---- a/tools/perf/util/cs-etm.h
-+++ b/tools/perf/util/cs-etm.h
-@@ -158,6 +158,7 @@ enum cs_etm_sample_type {
- CS_ETM_DISCONTINUITY,
- CS_ETM_EXCEPTION,
- CS_ETM_EXCEPTION_RET,
-+ CS_ETM_CONTEXT,
- };
-
- enum cs_etm_isa {
-@@ -184,6 +185,8 @@ struct cs_etm_packet {
- u8 last_instr_size;
- u8 trace_chan_id;
- int cpu;
-+ int el;
-+ pid_t tid;
- };
-
- #define CS_ETM_PACKET_MAX_BUFFER 1024
-@@ -244,8 +247,9 @@ enum cs_etm_pid_fmt {
- #include <opencsd/ocsd_if_types.h>
- int cs_etm__get_cpu(struct cs_etm_queue *etmq, u8 trace_chan_id, int *cpu);
- enum cs_etm_pid_fmt cs_etm__get_pid_fmt(struct cs_etm_queue *etmq);
--int cs_etm__etmq_set_tid_el(struct cs_etm_queue *etmq, pid_t tid,
-- u8 trace_chan_id, ocsd_ex_level el);
-+int cs_etm__etmq_update_decode_context(struct cs_etm_queue *etmq,
-+ u8 trace_chan_id, ocsd_ex_level el,
-+ pid_t tid);
- bool cs_etm__etmq_is_timeless(struct cs_etm_queue *etmq);
- void cs_etm__etmq_set_traceid_queue_timestamp(struct cs_etm_queue *etmq,
- u8 trace_chan_id);
+++ /dev/null
-From 33793177d52cc3face8cd19a594573df93c3b4c1 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Wed, 10 Jun 2026 22:45:08 -0300
-Subject: perf cs-etm: Reject CPU IDs that would overflow signed comparison
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 542e88a4c6f7b6edd1326ce767d4cb3c2ea9d61d ]
-
-metadata[j][CS_ETM_CPU] is a u64 from perf.data, but the comparison
-with max_cpu casts it to (int). A crafted value like 0xFFFFFFFF becomes
--1 after the cast, which compares less than max_cpu (0), so the queue
-array is never sized to accommodate it. When the value is later passed
-to cs_etm__get_queue(), it indexes queue_array with the original large
-value, causing an out-of-bounds access.
-
-Validate that CS_ETM_CPU fits in an int before using it in the signed
-comparison.
-
-Fixes: 57880a7966be510c ("perf: cs-etm: Allocate queues for all CPUs")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: James Clark <james.clark@arm.com>
-Cc: Adrian Hunter <adrian.hunter@intel.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/cs-etm.c | 9 ++++++++-
- 1 file changed, 8 insertions(+), 1 deletion(-)
-
---- a/tools/perf/util/cs-etm.c
-+++ b/tools/perf/util/cs-etm.c
-@@ -6,6 +6,7 @@
- * Author: Mathieu Poirier <mathieu.poirier@linaro.org>
- */
-
-+#include <limits.h>
- #include <linux/bitfield.h>
- #include <linux/bitops.h>
- #include <linux/coresight-pmu.h>
-@@ -3447,7 +3448,13 @@ int cs_etm__process_auxtrace_info_full(u
- goto err_free_metadata;
- }
-
-- if ((int) metadata[j][CS_ETM_CPU] > max_cpu)
-+ /* CPU id comes from perf.data and must fit max_cpu + 1 without overflow */
-+ if (metadata[j][CS_ETM_CPU] >= INT_MAX) {
-+ err = -EINVAL;
-+ goto err_free_metadata;
-+ }
-+
-+ if ((int)metadata[j][CS_ETM_CPU] > max_cpu)
- max_cpu = metadata[j][CS_ETM_CPU];
- }
-
+++ /dev/null
-From a2fbadc4fa90d99ec3c46a07dcf6f52e4f08b2ef Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 13 Jun 2026 14:40:36 -0300
-Subject: perf cs-etm: Require full global header in auxtrace_info size check
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 78d8ba680126f3545e8d0fba667e12d79fd4353b ]
-
-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:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- 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 4abe416e3febd2..aebef71d3a0a1d 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.53.0
-
+++ /dev/null
-From 3d3a6d2ca005f15e76c8e9761af300db4358bd27 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 13 Jun 2026 14:16:45 -0300
-Subject: perf cs-etm: Validate num_cpu before metadata allocation
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 312d91329b8fc6989a916a3f9a12d0674167b7e4 ]
-
-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:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/cs-etm.c | 12 ++++++++++++
- 1 file changed, 12 insertions(+)
-
---- a/tools/perf/util/cs-etm.c
-+++ b/tools/perf/util/cs-etm.c
-@@ -3410,6 +3410,18 @@ int cs_etm__process_auxtrace_info_full(u
- /* 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;
+++ /dev/null
-From f327b3e36fc33294281f83af5ad7a99038dcf366 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Mon, 4 May 2026 01:12:27 -0700
-Subject: perf debuginfo: Fix libdw API contract violations
-
-From: Ian Rogers <irogers@google.com>
-
-[ Upstream commit 31088ccf0312b1a547046f1f69890ede07834a30 ]
-
-Check return value of `dwfl_report_end` during offline initialization.
-Validate `dwfl_module_relocation_info` result before passing to `strcmp`
-to avoid potential segmentation faults.
-
-Additionally:
- - Fix a file descriptor leak in `debuginfo__init_offline_dwarf()` when
- `dwfl_report_offline()` or subsequent setup calls fail.
-
-Fixes: 6f1b6291cf73cb32 ("perf tools: Add util/debuginfo.[ch] files")
-Assisted-by: Gemini-CLI:Google Gemini 3
-Signed-off-by: Ian Rogers <irogers@google.com>
-Acked-by: Namhyung Kim <namhyung@kernel.org>
-Cc: Adrian Hunter <adrian.hunter@intel.com>
-Cc: Ingo Molnar <mingo@redhat.com>
-Cc: James Clark <james.clark@linaro.org>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Masami Hiramatsu <mhiramat@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Cc: Peter Zijlstra <peterz@infradead.org>
-Cc: Zecheng Li <zli94@ncsu.edu>
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/debuginfo.c | 9 ++++++---
- 1 file changed, 6 insertions(+), 3 deletions(-)
-
-diff --git a/tools/perf/util/debuginfo.c b/tools/perf/util/debuginfo.c
-index 19acf4775d3587..f5af3b4408b950 100644
---- a/tools/perf/util/debuginfo.c
-+++ b/tools/perf/util/debuginfo.c
-@@ -42,6 +42,7 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
- {
- GElf_Addr dummy;
- int fd;
-+ bool fd_consumed = false;
-
- fd = open(path, O_RDONLY);
- if (fd < 0)
-@@ -55,6 +56,7 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
- dbg->mod = dwfl_report_offline(dbg->dwfl, "", "", fd);
- if (!dbg->mod)
- goto error;
-+ fd_consumed = true;
-
- dbg->dbg = dwfl_module_getdwarf(dbg->mod, &dbg->bias);
- if (!dbg->dbg)
-@@ -62,13 +64,14 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
-
- dwfl_module_build_id(dbg->mod, &dbg->build_id, &dummy);
-
-- dwfl_report_end(dbg->dwfl, NULL, NULL);
-+ if (dwfl_report_end(dbg->dwfl, NULL, NULL) != 0)
-+ goto error;
-
- return 0;
- error:
- if (dbg->dwfl)
- dwfl_end(dbg->dwfl);
-- else
-+ if (!fd_consumed)
- close(fd);
- memset(dbg, 0, sizeof(*dbg));
-
-@@ -160,7 +163,7 @@ int debuginfo__get_text_offset(struct debuginfo *dbg, Dwarf_Addr *offs,
- /* Search the relocation related .text section */
- for (i = 0; i < n; i++) {
- p = dwfl_module_relocation_info(dbg->mod, i, &shndx);
-- if (strcmp(p, ".text") == 0) {
-+ if (p && strcmp(p, ".text") == 0) {
- /* OK, get the section header */
- scn = elf_getscn(elf, shndx);
- if (!scn)
---
-2.53.0
-
+++ /dev/null
-From 38090a76f4e23063a774b9efcb9e3e3a36128721 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Tue, 18 Mar 2025 22:07:33 -0700
-Subject: perf dso: Add support for reading the e_machine type for a dso
-
-From: Ian Rogers <irogers@google.com>
-
-[ Upstream commit afffec6f032926f70fc9f43f5d07cdfa09a0a184 ]
-
-For ELF file dsos read the e_machine from the ELF header. For kernel
-types assume the e_machine matches the perf tool. In other cases
-return EM_NONE.
-
-When reading from the ELF header use DSO__SWAP that may need
-dso->needs_swap initializing. Factor out dso__swap_init to allow this.
-
-Signed-off-by: Ian Rogers <irogers@google.com>
-Reviewed-by: Namhyung Kim <namhyung@kernel.org>
-Acked-by: Arnaldo Carvalho de Melo <acme@kernel.org>
-Link: https://lore.kernel.org/r/20250319050741.269828-7-irogers@google.com
-Signed-off-by: Namhyung Kim <namhyung@kernel.org>
-Stable-dep-of: 2ea64782a428 ("perf tools: Use snprintf() in dso__read_running_kernel_build_id()")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/dso.c | 89 ++++++++++++++++++++++++++++++++++++
- tools/perf/util/dso.h | 3 ++
- tools/perf/util/symbol-elf.c | 27 -----------
- 3 files changed, 92 insertions(+), 27 deletions(-)
-
-diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
-index 05a9dc6f41f820..7c12d53656887d 100644
---- a/tools/perf/util/dso.c
-+++ b/tools/perf/util/dso.c
-@@ -1191,6 +1191,68 @@ ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine,
- return data_read_write_offset(dso, machine, offset, data, size, true);
- }
-
-+uint16_t dso__e_machine(struct dso *dso, struct machine *machine)
-+{
-+ uint16_t e_machine = EM_NONE;
-+ int fd;
-+
-+ switch (dso__binary_type(dso)) {
-+ case DSO_BINARY_TYPE__KALLSYMS:
-+ case DSO_BINARY_TYPE__GUEST_KALLSYMS:
-+ case DSO_BINARY_TYPE__VMLINUX:
-+ case DSO_BINARY_TYPE__GUEST_VMLINUX:
-+ case DSO_BINARY_TYPE__GUEST_KMODULE:
-+ case DSO_BINARY_TYPE__GUEST_KMODULE_COMP:
-+ case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE:
-+ case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE_COMP:
-+ case DSO_BINARY_TYPE__KCORE:
-+ case DSO_BINARY_TYPE__GUEST_KCORE:
-+ case DSO_BINARY_TYPE__BPF_PROG_INFO:
-+ case DSO_BINARY_TYPE__BPF_IMAGE:
-+ case DSO_BINARY_TYPE__OOL:
-+ case DSO_BINARY_TYPE__JAVA_JIT:
-+ return EM_HOST;
-+ case DSO_BINARY_TYPE__DEBUGLINK:
-+ case DSO_BINARY_TYPE__BUILD_ID_CACHE:
-+ case DSO_BINARY_TYPE__BUILD_ID_CACHE_DEBUGINFO:
-+ case DSO_BINARY_TYPE__GNU_DEBUGDATA:
-+ case DSO_BINARY_TYPE__SYSTEM_PATH_DSO:
-+ case DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO:
-+ case DSO_BINARY_TYPE__FEDORA_DEBUGINFO:
-+ case DSO_BINARY_TYPE__UBUNTU_DEBUGINFO:
-+ case DSO_BINARY_TYPE__MIXEDUP_UBUNTU_DEBUGINFO:
-+ case DSO_BINARY_TYPE__BUILDID_DEBUGINFO:
-+ break;
-+ case DSO_BINARY_TYPE__NOT_FOUND:
-+ default:
-+ return EM_NONE;
-+ }
-+
-+ mutex_lock(dso__data_open_lock());
-+
-+ /*
-+ * dso__data(dso)->fd might be closed if other thread opened another
-+ * file (dso) due to open file limit (RLIMIT_NOFILE).
-+ */
-+ try_to_open_dso(dso, machine);
-+ fd = dso__data(dso)->fd;
-+ if (fd >= 0) {
-+ _Static_assert(offsetof(Elf32_Ehdr, e_machine) == 18, "Unexpected offset");
-+ _Static_assert(offsetof(Elf64_Ehdr, e_machine) == 18, "Unexpected offset");
-+ if (dso__needs_swap(dso) == DSO_SWAP__UNSET) {
-+ unsigned char eidata;
-+
-+ if (pread(fd, &eidata, sizeof(eidata), EI_DATA) == sizeof(eidata))
-+ dso__swap_init(dso, eidata);
-+ }
-+ if (dso__needs_swap(dso) != DSO_SWAP__UNSET &&
-+ pread(fd, &e_machine, sizeof(e_machine), 18) == sizeof(e_machine))
-+ e_machine = DSO__SWAP(dso, uint16_t, e_machine);
-+ }
-+ mutex_unlock(dso__data_open_lock());
-+ return e_machine;
-+}
-+
- /**
- * dso__data_read_addr - Read data from dso address
- * @dso: dso object
-@@ -1546,6 +1608,33 @@ void dso__put(struct dso *dso)
- RC_CHK_PUT(dso);
- }
-
-+int dso__swap_init(struct dso *dso, unsigned char eidata)
-+{
-+ static unsigned int const endian = 1;
-+
-+ dso__set_needs_swap(dso, DSO_SWAP__NO);
-+
-+ switch (eidata) {
-+ case ELFDATA2LSB:
-+ /* We are big endian, DSO is little endian. */
-+ if (*(unsigned char const *)&endian != 1)
-+ dso__set_needs_swap(dso, DSO_SWAP__YES);
-+ break;
-+
-+ case ELFDATA2MSB:
-+ /* We are little endian, DSO is big endian. */
-+ if (*(unsigned char const *)&endian != 0)
-+ dso__set_needs_swap(dso, DSO_SWAP__YES);
-+ break;
-+
-+ default:
-+ pr_err("unrecognized DSO data encoding %d\n", eidata);
-+ return -EINVAL;
-+ }
-+
-+ return 0;
-+}
-+
- void dso__set_build_id(struct dso *dso, struct build_id *bid)
- {
- RC_CHK_ACCESS(dso)->bid = *bid;
-diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h
-index 53eac48fd0cd81..4bca8a3c8b48e2 100644
---- a/tools/perf/util/dso.h
-+++ b/tools/perf/util/dso.h
-@@ -677,6 +677,8 @@ bool dso__sorted_by_name(const struct dso *dso);
- void dso__set_sorted_by_name(struct dso *dso);
- void dso__sort_by_name(struct dso *dso);
-
-+int dso__swap_init(struct dso *dso, unsigned char eidata);
-+
- void dso__set_build_id(struct dso *dso, struct build_id *bid);
- bool dso__build_id_equal(const struct dso *dso, struct build_id *bid);
- void dso__read_running_kernel_build_id(struct dso *dso,
-@@ -766,6 +768,7 @@ int dso__data_file_size(struct dso *dso, struct machine *machine);
- off_t dso__data_size(struct dso *dso, struct machine *machine);
- ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine,
- u64 offset, u8 *data, ssize_t size);
-+uint16_t dso__e_machine(struct dso *dso, struct machine *machine);
- ssize_t dso__data_read_addr(struct dso *dso, struct map *map,
- struct machine *machine, u64 addr,
- u8 *data, ssize_t size);
-diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
-index c7fcbb8fb644c3..029a1ef9b287b4 100644
---- a/tools/perf/util/symbol-elf.c
-+++ b/tools/perf/util/symbol-elf.c
-@@ -1186,33 +1186,6 @@ int filename__read_debuglink(const char *filename, char *debuglink,
-
- #endif
-
--static int dso__swap_init(struct dso *dso, unsigned char eidata)
--{
-- static unsigned int const endian = 1;
--
-- dso__set_needs_swap(dso, DSO_SWAP__NO);
--
-- switch (eidata) {
-- case ELFDATA2LSB:
-- /* We are big endian, DSO is little endian. */
-- if (*(unsigned char const *)&endian != 1)
-- dso__set_needs_swap(dso, DSO_SWAP__YES);
-- break;
--
-- case ELFDATA2MSB:
-- /* We are little endian, DSO is big endian. */
-- if (*(unsigned char const *)&endian != 0)
-- dso__set_needs_swap(dso, DSO_SWAP__YES);
-- break;
--
-- default:
-- pr_err("unrecognized DSO data encoding %d\n", eidata);
-- return -EINVAL;
-- }
--
-- return 0;
--}
--
- bool symsrc__possibly_runtime(struct symsrc *ss)
- {
- return ss->dynsym || ss->opdsec;
---
-2.53.0
-
+++ /dev/null
-From 295ddd9358516166b64d594f5ca07bc35cbb0a62 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Thu, 24 Jul 2025 09:32:46 -0700
-Subject: perf dso: Move build_id to dso_id
-
-From: Ian Rogers <irogers@google.com>
-
-[ Upstream commit d9f2ecbc5e47fca7bda7c13cff3b3534b1467b32 ]
-
-The dso_id previously contained the major, minor, inode and inode
-generation information from a mmap2 event - the inode generation would
-be zero when reading from /proc/pid/maps. The build_id was in the
-dso. With build ID mmap2 events these fields wouldn't be initialized
-which would largely mean the special empty case where any dso would
-match for equality. This isn't desirable as if a dso is replaced we
-want the comparison to yield a difference.
-
-To support detecting the difference between DSOs based on build_id,
-move the build_id out of the DSO and into the dso_id. The dso_id is
-also stored in the DSO so nothing is lost. Capture in the dso_id what
-parts have been initialized and rename dso_id__inject to
-dso_id__improve_id so that it is clear the dso_id is being improved
-upon with additional information. With the build_id in the dso_id, use
-memcmp to compare for equality.
-
-Signed-off-by: Ian Rogers <irogers@google.com>
-Link: https://lore.kernel.org/r/20250724163302.596743-7-irogers@google.com
-Signed-off-by: Namhyung Kim <namhyung@kernel.org>
-Stable-dep-of: 2ea64782a428 ("perf tools: Use snprintf() in dso__read_running_kernel_build_id()")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-buildid-list.c | 2
- tools/perf/builtin-inject.c | 36 ++++++----
- tools/perf/builtin-report.c | 11 ++-
- tools/perf/include/perf/perf_dlfilter.h | 2
- tools/perf/tests/symbols.c | 4 -
- tools/perf/util/build-id.c | 4 -
- tools/perf/util/dso.c | 109 +++++++++++++++++---------------
- tools/perf/util/dso.h | 75 ++++++++++++----------
- tools/perf/util/dsos.c | 18 ++---
- tools/perf/util/machine.c | 28 ++++----
- tools/perf/util/map.c | 13 +--
- tools/perf/util/map.h | 5 -
- tools/perf/util/sort.c | 29 ++++----
- tools/perf/util/synthetic-events.c | 18 +++--
- 14 files changed, 198 insertions(+), 156 deletions(-)
-
---- a/tools/perf/builtin-buildid-list.c
-+++ b/tools/perf/builtin-buildid-list.c
-@@ -31,7 +31,7 @@ static int buildid__map_cb(struct map *m
-
- memset(bid_buf, 0, sizeof(bid_buf));
- if (dso__has_build_id(dso))
-- build_id__snprintf(dso__bid_const(dso), bid_buf, sizeof(bid_buf));
-+ build_id__snprintf(dso__bid(dso), bid_buf, sizeof(bid_buf));
- printf("%s %16" PRIx64 " %16" PRIx64, bid_buf, map__start(map), map__end(map));
- if (dso_long_name != NULL)
- printf(" %s", dso_long_name);
---- a/tools/perf/builtin-inject.c
-+++ b/tools/perf/builtin-inject.c
-@@ -673,15 +673,17 @@ static int perf_event__repipe_mmap2(cons
- struct perf_sample *sample,
- struct machine *machine)
- {
-- struct dso_id id;
-- struct dso_id *dso_id = NULL;
-+ struct dso_id id = dso_id_empty;
-
-- if (!(event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID)) {
-+ if (event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) {
-+ build_id__init(&id.build_id, event->mmap2.build_id, event->mmap2.build_id_size);
-+ } else {
- id.maj = event->mmap2.maj;
- id.min = event->mmap2.min;
- id.ino = event->mmap2.ino;
- id.ino_generation = event->mmap2.ino_generation;
-- dso_id = &id;
-+ id.mmap2_valid = true;
-+ id.mmap2_ino_generation_valid = true;
- }
-
- return perf_event__repipe_common_mmap(
-@@ -689,7 +691,7 @@ static int perf_event__repipe_mmap2(cons
- event->mmap2.pid, event->mmap2.tid,
- event->mmap2.start, event->mmap2.len, event->mmap2.pgoff,
- event->mmap2.flags, event->mmap2.prot,
-- event->mmap2.filename, dso_id,
-+ event->mmap2.filename, &id,
- perf_event__process_mmap2);
- }
-
-@@ -757,19 +759,20 @@ static int perf_event__repipe_tracing_da
- static int dso__read_build_id(struct dso *dso)
- {
- struct nscookie nsc;
-+ struct build_id bid = { .size = 0, };
-
- if (dso__has_build_id(dso))
- return 0;
-
- mutex_lock(dso__lock(dso));
- nsinfo__mountns_enter(dso__nsinfo(dso), &nsc);
-- if (filename__read_build_id(dso__long_name(dso), dso__bid(dso)) > 0)
-- dso__set_has_build_id(dso);
-+ if (filename__read_build_id(dso__long_name(dso), &bid) > 0)
-+ dso__set_build_id(dso, &bid);
- else if (dso__nsinfo(dso)) {
- char *new_name = dso__filename_with_chroot(dso, dso__long_name(dso));
-
-- if (new_name && filename__read_build_id(new_name, dso__bid(dso)) > 0)
-- dso__set_has_build_id(dso);
-+ if (new_name && filename__read_build_id(new_name, &bid) > 0)
-+ dso__set_build_id(dso, &bid);
- free(new_name);
- }
- nsinfo__mountns_exit(&nsc);
-@@ -818,23 +821,26 @@ static bool perf_inject__lookup_known_bu
- struct dso *dso)
- {
- struct str_node *pos;
-- int bid_len;
-
- strlist__for_each_entry(pos, inject->known_build_ids) {
-+ struct build_id bid;
- const char *build_id, *dso_name;
-+ size_t bid_len;
-
- build_id = skip_spaces(pos->s);
- dso_name = strchr(build_id, ' ');
- bid_len = dso_name - pos->s;
-+ if (bid_len > sizeof(bid.data))
-+ bid_len = sizeof(bid.data);
- dso_name = skip_spaces(dso_name);
- if (strcmp(dso__long_name(dso), dso_name))
- continue;
-- for (int ix = 0; 2 * ix + 1 < bid_len; ++ix) {
-- dso__bid(dso)->data[ix] = (hex(build_id[2 * ix]) << 4 |
-- hex(build_id[2 * ix + 1]));
-+ for (size_t ix = 0; 2 * ix + 1 < bid_len; ++ix) {
-+ bid.data[ix] = (hex(build_id[2 * ix]) << 4 |
-+ hex(build_id[2 * ix + 1]));
- }
-- dso__bid(dso)->size = bid_len / 2;
-- dso__set_has_build_id(dso);
-+ bid.size = bid_len / 2;
-+ dso__set_build_id(dso, &bid);
- return true;
- }
- return false;
---- a/tools/perf/builtin-report.c
-+++ b/tools/perf/builtin-report.c
-@@ -855,17 +855,24 @@ static int maps__fprintf_task_cb(struct
- struct maps__fprintf_task_args *args = data;
- const struct dso *dso = map__dso(map);
- u32 prot = map__prot(map);
-+ const struct dso_id *dso_id = dso__id_const(dso);
- int ret;
-+ char buf[SBUILD_ID_SIZE];
-+
-+ if (dso_id->mmap2_valid)
-+ snprintf(buf, sizeof(buf), "%" PRIu64, dso_id->ino);
-+ else
-+ build_id__snprintf(&dso_id->build_id, buf, sizeof(buf));
-
- ret = fprintf(args->fp,
-- "%*s %" PRIx64 "-%" PRIx64 " %c%c%c%c %08" PRIx64 " %" PRIu64 " %s\n",
-+ "%*s %" PRIx64 "-%" PRIx64 " %c%c%c%c %08" PRIx64 " %s %s\n",
- args->indent, "", map__start(map), map__end(map),
- prot & PROT_READ ? 'r' : '-',
- prot & PROT_WRITE ? 'w' : '-',
- prot & PROT_EXEC ? 'x' : '-',
- map__flags(map) ? 's' : 'p',
- map__pgoff(map),
-- dso__id_const(dso)->ino, dso__name(dso));
-+ buf, dso__name(dso));
-
- if (ret < 0)
- return ret;
---- a/tools/perf/include/perf/perf_dlfilter.h
-+++ b/tools/perf/include/perf/perf_dlfilter.h
-@@ -87,7 +87,7 @@ struct perf_dlfilter_al {
- __u8 is_64_bit; /* Only valid if dso is not NULL */
- __u8 is_kernel_ip; /* True if in kernel space */
- __u32 buildid_size;
-- __u8 *buildid;
-+ const __u8 *buildid;
- /* Below members are only populated by resolve_ip() */
- __u8 filtered; /* True if this sample event will be filtered out */
- const char *comm;
---- a/tools/perf/tests/symbols.c
-+++ b/tools/perf/tests/symbols.c
-@@ -96,8 +96,8 @@ static int create_map(struct test_info *
- dso__put(dso);
-
- /* Create a dummy map at 0x100000 */
-- *map_p = map__new(ti->machine, 0x100000, 0xffffffff, 0, NULL,
-- PROT_EXEC, 0, NULL, filename, ti->thread);
-+ *map_p = map__new(ti->machine, 0x100000, 0xffffffff, 0, &dso_id_empty,
-+ PROT_EXEC, /*flags=*/0, filename, ti->thread);
- if (!*map_p) {
- pr_debug("Failed to create map!");
- return TEST_FAIL;
---- a/tools/perf/util/build-id.c
-+++ b/tools/perf/util/build-id.c
-@@ -236,7 +236,7 @@ char *__dso__build_id_filename(const str
- if (!dso__has_build_id(dso))
- return NULL;
-
-- build_id__snprintf(dso__bid_const(dso), sbuild_id, sizeof(sbuild_id));
-+ build_id__snprintf(dso__bid(dso), sbuild_id, sizeof(sbuild_id));
- linkname = build_id_cache__linkname(sbuild_id, NULL, 0);
- if (!linkname)
- return NULL;
-@@ -319,7 +319,7 @@ static int machine__write_buildid_table_
- }
-
- in_kernel = dso__kernel(dso) || is_kernel_module(name, PERF_RECORD_MISC_CPUMODE_UNKNOWN);
-- return write_buildid(name, name_len, dso__bid(dso), args->machine->pid,
-+ return write_buildid(name, name_len, &dso__id(dso)->build_id, args->machine->pid,
- in_kernel ? args->kmisc : args->umisc, args->fd);
- }
-
---- a/tools/perf/util/dso.c
-+++ b/tools/perf/util/dso.c
-@@ -215,7 +215,7 @@ int dso__read_binary_type_filename(const
- break;
- }
-
-- build_id__snprintf(dso__bid_const(dso), build_id_hex, sizeof(build_id_hex));
-+ build_id__snprintf(dso__bid(dso), build_id_hex, sizeof(build_id_hex));
- len = __symbol__join_symfs(filename, size, "/usr/lib/debug/.build-id/");
- snprintf(filename + len, size - len, "%.2s/%s.debug",
- build_id_hex, build_id_hex + 2);
-@@ -1376,64 +1376,76 @@ static void dso__set_long_name_id(struct
-
- static int __dso_id__cmp(const struct dso_id *a, const struct dso_id *b)
- {
-- if (a->maj > b->maj) return -1;
-- if (a->maj < b->maj) return 1;
-+ if (a->mmap2_valid && b->mmap2_valid) {
-+ if (a->maj > b->maj) return -1;
-+ if (a->maj < b->maj) return 1;
-
-- if (a->min > b->min) return -1;
-- if (a->min < b->min) return 1;
-+ if (a->min > b->min) return -1;
-+ if (a->min < b->min) return 1;
-
-- if (a->ino > b->ino) return -1;
-- if (a->ino < b->ino) return 1;
--
-- /*
-- * Synthesized MMAP events have zero ino_generation, avoid comparing
-- * them with MMAP events with actual ino_generation.
-- *
-- * I found it harmful because the mismatch resulted in a new
-- * dso that did not have a build ID whereas the original dso did have a
-- * build ID. The build ID was essential because the object was not found
-- * otherwise. - Adrian
-- */
-- if (a->ino_generation && b->ino_generation) {
-+ if (a->ino > b->ino) return -1;
-+ if (a->ino < b->ino) return 1;
-+ }
-+ if (a->mmap2_ino_generation_valid && b->mmap2_ino_generation_valid) {
- if (a->ino_generation > b->ino_generation) return -1;
- if (a->ino_generation < b->ino_generation) return 1;
- }
--
-+ if (build_id__is_defined(&a->build_id) && build_id__is_defined(&b->build_id)) {
-+ if (a->build_id.size != b->build_id.size)
-+ return a->build_id.size < b->build_id.size ? -1 : 1;
-+ return memcmp(a->build_id.data, b->build_id.data, a->build_id.size);
-+ }
- return 0;
- }
-
--bool dso_id__empty(const struct dso_id *id)
--{
-- if (!id)
-- return true;
--
-- return !id->maj && !id->min && !id->ino && !id->ino_generation;
--}
-+const struct dso_id dso_id_empty = {
-+ {
-+ .maj = 0,
-+ .min = 0,
-+ .ino = 0,
-+ .ino_generation = 0,
-+ },
-+ .mmap2_valid = false,
-+ .mmap2_ino_generation_valid = false,
-+ {
-+ .size = 0,
-+ }
-+};
-
--void __dso__inject_id(struct dso *dso, const struct dso_id *id)
-+void __dso__improve_id(struct dso *dso, const struct dso_id *id)
- {
- struct dsos *dsos = dso__dsos(dso);
- struct dso_id *dso_id = dso__id(dso);
-+ bool changed = false;
-
- /* dsos write lock held by caller. */
-
-- dso_id->maj = id->maj;
-- dso_id->min = id->min;
-- dso_id->ino = id->ino;
-- dso_id->ino_generation = id->ino_generation;
--
-- if (dsos)
-+ if (id->mmap2_valid && !dso_id->mmap2_valid) {
-+ dso_id->maj = id->maj;
-+ dso_id->min = id->min;
-+ dso_id->ino = id->ino;
-+ dso_id->mmap2_valid = true;
-+ changed = true;
-+ }
-+ if (id->mmap2_ino_generation_valid && !dso_id->mmap2_ino_generation_valid) {
-+ dso_id->ino_generation = id->ino_generation;
-+ dso_id->mmap2_ino_generation_valid = true;
-+ changed = true;
-+ }
-+ if (build_id__is_defined(&id->build_id) && !build_id__is_defined(&dso_id->build_id)) {
-+ dso_id->build_id = id->build_id;
-+ changed = true;
-+ }
-+ if (changed && dsos)
- dsos->sorted = false;
- }
-
- int dso_id__cmp(const struct dso_id *a, const struct dso_id *b)
- {
-- /*
-- * The second is always dso->id, so zeroes if not set, assume passing
-- * NULL for a means a zeroed id
-- */
-- if (dso_id__empty(a) || dso_id__empty(b))
-+ if (a == &dso_id_empty || b == &dso_id_empty) {
-+ /* There is no valid data to compare so the comparison always returns identical. */
- return 0;
-+ }
-
- return __dso_id__cmp(a, b);
- }
-@@ -1530,7 +1542,6 @@ struct dso *dso__new_id(const char *name
- dso->loaded = 0;
- dso->rel = 0;
- dso->sorted_by_name = 0;
-- dso->has_build_id = 0;
- dso->has_srcline = 1;
- dso->a2l_fails = 1;
- dso->kernel = DSO_SPACE__USER;
-@@ -1635,15 +1646,14 @@ int dso__swap_init(struct dso *dso, unsi
- return 0;
- }
-
--void dso__set_build_id(struct dso *dso, struct build_id *bid)
-+void dso__set_build_id(struct dso *dso, const struct build_id *bid)
- {
-- RC_CHK_ACCESS(dso)->bid = *bid;
-- RC_CHK_ACCESS(dso)->has_build_id = 1;
-+ dso__id(dso)->build_id = *bid;
- }
-
--bool dso__build_id_equal(const struct dso *dso, struct build_id *bid)
-+bool dso__build_id_equal(const struct dso *dso, const struct build_id *bid)
- {
-- const struct build_id *dso_bid = dso__bid_const(dso);
-+ const struct build_id *dso_bid = dso__bid(dso);
-
- if (dso_bid->size > bid->size && dso_bid->size == BUILD_ID_SIZE) {
- /*
-@@ -1662,18 +1672,20 @@ bool dso__build_id_equal(const struct ds
- void dso__read_running_kernel_build_id(struct dso *dso, struct machine *machine)
- {
- char path[PATH_MAX];
-+ struct build_id bid = { .size = 0, };
-
- if (machine__is_default_guest(machine))
- return;
- sprintf(path, "%s/sys/kernel/notes", machine->root_dir);
-- if (sysfs__read_build_id(path, dso__bid(dso)) == 0)
-- dso__set_has_build_id(dso);
-+ sysfs__read_build_id(path, &bid);
-+ dso__set_build_id(dso, &bid);
- }
-
- int dso__kernel_module_get_build_id(struct dso *dso,
- const char *root_dir)
- {
- char filename[PATH_MAX];
-+ struct build_id bid = { .size = 0, };
- /*
- * kernel module short names are of the form "[module]" and
- * we need just "module" here.
-@@ -1684,9 +1696,8 @@ int dso__kernel_module_get_build_id(stru
- "%s/sys/module/%.*s/notes/.note.gnu.build-id",
- root_dir, (int)strlen(name) - 1, name);
-
-- if (sysfs__read_build_id(filename, dso__bid(dso)) == 0)
-- dso__set_has_build_id(dso);
--
-+ sysfs__read_build_id(filename, &bid);
-+ dso__set_build_id(dso, &bid);
- return 0;
- }
-
---- a/tools/perf/util/dso.h
-+++ b/tools/perf/util/dso.h
-@@ -127,14 +127,33 @@ enum dso_load_errno {
- #define DSO__DATA_CACHE_SIZE 4096
- #define DSO__DATA_CACHE_MASK ~(DSO__DATA_CACHE_SIZE - 1)
-
--/*
-- * Data about backing storage DSO, comes from PERF_RECORD_MMAP2 meta events
-+/**
-+ * struct dso_id
-+ *
-+ * Data about backing storage DSO, comes from PERF_RECORD_MMAP2 meta events,
-+ * reading from /proc/pid/maps or synthesis of build_ids from DSOs. Possibly
-+ * incomplete at any particular use.
- */
- struct dso_id {
-- u32 maj;
-- u32 min;
-- u64 ino;
-- u64 ino_generation;
-+ /* Data related to the mmap2 event or read from /proc/pid/maps. */
-+ struct {
-+ u32 maj;
-+ u32 min;
-+ u64 ino;
-+ u64 ino_generation;
-+ };
-+ /** @mmap2_valid: Are the maj, min and ino fields valid? */
-+ bool mmap2_valid;
-+ /**
-+ * @mmap2_ino_generation_valid: Is the ino_generation valid? Generally
-+ * false for /proc/pid/maps mmap event.
-+ */
-+ bool mmap2_ino_generation_valid;
-+ /**
-+ * @build_id: A possibly populated build_id. build_id__is_defined checks
-+ * whether it is populated.
-+ */
-+ struct build_id build_id;
- };
-
- struct dso_cache {
-@@ -183,7 +202,6 @@ DECLARE_RC_STRUCT(dso) {
- u64 addr;
- struct symbol *symbol;
- } last_find_result;
-- struct build_id bid;
- u64 text_offset;
- u64 text_end;
- const char *short_name;
-@@ -216,7 +234,6 @@ DECLARE_RC_STRUCT(dso) {
- enum dso_swap_type needs_swap:2;
- bool is_kmod:1;
- u8 adjust_symbols:1;
-- u8 has_build_id:1;
- u8 header_build_id:1;
- u8 has_srcline:1;
- u8 hit:1;
-@@ -232,6 +249,9 @@ DECLARE_RC_STRUCT(dso) {
- };
-
- extern struct mutex _dso__data_open_lock;
-+extern const struct dso_id dso_id_empty;
-+
-+int dso_id__cmp(const struct dso_id *a, const struct dso_id *b);
-
- /* dso__for_each_symbol - iterate over the symbols of given type
- *
-@@ -302,31 +322,11 @@ static inline void dso__set_auxtrace_cac
- RC_CHK_ACCESS(dso)->auxtrace_cache = cache;
- }
-
--static inline struct build_id *dso__bid(struct dso *dso)
--{
-- return &RC_CHK_ACCESS(dso)->bid;
--}
--
--static inline const struct build_id *dso__bid_const(const struct dso *dso)
--{
-- return &RC_CHK_ACCESS(dso)->bid;
--}
--
- static inline struct dso_bpf_prog *dso__bpf_prog(struct dso *dso)
- {
- return &RC_CHK_ACCESS(dso)->bpf_prog;
- }
-
--static inline bool dso__has_build_id(const struct dso *dso)
--{
-- return RC_CHK_ACCESS(dso)->has_build_id;
--}
--
--static inline void dso__set_has_build_id(struct dso *dso)
--{
-- RC_CHK_ACCESS(dso)->has_build_id = true;
--}
--
- static inline bool dso__has_srcline(const struct dso *dso)
- {
- return RC_CHK_ACCESS(dso)->has_srcline;
-@@ -402,6 +402,16 @@ static inline const struct dso_id *dso__
- return &RC_CHK_ACCESS(dso)->id;
- }
-
-+static inline const struct build_id *dso__bid(const struct dso *dso)
-+{
-+ return &dso__id_const(dso)->build_id;
-+}
-+
-+static inline bool dso__has_build_id(const struct dso *dso)
-+{
-+ return build_id__is_defined(dso__bid(dso));
-+}
-+
- static inline struct rb_root_cached *dso__inlined_nodes(struct dso *dso)
- {
- return &RC_CHK_ACCESS(dso)->inlined_nodes;
-@@ -639,9 +649,6 @@ static inline void dso__set_text_offset(
- RC_CHK_ACCESS(dso)->text_offset = val;
- }
-
--int dso_id__cmp(const struct dso_id *a, const struct dso_id *b);
--bool dso_id__empty(const struct dso_id *id);
--
- struct dso *dso__new_id(const char *name, const struct dso_id *id);
- struct dso *dso__new(const char *name);
- void dso__delete(struct dso *dso);
-@@ -649,7 +656,7 @@ void dso__delete(struct dso *dso);
- int dso__cmp_id(struct dso *a, struct dso *b);
- void dso__set_short_name(struct dso *dso, const char *name, bool name_allocated);
- void dso__set_long_name(struct dso *dso, const char *name, bool name_allocated);
--void __dso__inject_id(struct dso *dso, const struct dso_id *id);
-+void __dso__improve_id(struct dso *dso, const struct dso_id *id);
-
- int dso__name_len(const struct dso *dso);
-
-@@ -679,8 +686,8 @@ void dso__sort_by_name(struct dso *dso);
-
- int dso__swap_init(struct dso *dso, unsigned char eidata);
-
--void dso__set_build_id(struct dso *dso, struct build_id *bid);
--bool dso__build_id_equal(const struct dso *dso, struct build_id *bid);
-+void dso__set_build_id(struct dso *dso, const struct build_id *bid);
-+bool dso__build_id_equal(const struct dso *dso, const struct build_id *bid);
- void dso__read_running_kernel_build_id(struct dso *dso,
- struct machine *machine);
- int dso__kernel_module_get_build_id(struct dso *dso, const char *root_dir);
---- a/tools/perf/util/dsos.c
-+++ b/tools/perf/util/dsos.c
-@@ -72,6 +72,7 @@ static int dsos__read_build_ids_cb(struc
- {
- struct dsos__read_build_ids_cb_args *args = data;
- struct nscookie nsc;
-+ struct build_id bid = { .size = 0, };
-
- if (args->with_hits && !dso__hit(dso) && !dso__is_vdso(dso))
- return 0;
-@@ -80,15 +81,15 @@ static int dsos__read_build_ids_cb(struc
- return 0;
- }
- nsinfo__mountns_enter(dso__nsinfo(dso), &nsc);
-- if (filename__read_build_id(dso__long_name(dso), dso__bid(dso)) > 0) {
-+ if (filename__read_build_id(dso__long_name(dso), &bid) > 0) {
-+ dso__set_build_id(dso, &bid);
- args->have_build_id = true;
-- dso__set_has_build_id(dso);
- } else if (errno == ENOENT && dso__nsinfo(dso)) {
- char *new_name = dso__filename_with_chroot(dso, dso__long_name(dso));
-
-- if (new_name && filename__read_build_id(new_name, dso__bid(dso)) > 0) {
-+ if (new_name && filename__read_build_id(new_name, &bid) > 0) {
-+ dso__set_build_id(dso, &bid);
- args->have_build_id = true;
-- dso__set_has_build_id(dso);
- }
- free(new_name);
- }
-@@ -284,7 +285,7 @@ struct dso *dsos__find(struct dsos *dsos
- struct dso *res;
-
- down_read(&dsos->lock);
-- res = __dsos__find_id(dsos, name, NULL, cmp_short, /*write_locked=*/false);
-+ res = __dsos__find_id(dsos, name, &dso_id_empty, cmp_short, /*write_locked=*/false);
- up_read(&dsos->lock);
- return res;
- }
-@@ -341,8 +342,8 @@ static struct dso *__dsos__findnew_id(st
- {
- struct dso *dso = __dsos__find_id(dsos, name, id, false, /*write_locked=*/true);
-
-- if (dso && dso_id__empty(dso__id(dso)) && !dso_id__empty(id))
-- __dso__inject_id(dso, id);
-+ if (dso)
-+ __dso__improve_id(dso, id);
-
- return dso ? dso : __dsos__addnew_id(dsos, name, id);
- }
-@@ -433,7 +434,8 @@ struct dso *dsos__findnew_module_dso(str
-
- down_write(&dsos->lock);
-
-- dso = __dsos__find_id(dsos, m->name, NULL, /*cmp_short=*/true, /*write_locked=*/true);
-+ dso = __dsos__find_id(dsos, m->name, &dso_id_empty, /*cmp_short=*/true,
-+ /*write_locked=*/true);
- if (dso) {
- up_write(&dsos->lock);
- return dso;
---- a/tools/perf/util/machine.c
-+++ b/tools/perf/util/machine.c
-@@ -1696,21 +1696,21 @@ int machine__process_mmap2_event(struct
- {
- struct thread *thread;
- struct map *map;
-- struct dso_id dso_id = {
-- .maj = event->mmap2.maj,
-- .min = event->mmap2.min,
-- .ino = event->mmap2.ino,
-- .ino_generation = event->mmap2.ino_generation,
-- };
-- struct build_id __bid, *bid = NULL;
-+ struct dso_id dso_id = dso_id_empty;
- int ret = 0;
-
- if (dump_trace)
- perf_event__fprintf_mmap2(event, stdout);
-
- if (event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) {
-- bid = &__bid;
-- build_id__init(bid, event->mmap2.build_id, event->mmap2.build_id_size);
-+ build_id__init(&dso_id.build_id, event->mmap2.build_id, event->mmap2.build_id_size);
-+ } else {
-+ dso_id.maj = event->mmap2.maj;
-+ dso_id.min = event->mmap2.min;
-+ dso_id.ino = event->mmap2.ino;
-+ dso_id.ino_generation = event->mmap2.ino_generation;
-+ dso_id.mmap2_valid = true;
-+ dso_id.mmap2_ino_generation_valid = true;
- }
-
- if (sample->cpumode == PERF_RECORD_MISC_GUEST_KERNEL ||
-@@ -1722,7 +1722,7 @@ int machine__process_mmap2_event(struct
- };
-
- strlcpy(xm.name, event->mmap2.filename, KMAP_NAME_LEN);
-- ret = machine__process_kernel_mmap_event(machine, &xm, bid);
-+ ret = machine__process_kernel_mmap_event(machine, &xm, &dso_id.build_id);
- if (ret < 0)
- goto out_problem;
- return 0;
-@@ -1736,7 +1736,7 @@ int machine__process_mmap2_event(struct
- map = map__new(machine, event->mmap2.start,
- event->mmap2.len, event->mmap2.pgoff,
- &dso_id, event->mmap2.prot,
-- event->mmap2.flags, bid,
-+ event->mmap2.flags,
- event->mmap2.filename, thread);
-
- if (map == NULL)
-@@ -1794,8 +1794,8 @@ int machine__process_mmap_event(struct m
- prot = PROT_EXEC;
-
- map = map__new(machine, event->mmap.start,
-- event->mmap.len, event->mmap.pgoff,
-- NULL, prot, 0, NULL, event->mmap.filename, thread);
-+ event->mmap.len, event->mmap.pgoff,
-+ &dso_id_empty, prot, /*flags=*/0, event->mmap.filename, thread);
-
- if (map == NULL)
- goto out_problem_map;
-@@ -3155,7 +3155,7 @@ struct dso *machine__findnew_dso_id(stru
-
- struct dso *machine__findnew_dso(struct machine *machine, const char *filename)
- {
-- return machine__findnew_dso_id(machine, filename, NULL);
-+ return machine__findnew_dso_id(machine, filename, &dso_id_empty);
- }
-
- char *machine__resolve_kernel_addr(void *vmachine, unsigned long long *addrp, char **modp)
---- a/tools/perf/util/map.c
-+++ b/tools/perf/util/map.c
-@@ -120,8 +120,8 @@ static void map__init(struct map *map, u
- }
-
- struct map *map__new(struct machine *machine, u64 start, u64 len,
-- u64 pgoff, struct dso_id *id,
-- u32 prot, u32 flags, struct build_id *bid,
-+ u64 pgoff, const struct dso_id *id,
-+ u32 prot, u32 flags,
- char *filename, struct thread *thread)
- {
- struct map *result;
-@@ -132,7 +132,7 @@ struct map *map__new(struct machine *mac
- map = zalloc(sizeof(*map));
- if (ADD_RC_CHK(result, map)) {
- char newfilename[PATH_MAX];
-- struct dso *dso, *header_bid_dso;
-+ struct dso *dso;
- int anon, no_dso, vdso, android;
-
- android = is_android_lib(filename);
-@@ -189,16 +189,15 @@ struct map *map__new(struct machine *mac
- dso__set_nsinfo(dso, nsi);
- mutex_unlock(dso__lock(dso));
-
-- if (build_id__is_defined(bid)) {
-- dso__set_build_id(dso, bid);
-- } else {
-+ if (!build_id__is_defined(&id->build_id)) {
- /*
- * If the mmap event had no build ID, search for an existing dso from the
- * build ID header by name. Otherwise only the dso loaded at the time of
- * reading the header will have the build ID set and all future mmaps will
- * have it missing.
- */
-- header_bid_dso = dsos__find(&machine->dsos, filename, false);
-+ struct dso *header_bid_dso = dsos__find(&machine->dsos, filename, false);
-+
- if (header_bid_dso && dso__header_build_id(header_bid_dso)) {
- dso__set_build_id(dso, dso__bid(header_bid_dso));
- dso__set_header_build_id(dso, 1);
---- a/tools/perf/util/map.h
-+++ b/tools/perf/util/map.h
-@@ -173,11 +173,10 @@ struct thread;
- __map__for_each_symbol_by_name(map, sym_name, (pos), idx)
-
- struct dso_id;
--struct build_id;
-
- struct map *map__new(struct machine *machine, u64 start, u64 len,
-- u64 pgoff, struct dso_id *id, u32 prot, u32 flags,
-- struct build_id *bid, char *filename, struct thread *thread);
-+ u64 pgoff, const struct dso_id *id, u32 prot, u32 flags,
-+ char *filename, struct thread *thread);
- struct map *map__new2(u64 start, struct dso *dso);
- void map__delete(struct map *map);
- struct map *map__clone(struct map *map);
---- a/tools/perf/util/sort.c
-+++ b/tools/perf/util/sort.c
-@@ -1579,22 +1579,27 @@ sort__dcacheline_cmp(struct hist_entry *
- if (rc)
- return rc;
- /*
-- * Addresses with no major/minor numbers are assumed to be
-+ * Addresses with no major/minor numbers or build ID are assumed to be
- * anonymous in userspace. Sort those on pid then address.
- *
- * The kernel and non-zero major/minor mapped areas are
- * assumed to be unity mapped. Sort those on address.
- */
-+ if (left->cpumode != PERF_RECORD_MISC_KERNEL && (map__flags(l_map) & MAP_SHARED) == 0) {
-+ const struct dso_id *dso_id = dso__id_const(l_dso);
-
-- if ((left->cpumode != PERF_RECORD_MISC_KERNEL) &&
-- (!(map__flags(l_map) & MAP_SHARED)) && !dso__id(l_dso)->maj && !dso__id(l_dso)->min &&
-- !dso__id(l_dso)->ino && !dso__id(l_dso)->ino_generation) {
-- /* userspace anonymous */
--
-- if (thread__pid(left->thread) > thread__pid(right->thread))
-- return -1;
-- if (thread__pid(left->thread) < thread__pid(right->thread))
-- return 1;
-+ if (!dso_id->mmap2_valid)
-+ dso_id = dso__id_const(r_dso);
-+
-+ if (!build_id__is_defined(&dso_id->build_id) &&
-+ (!dso_id->mmap2_valid || (dso_id->maj == 0 && dso_id->min == 0))) {
-+ /* userspace anonymous */
-+
-+ if (thread__pid(left->thread) > thread__pid(right->thread))
-+ return -1;
-+ if (thread__pid(left->thread) < thread__pid(right->thread))
-+ return 1;
-+ }
- }
-
- addr:
-@@ -1619,6 +1624,7 @@ static int hist_entry__dcacheline_snprin
- if (he->mem_info) {
- struct map *map = mem_info__daddr(he->mem_info)->ms.map;
- struct dso *dso = map ? map__dso(map) : NULL;
-+ const struct dso_id *dso_id = dso ? dso__id_const(dso) : &dso_id_empty;
-
- addr = cl_address(mem_info__daddr(he->mem_info)->al_addr, chk_double_cl);
- ms = &mem_info__daddr(he->mem_info)->ms;
-@@ -1627,8 +1633,7 @@ static int hist_entry__dcacheline_snprin
- if ((he->cpumode != PERF_RECORD_MISC_KERNEL) &&
- map && !(map__prot(map) & PROT_EXEC) &&
- (map__flags(map) & MAP_SHARED) &&
-- (dso__id(dso)->maj || dso__id(dso)->min || dso__id(dso)->ino ||
-- dso__id(dso)->ino_generation))
-+ (!dso_id->mmap2_valid || (dso_id->maj == 0 && dso_id->min == 0)))
- level = 's';
- else if (!map)
- level = 'X';
---- a/tools/perf/util/synthetic-events.c
-+++ b/tools/perf/util/synthetic-events.c
-@@ -371,7 +371,7 @@ static void perf_record_mmap2__read_buil
- struct nsinfo *nsi;
- struct nscookie nc;
- struct dso *dso = NULL;
-- struct dso_id id;
-+ struct dso_id dso_id = dso_id_empty;
- int rc;
-
- if (is_kernel) {
-@@ -379,12 +379,18 @@ static void perf_record_mmap2__read_buil
- goto out;
- }
-
-- id.maj = event->maj;
-- id.min = event->min;
-- id.ino = event->ino;
-- id.ino_generation = event->ino_generation;
-+ if (event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) {
-+ build_id__init(&dso_id.build_id, event->build_id, event->build_id_size);
-+ } else {
-+ dso_id.maj = event->maj;
-+ dso_id.min = event->min;
-+ dso_id.ino = event->ino;
-+ dso_id.ino_generation = event->ino_generation;
-+ dso_id.mmap2_valid = true;
-+ dso_id.mmap2_ino_generation_valid = true;
-+ };
-
-- dso = dsos__findnew_id(&machine->dsos, event->filename, &id);
-+ dso = dsos__findnew_id(&machine->dsos, event->filename, &dso_id);
- if (dso && dso__has_build_id(dso)) {
- bid = *dso__bid(dso);
- rc = 0;
+++ /dev/null
-From a70398930ce7dc7600103884f6fe27e710d871ea Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Mon, 17 Mar 2025 21:31:50 -0700
-Subject: perf dso: Use lock annotations to fix asan deadlock
-
-From: Ian Rogers <irogers@google.com>
-
-[ Upstream commit 5ac22c35aa8519f1fb5522fb3aff915f28a9365e ]
-
-dso__list_del with address sanitizer and/or reference count checking
-will call dso__put that can call dso__data_close reentrantly trying to
-lock the dso__data_open_lock and deadlocking. Switch from pthread
-mutexes to perf's mutex so that lock checking is performed in debug
-builds. Add lock annotations that diagnosed the problem. Release the
-dso__data_open_lock around the dso__put to avoid the deadlock.
-
-Change the declaration of dso__data_get_fd to return a boolean,
-indicating the fd is valid and the lock is held, to make it compatible
-with the thread safety annotations as a try lock.
-
-Signed-off-by: Ian Rogers <irogers@google.com>
-Link: https://lore.kernel.org/r/20250318043151.137973-3-irogers@google.com
-Signed-off-by: Namhyung Kim <namhyung@kernel.org>
-Stable-dep-of: 2ea64782a428 ("perf tools: Use snprintf() in dso__read_running_kernel_build_id()")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/tests/dso-data.c | 4 +-
- tools/perf/util/dso.c | 74 +++++++++++++++---------
- tools/perf/util/dso.h | 15 +++--
- tools/perf/util/unwind-libunwind-local.c | 16 ++---
- 4 files changed, 66 insertions(+), 43 deletions(-)
-
-diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c
-index 5286ae8bd2d70d..06be7c5d849554 100644
---- a/tools/perf/tests/dso-data.c
-+++ b/tools/perf/tests/dso-data.c
-@@ -106,9 +106,9 @@ struct test_data_offset offsets[] = {
- /* move it from util/dso.c for compatibility */
- static int dso__data_fd(struct dso *dso, struct machine *machine)
- {
-- int fd = dso__data_get_fd(dso, machine);
-+ int fd = -1;
-
-- if (fd >= 0)
-+ if (dso__data_get_fd(dso, machine, &fd))
- dso__data_put_fd(dso);
-
- return fd;
-diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
-index 5c6e85fdae0de1..05a9dc6f41f820 100644
---- a/tools/perf/util/dso.c
-+++ b/tools/perf/util/dso.c
-@@ -490,11 +490,25 @@ void dso__set_module_info(struct dso *dso, struct kmod_path *m,
- /*
- * Global list of open DSOs and the counter.
- */
-+struct mutex _dso__data_open_lock;
- static LIST_HEAD(dso__data_open);
--static long dso__data_open_cnt;
--static pthread_mutex_t dso__data_open_lock = PTHREAD_MUTEX_INITIALIZER;
-+static long dso__data_open_cnt GUARDED_BY(_dso__data_open_lock);
-
--static void dso__list_add(struct dso *dso)
-+static void dso__data_open_lock_init(void)
-+{
-+ mutex_init(&_dso__data_open_lock);
-+}
-+
-+static struct mutex *dso__data_open_lock(void) LOCK_RETURNED(_dso__data_open_lock)
-+{
-+ static pthread_once_t data_open_lock_once = PTHREAD_ONCE_INIT;
-+
-+ pthread_once(&data_open_lock_once, dso__data_open_lock_init);
-+
-+ return &_dso__data_open_lock;
-+}
-+
-+static void dso__list_add(struct dso *dso) EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock)
- {
- list_add_tail(&dso__data(dso)->open_entry, &dso__data_open);
- #ifdef REFCNT_CHECKING
-@@ -505,11 +519,13 @@ static void dso__list_add(struct dso *dso)
- dso__data_open_cnt++;
- }
-
--static void dso__list_del(struct dso *dso)
-+static void dso__list_del(struct dso *dso) EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock)
- {
- list_del_init(&dso__data(dso)->open_entry);
- #ifdef REFCNT_CHECKING
-+ mutex_unlock(dso__data_open_lock());
- dso__put(dso__data(dso)->dso);
-+ mutex_lock(dso__data_open_lock());
- #endif
- WARN_ONCE(dso__data_open_cnt <= 0,
- "DSO data fd counter out of bounds.");
-@@ -518,7 +534,7 @@ static void dso__list_del(struct dso *dso)
-
- static void close_first_dso(void);
-
--static int do_open(char *name)
-+static int do_open(char *name) EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock)
- {
- int fd;
- char sbuf[STRERR_BUFSIZE];
-@@ -545,6 +561,7 @@ char *dso__filename_with_chroot(const struct dso *dso, const char *filename)
- }
-
- static int __open_dso(struct dso *dso, struct machine *machine)
-+ EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock)
- {
- int fd = -EINVAL;
- char *root_dir = (char *)"";
-@@ -610,6 +627,7 @@ static void check_data_close(void);
- * list/count of open DSO objects.
- */
- static int open_dso(struct dso *dso, struct machine *machine)
-+ EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock)
- {
- int fd;
- struct nscookie nsc;
-@@ -635,7 +653,7 @@ static int open_dso(struct dso *dso, struct machine *machine)
- return fd;
- }
-
--static void close_data_fd(struct dso *dso)
-+static void close_data_fd(struct dso *dso) EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock)
- {
- if (dso__data(dso)->fd >= 0) {
- close(dso__data(dso)->fd);
-@@ -652,12 +670,12 @@ static void close_data_fd(struct dso *dso)
- * Close @dso's data file descriptor and updates
- * list/count of open DSO objects.
- */
--static void close_dso(struct dso *dso)
-+static void close_dso(struct dso *dso) EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock)
- {
- close_data_fd(dso);
- }
-
--static void close_first_dso(void)
-+static void close_first_dso(void) EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock)
- {
- struct dso_data *dso_data;
- struct dso *dso;
-@@ -702,7 +720,7 @@ void reset_fd_limit(void)
- fd_limit = 0;
- }
-
--static bool may_cache_fd(void)
-+static bool may_cache_fd(void) EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock)
- {
- if (!fd_limit)
- fd_limit = get_fd_limit();
-@@ -718,7 +736,7 @@ static bool may_cache_fd(void)
- * for opened dso file descriptors. The limit is half
- * of the RLIMIT_NOFILE files opened.
- */
--static void check_data_close(void)
-+static void check_data_close(void) EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock)
- {
- bool cache_fd = may_cache_fd();
-
-@@ -734,12 +752,13 @@ static void check_data_close(void)
- */
- void dso__data_close(struct dso *dso)
- {
-- pthread_mutex_lock(&dso__data_open_lock);
-+ mutex_lock(dso__data_open_lock());
- close_dso(dso);
-- pthread_mutex_unlock(&dso__data_open_lock);
-+ mutex_unlock(dso__data_open_lock());
- }
-
- static void try_to_open_dso(struct dso *dso, struct machine *machine)
-+ EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock)
- {
- enum dso_binary_type binary_type_data[] = {
- DSO_BINARY_TYPE__BUILD_ID_CACHE,
-@@ -781,25 +800,27 @@ static void try_to_open_dso(struct dso *dso, struct machine *machine)
- * returns file descriptor. It should be paired with
- * dso__data_put_fd() if it returns non-negative value.
- */
--int dso__data_get_fd(struct dso *dso, struct machine *machine)
-+bool dso__data_get_fd(struct dso *dso, struct machine *machine, int *fd)
- {
-+ *fd = -1;
- if (dso__data(dso)->status == DSO_DATA_STATUS_ERROR)
-- return -1;
-+ return false;
-
-- if (pthread_mutex_lock(&dso__data_open_lock) < 0)
-- return -1;
-+ mutex_lock(dso__data_open_lock());
-
- try_to_open_dso(dso, machine);
-
-- if (dso__data(dso)->fd < 0)
-- pthread_mutex_unlock(&dso__data_open_lock);
-+ *fd = dso__data(dso)->fd;
-+ if (*fd >= 0)
-+ return true;
-
-- return dso__data(dso)->fd;
-+ mutex_unlock(dso__data_open_lock());
-+ return false;
- }
-
- void dso__data_put_fd(struct dso *dso __maybe_unused)
- {
-- pthread_mutex_unlock(&dso__data_open_lock);
-+ mutex_unlock(dso__data_open_lock());
- }
-
- bool dso__data_status_seen(struct dso *dso, enum dso_data_status_seen by)
-@@ -951,7 +972,7 @@ static ssize_t file_read(struct dso *dso, struct machine *machine,
- {
- ssize_t ret;
-
-- pthread_mutex_lock(&dso__data_open_lock);
-+ mutex_lock(dso__data_open_lock());
-
- /*
- * dso__data(dso)->fd might be closed if other thread opened another
-@@ -967,7 +988,7 @@ static ssize_t file_read(struct dso *dso, struct machine *machine,
-
- ret = pread(dso__data(dso)->fd, data, DSO__DATA_CACHE_SIZE, offset);
- out:
-- pthread_mutex_unlock(&dso__data_open_lock);
-+ mutex_unlock(dso__data_open_lock());
- return ret;
- }
-
-@@ -1075,7 +1096,7 @@ static int file_size(struct dso *dso, struct machine *machine)
- struct stat st;
- char sbuf[STRERR_BUFSIZE];
-
-- pthread_mutex_lock(&dso__data_open_lock);
-+ mutex_lock(dso__data_open_lock());
-
- /*
- * dso__data(dso)->fd might be closed if other thread opened another
-@@ -1099,7 +1120,7 @@ static int file_size(struct dso *dso, struct machine *machine)
- dso__data(dso)->file_size = st.st_size;
-
- out:
-- pthread_mutex_unlock(&dso__data_open_lock);
-+ mutex_unlock(dso__data_open_lock());
- return ret;
- }
-
-@@ -1608,11 +1629,10 @@ size_t dso__fprintf(struct dso *dso, FILE *fp)
-
- enum dso_type dso__type(struct dso *dso, struct machine *machine)
- {
-- int fd;
-+ int fd = -1;
- enum dso_type type = DSO__TYPE_UNKNOWN;
-
-- fd = dso__data_get_fd(dso, machine);
-- if (fd >= 0) {
-+ if (dso__data_get_fd(dso, machine, &fd)) {
- type = dso__type_fd(fd);
- dso__data_put_fd(dso);
- }
-diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h
-index c0472a41147c3c..53eac48fd0cd81 100644
---- a/tools/perf/util/dso.h
-+++ b/tools/perf/util/dso.h
-@@ -231,6 +231,8 @@ DECLARE_RC_STRUCT(dso) {
- char name[];
- };
-
-+extern struct mutex _dso__data_open_lock;
-+
- /* dso__for_each_symbol - iterate over the symbols of given type
- *
- * @dso: the 'struct dso *' in which symbols are iterated
-@@ -652,7 +654,7 @@ void __dso__inject_id(struct dso *dso, const struct dso_id *id);
- int dso__name_len(const struct dso *dso);
-
- struct dso *dso__get(struct dso *dso);
--void dso__put(struct dso *dso);
-+void dso__put(struct dso *dso) LOCKS_EXCLUDED(_dso__data_open_lock);
-
- static inline void __dso__zput(struct dso **dso)
- {
-@@ -732,8 +734,8 @@ void dso__set_module_info(struct dso *dso, struct kmod_path *m,
- * The current usage of the dso__data_* interface is as follows:
- *
- * Get DSO's fd:
-- * int fd = dso__data_get_fd(dso, machine);
-- * if (fd >= 0) {
-+ * int fd;
-+ * if (dso__data_get_fd(dso, machine, &fd)) {
- * USE 'fd' SOMEHOW
- * dso__data_put_fd(dso);
- * }
-@@ -755,9 +757,10 @@ void dso__set_module_info(struct dso *dso, struct kmod_path *m,
- *
- * TODO
- */
--int dso__data_get_fd(struct dso *dso, struct machine *machine);
--void dso__data_put_fd(struct dso *dso);
--void dso__data_close(struct dso *dso);
-+bool dso__data_get_fd(struct dso *dso, struct machine *machine, int *fd)
-+ EXCLUSIVE_TRYLOCK_FUNCTION(true, _dso__data_open_lock);
-+void dso__data_put_fd(struct dso *dso) UNLOCK_FUNCTION(_dso__data_open_lock);
-+void dso__data_close(struct dso *dso) LOCKS_EXCLUDED(_dso__data_open_lock);
-
- int dso__data_file_size(struct dso *dso, struct machine *machine);
- off_t dso__data_size(struct dso *dso, struct machine *machine);
-diff --git a/tools/perf/util/unwind-libunwind-local.c b/tools/perf/util/unwind-libunwind-local.c
-index 16c2b03831f39a..4c076eba86226c 100644
---- a/tools/perf/util/unwind-libunwind-local.c
-+++ b/tools/perf/util/unwind-libunwind-local.c
-@@ -330,8 +330,7 @@ static int read_unwind_spec_eh_frame(struct dso *dso, struct unwind_info *ui,
- int ret, fd;
-
- if (dso__data(dso)->eh_frame_hdr_offset == 0) {
-- fd = dso__data_get_fd(dso, ui->machine);
-- if (fd < 0)
-+ if (!dso__data_get_fd(dso, ui->machine, &fd))
- return -EINVAL;
-
- /* Check the .eh_frame section for unwinding info */
-@@ -372,8 +371,7 @@ static int read_unwind_spec_debug_frame(struct dso *dso,
- * has to be pointed by symsrc_filename
- */
- if (ofs == 0) {
-- fd = dso__data_get_fd(dso, machine);
-- if (fd >= 0) {
-+ if (dso__data_get_fd(dso, machine, &fd) {
- ofs = elf_section_offset(fd, ".debug_frame");
- dso__data_put_fd(dso);
- }
-@@ -485,14 +483,16 @@ find_proc_info(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi,
- /* Check the .debug_frame section for unwinding info */
- if (ret < 0 &&
- !read_unwind_spec_debug_frame(dso, ui->machine, &segbase)) {
-- int fd = dso__data_get_fd(dso, ui->machine);
-- int is_exec = elf_is_exec(fd, dso__name(dso));
-+ int fd;
- u64 start = map__start(map);
-- unw_word_t base = is_exec ? 0 : start;
-+ unw_word_t base = start;
- const char *symfile;
-
-- if (fd >= 0)
-+ if (dso__data_get_fd(dso, ui->machine, &fd)) {
-+ if (elf_is_exec(fd, dso__name(dso)))
-+ base = 0;
- dso__data_put_fd(dso);
-+ }
-
- symfile = dso__symsrc_filename(dso) ?: dso__name(dso);
-
---
-2.53.0
-
+++ /dev/null
-From 74a5f7a871a93ad8a713e5dc3674dea340ebb385 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 2 May 2026 14:37:39 -0300
-Subject: perf header: Sanity check HEADER_EVENT_DESC attr.size before swap
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 944f65c8b8231d26d4db6be67bcb641142603cb4 ]
-
-read_event_desc() reads nre (event count), sz (attr size), and nr
-(IDs per event) from the file and uses them to control allocations
-and loops without validating them against the section size.
-
-A crafted perf.data could trigger large allocations or many loop
-iterations before __do_read() eventually rejects the reads.
-
-Add bounds checks in read_event_desc():
-- Reject sz smaller than PERF_ATTR_SIZE_VER0.
-- Require at least one event (nre > 0).
-- Check that nre events fit in the remaining section, using the
- minimum per-event footprint of sz + sizeof(u32).
-- Pre-swap attr->size to native byte order, then reject values
- below PERF_ATTR_SIZE_VER0 or above sz before calling
- perf_event__attr_swap() to prevent heap out-of-bounds access.
-- Handle ABI0 (attr.size == 0): substitute PERF_ATTR_SIZE_VER0,
- and on native-endian files write the value back so
- free_event_desc() does not treat the zero as its end-of-array
- sentinel (it iterates while attr.size != 0). The swap path
- skips the write-back — perf_event__attr_swap() has its own
- ABI0 fallback that sets VER0 after swapping.
-- Check that nr IDs fit in the remaining section before allocating.
-
-Fixes: b30b61729246 ("perf tools: Fix a problem when opening old perf.data with different byte order")
-Reported-by: sashiko-bot@kernel.org # Running on a local machine
-Reviewed-by: Ian Rogers <irogers@google.com>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Cc: Wang Nan <wangnan0@huawei.com>
-Assisted-by: Claude:claude-opus-4.6-1m
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/header.c | 54 ++++++++++++++++++++++++++++++++++++++++
- 1 file changed, 54 insertions(+)
-
-diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
-index 7b99f58f7bf269..8c15ce90466997 100644
---- a/tools/perf/util/header.c
-+++ b/tools/perf/util/header.c
-@@ -1940,9 +1940,28 @@ static struct evsel *read_event_desc(struct feat_fd *ff)
- if (do_read_u32(ff, &nre))
- goto error;
-
-+ /* Size of each of the nre attributes. */
- if (do_read_u32(ff, &sz))
- goto error;
-
-+ /*
-+ * Require at least one event with an attr no smaller than the
-+ * first published struct, and reject sz values where
-+ * sz + sizeof(u32) would overflow size_t (possible on 32-bit)
-+ * or nre == UINT32_MAX where nre + 1 wraps to 0 in the calloc.
-+ *
-+ * The minimum section footprint per event is sz bytes for the
-+ * attr plus a u32 for the id count, check that nre events fit.
-+ */
-+ if (!nre || sz < PERF_ATTR_SIZE_VER0 ||
-+ sz > ff->size || (size_t)sz > SIZE_MAX - sizeof(u32) ||
-+ nre == UINT32_MAX ||
-+ nre > (ff->size - ff->offset) / (sz + sizeof(u32))) {
-+ pr_err("Invalid HEADER_EVENT_DESC: nre=%u sz=%u (min %d)\n",
-+ nre, sz, PERF_ATTR_SIZE_VER0);
-+ goto error;
-+ }
-+
- /* buffer to hold on file attr struct */
- buf = malloc(sz);
- if (!buf)
-@@ -1958,6 +1977,9 @@ static struct evsel *read_event_desc(struct feat_fd *ff)
- msz = sz;
-
- for (i = 0, evsel = events; i < nre; evsel++, i++) {
-+ struct perf_event_attr *attr = buf;
-+ u32 attr_size;
-+
- evsel->core.idx = i;
-
- /*
-@@ -1967,6 +1989,32 @@ static struct evsel *read_event_desc(struct feat_fd *ff)
- if (__do_read(ff, buf, sz))
- goto error;
-
-+ /* Reject before attr_swap to prevent OOB via bswap_safe() */
-+ attr_size = ff->ph->needs_swap ? bswap_32(attr->size) : attr->size;
-+ /* ABI0: size == 0 means the producer didn't set it */
-+ if (!attr_size) {
-+ attr_size = PERF_ATTR_SIZE_VER0;
-+ /*
-+ * Write back so free_event_desc() doesn't
-+ * treat this event as the end-of-array sentinel
-+ * (it iterates while attr.size != 0).
-+ *
-+ * Only for native — the swap path must NOT
-+ * write native-endian VER0 here because
-+ * perf_event__attr_swap() would re-swap it
-+ * to 0x40000000, defeating bswap_safe() bounds.
-+ * perf_event__attr_swap() has its own ABI0
-+ * fallback that sets VER0 after swapping.
-+ */
-+ if (!ff->ph->needs_swap)
-+ attr->size = attr_size;
-+ }
-+ if (attr_size < PERF_ATTR_SIZE_VER0 || attr_size > sz) {
-+ pr_err("Event %d attr.size (%u) invalid (min: %d, max: %u)\n",
-+ i, attr_size, PERF_ATTR_SIZE_VER0, sz);
-+ goto error;
-+ }
-+
- if (ff->ph->needs_swap)
- perf_event__attr_swap(buf);
-
-@@ -1988,6 +2036,12 @@ static struct evsel *read_event_desc(struct feat_fd *ff)
- if (!nr)
- continue;
-
-+ /* Prevent oversized allocation from crafted nr */
-+ if (nr > (ff->size - ff->offset) / sizeof(*id)) {
-+ pr_err("Event %d: id count %u exceeds remaining section\n", i, nr);
-+ goto error;
-+ }
-+
- id = calloc(nr, sizeof(*id));
- if (!id)
- goto error;
---
-2.53.0
-
+++ /dev/null
-From 6da2b221ce3fa75964b2932e94385d3057641443 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sun, 7 Jun 2026 14:35:28 -0300
-Subject: perf hists: Fix snprintf() in hists__scnprintf_title() UID filter
- path
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 227a8748742f0263f1fe3131449b44563b77a209 ]
-
-hists__scnprintf_title() accumulates formatted output into a buffer
-using scnprintf() for all filter clauses except the UID filter, which
-uses snprintf(). If the buffer fills up and snprintf() returns more
-than the remaining space, printed exceeds size and the next 'size -
-printed' underflows, causing later scnprintf() calls to write past
-the buffer.
-
-Switch the UID filter clause to scnprintf() to match the rest of the
-function.
-
-Fixes: 25c312dbf88ca402 ("perf hists: Move hists__scnprintf_title() away from the TUI code")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Reviewed-by: Ian Rogers <irogers@google.com>
-Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/hist.c | 7 ++++---
- 1 file changed, 4 insertions(+), 3 deletions(-)
-
-diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c
-index 694faf405e11cf..299bfca446a698 100644
---- a/tools/perf/util/hist.c
-+++ b/tools/perf/util/hist.c
-@@ -2816,9 +2816,10 @@ int __hists__scnprintf_title(struct hists *hists, char *bf, size_t size, bool sh
- ev_name, sample_freq_str, enable_ref ? ref : " ", nr_events);
-
-
-- if (hists->uid_filter_str)
-- printed += snprintf(bf + printed, size - printed,
-- ", UID: %s", hists->uid_filter_str);
-+ if (hists->uid_filter_str) {
-+ printed += scnprintf(bf + printed, size - printed,
-+ ", UID: %s", hists->uid_filter_str);
-+ }
- if (thread) {
- if (hists__has(hists, thread)) {
- printed += scnprintf(bf + printed, size - printed,
---
-2.53.0
-
+++ /dev/null
-From b386345dedddd6b741c541fa75444d7e1ad04de8 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Tue, 13 Jan 2026 15:29:02 -0800
-Subject: perf inject: Add --convert-callchain option
-
-From: Namhyung Kim <namhyung@kernel.org>
-
-[ Upstream commit 92ea788d2af4e65ad7a144ccfff50667e9a0d227 ]
-
-There are applications not built with frame pointers, so DWARF is needed
-to get the stack traces.
-
-`perf record --call-graph dwarf` saves the stack and register data for
-each sample to get the stacktrace offline. But sometimes this data may
-have sensitive information and we don't want to keep them in the file.
-
-This new 'perf inject --convert-callchain' option creates the callchains
-and discards the stack and register after that.
-
-This saves storage space and processing time for the new data file.
-
-Of course, users should remove the original data file to not keep
-sensitive data around. :)
-
-The down side is that it cannot handle inlined callchain entries as they
-all have the same IPs.
-
-Maybe we can add an option to 'perf report' to look up inlined functions
-using DWARF - IIUC it doesn't require stack and register data.
-
-This is an example.
-
- $ perf record --call-graph dwarf -- perf test -w noploop
-
- $ perf report --stdio --no-children --percent-limit=0 > output-prev
-
- $ perf inject -i perf.data --convert-callchain -o perf.data.out
-
- $ perf report --stdio --no-children --percent-limit=0 -i perf.data.out > output-next
-
- $ diff -u output-prev output-next
- ...
- 0.23% perf ld-linux-x86-64.so.2 [.] _dl_relocate_object_no_relro
- |
- - ---elf_dynamic_do_Rela (inlined)
- - _dl_relocate_object_no_relro
- + ---_dl_relocate_object_no_relro
- _dl_relocate_object
- dl_main
- _dl_sysdep_start
- - _dl_start_final (inlined)
- _dl_start
- _start
-
-Reviewed-by: Ian Rogers <irogers@google.com>
-Signed-off-by: Namhyung Kim <namhyung@kernel.org>
-Cc: Adrian Hunter <adrian.hunter@intel.com>
-Cc: Ingo Molnar <mingo@kernel.org>
-Cc: James Clark <james.clark@linaro.org>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Peter Zijlstra <peterz@infradead.org>
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Stable-dep-of: 059e9100d82a ("perf event: Fix size of synthesized sample with branch stacks")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/Documentation/perf-inject.txt | 5 +
- tools/perf/builtin-inject.c | 152 +++++++++++++++++++++++
- 2 files changed, 157 insertions(+)
-
-diff --git a/tools/perf/Documentation/perf-inject.txt b/tools/perf/Documentation/perf-inject.txt
-index c972032f4ca0d2..95dfdf39666efe 100644
---- a/tools/perf/Documentation/perf-inject.txt
-+++ b/tools/perf/Documentation/perf-inject.txt
-@@ -109,6 +109,11 @@ include::itrace.txt[]
- should be used, and also --buildid-all and --switch-events may be
- useful.
-
-+--convert-callchain::
-+ Parse DWARF callchains and convert them to usual callchains. This also
-+ discards stack and register data from the samples. This will lose
-+ inlined callchain entries.
-+
- :GMEXAMPLECMD: inject
- :GMEXAMPLESUBCMD:
- include::guestmount.txt[]
-diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
-index 11e49cafa3af9d..241d7e36914872 100644
---- a/tools/perf/builtin-inject.c
-+++ b/tools/perf/builtin-inject.c
-@@ -122,6 +122,7 @@ struct perf_inject {
- bool in_place_update;
- bool in_place_update_dry_run;
- bool copy_kcore_dir;
-+ bool convert_callchain;
- const char *input_name;
- struct perf_data output;
- u64 bytes_written;
-@@ -133,6 +134,7 @@ struct perf_inject {
- struct guest_session guest_session;
- struct strlist *known_build_ids;
- const struct evsel *mmap_evsel;
-+ struct ip_callchain *raw_callchain;
- };
-
- struct event_entry {
-@@ -396,6 +398,90 @@ static int perf_event__repipe_sample(const struct perf_tool *tool,
- return perf_event__repipe_synth(tool, event);
- }
-
-+static int perf_event__convert_sample_callchain(const struct perf_tool *tool,
-+ union perf_event *event,
-+ struct perf_sample *sample,
-+ struct evsel *evsel,
-+ struct machine *machine)
-+{
-+ struct perf_inject *inject = container_of(tool, struct perf_inject, tool);
-+ struct callchain_cursor *cursor = get_tls_callchain_cursor();
-+ union perf_event *event_copy = (void *)inject->event_copy;
-+ struct callchain_cursor_node *node;
-+ struct thread *thread;
-+ u64 sample_type = evsel->core.attr.sample_type;
-+ u32 sample_size = event->header.size;
-+ u64 i, k;
-+ int ret;
-+
-+ if (event_copy == NULL) {
-+ inject->event_copy = malloc(PERF_SAMPLE_MAX_SIZE);
-+ if (!inject->event_copy)
-+ return -ENOMEM;
-+
-+ event_copy = (void *)inject->event_copy;
-+ }
-+
-+ if (cursor == NULL)
-+ return -ENOMEM;
-+
-+ callchain_cursor_reset(cursor);
-+
-+ thread = machine__find_thread(machine, sample->tid, sample->pid);
-+ if (thread == NULL)
-+ goto out;
-+
-+ /* this will parse DWARF using stack and register data */
-+ ret = thread__resolve_callchain(thread, cursor, evsel, sample,
-+ /*parent=*/NULL, /*root_al=*/NULL,
-+ PERF_MAX_STACK_DEPTH);
-+ thread__put(thread);
-+ if (ret != 0)
-+ goto out;
-+
-+ /* copy kernel callchain and context entries */
-+ for (i = 0; i < sample->callchain->nr; i++) {
-+ inject->raw_callchain->ips[i] = sample->callchain->ips[i];
-+ if (sample->callchain->ips[i] == PERF_CONTEXT_USER) {
-+ i++;
-+ break;
-+ }
-+ }
-+ if (i == 0 || inject->raw_callchain->ips[i - 1] != PERF_CONTEXT_USER)
-+ inject->raw_callchain->ips[i++] = PERF_CONTEXT_USER;
-+
-+ node = cursor->first;
-+ for (k = 0; k < cursor->nr && i < PERF_MAX_STACK_DEPTH; k++) {
-+ if (machine__kernel_ip(machine, node->ip))
-+ /* kernel IPs were added already */;
-+ else if (node->ms.sym && node->ms.sym->inlined)
-+ /* we can't handle inlined callchains */;
-+ else
-+ inject->raw_callchain->ips[i++] = node->ip;
-+
-+ node = node->next;
-+ }
-+
-+ inject->raw_callchain->nr = i;
-+ sample->callchain = inject->raw_callchain;
-+
-+out:
-+ memcpy(event_copy, event, sizeof(event->header));
-+
-+ /* adjust sample size for stack and regs */
-+ sample_size -= sample->user_stack.size;
-+ sample_size -= (hweight64(evsel->core.attr.sample_regs_user) + 1) * sizeof(u64);
-+ sample_size += (sample->callchain->nr + 1) * sizeof(u64);
-+ event_copy->header.size = sample_size;
-+
-+ /* remove sample_type {STACK,REGS}_USER for synthesize */
-+ sample_type &= ~(PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER);
-+
-+ perf_event__synthesize_sample(event_copy, sample_type,
-+ evsel->core.attr.read_format, sample);
-+ return perf_event__repipe_synth(tool, event_copy);
-+}
-+
- static struct dso *findnew_dso(int pid, int tid, const char *filename,
- const struct dso_id *id, struct machine *machine)
- {
-@@ -2274,6 +2360,15 @@ static int __cmd_inject(struct perf_inject *inject)
- /* Allow space in the header for guest attributes */
- output_data_offset += gs->session->header.data_offset;
- output_data_offset = roundup(output_data_offset, 4096);
-+ } else if (inject->convert_callchain) {
-+ inject->tool.sample = perf_event__convert_sample_callchain;
-+ inject->tool.fork = perf_event__repipe_fork;
-+ inject->tool.comm = perf_event__repipe_comm;
-+ inject->tool.exit = perf_event__repipe_exit;
-+ inject->tool.mmap = perf_event__repipe_mmap;
-+ inject->tool.mmap2 = perf_event__repipe_mmap2;
-+ inject->tool.ordered_events = true;
-+ inject->tool.ordering_requires_timestamps = true;
- }
-
- if (!inject->itrace_synth_opts.set)
-@@ -2326,6 +2421,23 @@ static int __cmd_inject(struct perf_inject *inject)
- perf_header__set_feat(&session->header,
- HEADER_BRANCH_STACK);
- }
-+
-+ /*
-+ * The converted data file won't have stack and registers.
-+ * Update the perf_event_attr to remove them before writing.
-+ */
-+ if (inject->convert_callchain) {
-+ struct evsel *evsel;
-+
-+ evlist__for_each_entry(session->evlist, evsel) {
-+ evsel__reset_sample_bit(evsel, REGS_USER);
-+ evsel__reset_sample_bit(evsel, STACK_USER);
-+ evsel->core.attr.sample_regs_user = 0;
-+ evsel->core.attr.sample_stack_user = 0;
-+ evsel->core.attr.exclude_callchain_user = 0;
-+ }
-+ }
-+
- session->header.data_offset = output_data_offset;
- session->header.data_size = inject->bytes_written;
- perf_session__inject_header(session, session->evlist, fd, &inj_fc.fc,
-@@ -2350,6 +2462,18 @@ static int __cmd_inject(struct perf_inject *inject)
- return ret;
- }
-
-+static bool evsel__has_dwarf_callchain(struct evsel *evsel)
-+{
-+ struct perf_event_attr *attr = &evsel->core.attr;
-+ const u64 dwarf_callchain_flags =
-+ PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_CALLCHAIN;
-+
-+ if (!attr->exclude_callchain_user)
-+ return false;
-+
-+ return (attr->sample_type & dwarf_callchain_flags) == dwarf_callchain_flags;
-+}
-+
- int cmd_inject(int argc, const char **argv)
- {
- struct perf_inject inject = {
-@@ -2418,6 +2542,8 @@ int cmd_inject(int argc, const char **argv)
- OPT_STRING(0, "guestmount", &symbol_conf.guestmount, "directory",
- "guest mount directory under which every guest os"
- " instance has a subdir"),
-+ OPT_BOOLEAN(0, "convert-callchain", &inject.convert_callchain,
-+ "Generate callchains using DWARF and drop register/stack data"),
- OPT_END()
- };
- const char * const inject_usage[] = {
-@@ -2433,6 +2559,9 @@ int cmd_inject(int argc, const char **argv)
-
- #ifndef HAVE_JITDUMP
- set_option_nobuild(options, 'j', "jit", "NO_LIBELF=1", true);
-+#endif
-+#ifndef HAVE_LIBDW_SUPPORT
-+ set_option_nobuild(options, 0, "convert-callchain", "NO_LIBDW=1", true);
- #endif
- argc = parse_options(argc, argv, options, inject_usage, 0);
-
-@@ -2589,6 +2718,28 @@ int cmd_inject(int argc, const char **argv)
- }
- }
-
-+ if (inject.convert_callchain) {
-+ struct evsel *evsel;
-+
-+ if (inject.output.is_pipe || inject.session->data->is_pipe) {
-+ pr_err("--convert-callchain cannot work with pipe\n");
-+ goto out_delete;
-+ }
-+
-+ evlist__for_each_entry(inject.session->evlist, evsel) {
-+ if (!evsel__has_dwarf_callchain(evsel)) {
-+ pr_err("--convert-callchain requires DWARF call graph.\n");
-+ goto out_delete;
-+ }
-+ }
-+
-+ inject.raw_callchain = calloc(PERF_MAX_STACK_DEPTH, sizeof(u64));
-+ if (inject.raw_callchain == NULL) {
-+ pr_err("callchain allocation failed\n");
-+ goto out_delete;
-+ }
-+ }
-+
- #ifdef HAVE_JITDUMP
- if (inject.jit_mode) {
- inject.tool.mmap2 = perf_event__repipe_mmap2;
-@@ -2619,5 +2770,6 @@ int cmd_inject(int argc, const char **argv)
- free(inject.itrace_synth_opts.vm_tm_corr_args);
- free(inject.event_copy);
- free(inject.guest_session.ev.event_buf);
-+ free(inject.raw_callchain);
- return ret;
- }
---
-2.53.0
-
+++ /dev/null
-From e786a04b4a5461dd7e2829422314a5a6d5a664d9 Mon Sep 17 00:00:00 2001
-From: Ian Rogers <irogers@google.com>
-Date: Thu, 22 Jan 2026 09:58:46 -0800
-Subject: perf inject: With --convert-callchain ignore the dummy event for dwarf stacks
-
-From: Ian Rogers <irogers@google.com>
-
-commit e786a04b4a5461dd7e2829422314a5a6d5a664d9 upstream.
-
-On hybrid systems there is generally >1 event and a dummy event.
-
-The perf inject --convert-callchain option is failing to convert
-perf.data files on such systems reporting "--convert-callchain requires
-DWARF call graph."
-
-The failing event is the dummy event that doesn't need to be set up for
-samples.
-
-As such ignore this event when checking the evsels.
-
-Fixes: 92ea788d2af4e65a ("perf inject: Add --convert-callchain option")
-Signed-off-by: Ian Rogers <irogers@google.com>
-Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Cc: Adrian Hunter <adrian.hunter@intel.com>
-Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
-Cc: Ingo Molnar <mingo@redhat.com>
-Cc: James Clark <james.clark@linaro.org>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Cc: Peter Zijlstra <peterz@infradead.org>
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
----
- tools/perf/builtin-inject.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
---- a/tools/perf/builtin-inject.c
-+++ b/tools/perf/builtin-inject.c
-@@ -2733,7 +2733,7 @@ int cmd_inject(int argc, const char **ar
- }
-
- evlist__for_each_entry(inject.session->evlist, evsel) {
-- if (!evsel__has_dwarf_callchain(evsel)) {
-+ if (!evsel__has_dwarf_callchain(evsel) && !evsel__is_dummy_event(evsel)) {
- pr_err("--convert-callchain requires DWARF call graph.\n");
- goto out_delete;
- }
+++ /dev/null
-From 552413aaffba1685e9586d523da9030f028f221b Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sun, 7 Jun 2026 21:06:54 -0300
-Subject: perf intel-pt: Fix snprintf size tracking bug in insn decoder
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit b6bb3b005dcdd960b8e0b7f9d6869132b3de08d5 ]
-
-dump_insn() tracks remaining buffer space with a 'left' variable,
-but the loop subtracts the cumulative offset 'n' each iteration
-instead of just the per-iteration delta:
-
- n += snprintf(x->out + n, left, "%02x ", inbuf[i]);
- left -= n; /* BUG: n is cumulative, not the delta */
-
-After two iterations left goes massively negative, wrapping to a
-huge value when passed as size_t to snprintf(), disabling all bounds
-checking for the rest of the loop.
-
-Switch to scnprintf() accumulation using sizeof(x->out) - n as the
-remaining space, which is always correct and eliminates the separate
-'left' variable entirely.
-
-Fixes: 48d02a1d5c137d36 ("perf script: Add 'brstackinsn' for branch stacks")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Adrian Hunter <adrian.hunter@intel.com>
-Cc: Andi Kleen <ak@linux.intel.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- .../util/intel-pt-decoder/intel-pt-insn-decoder.c | 11 +++--------
- 1 file changed, 3 insertions(+), 8 deletions(-)
-
-diff --git a/tools/perf/util/intel-pt-decoder/intel-pt-insn-decoder.c b/tools/perf/util/intel-pt-decoder/intel-pt-insn-decoder.c
-index 47cf35799a4dfc..8cd0c31e2b936a 100644
---- a/tools/perf/util/intel-pt-decoder/intel-pt-insn-decoder.c
-+++ b/tools/perf/util/intel-pt-decoder/intel-pt-insn-decoder.c
-@@ -223,7 +223,6 @@ const char *dump_insn(struct perf_insn *x, uint64_t ip __maybe_unused,
- {
- struct insn insn;
- int n, i, ret;
-- int left;
-
- ret = insn_decode(&insn, inbuf, inlen,
- x->is64bit ? INSN_MODE_64 : INSN_MODE_32);
-@@ -232,13 +231,9 @@ const char *dump_insn(struct perf_insn *x, uint64_t ip __maybe_unused,
- return "<bad>";
- if (lenp)
- *lenp = insn.length;
-- left = sizeof(x->out);
-- n = snprintf(x->out, left, "insn: ");
-- left -= n;
-- for (i = 0; i < insn.length; i++) {
-- n += snprintf(x->out + n, left, "%02x ", inbuf[i]);
-- left -= n;
-- }
-+ n = scnprintf(x->out, sizeof(x->out), "insn: ");
-+ for (i = 0; i < insn.length; i++)
-+ n += scnprintf(x->out + n, sizeof(x->out) - n, "%02x ", inbuf[i]);
- return x->out;
- }
-
---
-2.53.0
-
+++ /dev/null
-From 1293dacbbd43ab9848ac4655f6f2ba1dcc5a96ad Mon Sep 17 00:00:00 2001
-From: Arnaldo Carvalho de Melo <acme@kernel.org>
-Date: Wed, 9 Apr 2025 10:35:31 -0300
-Subject: perf libunwind arm64: Fix missing close parens in an if statement
-
-From: Arnaldo Carvalho de Melo <acme@kernel.org>
-
-commit 1293dacbbd43ab9848ac4655f6f2ba1dcc5a96ad upstream.
-
-While testing building with libunwind (using LIBUNWIND=1) in various
-arches I noticed a problem on arm64, on an rpi5 system, a missing close
-parens in a change related to dso__data_get_fd() usage, fix it.
-
-Fixes: 5ac22c35aa8519f1 ("perf dso: Use lock annotations to fix asan deadlock")
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Link: https://lore.kernel.org/r/Z_Z3o8KvB2i5c6ab@x1
-Signed-off-by: Namhyung Kim <namhyung@kernel.org>
-Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
----
- tools/perf/util/unwind-libunwind-local.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
---- a/tools/perf/util/unwind-libunwind-local.c
-+++ b/tools/perf/util/unwind-libunwind-local.c
-@@ -371,7 +371,7 @@ static int read_unwind_spec_debug_frame(
- * has to be pointed by symsrc_filename
- */
- if (ofs == 0) {
-- if (dso__data_get_fd(dso, machine, &fd) {
-+ if (dso__data_get_fd(dso, machine, &fd)) {
- ofs = elf_section_offset(fd, ".debug_frame");
- dso__data_put_fd(dso);
- }
+++ /dev/null
-From 97c215b8b1ecb9d11b3fdf20742eb525b27da551 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 13 Jun 2026 13:59:39 -0300
-Subject: perf machine: Use snprintf() for guestmount path construction
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit fe63d3bca288c5bb983304efd5fc3a5ff3183403 ]
-
-machines__findnew() and machines__create_guest_kernel_maps() use
-sprintf() to build paths by prepending symbol_conf.guestmount.
-Both 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.
-
-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:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- 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 71d3d034ba095e..b1817634996964 100644
---- a/tools/perf/util/machine.c
-+++ b/tools/perf/util/machine.c
-@@ -289,7 +289,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;
-
-@@ -1214,9 +1214,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.53.0
-
+++ /dev/null
-From 2468f12df21a946eec5d1654a289cda9cb90e6b9 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Thu, 11 Jun 2026 09:41:18 -0700
-Subject: perf maps: Add maps__mutate_mapping
-
-From: Ian Rogers <irogers@google.com>
-
-[ Upstream commit 75a4888b7029a1f98613aef91f517b2ee1f03d43 ]
-
-During kernel ELF symbol parsing (dso__process_kernel_symbol), proc
-kallsyms image loading (dso__load_kernel_sym,
-dso__load_guest_kernel_sym), and dynamic kernel memory map alignment
-updates (machine__update_kernel_mmap), the loader directly modifies live
-virtual address boundary keys fields on map objects.
-
-If these boundaries are mutated while the map pointer actively resides
-inside the parent maps cache array list (kmaps) outside of any lock
-closure, an unsafe concurrent window is exposed where parallel worker
-lookup threads (e.g., inside perf top) can mistakenly assume the cache
-remains sorted based on stale parameters, executing binary search
-queries (bsearch) across an unsorted range and triggering lookup
-failures.
-
-Fix this by introducing maps__mutate_mapping() that explicitly acquires
-the parent maps write semaphore lock, executes an incoming mutation
-callback block to perform the field updates under lock protection, and
-invalidates the sorted tracking flags prior to releasing the write lock.
-
-This guarantees synchronization invariants, closing the concurrent
-lookup race window. The adjacent module alignment pass inside
-machine__create_kernel_maps() is safely preserved as a high-performance
-lockless pass, as its invocation lifecycle bounds remain strictly
-single-threaded by contract during session initialization construction.
-
-To safely support this unconditional down_write write lock mutator
-without recursive read-to-write self-deadlock upgrades during lazy
-symbol loading, we introduce a public maps__load_maps() API.
-
-It copies map pointers under a brief read lock and force-loads all
-modules locklessly outside the lock. Callers (such as perf inject) must
-pre-load all kernel symbol maps up front at startup using
-maps__load_maps(), completely bypassing dynamic runtime mutations.
-
-Fixes: 39b12f781271 ("perf tools: Make it possible to read object code from vmlinux")
-
-Assisted-by: Antigravity:gemini-3.1-pro
-Signed-off-by: Ian Rogers <irogers@google.com>
-Tested-by: James Clark <james.clark@linaro.org>
-Cc: Adrian Hunter <adrian.hunter@intel.com>
-Cc: Gabriel Marin <gmx@google.com>
-Cc: Ingo Molnar <mingo@redhat.com>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Cc: Peter Zijlstra <peterz@infradead.org>
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/machine.c | 32 +++++---
- tools/perf/util/maps.c | 148 ++++++++++++++++++++++++++++-------
- tools/perf/util/maps.h | 3 +
- tools/perf/util/symbol-elf.c | 41 ++++++----
- tools/perf/util/symbol.c | 17 +++-
- 5 files changed, 183 insertions(+), 58 deletions(-)
-
-diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
-index 06a168ce5d9044..557853fcb5479b 100644
---- a/tools/perf/util/machine.c
-+++ b/tools/perf/util/machine.c
-@@ -1489,22 +1489,30 @@ static void machine__set_kernel_mmap(struct machine *machine,
- map__set_end(machine->vmlinux_map, ~0ULL);
- }
-
--static int machine__update_kernel_mmap(struct machine *machine,
-- u64 start, u64 end)
-+struct kernel_mmap_mutation_ctx {
-+ u64 start;
-+ u64 end;
-+};
-+
-+static int kernel_mmap_mutate_cb(struct map *map, void *data)
- {
-- struct map *orig, *updated;
-- int err;
-+ struct kernel_mmap_mutation_ctx *ctx = data;
-
-- orig = machine->vmlinux_map;
-- updated = map__get(orig);
-+ map__set_start(map, ctx->start);
-+ map__set_end(map, ctx->end);
-+ if (ctx->start == 0 && ctx->end == 0)
-+ map__set_end(map, ~0ULL);
-+ return 0;
-+}
-
-- machine->vmlinux_map = updated;
-- maps__remove(machine__kernel_maps(machine), orig);
-- machine__set_kernel_mmap(machine, start, end);
-- err = maps__insert(machine__kernel_maps(machine), updated);
-- map__put(orig);
-+static int machine__update_kernel_mmap(struct machine *machine,
-+ u64 start, u64 end)
-+{
-+ struct kernel_mmap_mutation_ctx ctx = { .start = start, .end = end };
-
-- return err;
-+ return maps__mutate_mapping(machine__kernel_maps(machine),
-+ machine->vmlinux_map,
-+ kernel_mmap_mutate_cb, &ctx);
- }
-
- int machine__create_kernel_maps(struct machine *machine)
-diff --git a/tools/perf/util/maps.c b/tools/perf/util/maps.c
-index 7b8f48677c3185..51726d8de2cfd1 100644
---- a/tools/perf/util/maps.c
-+++ b/tools/perf/util/maps.c
-@@ -539,6 +539,48 @@ void maps__remove(struct maps *maps, struct map *map)
- up_write(maps__lock(maps));
- }
-
-+/**
-+ * maps__mutate_mapping - Apply write-protected mutations to a map.
-+ * @maps: The maps collection containing the map.
-+ * @map: The map to mutate.
-+ * @mutate_cb: Callback function that performs the actual mutations.
-+ * @data: Private data passed to the callback.
-+ *
-+ * This acquires the write lock on the maps semaphore to safely protect
-+ * concurrent readers from seeing partially mutated or unsorted map boundaries.
-+ *
-+ * WARNING: Acquiring down_write() here can trigger a recursive self-deadlock if
-+ * the caller already holds the read lock (e.g., during maps__for_each_map() or
-+ * maps__find() iteration paths that trigger lazy symbol loading). To completely
-+ * avoid this deadlock, all kernel/module maps must be pre-loaded up-front (via
-+ * maps__load_maps()) under a clean, single-threaded context before entering
-+ * multi-threaded event processing loops.
-+ */
-+int maps__mutate_mapping(struct maps *maps, struct map *map,
-+ int (*mutate_cb)(struct map *map, void *data), void *data)
-+{
-+ int err = 0;
-+
-+ if (maps) {
-+ down_write(maps__lock(maps));
-+
-+ err = mutate_cb(map, data);
-+
-+ RC_CHK_ACCESS(maps)->maps_by_address_sorted = false;
-+ RC_CHK_ACCESS(maps)->maps_by_name_sorted = false;
-+
-+ up_write(maps__lock(maps));
-+
-+#ifdef HAVE_LIBDW_SUPPORT
-+ libdw__invalidate_dwfl(maps, maps__libdw_addr_space_dwfl(maps));
-+#endif
-+ } else {
-+ err = mutate_cb(map, data);
-+ }
-+
-+ return err;
-+}
-+
- bool maps__empty(struct maps *maps)
- {
- bool res;
-@@ -589,6 +631,41 @@ int maps__for_each_map(struct maps *maps, int (*cb)(struct map *map, void *data)
- return ret;
- }
-
-+int maps__load_maps(struct maps *maps)
-+{
-+ struct map **maps_copy;
-+ unsigned int nr_maps;
-+ int err = 0;
-+
-+ if (!maps)
-+ return 0;
-+
-+ down_read(maps__lock(maps));
-+ nr_maps = maps__nr_maps(maps);
-+ if (nr_maps == 0) {
-+ up_read(maps__lock(maps));
-+ return 0;
-+ }
-+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
-+ if (!maps_copy) {
-+ up_read(maps__lock(maps));
-+ return -ENOMEM;
-+ }
-+ for (unsigned int i = 0; i < nr_maps; i++)
-+ maps_copy[i] = map__get(maps__maps_by_address(maps)[i]);
-+ up_read(maps__lock(maps));
-+
-+ for (unsigned int i = 0; i < nr_maps; i++) {
-+ if (map__load(maps_copy[i]) < 0) {
-+ pr_warning("Failed to load map %s\n", dso__name(map__dso(maps_copy[i])));
-+ err = -1;
-+ }
-+ map__put(maps_copy[i]);
-+ }
-+ free(maps_copy);
-+ return err;
-+}
-+
- void maps__remove_maps(struct maps *maps, bool (*cb)(struct map *map, void *data), void *data)
- {
- struct map **maps_by_address;
-@@ -623,40 +700,57 @@ struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp)
- return result;
- }
-
--struct maps__find_symbol_by_name_args {
-- struct map **mapp;
-- const char *name;
-- struct symbol *sym;
--};
--
--static int maps__find_symbol_by_name_cb(struct map *map, void *data)
-+struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
- {
-- struct maps__find_symbol_by_name_args *args = data;
-+ struct map **maps_copy;
-+ unsigned int nr_maps;
-+ struct symbol *sym = NULL;
-
-- args->sym = map__find_symbol_by_name(map, args->name);
-- if (!args->sym)
-- return 0;
-+ if (!maps)
-+ return NULL;
-
-- if (!map__contains_symbol(map, args->sym)) {
-- args->sym = NULL;
-- return 0;
-+ /*
-+ * First, ensure all maps are loaded. We pre-load them outside of any
-+ * read-to-write locks to avoid deadlocks. Even if some fail, we proceed.
-+ */
-+ maps__load_maps(maps);
-+
-+ /*
-+ * Create a local snapshot of the maps while holding the read lock.
-+ * This prevents deadlocking if iteration triggers further map insertions.
-+ */
-+ down_read(maps__lock(maps));
-+ nr_maps = maps__nr_maps(maps);
-+ maps_copy = calloc(nr_maps, sizeof(*maps_copy));
-+ if (maps_copy) {
-+ for (unsigned int i = 0; i < nr_maps; i++) {
-+ struct map *map = maps__maps_by_address(maps)[i];
-+
-+ maps_copy[i] = map__get(map);
-+ }
- }
-+ up_read(maps__lock(maps));
-
-- if (args->mapp != NULL)
-- *args->mapp = map__get(map);
-- return 1;
--}
-+ if (!maps_copy)
-+ return NULL;
-
--struct symbol *maps__find_symbol_by_name(struct maps *maps, const char *name, struct map **mapp)
--{
-- struct maps__find_symbol_by_name_args args = {
-- .mapp = mapp,
-- .name = name,
-- .sym = NULL,
-- };
-+ for (unsigned int i = 0; i < nr_maps; i++) {
-+ struct map *map = maps_copy[i];
-+
-+ sym = map__find_symbol_by_name(map, name);
-+ if (sym && map__contains_symbol(map, sym)) {
-+ if (mapp)
-+ *mapp = map__get(map);
-+ break;
-+ }
-+ sym = NULL;
-+ }
-+
-+ for (unsigned int i = 0; i < nr_maps; i++)
-+ map__put(maps_copy[i]);
-
-- maps__for_each_map(maps, maps__find_symbol_by_name_cb, &args);
-- return args.sym;
-+ free(maps_copy);
-+ return sym;
- }
-
- int maps__find_ams(struct maps *maps, struct addr_map_symbol *ams)
-diff --git a/tools/perf/util/maps.h b/tools/perf/util/maps.h
-index d9aa62ed968ac4..df22f8c1b32bd1 100644
---- a/tools/perf/util/maps.h
-+++ b/tools/perf/util/maps.h
-@@ -55,8 +55,11 @@ void maps__set_unwind_libunwind_ops(struct maps *maps, const struct unwind_libun
-
- size_t maps__fprintf(struct maps *maps, FILE *fp);
-
-+int maps__load_maps(struct maps *maps);
- int maps__insert(struct maps *maps, struct map *map);
- void maps__remove(struct maps *maps, struct map *map);
-+int maps__mutate_mapping(struct maps *maps, struct map *map,
-+ int (*mutate_cb)(struct map *map, void *data), void *data);
-
- struct map *maps__find(struct maps *maps, u64 addr);
- struct symbol *maps__find_symbol(struct maps *maps, u64 addr, struct map **mapp);
-diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
-index 90f11a6eb19fea..4ba49975970a9b 100644
---- a/tools/perf/util/symbol-elf.c
-+++ b/tools/perf/util/symbol-elf.c
-@@ -1418,6 +1418,24 @@ static u64 ref_reloc(struct kmap *kmap)
- void __weak arch__sym_update(struct symbol *s __maybe_unused,
- GElf_Sym *sym __maybe_unused) { }
-
-+struct remap_kernel_ctx {
-+ u64 sh_addr;
-+ u64 sh_size;
-+ u64 sh_offset;
-+ struct kmap *kmap;
-+};
-+
-+static int remap_kernel_cb(struct map *map, void *data)
-+{
-+ struct remap_kernel_ctx *ctx = data;
-+
-+ map__set_start(map, ctx->sh_addr + ref_reloc(ctx->kmap));
-+ map__set_end(map, map__start(map) + ctx->sh_size);
-+ map__set_pgoff(map, ctx->sh_offset);
-+ map__set_mapping_type(map, MAPPING_TYPE__DSO);
-+ return 0;
-+}
-+
- static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
- GElf_Sym *sym, GElf_Shdr *shdr,
- struct maps *kmaps, struct kmap *kmap,
-@@ -1448,22 +1466,15 @@ static int dso__process_kernel_symbol(struct dso *dso, struct map *map,
- * map to the kernel dso.
- */
- if (*remap_kernel && dso__kernel(dso) && !kmodule) {
-+ struct remap_kernel_ctx ctx = {
-+ .sh_addr = shdr->sh_addr,
-+ .sh_size = shdr->sh_size,
-+ .sh_offset = shdr->sh_offset,
-+ .kmap = kmap
-+ };
-+
- *remap_kernel = false;
-- map__set_start(map, shdr->sh_addr + ref_reloc(kmap));
-- map__set_end(map, map__start(map) + shdr->sh_size);
-- map__set_pgoff(map, shdr->sh_offset);
-- map__set_mapping_type(map, MAPPING_TYPE__DSO);
-- /* Ensure maps are correctly ordered */
-- if (kmaps) {
-- int err;
-- struct map *tmp = map__get(map);
--
-- maps__remove(kmaps, map);
-- err = maps__insert(kmaps, map);
-- map__put(tmp);
-- if (err)
-- return err;
-- }
-+ maps__mutate_mapping(kmaps, map, remap_kernel_cb, &ctx);
- }
-
- /*
-diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
-index b1b4a2d4b7e46c..e792dd22b54983 100644
---- a/tools/perf/util/symbol.c
-+++ b/tools/perf/util/symbol.c
-@@ -42,6 +42,13 @@
- #include <symbol/kallsyms.h>
- #include <sys/utsname.h>
-
-+static int map_fixup_cb(struct map *map, void *data __maybe_unused)
-+{
-+ map__fixup_start(map);
-+ map__fixup_end(map);
-+ return 0;
-+}
-+
- static int dso__load_kernel_sym(struct dso *dso, struct map *map);
- static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map);
- static bool symbol__is_idle(const char *name);
-@@ -2227,10 +2234,11 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map)
- free(kallsyms_allocated_filename);
-
- if (err > 0 && !dso__is_kcore(dso)) {
-+ struct maps *kmaps = map__kmaps(map);
-+
- dso__set_binary_type(dso, DSO_BINARY_TYPE__KALLSYMS);
- dso__set_long_name(dso, DSO__NAME_KALLSYMS, false);
-- map__fixup_start(map);
-- map__fixup_end(map);
-+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
- }
-
- return err;
-@@ -2270,10 +2278,11 @@ static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map)
- if (err > 0)
- pr_debug("Using %s for symbols\n", kallsyms_filename);
- if (err > 0 && !dso__is_kcore(dso)) {
-+ struct maps *kmaps = map__kmaps(map);
-+
- dso__set_binary_type(dso, DSO_BINARY_TYPE__GUEST_KALLSYMS);
- dso__set_long_name(dso, machine->mmap_name, false);
-- map__fixup_start(map);
-- map__fixup_end(map);
-+ maps__mutate_mapping(kmaps, map, map_fixup_cb, NULL);
- }
-
- return err;
---
-2.53.0
-
+++ /dev/null
-From 2ca4d08f1f172fbd844597d300b5af0cf362dbbd Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 6 Jun 2026 11:03:29 -0300
-Subject: perf mmap: Fix NULL deref in aio cleanup on alloc failure
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 25627346b10e6a564610ea2c49dc6dd54812226d ]
-
-perf_mmap__aio_mmap() sets map->aio.nr_cblocks before allocating the
-data array. If calloc() for aiocb or cblocks fails before the data
-array is allocated, the return -1 path leads to perf_mmap__aio_munmap()
-which loops nr_cblocks times calling perf_mmap__aio_free(). Both
-versions of perf_mmap__aio_free() (NUMA and non-NUMA) dereference
-map->aio.data[idx] without checking if data is NULL, causing a NULL
-pointer dereference.
-
-Add NULL checks for map->aio.data at the top of both
-perf_mmap__aio_free() variants so the cleanup path is safe when
-allocation fails partway through perf_mmap__aio_mmap().
-
-Fixes: d3d1af6f011a553a ("perf record: Enable asynchronous trace writing")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Alexey Budankov <alexey.budankov@linux.intel.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/mmap.c | 10 ++++++----
- 1 file changed, 6 insertions(+), 4 deletions(-)
-
-diff --git a/tools/perf/util/mmap.c b/tools/perf/util/mmap.c
-index 5d2685d9ab4ea6..d011968c031c03 100644
---- a/tools/perf/util/mmap.c
-+++ b/tools/perf/util/mmap.c
-@@ -88,10 +88,10 @@ static int perf_mmap__aio_alloc(struct mmap *map, int idx)
-
- static void perf_mmap__aio_free(struct mmap *map, int idx)
- {
-- if (map->aio.data[idx]) {
-- munmap(map->aio.data[idx], mmap__mmap_len(map));
-- map->aio.data[idx] = NULL;
-- }
-+ if (!map->aio.data || !map->aio.data[idx])
-+ return;
-+ munmap(map->aio.data[idx], mmap__mmap_len(map));
-+ map->aio.data[idx] = NULL;
- }
-
- static int perf_mmap__aio_bind(struct mmap *map, int idx, struct perf_cpu cpu, int affinity)
-@@ -140,6 +140,8 @@ static int perf_mmap__aio_alloc(struct mmap *map, int idx)
-
- static void perf_mmap__aio_free(struct mmap *map, int idx)
- {
-+ if (!map->aio.data)
-+ return;
- zfree(&(map->aio.data[idx]));
- }
-
---
-2.53.0
-
+++ /dev/null
-From 1b91e0c3d517cbedcefdb361c7112c078b33d84a Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Fri, 5 Jun 2026 10:56:33 -0300
-Subject: perf mmap: Guard cpu__get_node() return in aio_bind()
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit f32dc302a090f48893477ef297a888db109ec0bd ]
-
-perf_mmap__aio_bind() passes the cpu__get_node() return value directly
-to an unsigned long variable (node_index). When cpu__get_node() returns
--1 for an unknown CPU, the implicit int-to-unsigned-long conversion
-sign-extends it to ULONG_MAX.
-
-This causes bitmap_zalloc(ULONG_MAX + 1) which wraps to
-bitmap_zalloc(0), returning a zero-sized allocation. The subsequent
-__set_bit(ULONG_MAX, node_mask) then writes massively out of bounds.
-
-Check the return value in a signed temporary before assigning to
-node_index, and skip the NUMA binding when the node is unknown.
-
-Fixes: c44a8b44ca9f ("perf record: Bind the AIO user space buffers to nodes")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Alexey Budankov <alexey.budankov@linux.intel.com>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/mmap.c | 8 +++++++-
- 1 file changed, 7 insertions(+), 1 deletion(-)
-
-diff --git a/tools/perf/util/mmap.c b/tools/perf/util/mmap.c
-index 43b02293f1d2da..5d2685d9ab4ea6 100644
---- a/tools/perf/util/mmap.c
-+++ b/tools/perf/util/mmap.c
-@@ -103,9 +103,15 @@ static int perf_mmap__aio_bind(struct mmap *map, int idx, struct perf_cpu cpu, i
- int err = 0;
-
- if (affinity != PERF_AFFINITY_SYS && cpu__max_node() > 1) {
-+ int node;
-+
- data = map->aio.data[idx];
- mmap_len = mmap__mmap_len(map);
-- node_index = cpu__get_node(cpu);
-+ node = cpu__get_node(cpu);
-+ /* -1 sign-extends to ULONG_MAX, wrapping bitmap_zalloc(0) and OOB __set_bit */
-+ if (node < 0)
-+ return 0;
-+ node_index = node;
- node_mask = bitmap_zalloc(node_index + 1);
- if (!node_mask) {
- pr_err("Failed to allocate node mask for mbind: error %m\n");
---
-2.53.0
-
+++ /dev/null
-From 2be632851d4fccb6813c2dd43629a5af9a01aaa4 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sun, 7 Jun 2026 21:03:13 -0300
-Subject: perf pmu: Fix perf_pmu__parse_scale/unit() OOB access on empty sysfs
- file
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 33035f7dd4e49f3f117e70c5e36c8c1ae88d37f2 ]
-
-perf_pmu__parse_scale() reads a PMU scale file then accesses
-scale[sret - 1] to strip a trailing newline. Only sret < 0 is
-guarded, so an empty file (sret == 0) causes scale[-1] — a stack
-buffer underflow that reads and potentially writes out of bounds.
-
-perf_pmu__parse_unit() has the same pattern: alias->unit[sret - 1]
-with sret == 0 accesses the byte before the struct member, which
-may corrupt the adjacent pmu_name pointer field.
-
-Change both guards from sret < 0 to sret <= 0 so that empty files
-are treated as read errors.
-
-Fixes: 410136f5dd96b601 ("tools/perf/stat: Add event unit and scale support")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Stephane Eranian <eranian@google.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/pmu.c | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
-index bf9793a995316b..3a3a56f06880ad 100644
---- a/tools/perf/util/pmu.c
-+++ b/tools/perf/util/pmu.c
-@@ -314,7 +314,7 @@ static int perf_pmu__parse_scale(struct perf_pmu *pmu, struct perf_pmu_alias *al
- goto error;
-
- sret = read(fd, scale, sizeof(scale)-1);
-- if (sret < 0)
-+ if (sret <= 0)
- goto error;
-
- if (scale[sret - 1] == '\n')
-@@ -346,7 +346,7 @@ static int perf_pmu__parse_unit(struct perf_pmu *pmu, struct perf_pmu_alias *ali
- return -1;
-
- sret = read(fd, alias->unit, UNIT_MAX_LEN);
-- if (sret < 0)
-+ if (sret <= 0)
- goto error;
-
- close(fd);
---
-2.53.0
-
+++ /dev/null
-From 68de90895a69326c0013eb8251e2f480a39c6520 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sun, 7 Jun 2026 21:01:43 -0300
-Subject: perf pmu: Fix pmu_id() heap underwrite on empty identifier file
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 836455e6dbd34eb3d12eeab5e2d2b9a7f1512459 ]
-
-pmu_id() calls filename__read_str() then strips the trailing newline
-via str[len - 1] = 0. If the PMU identifier file is empty,
-filename__read_str() succeeds with len = 0. len - 1 underflows
-size_t to SIZE_MAX, writing a null byte before the heap allocation.
-
-Add a len == 0 check before the newline stripping.
-
-Fixes: 51d548471510843e ("perf pmu: Add pmu_id()")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: John Garry <john.g.garry@oracle.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/pmu.c | 6 ++++++
- 1 file changed, 6 insertions(+)
-
-diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
-index 8885998c195308..bf9793a995316b 100644
---- a/tools/perf/util/pmu.c
-+++ b/tools/perf/util/pmu.c
-@@ -796,6 +796,12 @@ static char *pmu_id(const char *name)
- if (filename__read_str(path, &str, &len) < 0)
- return NULL;
-
-+ /* empty identifier file — nothing useful */
-+ if (len == 0) {
-+ free(str);
-+ return NULL;
-+ }
-+
- str[len - 1] = 0; /* remove line feed */
-
- return str;
---
-2.53.0
-
+++ /dev/null
-From 2e0a291e0c556ad0b6df41ed1eacf4bbc33ea93e Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Fri, 10 Apr 2026 12:13:44 +0100
-Subject: perf pmu: Skip test on Arm64 when #slots is zero
-
-From: Leo Yan <leo.yan@arm.com>
-
-[ Upstream commit 2e2ba7d1ea554ee6e9e751a53eebf3e9270b0670 ]
-
-Some Arm64 PMUs expose 'caps/slots' as 0 when the slot count is not
-implemented, tool_pmu__read_event() currently returns false for this,
-so metrics that reference #slots are reported as syntax error.
-
-Since the commit 3a61fd866ef9 ("perf expr: Return -EINVAL for syntax
-error in expr__find_ids()"), these syntax errors are populated as
-failures and make the PMU metric test fail:
-
- 9.3: Parsing of PMU event table metrics:
- --- start ---
- ...
-
- Found metric 'backend_bound'
- metric expr 100 * (stall_slot_backend / (#slots * cpu_cycles)) for backend_bound
- parsing metric: 100 * (stall_slot_backend / (#slots * cpu_cycles))
- Failure to read '#slots'
- literal: #slots = nan
- syntax error
- Fail to parse metric or group `backend_bound'
-
- ...
- ---- end(-1) ----
- 9.3: Parsing of PMU event table metrics : FAILED!
-
-This commit introduces a new function is_expected_broken_metric() to
-identify broken metrics, and treats metrics containing "#slots" as
-expected broken when #slots == 0 on Arm64 platforms.
-
-Fixes: 3a61fd866ef9aaa1 ("perf expr: Return -EINVAL for syntax error in expr__find_ids()")
-Reviewed-by: Ian Rogers <irogers@google.com>
-Reviewed-by: James Clark <james.clark@linaro.org>
-Signed-off-by: Leo Yan <leo.yan@arm.com>
-Cc: Adrian Hunter <adrian.hunter@intel.com>
-Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Mark Rutland <mark.rutland@arm.com>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/tests/pmu-events.c | 24 ++++++++++++++++++++++--
- 1 file changed, 22 insertions(+), 2 deletions(-)
-
-diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c
-index db004d26fcb005..8c73ab189c5fce 100644
---- a/tools/perf/tests/pmu-events.c
-+++ b/tools/perf/tests/pmu-events.c
-@@ -15,6 +15,7 @@
- #include "util/expr.h"
- #include "util/hashmap.h"
- #include "util/parse-events.h"
-+#include "util/tool_pmu.h"
- #include "metricgroup.h"
- #include "stat.h"
-
-@@ -864,6 +865,26 @@ struct metric {
- struct metric_ref metric_ref;
- };
-
-+static bool is_expected_broken_metric(const struct pmu_metric *pm)
-+{
-+ if (!strcmp(pm->metric_name, "M1") || !strcmp(pm->metric_name, "M2") ||
-+ !strcmp(pm->metric_name, "M3"))
-+ return true;
-+
-+#if defined(__aarch64__)
-+ /*
-+ * Arm64 platforms may return "#slots == 0", which is treated as a
-+ * syntax error by the parser. Don't test these metrics when running
-+ * on such platforms.
-+ */
-+ if (strstr(pm->metric_expr, "#slots") &&
-+ !tool_pmu__cpu_slots_per_cycle())
-+ return true;
-+#endif
-+
-+ return false;
-+}
-+
- static int test__parsing_callback(const struct pmu_metric *pm,
- const struct pmu_metrics_table *table,
- void *data)
-@@ -902,8 +923,7 @@ static int test__parsing_callback(const struct pmu_metric *pm,
-
- err = metricgroup__parse_groups_test(evlist, table, pm->metric_name, &metric_events);
- if (err) {
-- if (!strcmp(pm->metric_name, "M1") || !strcmp(pm->metric_name, "M2") ||
-- !strcmp(pm->metric_name, "M3")) {
-+ if (is_expected_broken_metric(pm)) {
- (*failures)--;
- pr_debug("Expected broken metric %s skipping\n", pm->metric_name);
- err = 0;
---
-2.53.0
-
+++ /dev/null
-From 942efef4b7b7d031c09e6719131040dfda8d3aac Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Fri, 23 Jan 2026 13:32:04 +0000
-Subject: perf: Remove redundant kernel.h include
-
-From: Leo Yan <leo.yan@arm.com>
-
-[ Upstream commit 7a0ba3891104da77cfd1a16d41699e0fdf45603a ]
-
-Now that the bitfield dependency is resolved, the explicit inclusion of
-kernel.h is no longer needed.
-
-Remove the redundant include.
-
-Signed-off-by: Leo Yan <leo.yan@arm.com>
-Cc: Ian Rogers <irogers@google.com>
-Cc: James Clark <james.clark@linaro.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Stable-dep-of: 542e88a4c6f7 ("perf cs-etm: Reject CPU IDs that would overflow signed comparison")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/arch/arm64/util/header.c | 1 -
- tools/perf/util/cs-etm.c | 1 -
- 2 files changed, 2 deletions(-)
-
-diff --git a/tools/perf/arch/arm64/util/header.c b/tools/perf/arch/arm64/util/header.c
-index 741df3614a09ac..d0ccc727593ca2 100644
---- a/tools/perf/arch/arm64/util/header.c
-+++ b/tools/perf/arch/arm64/util/header.c
-@@ -1,4 +1,3 @@
--#include <linux/kernel.h>
- #include <linux/bits.h>
- #include <linux/bitfield.h>
- #include <stdio.h>
-diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
-index 3fd669817eb9bf..776bef4e00156e 100644
---- a/tools/perf/util/cs-etm.c
-+++ b/tools/perf/util/cs-etm.c
-@@ -6,7 +6,6 @@
- * Author: Mathieu Poirier <mathieu.poirier@linaro.org>
- */
-
--#include <linux/kernel.h>
- #include <linux/bitfield.h>
- #include <linux/bitops.h>
- #include <linux/coresight-pmu.h>
---
-2.53.0
-
+++ /dev/null
-From 198e08ffc86f5ecfb3818a30c56a395ebafc799b Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Tue, 5 May 2026 17:45:42 -0700
-Subject: perf sched: Add missing mmap2 handler in timehist
-
-From: Ian Rogers <irogers@google.com>
-
-[ Upstream commit 91182741369b261c441e63e6678893032a6d7e4c ]
-
-perf_sched__timehist() registers event handlers for options using the
-sched->tool struct. It registers handlers for MMAP, COMM, EXIT, FORK, etc.
-but completely omits registering a handler for MMAP2 events.
-
-Failing to register both MMAP and MMAP2 handlers causes modern systems
-(which primarily output MMAP2 records) to silently drop VMA map mappings.
-This results in uninitialized machine/thread mapping structures, making it
-impossible to resolve shared library instruction pointers (IPs) to dynamic
-symbols/DSOs during timehist callchain analysis.
-
-Fix this by correctly registering perf_event__process_mmap2 in
-sched->tool inside perf_sched__timehist().
-
-Fixes: 49394a2a24c78ce0 ("perf sched timehist: Introduce timehist command")
-Assisted-by: Gemini-CLI:Google Gemini 3
-Signed-off-by: Ian Rogers <irogers@google.com>
-Cc: Adrian Hunter <adrian.hunter@intel.com>
-Cc: David Ahern <dsahern@gmail.com>
-Cc: Gabriel Marin <gmx@google.com>
-Cc: Ingo Molnar <mingo@redhat.com>
-Cc: James Clark <james.clark@linaro.org>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Cc: Peter Zijlstra <peterz@infradead.org>
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-sched.c | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
-index 64bf3ac237f2ef..fa5f00d47e6277 100644
---- a/tools/perf/builtin-sched.c
-+++ b/tools/perf/builtin-sched.c
-@@ -3261,6 +3261,7 @@ static int perf_sched__timehist(struct perf_sched *sched)
- */
- sched->tool.sample = perf_timehist__process_sample;
- sched->tool.mmap = perf_event__process_mmap;
-+ sched->tool.mmap2 = perf_event__process_mmap2;
- sched->tool.comm = perf_event__process_comm;
- sched->tool.exit = perf_event__process_exit;
- sched->tool.fork = perf_event__process_fork;
---
-2.53.0
-
+++ /dev/null
-From 6007424409b0a570f8132a408e0f317dd33c8830 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 6 Jun 2026 21:48:13 -0300
-Subject: perf sched: Bounds-check prio before test_bit() in timehist
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 4477dc01fcfc7f404772a67e0c1e056541ceb61d ]
-
-timehist_skip_sample() reads prio from untrusted tracepoint data via
-perf_sample__intval(sample, "prev_prio") without bounds validation.
-A crafted perf.data with prev_prio >= MAX_PRIO (140) causes test_bit()
-to read past the end of the prio_bitmap, which is only MAX_PRIO bits.
-
-Add a prio >= 0 guard before the test_bit() call and skip out-of-range
-values (>= MAX_PRIO) that can never match the user's filter set.
-
-The original prio != -1 already let all negatives other than -1 through
-(after an undefined-behavior bitmap read); the new prio >= 0 guard
-preserves that pass-through behavior — negative means "no priority
-info", so the event is shown unfiltered — while fixing the OOB.
-Values >= MAX_PRIO are skipped because they cannot be represented in
-the filter bitmap.
-
-Fixes: 9b3a48bbe20d9692 ("perf sched timehist: Add --prio option")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Reviewed-by: Ian Rogers <irogers@google.com>
-Cc: Yang Jihong <yangjihong@bytedance.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-sched.c | 4 +++-
- 1 file changed, 3 insertions(+), 1 deletion(-)
-
-diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
-index d599ad421df108..7cf56e271a993a 100644
---- a/tools/perf/builtin-sched.c
-+++ b/tools/perf/builtin-sched.c
-@@ -2646,7 +2646,9 @@ static bool timehist_skip_sample(struct perf_sched *sched,
- else if (evsel__name_is(evsel, "sched:sched_switch"))
- prio = evsel__intval(evsel, sample, "prev_prio");
-
-- if (prio != -1 && !test_bit(prio, sched->prio_bitmap)) {
-+ /* negative prio means no info; out-of-range prio can't match the filter */
-+ if (prio >= 0 &&
-+ (prio >= MAX_PRIO || !test_bit(prio, sched->prio_bitmap))) {
- rc = true;
- sched->skipped_samples++;
- }
---
-2.53.0
-
+++ /dev/null
-From f68c59a0bd704d8491bc39474d5f73d19e496808 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Fri, 5 Jun 2026 11:12:08 -0300
-Subject: perf sched: Clean up idle_threads entry on init failure
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit cda5a94ad9181cd60cbf04be11d524201bf489a2 ]
-
-get_idle_thread() allocates a thread via thread__new() and stores it in
-idle_threads[cpu], then calls init_idle_thread() to set up the private
-data. If init_idle_thread() fails (e.g. OOM for the idle_thread_runtime
-struct), the function returns NULL but leaves the partially initialized
-thread in idle_threads[cpu].
-
-On subsequent calls for the same CPU, get_idle_thread() finds a non-NULL
-idle_threads[cpu], skips allocation, and returns thread__get() on a
-thread that has no priv data. Callers then get a thread whose
-thread__priv() returns NULL, leading to unexpected behavior.
-
-Release the thread and reset the slot to NULL on init failure so the
-entry doesn't persist in a corrupted state.
-
-Fixes: 49394a2a24c7 ("perf sched timehist: Introduce timehist command")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: David Ahern <dsahern@gmail.com>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-sched.c | 5 ++++-
- 1 file changed, 4 insertions(+), 1 deletion(-)
-
-diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
-index 4596037b5763c0..fd93ab11bd37ed 100644
---- a/tools/perf/builtin-sched.c
-+++ b/tools/perf/builtin-sched.c
-@@ -2496,8 +2496,11 @@ static struct thread *get_idle_thread(int cpu)
- idle_threads[cpu] = thread__new(0, 0);
-
- if (idle_threads[cpu]) {
-- if (init_idle_thread(idle_threads[cpu]) < 0)
-+ if (init_idle_thread(idle_threads[cpu]) < 0) {
-+ /* clean up so next call doesn't find a half-initialized thread */
-+ thread__zput(idle_threads[cpu]);
- return NULL;
-+ }
- }
- }
-
---
-2.53.0
-
+++ /dev/null
-From 8ca288d57427734595a82345590be1fa3a9ad6c1 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 6 Jun 2026 21:49:16 -0300
-Subject: perf sched: Fix idle-hist callchain display using wrong rb_first
- variant
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit d9b99dc8148e0c1f5da3942131b47e0d21187a32 ]
-
-timehist_print_idlehist_callchain() calls rb_first_cached() on
-sorted_root, but the sort function (callchain_param.sort) populates it
-via rb_insert_color() on the plain rb_root member — not the cached
-variant. This means rb_leftmost is never set, so rb_first_cached()
-always returns NULL and the entire callchain summary is silently
-dropped from --idle-hist output.
-
-The original code in ba957ebb54893aca ("perf sched timehist: Show
-callchains for idle stat") was correct — it used struct rb_root and
-rb_first(). The bug was introduced when sorted_root was converted to
-rb_root_cached without converting the sort insertion path to use
-rb_insert_color_cached().
-
-Use rb_first(&root->rb_root) to match how the tree was populated.
-
-Fixes: cb4c13a5137766c3 ("perf sched: Use cached rbtrees")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Davidlohr Bueso <dave@stgolabs.net>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Acked-by: Ian Rogers <irogers@google.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-sched.c | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
-
-diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
-index 7cf56e271a993a..0741d926590468 100644
---- a/tools/perf/builtin-sched.c
-+++ b/tools/perf/builtin-sched.c
-@@ -3126,7 +3126,8 @@ static size_t timehist_print_idlehist_callchain(struct rb_root_cached *root)
- size_t ret = 0;
- FILE *fp = stdout;
- struct callchain_node *chain;
-- struct rb_node *rb_node = rb_first_cached(root);
-+ /* sort() uses rb_insert_color() on rb_root, not rb_root_cached */
-+ struct rb_node *rb_node = rb_first(&root->rb_root);
-
- printf(" %16s %8s %s\n", "Idle time (msec)", "Count", "Callchains");
- printf(" %.16s %.8s %.50s\n", graph_dotted_line, graph_dotted_line,
---
-2.53.0
-
+++ /dev/null
-From 15f991f106f73950850b3029d6e163b7d32ad579 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Thu, 4 Jun 2026 12:56:02 -0300
-Subject: perf sched: Fix NULL dereference in latency_runtime_event
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 8cbca8a480e15f6326ce94287570993f27a4b2d5 ]
-
-latency_runtime_event() passes the return value of
-machine__findnew_thread() directly to thread_atoms_search() at line
-1216, before checking for NULL at line 1220. thread_atoms_search()
-calls pid_cmp() which dereferences the thread pointer via
-thread__tid(), causing a NULL pointer dereference if the allocation
-fails.
-
-All other callers of thread_atoms_search() in this file
-(latency_switch_event, latency_wakeup_event,
-latency_migrate_task_event) correctly check for NULL first.
-
-Move the atoms assignment after the NULL check to match the pattern
-used by the other callers.
-
-Fixes: b91fc39f4ad7 ("perf machine: Protect the machine->threads with a rwlock")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-sched.c | 4 +++-
- 1 file changed, 3 insertions(+), 1 deletion(-)
-
-diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
-index 00d50c356e43c1..12db6fe6e05a6a 100644
---- a/tools/perf/builtin-sched.c
-+++ b/tools/perf/builtin-sched.c
-@@ -1214,13 +1214,15 @@ static int latency_runtime_event(struct perf_sched *sched,
- const u32 pid = evsel__intval(evsel, sample, "pid");
- const u64 runtime = evsel__intval(evsel, sample, "runtime");
- struct thread *thread = machine__findnew_thread(machine, -1, pid);
-- struct work_atoms *atoms = thread_atoms_search(&sched->atom_root, thread, &sched->cmp_pid);
-+ struct work_atoms *atoms;
- u64 timestamp = sample->time;
- int cpu = sample->cpu, err = -1;
-
- if (thread == NULL)
- return -1;
-
-+ atoms = thread_atoms_search(&sched->atom_root, thread, &sched->cmp_pid);
-+
- /* 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",
---
-2.53.0
-
+++ /dev/null
-From 0d2616e375487df9410dd818229be85ded869f7e Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Thu, 4 Jun 2026 18:25:57 -0300
-Subject: perf sched: Fix register_pid() overflow, strcpy, and BUG_ON
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 5949d339f5ec98752d56dcd4e36f619a59d513a5 ]
-
-register_pid() has several issues when processing untrusted perf.data:
-
-1. Integer overflow: (pid + 1) * sizeof(struct task_desc *) can wrap
- to a small value on 32-bit systems when pid is large (e.g.
- 0x40000000), causing realloc to return a tiny buffer followed by
- out-of-bounds writes in the initialization loop.
-
-2. Heap buffer overflow: strcpy(task->comm, comm) copies the
- untrusted comm string into a fixed 20-byte COMM_LEN buffer with
- no length check.
-
-3. BUG_ON on allocation failure: perf.data is untrusted input, so
- allocation failures should be handled gracefully rather than
- killing the process.
-
-4. Realloc of sched->tasks assigned directly back, leaking the old
- pointer on failure; nr_tasks incremented before the realloc,
- leaving corrupted state on failure.
-
-Cap pid at PID_MAX_LIMIT (4194304, matching the kernel's maximum
-on 64-bit), replace strcpy with strlcpy, guard against NULL comm,
-replace BUG_ON with NULL returns using safe realloc patterns, and
-add NULL checks in callers that dereference the result.
-
-Fixes: ec156764d424 ("perf sched: Import schedbench.c")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Ingo Molnar <mingo@elte.hu>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-sched.c | 40 ++++++++++++++++++++++++++++----------
- 1 file changed, 30 insertions(+), 10 deletions(-)
-
-diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
-index 12db6fe6e05a6a..4596037b5763c0 100644
---- a/tools/perf/builtin-sched.c
-+++ b/tools/perf/builtin-sched.c
-@@ -51,6 +51,7 @@
- #define COMM_LEN 20
- #define SYM_LEN 129
- #define MAX_PID 1024000
-+#define PID_MAX_LIMIT 4194304 /* kernel limit on 64-bit */
- #define MAX_PRIO 140
-
- static const char *cpu_list;
-@@ -440,17 +441,28 @@ static void add_sched_event_sleep(struct perf_sched *sched, struct task_desc *ta
- static struct task_desc *register_pid(struct perf_sched *sched,
- unsigned long pid, const char *comm)
- {
-- struct task_desc *task;
-+ struct task_desc *task, **tasks_p;
- static int pid_max;
-
-+ /* perf.data is untrusted — cap pid to prevent overflow in size calculations */
-+ if (pid >= PID_MAX_LIMIT) {
-+ pr_err("pid %lu exceeds limit %d, skipping\n", pid, PID_MAX_LIMIT);
-+ return NULL;
-+ }
-+
- if (sched->pid_to_task == NULL) {
- if (sysctl__read_int("kernel/pid_max", &pid_max) < 0)
- pid_max = MAX_PID;
-- BUG_ON((sched->pid_to_task = calloc(pid_max, sizeof(struct task_desc *))) == NULL);
-+ sched->pid_to_task = calloc(pid_max, sizeof(struct task_desc *));
-+ if (sched->pid_to_task == NULL)
-+ return NULL;
- }
- if (pid >= (unsigned long)pid_max) {
-- BUG_ON((sched->pid_to_task = realloc(sched->pid_to_task, (pid + 1) *
-- sizeof(struct task_desc *))) == NULL);
-+ void *p = realloc(sched->pid_to_task, (pid + 1) * sizeof(struct task_desc *));
-+
-+ if (p == NULL)
-+ return NULL;
-+ sched->pid_to_task = p;
- while (pid >= (unsigned long)pid_max)
- sched->pid_to_task[pid_max++] = NULL;
- }
-@@ -461,9 +473,11 @@ static struct task_desc *register_pid(struct perf_sched *sched,
- return task;
-
- task = zalloc(sizeof(*task));
-+ if (task == NULL)
-+ return NULL;
- task->pid = pid;
-- task->nr = sched->nr_tasks;
-- strcpy(task->comm, comm);
-+ if (comm)
-+ strlcpy(task->comm, comm, sizeof(task->comm));
- /*
- * every task starts in sleeping state - this gets ignored
- * if there's no wakeup pointing to this sleep state:
-@@ -471,10 +485,12 @@ static struct task_desc *register_pid(struct perf_sched *sched,
- add_sched_event_sleep(sched, task, 0, 0);
-
- sched->pid_to_task[pid] = task;
-- sched->nr_tasks++;
-- sched->tasks = realloc(sched->tasks, sched->nr_tasks * sizeof(struct task_desc *));
-- BUG_ON(!sched->tasks);
-- sched->tasks[task->nr] = task;
-+ tasks_p = realloc(sched->tasks, (sched->nr_tasks + 1) * sizeof(struct task_desc *));
-+ if (!tasks_p)
-+ return NULL;
-+ sched->tasks = tasks_p;
-+ sched->tasks[sched->nr_tasks] = task;
-+ task->nr = sched->nr_tasks++;
-
- if (verbose > 0)
- printf("registered task #%ld, PID %ld (%s)\n", sched->nr_tasks, pid, comm);
-@@ -838,6 +854,8 @@ replay_wakeup_event(struct perf_sched *sched,
-
- waker = register_pid(sched, sample->tid, "<unknown>");
- wakee = register_pid(sched, pid, comm);
-+ if (waker == NULL || wakee == NULL)
-+ return -1;
-
- add_sched_event_wakeup(sched, waker, sample->time, wakee);
- return 0;
-@@ -880,6 +898,8 @@ static int replay_switch_event(struct perf_sched *sched,
-
- prev = register_pid(sched, prev_pid, prev_comm);
- next = register_pid(sched, next_pid, next_comm);
-+ if (prev == NULL || next == NULL)
-+ return -1;
-
- sched->cpu_last_switched[cpu] = timestamp;
-
---
-2.53.0
-
+++ /dev/null
-From 7e45435541778380e6b352e5d1d5363b05b61536 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Wed, 2 Jul 2025 18:49:38 -0700
-Subject: perf sched: Fix thread leaks in 'perf sched timehist'
-
-From: Namhyung Kim <namhyung@kernel.org>
-
-[ Upstream commit e2eb59260c4f6bac403491d0112891766b8650d1 ]
-
-Add missing thread__put() after machine__findnew_thread() or
-timehist_get_thread(). Also idle threads' last_thread should be
-refcounted properly.
-
-Fixes: 699b5b920db04a6f ("perf sched timehist: Save callchain when entering idle")
-Reviewed-by: Ian Rogers <irogers@google.com>
-Tested-by: Ian Rogers <irogers@google.com>
-Link: https://lore.kernel.org/r/20250703014942.1369397-5-namhyung@kernel.org
-Signed-off-by: Namhyung Kim <namhyung@kernel.org>
-Stable-dep-of: a99d6394cd48 ("perf sched: Use thread__put() in free_idle_threads()")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-sched.c | 48 +++++++++++++++++++++++++++++---------
- 1 file changed, 37 insertions(+), 11 deletions(-)
-
-diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
-index 1203425d2b8a55..f3a03be9471aa0 100644
---- a/tools/perf/builtin-sched.c
-+++ b/tools/perf/builtin-sched.c
-@@ -2386,8 +2386,10 @@ static void save_task_callchain(struct perf_sched *sched,
- return;
- }
-
-- if (!sched->show_callchain || sample->callchain == NULL)
-+ if (!sched->show_callchain || sample->callchain == NULL) {
-+ thread__put(thread);
- return;
-+ }
-
- cursor = get_tls_callchain_cursor();
-
-@@ -2396,10 +2398,12 @@ static void save_task_callchain(struct perf_sched *sched,
- if (verbose > 0)
- pr_err("Failed to resolve callchain. Skipping\n");
-
-+ thread__put(thread);
- return;
- }
-
- callchain_cursor_commit(cursor);
-+ thread__put(thread);
-
- while (true) {
- struct callchain_cursor_node *node;
-@@ -2476,8 +2480,17 @@ static void free_idle_threads(void)
- return;
-
- for (i = 0; i < idle_max_cpu; ++i) {
-- if ((idle_threads[i]))
-- thread__delete(idle_threads[i]);
-+ struct thread *idle = idle_threads[i];
-+
-+ if (idle) {
-+ struct idle_thread_runtime *itr;
-+
-+ itr = thread__priv(idle);
-+ if (itr)
-+ thread__put(itr->last_thread);
-+
-+ thread__delete(idle);
-+ }
- }
-
- free(idle_threads);
-@@ -2517,7 +2530,7 @@ static struct thread *get_idle_thread(int cpu)
- }
- }
-
-- return idle_threads[cpu];
-+ return thread__get(idle_threads[cpu]);
- }
-
- static void save_idle_callchain(struct perf_sched *sched,
-@@ -2572,7 +2585,8 @@ static struct thread *timehist_get_thread(struct perf_sched *sched,
- if (itr == NULL)
- return NULL;
-
-- itr->last_thread = thread;
-+ thread__put(itr->last_thread);
-+ itr->last_thread = thread__get(thread);
-
- /* copy task callchain when entering to idle */
- if (evsel__intval(evsel, sample, "next_pid") == 0)
-@@ -2643,6 +2657,7 @@ static void timehist_print_wakeup_event(struct perf_sched *sched,
- /* show wakeup unless both awakee and awaker are filtered */
- if (timehist_skip_sample(sched, thread, evsel, sample) &&
- timehist_skip_sample(sched, awakened, evsel, sample)) {
-+ thread__put(thread);
- return;
- }
-
-@@ -2659,6 +2674,8 @@ static void timehist_print_wakeup_event(struct perf_sched *sched,
- printf("awakened: %s", timehist_get_commstr(awakened));
-
- printf("\n");
-+
-+ thread__put(thread);
- }
-
- static int timehist_sched_wakeup_ignore(const struct perf_tool *tool __maybe_unused,
-@@ -2687,8 +2704,10 @@ static int timehist_sched_wakeup_event(const struct perf_tool *tool,
- return -1;
-
- tr = thread__get_runtime(thread);
-- if (tr == NULL)
-+ if (tr == NULL) {
-+ thread__put(thread);
- return -1;
-+ }
-
- if (tr->ready_to_run == 0)
- tr->ready_to_run = sample->time;
-@@ -2698,6 +2717,7 @@ static int timehist_sched_wakeup_event(const struct perf_tool *tool,
- !perf_time__skip_sample(&sched->ptime, sample->time))
- timehist_print_wakeup_event(sched, evsel, sample, machine, thread);
-
-+ thread__put(thread);
- return 0;
- }
-
-@@ -2725,6 +2745,7 @@ static void timehist_print_migration_event(struct perf_sched *sched,
-
- if (timehist_skip_sample(sched, thread, evsel, sample) &&
- timehist_skip_sample(sched, migrated, evsel, sample)) {
-+ thread__put(thread);
- return;
- }
-
-@@ -2752,6 +2773,7 @@ static void timehist_print_migration_event(struct perf_sched *sched,
- printf(" cpu %d => %d", ocpu, dcpu);
-
- printf("\n");
-+ thread__put(thread);
- }
-
- static int timehist_migrate_task_event(const struct perf_tool *tool,
-@@ -2771,8 +2793,10 @@ static int timehist_migrate_task_event(const struct perf_tool *tool,
- return -1;
-
- tr = thread__get_runtime(thread);
-- if (tr == NULL)
-+ if (tr == NULL) {
-+ thread__put(thread);
- return -1;
-+ }
-
- tr->migrations++;
- tr->migrated = sample->time;
-@@ -2782,6 +2806,7 @@ static int timehist_migrate_task_event(const struct perf_tool *tool,
- timehist_print_migration_event(sched, evsel, sample,
- machine, thread);
- }
-+ thread__put(thread);
-
- return 0;
- }
-@@ -2804,10 +2829,10 @@ static void timehist_update_task_prio(struct evsel *evsel,
- return;
-
- tr = thread__get_runtime(thread);
-- if (tr == NULL)
-- return;
-+ if (tr != NULL)
-+ tr->prio = next_prio;
-
-- tr->prio = next_prio;
-+ thread__put(thread);
- }
-
- static int timehist_sched_change_event(const struct perf_tool *tool,
-@@ -2819,7 +2844,7 @@ static int timehist_sched_change_event(const struct perf_tool *tool,
- struct perf_sched *sched = container_of(tool, struct perf_sched, tool);
- struct perf_time_interval *ptime = &sched->ptime;
- struct addr_location al;
-- struct thread *thread;
-+ struct thread *thread = NULL;
- struct thread_runtime *tr = NULL;
- u64 tprev, t = sample->time;
- int rc = 0;
-@@ -2944,6 +2969,7 @@ static int timehist_sched_change_event(const struct perf_tool *tool,
-
- evsel__save_time(evsel, sample->time, sample->cpu);
-
-+ thread__put(thread);
- addr_location__exit(&al);
- return rc;
- }
---
-2.53.0
-
+++ /dev/null
-From aeff520e1eb2a89c4f61def4ae298cd13e1e340a Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Thu, 4 Jun 2026 12:55:29 -0300
-Subject: perf sched: Fix thread reference leak in latency_switch_event
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 66ea9de60396a4dea5276bc87025884691876c36 ]
-
-In latency_switch_event(), after acquiring thread references for
-sched_out and sched_in via machine__findnew_thread(), the first
-add_sched_out_event() failure path does 'return -1', bypassing the
-out_put label that calls thread__put() on both references.
-
-The second and third add_sched_out_event() failures correctly use
-'goto out_put'. Fix the first one to match.
-
-Fixes: b91fc39f4ad7 ("perf machine: Protect the machine->threads with a rwlock")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-sched.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
-index 6d7106fb1c0cbc..c0fd6a8595d0f2 100644
---- a/tools/perf/builtin-sched.c
-+++ b/tools/perf/builtin-sched.c
-@@ -1175,7 +1175,7 @@ static int latency_switch_event(struct perf_sched *sched,
- }
- }
- if (add_sched_out_event(out_events, prev_state, timestamp))
-- return -1;
-+ goto out_put;
-
- in_events = thread_atoms_search(&sched->atom_root, sched_in, &sched->cmp_pid);
- if (!in_events) {
---
-2.53.0
-
+++ /dev/null
-From d14d51afef5376933d3142196548558a11e7f96e Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Fri, 5 Jun 2026 11:26:28 -0300
-Subject: perf sched: Replace BUG_ON and add NULL checks in replay event
- helpers
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 75eafe4a3a93a0143a20c0cc286bfb9008ac1478 ]
-
-get_new_event() has three issues:
-
-1. The zalloc() result is dereferenced without a NULL check, crashing
- on allocation failure.
-
-2. BUG_ON(!task->atoms) kills the process when realloc() fails.
- Since perf.data is untrusted input, this should be a graceful error.
-
-3. The realloc pattern assigns directly to task->atoms, losing the old
- pointer on failure. task->nr_events is also incremented before the
- realloc, leaving corrupted state on failure.
-
-Fix get_new_event() to:
- - Check the zalloc() result before dereferencing
- - Use a temporary for realloc() to avoid losing the old pointer
- - Increment nr_events only after successful realloc
- - Return NULL instead of calling BUG_ON on failure
-
-Also fix add_sched_event_wakeup() where zalloc() for wait_sem is
-passed to sem_init() without a NULL check.
-
-Update all callers (add_sched_event_run, add_sched_event_wakeup,
-add_sched_event_sleep) to handle NULL returns by returning early.
-The replay may produce incomplete output on OOM but will not crash.
-
-Fixes: ec156764d424 ("perf sched: Import schedbench.c")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Ingo Molnar <mingo@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-sched.c | 28 +++++++++++++++++++++++++---
- 1 file changed, 25 insertions(+), 3 deletions(-)
-
-diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
-index 8eb5c880a5b605..d599ad421df108 100644
---- a/tools/perf/builtin-sched.c
-+++ b/tools/perf/builtin-sched.c
-@@ -360,14 +360,25 @@ get_new_event(struct task_desc *task, u64 timestamp)
- struct sched_atom *event = zalloc(sizeof(*event));
- unsigned long idx = task->nr_events;
- size_t size;
-+ struct sched_atom **atoms_p;
-+
-+ if (event == NULL) {
-+ pr_err("ERROR: sched: failed to allocate event\n");
-+ return NULL;
-+ }
-
- event->timestamp = timestamp;
- event->nr = idx;
-
-+ size = sizeof(struct sched_atom *) * (task->nr_events + 1);
-+ atoms_p = realloc(task->atoms, size);
-+ if (!atoms_p) {
-+ pr_err("ERROR: sched: failed to grow atoms array\n");
-+ free(event);
-+ return NULL;
-+ }
-+ task->atoms = atoms_p;
- task->nr_events++;
-- size = sizeof(struct sched_atom *) * task->nr_events;
-- task->atoms = realloc(task->atoms, size);
-- BUG_ON(!task->atoms);
-
- task->atoms[idx] = event;
-
-@@ -398,6 +409,8 @@ static void add_sched_event_run(struct perf_sched *sched, struct task_desc *task
- }
-
- event = get_new_event(task, timestamp);
-+ if (event == NULL)
-+ return;
-
- event->type = SCHED_EVENT_RUN;
- event->duration = duration;
-@@ -411,6 +424,8 @@ static void add_sched_event_wakeup(struct perf_sched *sched, struct task_desc *t
- struct sched_atom *event, *wakee_event;
-
- event = get_new_event(task, timestamp);
-+ if (event == NULL)
-+ return;
- event->type = SCHED_EVENT_WAKEUP;
- event->wakee = wakee;
-
-@@ -425,6 +440,10 @@ static void add_sched_event_wakeup(struct perf_sched *sched, struct task_desc *t
- }
-
- wakee_event->wait_sem = zalloc(sizeof(*wakee_event->wait_sem));
-+ if (!wakee_event->wait_sem) {
-+ pr_err("ERROR: sched: failed to allocate semaphore\n");
-+ return;
-+ }
- sem_init(wakee_event->wait_sem, 0, 0);
- wakee_event->specific_wait = 1;
- event->wait_sem = wakee_event->wait_sem;
-@@ -437,6 +456,9 @@ static void add_sched_event_sleep(struct perf_sched *sched, struct task_desc *ta
- {
- struct sched_atom *event = get_new_event(task, timestamp);
-
-+ if (event == NULL)
-+ return;
-+
- event->type = SCHED_EVENT_SLEEP;
-
- sched->nr_sleep_events++;
---
-2.53.0
-
+++ /dev/null
-From a12dc855834bb7be2affdb52098a836fc82064e7 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Mon, 1 Jun 2026 19:53:41 -0300
-Subject: perf sched: Replace BUG_ON on invalid CPU with graceful skip
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 1e2c83f732deb329ebce23e26cbc482f4c4bf194 ]
-
-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
-Reviewed-by: Ian Rogers <irogers@google.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Stable-dep-of: 8cbca8a480e1 ("perf sched: Fix NULL dereference in latency_runtime_event")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- 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 c0fd6a8595d0f2..00d50c356e43c1 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;
-@@ -1216,7 +1221,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;
-@@ -1656,7 +1667,12 @@ static int map_switch_event(struct perf_sched *sched, struct evsel *evsel,
- 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.53.0
-
+++ /dev/null
-From 638a0bcd5724e3f1c6beecc3c1cf2c34ad5236e4 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Fri, 4 Oct 2024 22:37:56 +0530
-Subject: perf sched timehist: Add pre-migration wait time option
-
-From: Madadi Vineeth Reddy <vineethr@linux.ibm.com>
-
-[ Upstream commit cd912ab3b65445df7b95bbdd06ac29f823d4df98 ]
-
-pre-migration wait time is the time that a task unnecessarily spends
-on the runqueue of a CPU but doesn't get switched-in there. In terms
-of tracepoints, it is the time between sched:sched_wakeup and
-sched:sched_migrate_task.
-
-Let's say a task woke up on CPU2, then it got migrated to CPU4 and
-then it's switched-in to CPU4. So, here pre-migration wait time is
-time that it was waiting on runqueue of CPU2 after it is woken up.
-
-The general pattern for pre-migration to occur is:
-sched:sched_wakeup
-sched:sched_migrate_task
-sched:sched_switch
-
-The sched:sched_waking event is used to capture the wakeup time,
-as it aligns with the existing code and only introduces a negligible
-time difference.
-
-pre-migrations are generally not useful and it increases migrations.
-This metric would be helpful in testing patches mainly related to wakeup
-and load-balancer code paths as better wakeup logic would choose an
-optimal CPU where task would be switched-in and thereby reducing pre-
-migrations.
-
-The sample output(s) when -P or --pre-migrations is used:
-=================
- time cpu task name wait time sch delay run time pre-mig time
- [tid/pid] (msec) (msec) (msec) (msec)
---------------- ------ ------------------------------ --------- --------- --------- ---------
- 38456.720806 [0001] schbench[28634/28574] 4.917 4.768 1.004 0.000
- 38456.720810 [0001] rcu_preempt[18] 3.919 0.003 0.004 0.000
- 38456.721800 [0006] schbench[28779/28574] 23.465 23.465 1.999 0.000
- 38456.722800 [0002] schbench[28773/28574] 60.371 60.237 3.955 60.197
- 38456.722806 [0001] schbench[28634/28574] 0.004 0.004 1.996 0.000
- 38456.722811 [0001] rcu_preempt[18] 1.996 0.005 0.005 0.000
- 38456.723800 [0000] schbench[28833/28574] 4.000 4.000 3.999 0.000
- 38456.723800 [0004] schbench[28762/28574] 42.951 42.839 3.999 39.867
- 38456.723802 [0007] schbench[28812/28574] 43.947 43.817 3.999 40.866
- 38456.723804 [0001] schbench[28587/28574] 7.935 7.822 0.993 0.000
-
-Signed-off-by: Madadi Vineeth Reddy <vineethr@linux.ibm.com>
-Reviewed-by: Tim Chen <tim.c.chen@linux.intel.com>
-Link: https://lore.kernel.org/r/20241004170756.18064-1-vineethr@linux.ibm.com
-Signed-off-by: Namhyung Kim <namhyung@kernel.org>
-Stable-dep-of: a99d6394cd48 ("perf sched: Use thread__put() in free_idle_threads()")
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/Documentation/perf-sched.txt | 8 ++
- tools/perf/builtin-sched.c | 98 +++++++++++++++----------
- 2 files changed, 67 insertions(+), 39 deletions(-)
-
-diff --git a/tools/perf/Documentation/perf-sched.txt b/tools/perf/Documentation/perf-sched.txt
-index 3db64954a267a3..6dbbddb6464d01 100644
---- a/tools/perf/Documentation/perf-sched.txt
-+++ b/tools/perf/Documentation/perf-sched.txt
-@@ -221,6 +221,14 @@ OPTIONS for 'perf sched timehist'
- priorities are specified with -: 120-129. A combination of both can also be
- provided: 0,120-129.
-
-+-P::
-+--pre-migrations::
-+ Show pre-migration wait time. pre-migration wait time is the time spent
-+ by a task waiting on a runqueue but not getting the chance to run there
-+ and is migrated to a different runqueue where it is finally run. This
-+ time between sched_wakeup and migrate_task is the pre-migration wait
-+ time.
-+
- OPTIONS for 'perf sched replay'
- ------------------------------
-
-diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
-index fd93ab11bd37ed..1203425d2b8a55 100644
---- a/tools/perf/builtin-sched.c
-+++ b/tools/perf/builtin-sched.c
-@@ -229,6 +229,7 @@ struct perf_sched {
- bool show_wakeups;
- bool show_next;
- bool show_migrations;
-+ bool pre_migrations;
- bool show_state;
- bool show_prio;
- u64 skipped_samples;
-@@ -248,7 +249,9 @@ struct thread_runtime {
- u64 dt_iowait; /* time between CPU access by iowait (off cpu) */
- u64 dt_preempt; /* time between CPU access by preempt (off cpu) */
- u64 dt_delay; /* time between wakeup and sched-in */
-+ u64 dt_pre_mig; /* time between migration and wakeup */
- u64 ready_to_run; /* time of wakeup */
-+ u64 migrated; /* time when a thread is migrated */
-
- struct stats run_stats;
- u64 total_run_time;
-@@ -256,6 +259,7 @@ struct thread_runtime {
- u64 total_iowait_time;
- u64 total_preempt_time;
- u64 total_delay_time;
-+ u64 total_pre_mig_time;
-
- char last_state;
-
-@@ -2148,14 +2152,15 @@ static void timehist_header(struct perf_sched *sched)
- printf(" ");
- }
-
-- if (sched->show_prio) {
-- printf(" %-*s %-*s %9s %9s %9s",
-- comm_width, "task name", MAX_PRIO_STR_LEN, "prio",
-- "wait time", "sch delay", "run time");
-- } else {
-- printf(" %-*s %9s %9s %9s", comm_width,
-- "task name", "wait time", "sch delay", "run time");
-- }
-+ printf(" %-*s", comm_width, "task name");
-+
-+ if (sched->show_prio)
-+ printf(" %-*s", MAX_PRIO_STR_LEN, "prio");
-+
-+ printf(" %9s %9s %9s", "wait time", "sch delay", "run time");
-+
-+ if (sched->pre_migrations)
-+ printf(" %9s", "pre-mig time");
-
- if (sched->show_state)
- printf(" %s", "state");
-@@ -2170,17 +2175,15 @@ static void timehist_header(struct perf_sched *sched)
- if (sched->show_cpu_visual)
- printf(" %*s ", ncpus, "");
-
-- if (sched->show_prio) {
-- printf(" %-*s %-*s %9s %9s %9s",
-- comm_width, "[tid/pid]", MAX_PRIO_STR_LEN, "",
-- "(msec)", "(msec)", "(msec)");
-- } else {
-- printf(" %-*s %9s %9s %9s", comm_width,
-- "[tid/pid]", "(msec)", "(msec)", "(msec)");
-- }
-+ printf(" %-*s", comm_width, "[tid/pid]");
-
-- if (sched->show_state)
-- printf(" %5s", "");
-+ if (sched->show_prio)
-+ printf(" %-*s", MAX_PRIO_STR_LEN, "");
-+
-+ printf(" %9s %9s %9s", "(msec)", "(msec)", "(msec)");
-+
-+ if (sched->pre_migrations)
-+ printf(" %9s", "(msec)");
-
- printf("\n");
-
-@@ -2192,15 +2195,15 @@ static void timehist_header(struct perf_sched *sched)
- if (sched->show_cpu_visual)
- printf(" %.*s ", ncpus, graph_dotted_line);
-
-- if (sched->show_prio) {
-- printf(" %.*s %.*s %.9s %.9s %.9s",
-- comm_width, graph_dotted_line, MAX_PRIO_STR_LEN, graph_dotted_line,
-- graph_dotted_line, graph_dotted_line, graph_dotted_line);
-- } else {
-- printf(" %.*s %.9s %.9s %.9s", comm_width,
-- graph_dotted_line, graph_dotted_line, graph_dotted_line,
-- graph_dotted_line);
-- }
-+ printf(" %.*s", comm_width, graph_dotted_line);
-+
-+ if (sched->show_prio)
-+ printf(" %.*s", MAX_PRIO_STR_LEN, graph_dotted_line);
-+
-+ printf(" %.9s %.9s %.9s", graph_dotted_line, graph_dotted_line, graph_dotted_line);
-+
-+ if (sched->pre_migrations)
-+ printf(" %.9s", graph_dotted_line);
-
- if (sched->show_state)
- printf(" %.5s", graph_dotted_line);
-@@ -2256,6 +2259,8 @@ static void timehist_print_sample(struct perf_sched *sched,
-
- print_sched_time(tr->dt_delay, 6);
- print_sched_time(tr->dt_run, 6);
-+ if (sched->pre_migrations)
-+ print_sched_time(tr->dt_pre_mig, 6);
-
- if (sched->show_state)
- printf(" %5c ", thread__tid(thread) == 0 ? 'I' : state);
-@@ -2293,18 +2298,21 @@ static void timehist_print_sample(struct perf_sched *sched,
- * last_time = time of last sched change event for current task
- * (i.e, time process was last scheduled out)
- * ready_to_run = time of wakeup for current task
-+ * migrated = time of task migration to another CPU
- *
-- * -----|------------|------------|------------|------
-- * last ready tprev t
-+ * -----|-------------|-------------|-------------|-------------|-----
-+ * last ready migrated tprev t
- * time to run
- *
-- * |-------- dt_wait --------|
-- * |- dt_delay -|-- dt_run --|
-+ * |---------------- dt_wait ----------------|
-+ * |--------- dt_delay ---------|-- dt_run --|
-+ * |- dt_pre_mig -|
- *
-- * dt_run = run time of current task
-- * dt_wait = time between last schedule out event for task and tprev
-- * represents time spent off the cpu
-- * dt_delay = time between wakeup and schedule-in of task
-+ * dt_run = run time of current task
-+ * dt_wait = time between last schedule out event for task and tprev
-+ * represents time spent off the cpu
-+ * dt_delay = time between wakeup and schedule-in of task
-+ * dt_pre_mig = time between wakeup and migration to another CPU
- */
-
- static void timehist_update_runtime_stats(struct thread_runtime *r,
-@@ -2315,6 +2323,7 @@ static void timehist_update_runtime_stats(struct thread_runtime *r,
- r->dt_iowait = 0;
- r->dt_preempt = 0;
- r->dt_run = 0;
-+ r->dt_pre_mig = 0;
-
- if (tprev) {
- r->dt_run = t - tprev;
-@@ -2323,6 +2332,9 @@ static void timehist_update_runtime_stats(struct thread_runtime *r,
- pr_debug("time travel: wakeup time for task > previous sched_switch event\n");
- else
- r->dt_delay = tprev - r->ready_to_run;
-+
-+ if ((r->migrated > r->ready_to_run) && (r->migrated < tprev))
-+ r->dt_pre_mig = r->migrated - r->ready_to_run;
- }
-
- if (r->last_time > tprev)
-@@ -2346,6 +2358,7 @@ static void timehist_update_runtime_stats(struct thread_runtime *r,
- r->total_sleep_time += r->dt_sleep;
- r->total_iowait_time += r->dt_iowait;
- r->total_preempt_time += r->dt_preempt;
-+ r->total_pre_mig_time += r->dt_pre_mig;
- }
-
- static bool is_idle_sample(struct perf_sample *sample,
-@@ -2762,9 +2775,13 @@ static int timehist_migrate_task_event(const struct perf_tool *tool,
- return -1;
-
- tr->migrations++;
-+ tr->migrated = sample->time;
-
- /* show migrations if requested */
-- timehist_print_migration_event(sched, evsel, sample, machine, thread);
-+ if (sched->show_migrations) {
-+ timehist_print_migration_event(sched, evsel, sample,
-+ machine, thread);
-+ }
-
- return 0;
- }
-@@ -2916,11 +2933,13 @@ static int timehist_sched_change_event(const struct perf_tool *tool,
- /* last state is used to determine where to account wait time */
- tr->last_state = state;
-
-- /* sched out event for task so reset ready to run time */
-+ /* sched out event for task so reset ready to run time and migrated time */
- if (state == 'R')
- tr->ready_to_run = t;
- else
- tr->ready_to_run = 0;
-+
-+ tr->migrated = 0;
- }
-
- evsel__save_time(evsel, sample->time, sample->cpu);
-@@ -3363,8 +3382,8 @@ static int perf_sched__timehist(struct perf_sched *sched)
- goto out;
- }
-
-- if (sched->show_migrations &&
-- perf_session__set_tracepoints_handlers(session, migrate_handlers))
-+ if ((sched->show_migrations || sched->pre_migrations) &&
-+ perf_session__set_tracepoints_handlers(session, migrate_handlers))
- goto out;
-
- /* pre-allocate struct for per-CPU idle stats */
-@@ -3919,6 +3938,7 @@ int cmd_sched(int argc, const char **argv)
- OPT_BOOLEAN(0, "show-prio", &sched.show_prio, "Show task priority"),
- OPT_STRING(0, "prio", &sched.prio_str, "prio",
- "analyze events only for given task priority(ies)"),
-+ OPT_BOOLEAN('P', "pre-migrations", &sched.pre_migrations, "Show pre-migration wait time"),
- OPT_PARENT(sched_options)
- };
-
---
-2.53.0
-
+++ /dev/null
-From 4a601c1324dcd54899d2639c80a9be567ac4ed77 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Fri, 5 Jun 2026 11:17:03 -0300
-Subject: perf sched: Use thread__put() in free_idle_threads()
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit a99d6394cd48fed75b1d24733d5afe6837a61a3f ]
-
-free_idle_threads() calls thread__delete() directly instead of
-thread__put(), bypassing the reference counting lifecycle. Under
-REFCNT_CHECKING builds, this leaks the pointer handle since
-thread__delete() frees the object without going through the refcount
-wrapper.
-
-The idle threads are created via thread__new() (refcount=1) in
-get_idle_thread(). Callers get additional references via thread__get()
-which they release with thread__put(). free_idle_threads() drops the
-base reference — thread__put() is the correct call, matching the
-thread__new() acquisition.
-
-Fixes: 49394a2a24c7 ("perf sched timehist: Introduce timehist command")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: David Ahern <dsahern@gmail.com>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-sched.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
-index f3a03be9471aa0..8eb5c880a5b605 100644
---- a/tools/perf/builtin-sched.c
-+++ b/tools/perf/builtin-sched.c
-@@ -2489,7 +2489,7 @@ static void free_idle_threads(void)
- if (itr)
- thread__put(itr->last_thread);
-
-- thread__delete(idle);
-+ thread__put(idle);
- }
- }
-
---
-2.53.0
-
+++ /dev/null
-From 5709b7f407201e5c57ad5b40736ae821ac5ab396 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Fri, 5 Jun 2026 10:59:11 -0300
-Subject: perf stat: Bounds-check CPU index in topology aggregation callbacks
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 52e69b1c5b606b513d403dd4addc784c27a0c8e2 ]
-
-Six perf_env__get_*_aggr_by_cpu() functions access env->cpu[cpu.cpu]
-after only checking cpu.cpu != -1. env->cpu[] is allocated with
-env->nr_cpus_avail entries, so a CPU index from an untrusted perf.data
-file that exceeds that count causes an out-of-bounds heap read.
-
-Replace the != -1 guard with >= 0 && < env->nr_cpus_avail in all six
-functions. The >= 0 check also catches -1 and any other negative values
-that could bypass the old check.
-
-Affected functions:
- - perf_env__get_socket_aggr_by_cpu()
- - perf_env__get_die_aggr_by_cpu()
- - perf_env__get_cache_aggr_by_cpu()
- - perf_env__get_cluster_aggr_by_cpu()
- - perf_env__get_core_aggr_by_cpu()
- - perf_env__get_cpu_aggr_by_cpu()
-
-Fixes: 68d702f7a120 ("perf stat report: Add support to initialize aggr_map from file")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Ian Rogers <irogers@google.com>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-stat.c | 13 +++++++------
- 1 file changed, 7 insertions(+), 6 deletions(-)
-
-diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
-index e476598de80836..4331820b199601 100644
---- a/tools/perf/builtin-stat.c
-+++ b/tools/perf/builtin-stat.c
-@@ -1561,7 +1561,8 @@ static struct aggr_cpu_id perf_env__get_socket_aggr_by_cpu(struct perf_cpu cpu,
- struct perf_env *env = data;
- struct aggr_cpu_id id = aggr_cpu_id__empty();
-
-- if (cpu.cpu != -1)
-+ /* env->cpu[] has env->nr_cpus_avail entries; reject untrusted indices */
-+ if (cpu.cpu >= 0 && cpu.cpu < env->nr_cpus_avail)
- id.socket = env->cpu[cpu.cpu].socket_id;
-
- return id;
-@@ -1572,7 +1573,7 @@ static struct aggr_cpu_id perf_env__get_die_aggr_by_cpu(struct perf_cpu cpu, voi
- struct perf_env *env = data;
- struct aggr_cpu_id id = aggr_cpu_id__empty();
-
-- if (cpu.cpu != -1) {
-+ if (cpu.cpu >= 0 && cpu.cpu < env->nr_cpus_avail) {
- /*
- * die_id is relative to socket, so start
- * with the socket ID and then add die to
-@@ -1628,7 +1629,7 @@ static struct aggr_cpu_id perf_env__get_cache_aggr_by_cpu(struct perf_cpu cpu,
- struct perf_env *env = data;
- struct aggr_cpu_id id = aggr_cpu_id__empty();
-
-- if (cpu.cpu != -1) {
-+ if (cpu.cpu >= 0 && cpu.cpu < env->nr_cpus_avail) {
- u32 cache_level = (perf_stat.aggr_level) ?: stat_config.aggr_level;
-
- id.socket = env->cpu[cpu.cpu].socket_id;
-@@ -1645,7 +1646,7 @@ static struct aggr_cpu_id perf_env__get_cluster_aggr_by_cpu(struct perf_cpu cpu,
- struct perf_env *env = data;
- struct aggr_cpu_id id = aggr_cpu_id__empty();
-
-- if (cpu.cpu != -1) {
-+ if (cpu.cpu >= 0 && cpu.cpu < env->nr_cpus_avail) {
- id.socket = env->cpu[cpu.cpu].socket_id;
- id.die = env->cpu[cpu.cpu].die_id;
- id.cluster = env->cpu[cpu.cpu].cluster_id;
-@@ -1659,7 +1660,7 @@ static struct aggr_cpu_id perf_env__get_core_aggr_by_cpu(struct perf_cpu cpu, vo
- struct perf_env *env = data;
- struct aggr_cpu_id id = aggr_cpu_id__empty();
-
-- if (cpu.cpu != -1) {
-+ if (cpu.cpu >= 0 && cpu.cpu < env->nr_cpus_avail) {
- /*
- * core_id is relative to socket, die and cluster, we need a
- * global id. So we set socket, die id, cluster id and core id.
-@@ -1678,7 +1679,7 @@ static struct aggr_cpu_id perf_env__get_cpu_aggr_by_cpu(struct perf_cpu cpu, voi
- struct perf_env *env = data;
- struct aggr_cpu_id id = aggr_cpu_id__empty();
-
-- if (cpu.cpu != -1) {
-+ if (cpu.cpu >= 0 && cpu.cpu < env->nr_cpus_avail) {
- /*
- * core_id is relative to socket and die,
- * we need a global id. So we set
---
-2.53.0
-
+++ /dev/null
-From dcdee7052257bab063c42acf931aa4e905732e31 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 6 Jun 2026 11:17:45 -0300
-Subject: perf stat: Introduce perf_env__get_cpu_topology() to guard NULL
- env->cpu
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit afa4363a91a19dff65dceb7fbce7bba689bbc854 ]
-
-process_cpu_topology() in header.c frees env->cpu on old-format
-perf.data files that predate topology information, but leaves
-nr_cpus_avail set. The six perf_env__get_*_aggr_by_cpu() functions
-in builtin-stat.c pass the bounds check but dereference a NULL
-env->cpu pointer, crashing on old recordings.
-
-Introduce perf_env__get_cpu_topology() as a safe accessor that
-validates env->cpu, cpu.cpu >= 0, and cpu.cpu < nr_cpus_avail in
-one place, returning a struct cpu_topology_map pointer or NULL.
-Convert all six topology aggregation callbacks to use it.
-
-Fixes: 88031a0de7d68d13 ("perf stat: Switch to cpu version of cpu_map__get()")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Ian Rogers <irogers@google.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-stat.c | 51 +++++++++++++++++++++------------------
- tools/perf/util/env.h | 14 +++++++++++
- 2 files changed, 42 insertions(+), 23 deletions(-)
-
-diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
-index 4331820b199601..2b8b3354e0cf7b 100644
---- a/tools/perf/builtin-stat.c
-+++ b/tools/perf/builtin-stat.c
-@@ -1560,10 +1560,10 @@ static struct aggr_cpu_id perf_env__get_socket_aggr_by_cpu(struct perf_cpu cpu,
- {
- struct perf_env *env = data;
- struct aggr_cpu_id id = aggr_cpu_id__empty();
-+ struct cpu_topology_map *topo = perf_env__get_cpu_topology(env, cpu);
-
-- /* env->cpu[] has env->nr_cpus_avail entries; reject untrusted indices */
-- if (cpu.cpu >= 0 && cpu.cpu < env->nr_cpus_avail)
-- id.socket = env->cpu[cpu.cpu].socket_id;
-+ if (topo)
-+ id.socket = topo->socket_id;
-
- return id;
- }
-@@ -1572,15 +1572,16 @@ static struct aggr_cpu_id perf_env__get_die_aggr_by_cpu(struct perf_cpu cpu, voi
- {
- struct perf_env *env = data;
- struct aggr_cpu_id id = aggr_cpu_id__empty();
-+ struct cpu_topology_map *topo = perf_env__get_cpu_topology(env, cpu);
-
-- if (cpu.cpu >= 0 && cpu.cpu < env->nr_cpus_avail) {
-+ if (topo) {
- /*
- * die_id is relative to socket, so start
- * with the socket ID and then add die to
- * make a unique ID.
- */
-- id.socket = env->cpu[cpu.cpu].socket_id;
-- id.die = env->cpu[cpu.cpu].die_id;
-+ id.socket = topo->socket_id;
-+ id.die = topo->die_id;
- }
-
- return id;
-@@ -1628,12 +1629,13 @@ static struct aggr_cpu_id perf_env__get_cache_aggr_by_cpu(struct perf_cpu cpu,
- {
- struct perf_env *env = data;
- struct aggr_cpu_id id = aggr_cpu_id__empty();
-+ struct cpu_topology_map *topo = perf_env__get_cpu_topology(env, cpu);
-
-- if (cpu.cpu >= 0 && cpu.cpu < env->nr_cpus_avail) {
-+ if (topo) {
- u32 cache_level = (perf_stat.aggr_level) ?: stat_config.aggr_level;
-
-- id.socket = env->cpu[cpu.cpu].socket_id;
-- id.die = env->cpu[cpu.cpu].die_id;
-+ id.socket = topo->socket_id;
-+ id.die = topo->die_id;
- perf_env__get_cache_id_for_cpu(cpu, env, cache_level, &id);
- }
-
-@@ -1645,11 +1647,12 @@ static struct aggr_cpu_id perf_env__get_cluster_aggr_by_cpu(struct perf_cpu cpu,
- {
- struct perf_env *env = data;
- struct aggr_cpu_id id = aggr_cpu_id__empty();
-+ struct cpu_topology_map *topo = perf_env__get_cpu_topology(env, cpu);
-
-- if (cpu.cpu >= 0 && cpu.cpu < env->nr_cpus_avail) {
-- id.socket = env->cpu[cpu.cpu].socket_id;
-- id.die = env->cpu[cpu.cpu].die_id;
-- id.cluster = env->cpu[cpu.cpu].cluster_id;
-+ if (topo) {
-+ id.socket = topo->socket_id;
-+ id.die = topo->die_id;
-+ id.cluster = topo->cluster_id;
- }
-
- return id;
-@@ -1659,16 +1662,17 @@ static struct aggr_cpu_id perf_env__get_core_aggr_by_cpu(struct perf_cpu cpu, vo
- {
- struct perf_env *env = data;
- struct aggr_cpu_id id = aggr_cpu_id__empty();
-+ struct cpu_topology_map *topo = perf_env__get_cpu_topology(env, cpu);
-
-- if (cpu.cpu >= 0 && cpu.cpu < env->nr_cpus_avail) {
-+ if (topo) {
- /*
- * core_id is relative to socket, die and cluster, we need a
- * global id. So we set socket, die id, cluster id and core id.
- */
-- id.socket = env->cpu[cpu.cpu].socket_id;
-- id.die = env->cpu[cpu.cpu].die_id;
-- id.cluster = env->cpu[cpu.cpu].cluster_id;
-- id.core = env->cpu[cpu.cpu].core_id;
-+ id.socket = topo->socket_id;
-+ id.die = topo->die_id;
-+ id.cluster = topo->cluster_id;
-+ id.core = topo->core_id;
- }
-
- return id;
-@@ -1678,18 +1682,19 @@ static struct aggr_cpu_id perf_env__get_cpu_aggr_by_cpu(struct perf_cpu cpu, voi
- {
- struct perf_env *env = data;
- struct aggr_cpu_id id = aggr_cpu_id__empty();
-+ struct cpu_topology_map *topo = perf_env__get_cpu_topology(env, cpu);
-
-- if (cpu.cpu >= 0 && cpu.cpu < env->nr_cpus_avail) {
-+ if (topo) {
- /*
- * core_id is relative to socket and die,
- * we need a global id. So we set
- * socket, die id and core id
- */
-- id.socket = env->cpu[cpu.cpu].socket_id;
-- id.die = env->cpu[cpu.cpu].die_id;
-- id.core = env->cpu[cpu.cpu].core_id;
-- id.cpu = cpu;
-+ id.socket = topo->socket_id;
-+ id.die = topo->die_id;
-+ id.core = topo->core_id;
- }
-+ id.cpu = cpu;
-
- return id;
- }
-diff --git a/tools/perf/util/env.h b/tools/perf/util/env.h
-index 38de0af2a68081..0ba95396ecf04d 100644
---- a/tools/perf/util/env.h
-+++ b/tools/perf/util/env.h
-@@ -167,6 +167,20 @@ const char *perf_env__pmu_mappings(struct perf_env *env);
-
- int perf_env__read_cpu_topology_map(struct perf_env *env);
-
-+/*
-+ * Safe accessor for env->cpu[] topology array. env->cpu can be NULL when
-+ * reading old-format perf.data that predates topology information —
-+ * process_cpu_topology() in header.c frees it while nr_cpus_avail remains
-+ * set, so callers must not index env->cpu[] without this check.
-+ */
-+static inline struct cpu_topology_map *
-+perf_env__get_cpu_topology(struct perf_env *env, struct perf_cpu cpu)
-+{
-+ if (env->cpu && cpu.cpu >= 0 && cpu.cpu < env->nr_cpus_avail)
-+ return &env->cpu[cpu.cpu];
-+ return NULL;
-+}
-+
- void cpu_cache_level__free(struct cpu_cache_level *cache);
-
- const char *perf_env__arch(struct perf_env *env);
---
-2.53.0
-
+++ /dev/null
-From fdedca7203c4d5eb76d80a8d22d49483a19177be Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Mon, 8 Jun 2026 08:12:34 -0300
-Subject: perf symbols: Add bounds checks to elf_read_build_id() note iteration
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit acc56d3941fc2997a5a21ea9233a8ac3d87c4f2f ]
-
-elf_read_build_id() iterates ELF notes using pointer arithmetic
-driven by n_namesz and n_descsz from the note headers. Neither
-the note header read nor the subsequent name/desc advances are
-checked against the section boundary. A malformed ELF file with
-oversized note sizes causes out-of-bounds reads past the section
-data buffer.
-
-Add two bounds checks: verify the note header fits within the
-remaining section data, and verify that namesz + descsz (after
-alignment) fits before advancing the pointer.
-
-Fixes: fd7a346ea292074e ("perf symbols: Filename__read_build_id should look at .notes section too")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/symbol-elf.c | 18 ++++++++++++++++--
- 1 file changed, 16 insertions(+), 2 deletions(-)
-
-diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
-index 029a1ef9b287b4..90f11a6eb19fea 100644
---- a/tools/perf/util/symbol-elf.c
-+++ b/tools/perf/util/symbol-elf.c
-@@ -909,10 +909,24 @@ static int elf_read_build_id(Elf *elf, void *bf, size_t size)
- ptr = data->d_buf;
- while (ptr < (data->d_buf + data->d_size)) {
- GElf_Nhdr *nhdr = ptr;
-- size_t namesz = NOTE_ALIGN(nhdr->n_namesz),
-- descsz = NOTE_ALIGN(nhdr->n_descsz);
-+ size_t namesz, descsz, remaining;
- const char *name;
-
-+ /* ensure the note header fits within the section */
-+ if (ptr + sizeof(*nhdr) > data->d_buf + data->d_size)
-+ break;
-+
-+ namesz = NOTE_ALIGN(nhdr->n_namesz);
-+ descsz = NOTE_ALIGN(nhdr->n_descsz);
-+
-+ /* validate individually to avoid size_t overflow on 32-bit */
-+ remaining = data->d_buf + data->d_size - ptr - sizeof(*nhdr);
-+ if (namesz > remaining || descsz > remaining - namesz) {
-+ pr_warning("%s: oversized note: n_namesz=%u, n_descsz=%u\n",
-+ __func__, nhdr->n_namesz, nhdr->n_descsz);
-+ break;
-+ }
-+
- ptr += sizeof(*nhdr);
- name = ptr;
- ptr += namesz;
---
-2.53.0
-
+++ /dev/null
-From 387e0c7eed13e14603281c3399a3cc768a2ebf81 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Wed, 10 Jun 2026 16:09:45 -0300
-Subject: perf symbols: Add bounds checks to read_build_id() note iteration in
- minimal build
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 52e582e316c48c53bb3082c29f7862ebc554087e ]
-
-symbol-minimal.c's read_build_id() iterates ELF notes with the same
-pattern as symbol-elf.c's elf_read_build_id(): pointer arithmetic
-driven by n_namesz and n_descsz from 32-bit note header fields,
-without validating that the name and desc fit within the note section
-data. A malformed ELF file with oversized note sizes causes
-out-of-bounds reads past the section data buffer.
-
-Add the same bounds check as the libelf path: validate namesz and
-descsz individually against remaining data before advancing the
-pointer, avoiding size_t overflow on 32-bit.
-
-Fixes: b691f64360ecec49 ("perf symbols: Implement poor man's ELF parser")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/symbol-minimal.c | 11 ++++++++++-
- 1 file changed, 10 insertions(+), 1 deletion(-)
-
-diff --git a/tools/perf/util/symbol-minimal.c b/tools/perf/util/symbol-minimal.c
-index f114f75ebeb989..12ef02bbf2eea8 100644
---- a/tools/perf/util/symbol-minimal.c
-+++ b/tools/perf/util/symbol-minimal.c
-@@ -1,3 +1,4 @@
-+#include "debug.h"
- #include "dso.h"
- #include "symbol.h"
- #include "symsrc.h"
-@@ -44,7 +45,7 @@ static int read_build_id(void *note_data, size_t note_len, struct build_id *bid,
- ptr = note_data;
- while (ptr < (note_data + note_len)) {
- const char *name;
-- size_t namesz, descsz;
-+ size_t namesz, descsz, remaining;
-
- nhdr = ptr;
- if (need_swap) {
-@@ -56,6 +57,14 @@ static int read_build_id(void *note_data, size_t note_len, struct build_id *bid,
- namesz = NOTE_ALIGN(nhdr->n_namesz);
- descsz = NOTE_ALIGN(nhdr->n_descsz);
-
-+ /* validate individually to avoid size_t overflow on 32-bit */
-+ remaining = note_data + note_len - ptr - sizeof(*nhdr);
-+ if (namesz > remaining || descsz > remaining - namesz) {
-+ pr_warning("%s: oversized note: n_namesz=%u, n_descsz=%u\n",
-+ __func__, nhdr->n_namesz, nhdr->n_descsz);
-+ break;
-+ }
-+
- ptr += sizeof(*nhdr);
- name = ptr;
- ptr += namesz;
---
-2.53.0
-
+++ /dev/null
-From d89e7b0b17c6423161131b145e615a92dedeaa1b Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sun, 7 Jun 2026 21:05:15 -0300
-Subject: perf symbols: Bounds-check .gnu_debuglink section data
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 9c74f0aab398cb32ab250401f323c0fdc9a3a496 ]
-
-filename__read_debuglink() copies .gnu_debuglink section data into a
-caller-provided buffer via:
-
- strncpy(debuglink, data->d_buf, size);
-
-where size is PATH_MAX. If the ELF section is smaller than size and
-lacks a null terminator, strncpy reads past data->d_buf into adjacent
-memory. A malformed ELF file can trigger this, potentially causing a
-segfault or leaking heap data.
-
-Additionally, strncpy does not guarantee null termination when the
-source fills the buffer.
-
-Replace with an explicit memcpy bounded by both the output buffer
-size and the actual section data size (data->d_size), followed by
-explicit null termination.
-
-Fixes: e5a1845fc0aeca85 ("perf symbols: Split out util/symbol-elf.c")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/symbol-elf.c | 9 ++++++++-
- 1 file changed, 8 insertions(+), 1 deletion(-)
-
-diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
-index 3968645e1dfa08..923e7d1d8aad8a 100644
---- a/tools/perf/util/symbol-elf.c
-+++ b/tools/perf/util/symbol-elf.c
-@@ -1160,7 +1160,14 @@ int filename__read_debuglink(const char *filename, char *debuglink,
- goto out_elf_end;
-
- /* the start of this section is a zero-terminated string */
-- strncpy(debuglink, data->d_buf, size);
-+ if (data->d_size > 0) {
-+ size_t len = min(size - 1, data->d_size);
-+
-+ memcpy(debuglink, data->d_buf, len);
-+ debuglink[len] = '\0';
-+ } else {
-+ debuglink[0] = '\0';
-+ }
-
- err = 0;
-
---
-2.53.0
-
+++ /dev/null
-From f9a65684f7758d8381ec0b0f974618a7020a293b Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sun, 7 Jun 2026 22:40:20 -0300
-Subject: perf symbols: Bounds-check descsz in sysfs__read_build_id() GNU
- fallback
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 1b4e9fbdeabc549965e70ac0cd8095d57ff6df06 ]
-
-When sysfs__read_build_id() matches NT_GNU_BUILD_ID with the right
-namesz but the name content is not "GNU", it falls back to reading
-descsz bytes into the stack buffer bf[BUFSIZ]:
-
- } else if (read(fd, bf, descsz) != (ssize_t)descsz)
-
-Unlike the else branch which validates namesz + descsz against
-sizeof(bf), this path passes descsz directly to read() without any
-bounds check. A crafted sysfs file with a large n_descsz overflows
-the 8192-byte stack buffer.
-
-Add a descsz > sizeof(bf) check before the read, breaking out of
-the loop on oversized values.
-
-Fixes: e5a1845fc0aeca85 ("perf symbols: Split out util/symbol-elf.c")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/symbol-elf.c | 9 +++++++--
- 1 file changed, 7 insertions(+), 2 deletions(-)
-
-diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
-index 923e7d1d8aad8a..c7fcbb8fb644c3 100644
---- a/tools/perf/util/symbol-elf.c
-+++ b/tools/perf/util/symbol-elf.c
-@@ -1060,8 +1060,13 @@ int sysfs__read_build_id(const char *filename, struct build_id *bid)
- err = 0;
- break;
- }
-- } else if (read(fd, bf, descsz) != (ssize_t)descsz)
-- break;
-+ } else {
-+ /* descsz from untrusted file — clamp to buffer */
-+ if (descsz > sizeof(bf))
-+ break;
-+ if (read(fd, bf, descsz) != (ssize_t)descsz)
-+ break;
-+ }
- } else {
- size_t n;
-
---
-2.53.0
-
+++ /dev/null
-From 45ed275d8a4e761c4f0a446da25672469f952d5a Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Wed, 10 Jun 2026 19:32:22 -0300
-Subject: perf symbols: Break infinite loop on zero-filled notes in
- sysfs__read_build_id()
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 063c647b24f640657d6d9e2e90d620ea3ee19ae6 ]
-
-sysfs__read_build_id() iterates ELF note headers from sysfs files in a
-while(1) loop. If the file contains a zero-filled note header (both
-n_namesz and n_descsz are 0), the code computes n = namesz + descsz = 0
-and calls read(fd, bf, 0). read() with count 0 returns 0, which
-matches the expected (ssize_t)n value, so the error check passes and
-the loop repeats — reading the same zero bytes and spinning forever.
-
-This can happen with corrupted or zero-padded sysfs pseudo-files.
-
-Add a check for n == 0 before the read, since no valid ELF note has
-both name and description of zero length.
-
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Fixes: f1617b40596cb341 ("perf symbols: Record the build_ids of kernel modules too")
-Reviewed-by: Ian Rogers <irogers@google.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/symbol-elf.c | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
-index 4ba49975970a9b..bb3c1b68fe3706 100644
---- a/tools/perf/util/symbol-elf.c
-+++ b/tools/perf/util/symbol-elf.c
-@@ -1092,6 +1092,9 @@ int sysfs__read_build_id(const char *filename, struct build_id *bid)
- } else {
- n = namesz + descsz;
- }
-+ /* no valid note has both namesz and descsz zero */
-+ if (n == 0)
-+ break;
- if (read(fd, bf, n) != (ssize_t)n)
- break;
- }
---
-2.53.0
-
+++ /dev/null
-From e1c9337037614d1a0761570fec1b31035874a2c9 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Wed, 10 Jun 2026 19:28:43 -0300
-Subject: perf symbols: Fix bswap copy-paste error for 32-bit ELF p_filesz
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 081b387c7397498c583b1ba7c2fdaf4c6da6b538 ]
-
-filename__read_build_id() byte-swaps 32-bit ELF program headers on
-cross-endian files, but line 178 passes p_offset to bswap_32() instead
-of p_filesz:
-
- hdrs.phdr32[i].p_filesz = bswap_32(hdrs.phdr32[i].p_offset);
-
-This clobbers p_filesz with the already-swapped p_offset value. The
-64-bit path on line 182 is correct and swaps p_filesz from p_filesz.
-
-The consequence is that the PT_NOTE segment read uses the wrong size,
-which can cause either a short read (missing the build-id) or an
-oversized read (reading past the segment into adjacent data).
-
-Fix by swapping the correct field.
-
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Fixes: fef8f648bb47726d ("perf symbol: Fix use-after-free in filename__read_build_id")
-Reviewed-by: Ian Rogers <irogers@google.com>
-Cc: Ian Rogers <irogers@google.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/symbol-minimal.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/tools/perf/util/symbol-minimal.c b/tools/perf/util/symbol-minimal.c
-index 12ef02bbf2eea8..68f5a2622bd8bc 100644
---- a/tools/perf/util/symbol-minimal.c
-+++ b/tools/perf/util/symbol-minimal.c
-@@ -168,7 +168,7 @@ int filename__read_build_id(const char *filename, struct build_id *bid)
- if (elf32) {
- hdrs.phdr32[i].p_type = bswap_32(hdrs.phdr32[i].p_type);
- hdrs.phdr32[i].p_offset = bswap_32(hdrs.phdr32[i].p_offset);
-- hdrs.phdr32[i].p_filesz = bswap_32(hdrs.phdr32[i].p_offset);
-+ hdrs.phdr32[i].p_filesz = bswap_32(hdrs.phdr32[i].p_filesz);
- } else {
- hdrs.phdr64[i].p_type = bswap_32(hdrs.phdr64[i].p_type);
- hdrs.phdr64[i].p_offset = bswap_64(hdrs.phdr64[i].p_offset);
---
-2.53.0
-
+++ /dev/null
-From 2a6cdac63f01f3a6bce30d35b78eb1e730353523 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sun, 7 Jun 2026 21:04:50 -0300
-Subject: perf symbols: Fix signed overflow in sysfs__read_build_id() size
- check
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 6eaa8ee3e2abe5112e80e94c27196bb175689469 ]
-
-sysfs__read_build_id() reads ELF note headers from sysfs files. The
-note's namesz and descsz fields are used to compute the skip size:
-
- int n = namesz + descsz;
- if (n > (int)sizeof(bf))
-
-Both namesz and descsz are size_t from NOTE_ALIGN() of 32-bit note
-header fields. Their sum can exceed INT_MAX, overflowing the signed
-int n to a negative value. The check n > sizeof(bf) then evaluates
-false (negative < positive in signed comparison), and read(fd, bf, n)
-reinterprets the negative n as a huge size_t count — the kernel writes
-up to MAX_RW_COUNT bytes into the 8192-byte stack buffer.
-
-In practice the overflow is bounded by the sysfs file's actual size,
-so a real sysfs notes file won't trigger it organically. But crafted
-input (e.g. via a mounted debugfs/sysfs image) could.
-
-Fix by validating namesz and descsz individually against the buffer
-size before summing, and change n to size_t to avoid the signed
-overflow entirely.
-
-Fixes: f1617b40596cb341 ("perf symbols: Record the build_ids of kernel modules too")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/symbol-elf.c | 9 ++++++---
- 1 file changed, 6 insertions(+), 3 deletions(-)
-
-diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
-index 61f931997f2ecf..3968645e1dfa08 100644
---- a/tools/perf/util/symbol-elf.c
-+++ b/tools/perf/util/symbol-elf.c
-@@ -1063,14 +1063,17 @@ int sysfs__read_build_id(const char *filename, struct build_id *bid)
- } else if (read(fd, bf, descsz) != (ssize_t)descsz)
- break;
- } else {
-- int n = namesz + descsz;
-+ size_t n;
-
-- if (n > (int)sizeof(bf)) {
-+ /* int sum of namesz+descsz can overflow negative, bypassing size check */
-+ if (namesz > sizeof(bf) || descsz > sizeof(bf) - namesz) {
- n = sizeof(bf);
- pr_debug("%s: truncating reading of build id in sysfs file %s: n_namesz=%u, n_descsz=%u.\n",
- __func__, filename, nhdr.n_namesz, nhdr.n_descsz);
-+ } else {
-+ n = namesz + descsz;
- }
-- if (read(fd, bf, n) != n)
-+ if (read(fd, bf, n) != (ssize_t)n)
- break;
- }
- }
---
-2.53.0
-
+++ /dev/null
-From 43fa6a64625d86439ef972c35b5f664d9082158e Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Wed, 10 Jun 2026 19:29:34 -0300
-Subject: perf symbols: Validate p_filesz before use in
- filename__read_build_id()
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 2a3716544359d4312c81b0fa909a13301186da17 ]
-
-filename__read_build_id() stores ELF p_filesz in a ssize_t variable.
-A crafted 32-bit ELF with p_filesz = 0xFFFFFFFF produces ssize_t value
--1. The comparison `p_filesz > buf_size` evaluates false because signed
--1 is less than any non-negative buf_size, so the realloc is skipped and
-buf remains NULL.
-
-The subsequent read(fd, NULL, -1) returns -1, which equals p_filesz,
-passing the error check. read_build_id() then dereferences the NULL
-buffer.
-
-Add an explicit check for p_filesz <= 0 before using the value,
-catching both zero-length and sign-wrapped negative sizes from crafted
-ELF files.
-
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Fixes: ba0b7081f7a521d7 ("perf symbol-minimal: Fix ehdr reading in filename__read_build_id")
-Reviewed-by: Ian Rogers <irogers@google.com>
-Cc: Ian Rogers <irogers@google.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/symbol-minimal.c | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/tools/perf/util/symbol-minimal.c b/tools/perf/util/symbol-minimal.c
-index 68f5a2622bd8bc..f6bef8fcc75f97 100644
---- a/tools/perf/util/symbol-minimal.c
-+++ b/tools/perf/util/symbol-minimal.c
-@@ -179,6 +179,9 @@ int filename__read_build_id(const char *filename, struct build_id *bid)
- continue;
-
- p_filesz = elf32 ? hdrs.phdr32[i].p_filesz : hdrs.phdr64[i].p_filesz;
-+ /* ssize_t can go negative with crafted ELF p_filesz values */
-+ if (p_filesz <= 0)
-+ continue;
- if (p_filesz > buf_size) {
- void *tmp;
-
---
-2.53.0
-
+++ /dev/null
-From 35b16a7a2c4fc458304447128b86514ce9f70f3c Mon Sep 17 00:00:00 2001
-From: Chuck Lever <chuck.lever@oracle.com>
-Date: Tue, 10 Mar 2026 13:59:51 -0400
-Subject: perf synthetic-events: Fix stale build ID in module MMAP2 records
-
-From: Chuck Lever <chuck.lever@oracle.com>
-
-commit 35b16a7a2c4fc458304447128b86514ce9f70f3c upstream.
-
-perf_event__synthesize_modules() allocates a single union perf_event and
-reuses it across every kernel module callback.
-
-After the first module is processed, perf_record_mmap2__read_build_id()
-sets PERF_RECORD_MISC_MMAP_BUILD_ID in header.misc and writes that
-module's build ID into the event.
-
-On subsequent iterations the callback overwrites start, len, pid, and
-filename for the next module but never clears the stale build ID fields
-or the MMAP_BUILD_ID flag.
-
-When perf_record_mmap2__read_build_id() runs for the second module it
-sees the flag, reads the stale build ID into a dso_id, and
-__dso__improve_id() permanently poisons the DSO with the wrong build ID.
-
-Every module after the first therefore receives the first module's build
-ID in its MMAP2 record.
-
-On a system with the sunrpc and nfsd modules loaded, this causes perf
-script and perf report to show [unknown] for all module symbols.
-
-The latent bug has existed since commit d9f2ecbc5e47fca7 ("perf dso:
-Move build_id to dso_id") introduced the PERF_RECORD_MISC_MMAP_BUILD_ID
-check in perf_record_mmap2__read_build_id().
-
-Commit 53b00ff358dc75b1 ("perf record: Make --buildid-mmap the default")
-then exposed it to all users by making the MMAP2-with-build-ID path the
-default. Both commits were merged in the same series.
-
-Clear the MMAP_BUILD_ID flag and zero the build_id union before each
-call to perf_record_mmap2__read_build_id() so that every module starts
-with a clean slate.
-
-Fixes: d9f2ecbc5e47fca7 ("perf dso: Move build_id to dso_id")
-Reviewed-by: Ian Rogers <irogers@google.com>
-Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
-Cc: Adrian Hunter <adrian.hunter@intel.com>
-Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
-Cc: Ian Rogers <irogers@google.com>
-Cc: Ingo Molnar <mingo@redhat.com>
-Cc: James Clark <james.clark@linaro.org>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Mark Rutland <mark.rutland@arm.com>
-Cc: Namhyung Kim <namhyung@kernel.org>
-Cc: Peter Zijlstra <peterz@infradead.org>
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
----
- tools/perf/util/synthetic-events.c | 5 +++++
- 1 file changed, 5 insertions(+)
-
---- a/tools/perf/util/synthetic-events.c
-+++ b/tools/perf/util/synthetic-events.c
-@@ -702,6 +702,11 @@ static int perf_event__synthesize_module
-
- memcpy(event->mmap2.filename, dso__long_name(dso), dso__long_name_len(dso) + 1);
-
-+ /* Clear stale build ID from previous module iteration */
-+ event->mmap2.header.misc &= ~PERF_RECORD_MISC_MMAP_BUILD_ID;
-+ memset(event->mmap2.build_id, 0, sizeof(event->mmap2.build_id));
-+ event->mmap2.build_id_size = 0;
-+
- perf_record_mmap2__read_build_id(&event->mmap2, args->machine, false);
- } else {
- size = PERF_ALIGN(dso__long_name_len(dso) + 1, sizeof(u64));
+++ /dev/null
-From e04782c639cb25bbc4a3a833c7d1b70f8d5d728e Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 6 Jun 2026 11:21:32 -0300
-Subject: perf timechart: Fix cpu2y() OOB read on untrusted CPU index
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit e2496db45bfd8dfb6154ec415798fee330f1cc0a ]
-
-cpu2y() indexes topology_map[cpu] without bounds checking. The array
-is allocated with nr_cpus entries (from env->nr_cpus_online), but
-callers pass sample CPU values from perf.data which can exceed that
-size with cross-machine recordings.
-
-Track the topology_map allocation size and bounds-check the CPU
-argument in cpu2y() before indexing. Out-of-bounds CPUs fall back
-to the identity mapping (cpu2slot(cpu)), which is the same behavior
-as when no topology is available.
-
-Fixes: c507999790438cde ("perf timechart: Add support for topology")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Stanislav Fomichev <stfomichev@yandex-team.ru>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/svghelper.c | 10 ++++++----
- 1 file changed, 6 insertions(+), 4 deletions(-)
-
-diff --git a/tools/perf/util/svghelper.c b/tools/perf/util/svghelper.c
-index 2b04f47f4db0f0..d553a40f2c6add 100644
---- a/tools/perf/util/svghelper.c
-+++ b/tools/perf/util/svghelper.c
-@@ -46,13 +46,13 @@ static double cpu2slot(int cpu)
- }
-
- static int *topology_map;
-+static int topology_map_size;
-
- static double cpu2y(int cpu)
- {
-- if (topology_map)
-+ if (topology_map && cpu >= 0 && cpu < topology_map_size)
- return cpu2slot(topology_map[cpu]) * SLOT_MULT;
-- else
-- return cpu2slot(cpu) * SLOT_MULT;
-+ return cpu2slot(cpu) * SLOT_MULT;
- }
-
- static double time2pixels(u64 __time)
-@@ -734,7 +734,8 @@ static int str_to_bitmap(char *s, cpumask_t *b, int nr_cpus)
- return -1;
-
- perf_cpu_map__for_each_cpu(cpu, idx, map) {
-- if (cpu.cpu >= nr_cpus) {
-+ /* perf_cpu_map__new("") returns cpu.cpu == -1 */
-+ if (cpu.cpu < 0 || cpu.cpu >= nr_cpus) {
- ret = -1;
- break;
- }
-@@ -792,6 +793,7 @@ int svg_build_topology_map(struct perf_env *env)
- fprintf(stderr, "topology: no memory\n");
- goto exit;
- }
-+ topology_map_size = nr_cpus;
-
- for (i = 0; i < nr_cpus; i++)
- topology_map[i] = -1;
---
-2.53.0
-
+++ /dev/null
-From ee957ddaa2bc90819907eb28fef12305728eff63 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Thu, 4 Jun 2026 18:14:23 -0300
-Subject: perf tools: Add bounds check to cpu__get_node()
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 1e7921d7227de5da0dfc167943092c823ec7e49b ]
-
-cpu__get_node() accesses cpunode_map[cpu.cpu] without checking against
-max_cpu_num, the allocation size of cpunode_map. Callers such as
-builtin-kmem.c:evsel__process_alloc_event() pass sample->cpu from
-perf.data events, which may exceed the host's CPU count when analyzing
-cross-machine recordings.
-
-Add a bounds check against max_cpu_num before indexing, returning -1
-for out-of-range values. This is a central fix that protects all
-callers.
-
-Fixes: 86895b480a2f ("perf stat: Add --per-node agregation support")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/cpumap.c | 4 ++++
- 1 file changed, 4 insertions(+)
-
-diff --git a/tools/perf/util/cpumap.c b/tools/perf/util/cpumap.c
-index 27094211edd8a8..7cafa6672d4fb4 100644
---- a/tools/perf/util/cpumap.c
-+++ b/tools/perf/util/cpumap.c
-@@ -522,6 +522,10 @@ int cpu__get_node(struct perf_cpu cpu)
- return -1;
- }
-
-+ /* cpunode_map allocated for max_cpu_num entries; input may be untrusted */
-+ if (cpu.cpu < 0 || cpu.cpu >= max_cpu_num.cpu)
-+ return -1;
-+
- return cpunode_map[cpu.cpu];
- }
-
---
-2.53.0
-
+++ /dev/null
-From 866c203d6775ec390b55d33780deaadb0efd0c36 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 6 Jun 2026 20:37:52 -0300
-Subject: perf tools: Fix get_max_num() size_t underflow on empty sysfs file
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 0a012113bb3a44482c163f16f4db03ccaa37a339 ]
-
-get_max_num() reads a sysfs file (cpu/possible, cpu/present, or
-node/possible) and scans backward from the end to find the last
-number. If the file is empty, filename__read_str() returns num == 0.
-The loop `while (--num)` decrements the size_t from 0 to SIZE_MAX,
-reading backward across the heap until a comma or hyphen is found
-or unmapped memory is hit.
-
-Add an early return for empty files before the backward scan.
-
-Fixes: 7780c25bae59fd04 ("perf tools: Allow ability to map cpus to nodes easily")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Reviewed-by: Ian Rogers <irogers@google.com>
-Cc: Don Zickus <dzickus@redhat.com>
-Cc: Ian Rogers <irogers@google.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/cpumap.c | 6 ++++++
- 1 file changed, 6 insertions(+)
-
-diff --git a/tools/perf/util/cpumap.c b/tools/perf/util/cpumap.c
-index 7cafa6672d4fb4..388755a4eda2f2 100644
---- a/tools/perf/util/cpumap.c
-+++ b/tools/perf/util/cpumap.c
-@@ -402,6 +402,12 @@ static int get_max_num(char *path, int *max)
-
- buf[num] = '\0';
-
-+ /* empty file — nothing to parse */
-+ if (num == 0) {
-+ err = -1;
-+ goto out;
-+ }
-+
- /* start on the right, to find highest node num */
- while (--num) {
- if ((buf[num] == ',') || (buf[num] == '-')) {
---
-2.53.0
-
+++ /dev/null
-From d023b81607362e43e1b5a79f05cad140a08029f7 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sun, 7 Jun 2026 22:37:55 -0300
-Subject: perf tools: Fix thread__set_comm_from_proc() on empty comm file
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 31d596054550f793508abe7dd593853ece47d428 ]
-
-thread__set_comm_from_proc() calls procfs__read_str() then strips
-the trailing newline via comm[sz - 1] = '\0'. procfs__read_str()
-allocates the buffer before reading, so on an empty /proc/pid/comm
-(reachable during late exit teardown) it returns success with sz = 0
-and an unterminated heap buffer.
-
-The sz - 1 underflow was the original sashiko finding: it writes a
-null byte before the allocation. But even with a sz > 0 guard on
-the newline strip, the unterminated buffer would still be passed to
-thread__set_comm() which calls strlen() — an unbounded heap read.
-
-Fix by treating sz == 0 as failure: free the buffer and return -1.
-This is consistent with pmu.c's perf_pmu__parse_scale/unit which
-already treat len == 0 from filename__read_str as an error.
-
-Fixes: 2f3027ac28bf6bc3 ("perf thread: Introduce method to set comm from /proc/pid/self")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/thread.c | 5 +++++
- 1 file changed, 5 insertions(+)
-
-diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c
-index 309d573eac9a94..64f29370d59962 100644
---- a/tools/perf/util/thread.c
-+++ b/tools/perf/util/thread.c
-@@ -273,6 +273,11 @@ int thread__set_comm_from_proc(struct thread *thread)
- if (!(snprintf(path, sizeof(path), "%d/task/%d/comm",
- thread__pid(thread), thread__tid(thread)) >= (int)sizeof(path)) &&
- procfs__read_str(path, &comm, &sz) == 0) {
-+ /* sz==0: read got nothing, e.g. race during exit teardown */
-+ if (sz == 0) {
-+ free(comm);
-+ return -1;
-+ }
- comm[sz - 1] = '\0';
- err = thread__set_comm(thread, comm, 0);
- }
---
-2.53.0
-
+++ /dev/null
-From af74646eb6980580abfe8fcabf8f83c6102d4225 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Thu, 4 Jun 2026 12:55:06 -0300
-Subject: perf tools: Guard test_bit from out-of-bounds sample CPU
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit a5498ccf8079fc91c938f122ff9697b0c526b2fd ]
-
-When PERF_SAMPLE_CPU is absent from a perf.data file, sample->cpu is
-initialized to (u32)-1 by evsel__parse_sample(). Five call sites pass
-this value directly to test_bit(sample->cpu, cpu_bitmap), reading
-massively out of bounds past the DECLARE_BITMAP(..., MAX_NR_CPUS)
-allocation of 4096 bits.
-
-Add a sample->cpu >= MAX_NR_CPUS guard before each test_bit() call,
-matching the existing safe pattern in builtin-kwork.c. This catches
-both the (u32)-1 sentinel and any corrupted CPU value exceeding the
-bitmap size.
-
-Fixes: 5d67be97f890 ("perf report/annotate/script: Add option to specify a CPU range")
-Cc: Anton Blanchard <anton@samba.org>
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/builtin-annotate.c | 3 ++-
- tools/perf/builtin-diff.c | 3 ++-
- tools/perf/builtin-report.c | 3 ++-
- tools/perf/builtin-sched.c | 6 ++++--
- 4 files changed, 10 insertions(+), 5 deletions(-)
-
-diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
-index 3dc6197ef3fa6d..3882f400065c7c 100644
---- a/tools/perf/builtin-annotate.c
-+++ b/tools/perf/builtin-annotate.c
-@@ -298,7 +298,8 @@ static int process_sample_event(const struct perf_tool *tool,
- goto out_put;
- }
-
-- if (ann->cpu_list && !test_bit(sample->cpu, ann->cpu_bitmap))
-+ if (ann->cpu_list && (sample->cpu >= MAX_NR_CPUS ||
-+ !test_bit(sample->cpu, ann->cpu_bitmap)))
- goto out_put;
-
- if (!al.filtered &&
-diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c
-index 82fb7773e03e62..bf5611e1f5e006 100644
---- a/tools/perf/builtin-diff.c
-+++ b/tools/perf/builtin-diff.c
-@@ -417,7 +417,8 @@ static int diff__process_sample_event(const struct perf_tool *tool,
- goto out;
- }
-
-- if (cpu_list && !test_bit(sample->cpu, cpu_bitmap)) {
-+ if (cpu_list && (sample->cpu >= MAX_NR_CPUS ||
-+ !test_bit(sample->cpu, cpu_bitmap))) {
- ret = 0;
- goto out;
- }
-diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
-index 8700c39680662a..86cbca6285a51a 100644
---- a/tools/perf/builtin-report.c
-+++ b/tools/perf/builtin-report.c
-@@ -301,7 +301,8 @@ static int process_sample_event(const struct perf_tool *tool,
- if (symbol_conf.hide_unresolved && al.sym == NULL)
- goto out_put;
-
-- if (rep->cpu_list && !test_bit(sample->cpu, rep->cpu_bitmap))
-+ if (rep->cpu_list && (sample->cpu >= MAX_NR_CPUS ||
-+ !test_bit(sample->cpu, rep->cpu_bitmap)))
- goto out_put;
-
- if (sort__mode == SORT_MODE__BRANCH) {
-diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
-index fa5f00d47e6277..6d7106fb1c0cbc 100644
---- a/tools/perf/builtin-sched.c
-+++ b/tools/perf/builtin-sched.c
-@@ -2185,7 +2185,8 @@ static void timehist_print_sample(struct perf_sched *sched,
- char nstr[30];
- u64 wait_time;
-
-- if (cpu_list && !test_bit(sample->cpu, cpu_bitmap))
-+ if (cpu_list && (sample->cpu >= MAX_NR_CPUS ||
-+ !test_bit(sample->cpu, cpu_bitmap)))
- return;
-
- timestamp__scnprintf_usec(t, tstr, sizeof(tstr));
-@@ -2823,7 +2824,8 @@ static int timehist_sched_change_event(const struct perf_tool *tool,
- }
-
- if (!sched->idle_hist || thread__tid(thread) == 0) {
-- if (!cpu_list || test_bit(sample->cpu, cpu_bitmap))
-+ if (!cpu_list || (sample->cpu < MAX_NR_CPUS &&
-+ test_bit(sample->cpu, cpu_bitmap)))
- timehist_update_runtime_stats(tr, t, tprev);
-
- if (sched->idle_hist) {
---
-2.53.0
-
+++ /dev/null
-From 6bb248d226457f4db505e74b659d8af2022b8d69 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 6 Jun 2026 20:57:49 -0300
-Subject: perf tools: Use perf_env__get_cpu_topology() in machine__resolve()
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 5484b43a0ec8231c36fba6ead654cb72dbba8b8f ]
-
-machine__resolve() accesses env->cpu[al->cpu].socket_id after checking
-al->cpu >= 0 and env->cpu != NULL, but without validating al->cpu
-against env->nr_cpus_avail. Since al->cpu comes from the untrusted
-perf.data sample, a crafted file with a large CPU index causes an
-out-of-bounds heap read.
-
-Use perf_env__get_cpu_topology() which validates both NULL and bounds.
-Also bounds-check al->cpu before the cast to struct perf_cpu (int16_t):
-without this, values like 65536 silently truncate to 0, bypassing the
-accessor's internal check and returning CPU 0's topology.
-
-Fixes: 0c4c4debb0adda4c ("perf tools: Add processor socket info to hist_entry and addr_location")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Reviewed-by: Ian Rogers <irogers@google.com>
-Cc: Kan Liang <kan.liang@intel.com>
-Cc: Ian Rogers <irogers@google.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/event.c | 15 +++++++++++++--
- 1 file changed, 13 insertions(+), 2 deletions(-)
-
-diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c
-index aac96d5d191700..1e5085817e8250 100644
---- a/tools/perf/util/event.c
-+++ b/tools/perf/util/event.c
-@@ -12,6 +12,7 @@
- #include <linux/zalloc.h>
- #include "cpumap.h"
- #include "dso.h"
-+#include "env.h"
- #include "event.h"
- #include "debug.h"
- #include "hist.h"
-@@ -763,8 +764,18 @@ int machine__resolve(struct machine *machine, struct addr_location *al,
- if (al->cpu >= 0) {
- struct perf_env *env = machine->env;
-
-- if (env && env->cpu)
-- al->socket = env->cpu[al->cpu].socket_id;
-+ /*
-+ * Bounds-check al->cpu (s32) before casting to struct perf_cpu
-+ * (int16_t): without this, e.g. 65536 truncates to 0 and silently
-+ * returns CPU 0's topology. Can go once perf_cpu.cpu is widened.
-+ */
-+ if (env && al->cpu < env->nr_cpus_avail) {
-+ struct cpu_topology_map *topo;
-+
-+ topo = perf_env__get_cpu_topology(env, (struct perf_cpu){ al->cpu });
-+ if (topo)
-+ al->socket = topo->socket_id;
-+ }
- }
-
- if (al->map) {
---
-2.53.0
-
+++ /dev/null
-From 6860af79c10d3ed1e8095d743f4ff35e60cfb829 Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Sat, 6 Jun 2026 20:43:52 -0300
-Subject: perf tools: Use scnprintf() in cpu_map__snprint() to prevent overflow
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 7953a3a9b8e02e98c6e6958f291d0ae22393e46a ]
-
-cpu_map__snprint() accumulates snprintf() return values in ret.
-snprintf() returns the number of characters that *would have been
-written* on truncation, not the actual count. When a fragmented CPU
-list exceeds the buffer, ret grows past size, causing `size - ret` to
-underflow (both are size_t), and subsequent snprintf() calls write
-past the end of the caller's stack buffer.
-
-Switch to scnprintf() which returns the actual number of characters
-written, making ret accumulation safe by construction.
-
-Fixes: a24020e6b7cf6eb8 ("perf tools: Change cpu_map__fprintf output")
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Reviewed-by: Ian Rogers <irogers@google.com>
-Cc: Jiri Olsa <jolsa@kernel.org>
-Cc: Ian Rogers <irogers@google.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/cpumap.c | 18 +++++++++---------
- 1 file changed, 9 insertions(+), 9 deletions(-)
-
-diff --git a/tools/perf/util/cpumap.c b/tools/perf/util/cpumap.c
-index 388755a4eda2f2..f6a5daffeeb10e 100644
---- a/tools/perf/util/cpumap.c
-+++ b/tools/perf/util/cpumap.c
-@@ -625,21 +625,21 @@ size_t cpu_map__snprint(struct perf_cpu_map *map, char *buf, size_t size)
- if (start == -1) {
- start = i;
- if (last) {
-- ret += snprintf(buf + ret, size - ret,
-- "%s%d", COMMA,
-- perf_cpu_map__cpu(map, i).cpu);
-+ ret += scnprintf(buf + ret, size - ret,
-+ "%s%d", COMMA,
-+ perf_cpu_map__cpu(map, i).cpu);
- }
- } else if (((i - start) != (cpu.cpu - perf_cpu_map__cpu(map, start).cpu)) || last) {
- int end = i - 1;
-
- if (start == end) {
-- ret += snprintf(buf + ret, size - ret,
-- "%s%d", COMMA,
-- perf_cpu_map__cpu(map, start).cpu);
-+ ret += scnprintf(buf + ret, size - ret,
-+ "%s%d", COMMA,
-+ perf_cpu_map__cpu(map, start).cpu);
- } else {
-- ret += snprintf(buf + ret, size - ret,
-- "%s%d-%d", COMMA,
-- perf_cpu_map__cpu(map, start).cpu, perf_cpu_map__cpu(map, end).cpu);
-+ ret += scnprintf(buf + ret, size - ret,
-+ "%s%d-%d", COMMA,
-+ perf_cpu_map__cpu(map, start).cpu, perf_cpu_map__cpu(map, end).cpu);
- }
- first = false;
- start = i;
---
-2.53.0
-
+++ /dev/null
-From 5de6778c41c7e0c4eb12f4f80835a6458786ec1e Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Wed, 10 Jun 2026 20:34:38 -0300
-Subject: perf tools: Use snprintf() for root_dir path construction
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 7b0df6f4d498b1608afccfd6dffb264e6da91693 ]
-
-get_kernel_version() in machine.c and dso__load_guest_kernel_sym() in
-symbol.c use sprintf() to construct paths by prepending root_dir to
-"/proc/version" and "/proc/kallsyms" respectively. Both write into
-PATH_MAX stack buffers, but root_dir comes from --guestmount or KVM
-configuration and is not length-checked. A root_dir at or near
-PATH_MAX causes a stack buffer overflow.
-
-Switch to snprintf() with sizeof(path) to prevent overflow.
-
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Fixes: a1645ce12adb6c9c ("perf: 'perf kvm' tool for monitoring guest performance from host")
-Cc: Zhang Yanmin <yanmin_zhang@linux.intel.com>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/machine.c | 2 +-
- tools/perf/util/symbol.c | 2 +-
- 2 files changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
-index 557853fcb5479b..71d3d034ba095e 100644
---- a/tools/perf/util/machine.c
-+++ b/tools/perf/util/machine.c
-@@ -1294,7 +1294,7 @@ static char *get_kernel_version(const char *root_dir)
- char *name, *tmp;
- const char *prefix = "Linux version ";
-
-- sprintf(version, "%s/proc/version", root_dir);
-+ snprintf(version, sizeof(version), "%s/proc/version", root_dir);
- file = fopen(version, "r");
- if (!file)
- return NULL;
-diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
-index e792dd22b54983..92b31c1a73a839 100644
---- a/tools/perf/util/symbol.c
-+++ b/tools/perf/util/symbol.c
-@@ -2270,7 +2270,7 @@ static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map)
- if (!kallsyms_filename)
- return -1;
- } else {
-- sprintf(path, "%s/proc/kallsyms", machine->root_dir);
-+ snprintf(path, sizeof(path), "%s/proc/kallsyms", machine->root_dir);
- kallsyms_filename = path;
- }
-
---
-2.53.0
-
+++ /dev/null
-From 40724299dc87fbe1e19146b0004764408ebc6c6c Mon Sep 17 00:00:00 2001
-From: Sasha Levin <sashal@kernel.org>
-Date: Mon, 8 Jun 2026 07:04:25 -0300
-Subject: perf tools: Use snprintf() in dso__read_running_kernel_build_id()
-
-From: Arnaldo Carvalho de Melo <acme@redhat.com>
-
-[ Upstream commit 2ea64782a428bed74f595961e651ceb8c4c5bf22 ]
-
-dso__read_running_kernel_build_id() uses sprintf() to format a sysfs
-path from machine->root_dir into a PATH_MAX buffer. If root_dir is
-close to PATH_MAX in length, appending "/sys/kernel/notes" (18 bytes)
-overflows the stack buffer.
-
-Switch to snprintf() with sizeof(path) to prevent the overflow.
-
-Reported-by: sashiko-bot <sashiko-bot@kernel.org>
-Fixes: cdd059d731eeb466 ("perf tools: Move dso_* related functions into dso object")
-Cc: Jiri Olsa <jolsa@kernel.org>
-Assisted-by: Claude:claude-opus-4.6
-Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-Signed-off-by: Sasha Levin <sashal@kernel.org>
----
- tools/perf/util/dso.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
-index 9792c8d38a314b..0c8492ea530fc1 100644
---- a/tools/perf/util/dso.c
-+++ b/tools/perf/util/dso.c
-@@ -1676,7 +1676,7 @@ void dso__read_running_kernel_build_id(struct dso *dso, struct machine *machine)
-
- if (machine__is_default_guest(machine))
- return;
-- sprintf(path, "%s/sys/kernel/notes", machine->root_dir);
-+ snprintf(path, sizeof(path), "%s/sys/kernel/notes", machine->root_dir);
- sysfs__read_build_id(path, &bid);
- dso__set_build_id(dso, &bid);
- }
---
-2.53.0
-
eventpoll-fix-epoll_wait-report-false-negative.patch
gpiolib-acpi-only-trigger-activeboth-interrupts-on-b.patch
staging-nvec-fix-use-after-free-in-nvec_rx_completed.patch
-perf-debuginfo-fix-libdw-api-contract-violations.patch
coresight-cti-fix-dt-filter-signals-silently-ignored.patch
coresight-etm4x-correct-trcvmidcctlr1-save-and-resto.patch
pci-aspm-don-t-reconfigure-aspm-entering-low-power-s.patch
pci-check-rom-header-and-data-structure-addr-before-.patch
x86-platform-olpc-xo15-drop-wakeup-source-on-driver-.patch
platform-x86-xo15-ebook-fix-wakeup-source-and-gpe-ha.patch
-perf-sched-add-missing-mmap2-handler-in-timehist.patch
pci-loongson-do-not-ignore-downstream-devices-on-ext.patch
rust-alloc-fix-assert-in-vec-reserve-doc-test.patch
bus-mhi-ep-fix-potential-deadlock-in-mhi_ep_reset_wo.patch
pci-qcom-set-max-opp-before-dbi-access-during-resume.patch
phy-phy-can-transceiver-check-driver-match-and-drive.patch
-perf-pmu-skip-test-on-arm64-when-slots-is-zero.patch
clk-at91-sam9x7-fix-gmac_gclk-clock-definition.patch
coresight-fix-source-not-disabled-on-idr_alloc_u32-f.patch
mailbox-mtk-adsp-fix-uaf-during-device-teardown.patch
-perf-arm-spe-use-metadata-to-decide-the-data-source-.patch
-perf-arm-spe-use-arm_spe_op_branch_eret-when-synthes.patch
-perf-arm-spe-correctly-set-sample-flags.patch
-perf-arm-spe-add-branch-stack.patch
-perf-inject-add-convert-callchain-option.patch
staging-most-video-avoid-double-free-on-video-regist.patch
usb-host-max3421-fix-shift-out-of-bounds-in-max3421_.patch
usb-host-max3421-reject-hub-port-requests-for-non-ex.patch
char-tlclk-fix-use-after-free-in-tlclk_cleanup.patch
pci-qcom-disable-aspm-l0s-for-sa8775p.patch
-perf-header-sanity-check-header_event_desc-attr.size.patch
iio-light-si1133-reset-counter-to-prevent-race-condi.patch
iio-light-si1133-prevent-race-condition-on-timeout.patch
iio-magnetometer-ak8975-fix-potential-kernel-stack-m.patch
thunderbolt-debugfs-fix-margining-error-counter-buff.patch
dmaengine-imx-sdma-refine-spba-bus-searching-in-prob.patch
perf-fix-off-by-one-stack-buffer-overflow-in-kallsym.patch
-perf-tools-guard-test_bit-from-out-of-bounds-sample-.patch
-perf-sched-fix-thread-reference-leak-in-latency_swit.patch
-perf-sched-replace-bug_on-on-invalid-cpu-with-gracef.patch
-perf-sched-fix-null-dereference-in-latency_runtime_e.patch
-perf-tools-add-bounds-check-to-cpu__get_node.patch
-perf-sched-fix-register_pid-overflow-strcpy-and-bug_.patch
-perf-mmap-guard-cpu__get_node-return-in-aio_bind.patch
-perf-stat-bounds-check-cpu-index-in-topology-aggrega.patch
-perf-c2c-bounds-check-cpu-and-node-ids-before-bitmap.patch
-perf-c2c-bounds-check-cpu-ids-in-setup_nodes-topolog.patch
-perf-sched-clean-up-idle_threads-entry-on-init-failu.patch
-perf-sched-timehist-add-pre-migration-wait-time-opti.patch
-perf-sched-fix-thread-leaks-in-perf-sched-timehist.patch
-perf-sched-use-thread__put-in-free_idle_threads.patch
-perf-sched-replace-bug_on-and-add-null-checks-in-rep.patch
-perf-mmap-fix-null-deref-in-aio-cleanup-on-alloc-fai.patch
-perf-stat-introduce-perf_env__get_cpu_topology-to-gu.patch
-perf-c2c-fix-use-after-free-in-he__get_c2c_hists-err.patch
-perf-timechart-fix-cpu2y-oob-read-on-untrusted-cpu-i.patch
dmaengine-qcom-gpi-set-dma_private-capability.patch
dmaengine-fix-possible-use-after-free.patch
dmaengine-dma-axi-dmac-properly-free-struct-axi_dmac.patch
pci-meson-propagate-devm_add_action_or_reset-failure.patch
pci-meson-add-missing-remove-callback.patch
fs-ntfs3-resize-log-one_page_buf-when-adopting-on-di.patch
-perf-tools-fix-get_max_num-size_t-underflow-on-empty.patch
-perf-tools-use-scnprintf-in-cpu_map__snprint-to-prev.patch
-perf-tools-use-perf_env__get_cpu_topology-in-machine.patch
pci-rcar-host-remove-unused-list_head-res.patch
-perf-sched-bounds-check-prio-before-test_bit-in-time.patch
-perf-sched-fix-idle-hist-callchain-display-using-wro.patch
-perf-bpf-use-scnprintf-in-snprintf_hex-and-synthesiz.patch
-perf-hists-fix-snprintf-in-hists__scnprintf_title-ui.patch
xprtrdma-fix-ep-kref-imbalance-on-addr_change.patch
xprtrdma-initialize-re_id-before-removal-registratio.patch
xprtrdma-check-frwr_wp_create-during-connect.patch
xprtrdma-sanitize-the-reply-credit-grant-after-parsi.patch
xprtrdma-repost-receive-buffers-for-malformed-replie.patch
xprtrdma-return-sendctx-slot-after-send-preparation-.patch
-perf-cs-etm-queue-context-packets-for-frontend.patch
-perf-pmu-fix-pmu_id-heap-underwrite-on-empty-identif.patch
-perf-pmu-fix-perf_pmu__parse_scale-unit-oob-access-o.patch
tools-lib-api-fix-missing-null-termination-in-filena.patch
-perf-symbols-fix-signed-overflow-in-sysfs__read_buil.patch
-perf-symbols-bounds-check-.gnu_debuglink-section-dat.patch
-perf-intel-pt-fix-snprintf-size-tracking-bug-in-insn.patch
-perf-tools-fix-thread__set_comm_from_proc-on-empty-c.patch
-perf-symbols-bounds-check-descsz-in-sysfs__read_buil.patch
-perf-dso-use-lock-annotations-to-fix-asan-deadlock.patch
-perf-dso-add-support-for-reading-the-e_machine-type-.patch
-perf-build-id-change-sprintf-functions-to-snprintf.patch
-perf-dso-move-build_id-to-dso_id.patch
-perf-tools-use-snprintf-in-dso__read_running_kernel_.patch
tools-lib-api-fix-filename__write_int-writing-uninit.patch
tools-lib-api-fix-mount_overload-snprintf-truncation.patch
-perf-bpf-add-null-check-for-btf__type_by_id-in-synth.patch
-perf-symbols-add-bounds-checks-to-elf_read_build_id-.patch
-perf-symbols-add-bounds-checks-to-read_build_id-note.patch
pci-mediatek-fix-possible-truncation-in-mtk_pcie_par.patch
pci-mediatek-use-actual-physical-address-instead-of-.patch
revert-pci-msi-unmap-msi-x-region-on-error.patch
i3c-master-prevent-reuse-of-dynamic-address-on-devic.patch
apparmor-fix-label-can-not-be-immediately-before-a-d.patch
sparc-led-avoid-trimming-a-newline-from-empty-writes.patch
-perf-maps-add-maps__mutate_mapping.patch
-perf-symbols-fix-bswap-copy-paste-error-for-32-bit-e.patch
-perf-symbols-validate-p_filesz-before-use-in-filenam.patch
-perf-symbols-break-infinite-loop-on-zero-filled-note.patch
-perf-tools-use-snprintf-for-root_dir-path-constructi.patch
-perf-bpf-validate-func_info_rec_size-and-sub_id-in-s.patch
-perf-bpf-bounds-check-array-offsets-in-bpil_offs_to_.patch
-perf-remove-redundant-kernel.h-include.patch
-perf-cs-etm-reject-cpu-ids-that-would-overflow-signe.patch
gpio-mlxbf3-fail-probe-if-gpiochip-registration-fail.patch
drm-i915-clear-crtc-color-blob-pointers-after-droppi.patch
spi-dw-fix-wrong-baudr-setting-after-resume.patch
xfrm-fix-xfrm-state-cache-insertion-race.patch
xfrm-annotate-data-races-around-xfrm_policy_count-an.patch
xfrm-validate-selector-family-and-prefixlen-during-m.patch
-perf-machine-use-snprintf-for-guestmount-path-constr.patch
-perf-cs-etm-validate-num_cpu-before-metadata-allocat.patch
-perf-cs-etm-require-full-global-header-in-auxtrace_i.patch
-perf-cs-etm-bounds-check-cpu-in-cs_etm__get_queue.patch
asoc-tlv320aic3x-restrict-clkdiv-bypass-q-values-in-.patch
drm-amdkfd-avoid-double-unpin-of-doorbell-mmio-bos-o.patch
drm-amdkfd-fix-list_del-corruption-in-kfd_criu_resum.patch
bpf-reject-bpf_map_type_inode_storage-creation-if-bpf-lsm-is-uninitialized.patch
perf-x86-amd-brs-fix-kernel-address-leakage.patch
dibs-loopback-validate-offset-and-size-in-move_data.patch
-perf-inject-with-convert-callchain-ignore-the-dummy-event-for-dwarf-stacks.patch
seqlock-fix-scoped_seqlock_read-kernel-doc.patch
-perf-callchain-handle-multiple-address-spaces.patch
ksmbd-fix-durable-reconnect-double-bind-race-in-ksmbd_reopen_durable_fd.patch
rtnetlink-make-per-netns-rtnl-dereference-helpers-to-macro.patch
-perf-arm-spe-use-old-behavior-when-opening-old-spe-files.patch
net-airoha-fix-channel-configuration-for-ets-qdisc.patch
jiffies-cast-to-unsigned-long-in-secs_to_jiffies-conversion.patch
afs-fix-afs_atcell_get_link-to-check-if-ws_cell-is-unset-first.patch
afs-fix-afs_dynroot_readdir-to-not-use-the-rcu-read-lock.patch
-perf-libunwind-arm64-fix-missing-close-parens-in-an-if-statement.patch
crypto-ccp-fix-__sev_snp_shutdown_locked.patch
crypto-ccp-fix-dereferencing-uninitialized-error-pointer.patch
crypto-ccp-fix-snp-panic-notifier-unregistration.patch
bluetooth-hci_core-remove-check-of-bdaddr_any-in-hci_conn_hash_lookup_big_state.patch
bluetooth-hci_sync-fix-attempting-to-send-hci_disconnect-to-bis-handle.patch
crypto-ccp-always-pass-in-an-error-pointer-to-__sev_platform_shutdown_locked.patch
-perf-build-id-ensure-snprintf-string-is-empty-when-size-is-0.patch
i40e-drop-udp_tunnel_get_rx_info-call-from-i40e_open.patch
ice-drop-udp_tunnel_get_rx_info-call-from-ndo_open.patch
crypto-ccp-fix-leaking-the-same-page-twice.patch
-perf-synthetic-events-fix-stale-build-id-in-module-mmap2-records.patch
bluetooth-l2cap-fix-regressions-caused-by-reusing-ident.patch
bluetooth-6lowpan-fix-using-chan-conn-as-indication-to-no-remote-netdev.patch
bluetooth-l2cap-fix-tx-ident-leak-for-commands-without-a-response.patch
dpll-fix-null-pointer-dereference-in-dpll_msg_add_pin_ref_sync.patch
-perf-build-id-fix-off-by-one-bug-when-printing-kernel-module-build-id.patch