]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
perf cs-etm: Bounds-check CPU in cs_etm__get_queue()
authorArnaldo Carvalho de Melo <acme@redhat.com>
Sat, 13 Jun 2026 17:55:32 +0000 (14:55 -0300)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Wed, 17 Jun 2026 12:21:03 +0000 (09:21 -0300)
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>
tools/perf/util/cs-etm.c

index d121c8f22028d5bac6767ec0514d9e22f46262b1..5d0664ff73b79122295712428a06318213738f59 100644 (file)
@@ -292,8 +292,11 @@ static struct cs_etm_queue *cs_etm__get_queue(struct cs_etm_auxtrace *etm, int c
 {
        if (etm->per_thread_decoding)
                return etm->queues.queue_array[0].priv;
-       else
-               return etm->queues.queue_array[cpu].priv;
+
+       if (cpu < 0 || cpu >= (int)etm->queues.nr_queues)
+               return NULL;
+
+       return etm->queues.queue_array[cpu].priv;
 }
 
 static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id,
@@ -306,6 +309,9 @@ static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id
         * queue associated with that CPU so only one decoder is made.
         */
        etmq = cs_etm__get_queue(etm, cpu_metadata[CS_ETM_CPU]);
+       if (!etmq)
+               return -EINVAL;
+
        if (etmq->format == UNFORMATTED)
                return cs_etm__insert_trace_id_node(etmq, trace_chan_id,
                                                    cpu_metadata);
@@ -318,6 +324,9 @@ static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id
                int ret;
 
                etmq = etm->queues.queue_array[i].priv;
+               if (!etmq)
+                       continue;
+
                ret = cs_etm__insert_trace_id_node(etmq, trace_chan_id,
                                                   cpu_metadata);
                if (ret)
@@ -358,6 +367,9 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu,
        u32 sink_id = FIELD_GET(CS_AUX_HW_ID_SINK_ID_MASK, hw_id);
        u8 trace_id = FIELD_GET(CS_AUX_HW_ID_TRACE_ID_MASK, hw_id);
 
+       if (!etmq)
+               return -EINVAL;
+
        /*
         * Check sink id hasn't changed in per-cpu mode. In per-thread mode,
         * let it pass for now until an actual overlapping trace ID is hit. In
@@ -375,6 +387,9 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu,
        for (unsigned int i = 0; i < etm->queues.nr_queues; ++i) {
                struct cs_etm_queue *other_etmq = etm->queues.queue_array[i].priv;
 
+               if (!other_etmq)
+                       continue;
+
                /* Different sinks, skip */
                if (other_etmq->sink_id != etmq->sink_id)
                        continue;
@@ -396,6 +411,9 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu,
        }
 
        cpu_data = get_cpu_data(etm, cpu);
+       if (!cpu_data)
+               return -EINVAL;
+
        ret = cs_etm__insert_trace_id_node(etmq, trace_id, cpu_data);
        if (ret)
                return ret;
@@ -3144,6 +3162,9 @@ static int cs_etm__queue_aux_fragment(struct perf_session *session, off_t file_o
            aux_offset + aux_size <= auxtrace_event->offset + auxtrace_event->size) {
                struct cs_etm_queue *etmq = cs_etm__get_queue(etm, auxtrace_event->cpu);
 
+               if (!etmq)
+                       return -EINVAL;
+
                /*
                 * If this AUX event was inside this buffer somewhere, create a new auxtrace event
                 * based on the sizes of the aux event, and queue that fragment.