]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
perf tools: Harden compressed event processing
authorArnaldo Carvalho de Melo <acme@redhat.com>
Sat, 2 May 2026 17:47:38 +0000 (14:47 -0300)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Fri, 29 May 2026 14:44:34 +0000 (11:44 -0300)
Add several hardening checks to the compressed event decompression
pipeline:

1. Guard against decomp_last_rem underflow: check that
   decomp_last->head does not exceed decomp_last->size before
   subtracting.  A u64 underflow here would produce a huge
   decomp_len, causing an oversized mmap allocation.

2. Validate comp_mmap_len from the HEADER_COMPRESSED feature
   section: reject values that are not 4K-aligned or smaller than
   4096.  The downstream decompression path checks allocation
   sizes against SIZE_MAX, which handles 32-bit safety.

3. Validate COMPRESSED event header size: reject events where
   header.size is too small to contain the fixed struct fields,
   preventing underflow in the payload size calculation.

4. Validate COMPRESSED2 event data_size: check that data_size
   does not exceed the available payload (header.size minus the
   fixed struct fields) for the newer compressed format.

5. Reject compressed events when the HEADER_COMPRESSED feature
   is missing from the file header, which means no decompression
   context was initialized.

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>
Assisted-by: Claude:claude-opus-4.6-1m
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/util/header.c
tools/perf/util/tool.c

index 2fea0172140e4abd625e1642917313e5234feca6..f771a76321c10a0286dc7382b0679def4062627f 100644 (file)
@@ -3861,6 +3861,23 @@ static int process_compressed(struct feat_fd *ff,
        if (do_read_u32(ff, &(env->comp_mmap_len)))
                return -1;
 
+       /*
+        * FIXME: perf.data should record the recording system's page
+        * size — it affects mmap buffer alignment, sample addresses,
+        * and data_page_size/code_page_size interpretation.  Without
+        * it we assume 4K (the smallest Linux page size) as a safe
+        * minimum alignment for comp_mmap_len validation.
+        *
+        * No upper-bound cap: perf_session__process_compressed_event()
+        * checks decomp_len + sizeof(struct decomp) against SIZE_MAX
+        * before allocating, which handles 32-bit safety.
+        */
+       if (env->comp_mmap_len < 4096 || env->comp_mmap_len % 4096) {
+               pr_err("Invalid HEADER_COMPRESSED: comp_mmap_len (%u) must be a 4K-aligned value >= 4096\n",
+                      env->comp_mmap_len);
+               return -1;
+       }
+
        return 0;
 }
 
index 225a77d530ce8ab3b01f612250fc1b5ec64ff16f..18641919473a859f1e636d496d05ca7116493d08 100644 (file)
@@ -24,7 +24,15 @@ static int perf_session__process_compressed_event(const struct perf_tool *tool _
        size_t mmap_len, decomp_len = perf_session__env(session)->comp_mmap_len;
        struct decomp *decomp, *decomp_last = session->active_decomp->decomp_last;
 
+       if (!decomp_len) {
+               pr_err("Compressed events found but HEADER_COMPRESSED not set\n");
+               return -1;
+       }
+
        if (decomp_last) {
+               /* Prevent u64 underflow in decomp_last_rem */
+               if (decomp_last->head > decomp_last->size)
+                       return -1;
                decomp_last_rem = decomp_last->size - decomp_last->head;
                decomp_len += decomp_last_rem;
        }
@@ -47,14 +55,37 @@ static int perf_session__process_compressed_event(const struct perf_tool *tool _
                decomp->size = decomp_last_rem;
        }
 
+       /*
+        * Events are read directly from the mmap'd file; fields could
+        * theoretically change via a FUSE-backed file, but that applies
+        * to the entire event processing pipeline, not just here.
+        */
        if (event->header.type == PERF_RECORD_COMPRESSED) {
+               if (event->header.size < sizeof(struct perf_record_compressed))
+                       goto err_decomp;
                src = (void *)event + sizeof(struct perf_record_compressed);
                src_size = event->pack.header.size - sizeof(struct perf_record_compressed);
        } else if (event->header.type == PERF_RECORD_COMPRESSED2) {
+               /*
+                * prefetch_event() only guarantees that the 8-byte
+                * event header fits; validate that header.size covers
+                * the data_size field before accessing it, otherwise a
+                * crafted event reads data_size from adjacent memory.
+                */
+               if (event->header.size < sizeof(struct perf_record_compressed2))
+                       goto err_decomp;
                src = (void *)event + sizeof(struct perf_record_compressed2);
                src_size = event->pack2.data_size;
+               /*
+                * data_size is independent of header.size (which
+                * includes padding); verify it doesn't exceed the
+                * actual payload to prevent out-of-bounds reads in
+                * zstd_decompress_stream().
+                */
+               if (src_size > event->header.size - sizeof(struct perf_record_compressed2))
+                       goto err_decomp;
        } else {
-               return -1;
+               goto err_decomp;
        }
 
        decomp_size = zstd_decompress_stream(session->active_decomp->zstd_decomp, src, src_size,
@@ -77,6 +108,11 @@ static int perf_session__process_compressed_event(const struct perf_tool *tool _
        pr_debug("decomp (B): %zd to %zd\n", src_size, decomp_size);
 
        return 0;
+
+err_decomp:
+       munmap(decomp, mmap_len);
+       pr_err("Couldn't decompress data\n");
+       return -1;
 }
 #endif