mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
perf tools: Harden compressed event processing
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>
This commit is contained in:
parent
1c9d2ac2ea
commit
6e803f1e01
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user