Re: [PATCH 02/12] perf jitdump: Validate code_size against total_size in code load
[email protected] Wed, 05 Aug 2026 21:42:41 +0000
| Newsgroups | org.kernel.vger.linux-perf-users |
|---|---|
| Message-ID | <[email protected]> |
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider: New issues: - [Medium] jd->debug_data and jd->unwinding_data are leaked on the new validation failure path. Pre-existing issues: - [High] Integer underflow when calculating unwinding_data_size in jit_repipe_unwinding_info leads to a massive heap allocation and out-of-bounds read. - [High] Missing snprintf truncation clamping in jit_repipe_code_move and jit_repipe_code_load leads to an undersized event allocation calculation and out-of-bounds heap write. - [High] jit_get_next_entry iterates over an unvalidated nr_entry array using fixed-size indexing for a variable-length structure, leading to out-of-bounds byte-swapping. - [High] The parser lacks total_size boundary checks for JIT_CODE_MOVE, leading to uninitialized heap reads. - [High] Lack of validation between buffer size and nr_entry leads to heap OOB read and write in debug info parsing. - [Medium] The perf_event allocated via calloc is unconditionally leaked in jit_repipe_code_move. - [Medium] Multiple memory leaks of dynamically allocated buffers in the jitdump parser, including missing cleanup in jit_close and overwrites in debug info. - [Medium] The ALIGN_8 macro evaluates using signed arithmetic, causing integer wrap and massive length corruption for mmap2 events. - [Medium] Truncation of 64-bit unwinding_mapped_size to 32-bit int can corrupt mmap2 event length. -- --- Patch [2]: [PATCH 02/12] perf jitdump: Validate code_size against total_size in code load --- commit 97b091976e049b90397f99d555b22a4934abb49d Author: Arnaldo Carvalho de Melo <[email protected]> perf jitdump: Validate code_size against total_size in code load This commit introduces validation checks to ensure that the code_size does not exceed the available size in the JIT load record. It adds bounds checking to prevent out-of-bounds heap reads when calculating the code blob pointer. > diff --git a/tools/perf/util/jitdump.c b/tools/perf/util/jitdump.c > index 4b7c7ba7cd95d..3195f94187164 100644 > --- a/tools/perf/util/jitdump.c > +++ b/tools/perf/util/jitdump.c > @@ -9,6 +9,7 @@ > #include <fcntl.h> > #include <unistd.h> > #include <inttypes.h> > +#include <limits.h> > #include <byteswap.h> > #include <sys/stat.h> > #include <sys/mman.h> > @@ -450,6 +451,16 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr) > csize = jr->load.code_size; > usize = jd->unwinding_mapped_size; > addr = jr->load.code_addr; > + > + /* code blob lives at the end of the record, validate it fits */ > + if (jr->load.p.total_size < sizeof(jr->load) || > + jr->load.code_size > jr->load.p.total_size - sizeof(jr->load) || > + jr->load.code_size > INT_MAX) { > + pr_warning("jitdump: invalid code_size %" PRIu64 " (total_size=%u) in code_load record\n", > + (uint64_t)jr->load.code_size, jr->load.p.total_size); > + return -1; [Severity: Medium] Does this early return skip freeing the buffered debug and unwinding data? Later in the function, these dynamically allocated buffers are freed: if (jd->debug_data && jd->nr_debug_entries) { zfree(&jd->debug_data); jd->nr_debug_entries = 0; } if (jd->unwinding_data && jd->eh_frame_hdr_size) { zfree(&jd->unwinding_data); ... If the code returns -1 here, do jd->debug_data and jd->unwinding_data remain allocated, permanently leaking since jit_close() does not appear to clean them up? > + } > + > sym = (void *)((unsigned long)jr + sizeof(jr->load)); > code = (unsigned long)jr + jr->load.p.total_size - csize; > count = jr->load.code_index; [ ... ] Other findings in this file: [Severity: High] This is a pre-existing issue, but does integer underflow occur when calculating unwinding_data_size in jit_repipe_unwinding_info()? tools/perf/util/jitdump.c:jit_repipe_unwinding_info() { ... unwinding_data_size = jr->prefix.total_size - sizeof(jr->unwinding); unwinding_data = malloc(unwinding_data_size); memcpy(unwinding_data, &jr->unwinding.unwinding_data, unwinding_data_size); ... } If total_size is smaller than sizeof(jr->unwinding), does the unsigned 32-bit subtraction underflow to a massive value, causing malloc to allocate too much memory and memcpy to perform an out-of-bounds read? [Severity: Medium] This is a pre-existing issue, but is the perf_event leaked in jit_repipe_code_move()? tools/perf/util/jitdump.c:jit_repipe_code_move() { ... event = calloc(1, sizeof(*event) + 16); if (!event) return -1; ... return ret; } The event pointer is allocated via calloc but is never freed on the success or error return paths. Does this gradually exhaust memory when parsing large dumps? [Severity: High] This is a pre-existing issue, but does missing snprintf truncation clamping lead to out-of-bounds heap writes? tools/perf/util/jitdump.c:jit_repipe_code_move() { ... size = snprintf(filename, PATH_MAX, "%s/jitted-%d-%" PRIu64 ".so", ...); size++; /* for \0 */ event->mmap2.header.size = (sizeof(event->mmap2) - (sizeof(event->mmap2.filename) - size) + idr_size); ... } When generating the filename, snprintf returns the length that would have been written. If this exceeds PATH_MAX, does the returned size wrap in the unsigned arithmetic for header.size, underflowing it and pushing the trailing id pointer out of the allocated event buffer bounds? [Severity: Medium] This is a pre-existing issue, but are dynamic buffers repeatedly leaked in jit_repipe_debug_info() and jit_close()? tools/perf/util/jitdump.c:jit_repipe_debug_info() { ... data = malloc(sz); /* ... */ jd->debug_data = data; ... } Repeated JIT_CODE_DEBUG_INFO records will malloc a new buffer and overwrite jd->debug_data without freeing the old one. Additionally, jit_close() frees the jd struct resources but does not seem to clean up internal heap buffers like debug_data and unwinding_data. Does this leave them un-freed indefinitely? [Severity: High] This is a pre-existing issue, but does standard array indexing on a variable length struct cause memory corruption during bswap? tools/perf/util/jitdump.c:jit_get_next_entry() { ... for (n = 0 ; n < jr->info.nr_entry; n++) { jr->info.entries[n].addr = bswap_64(jr->info.entries[n].addr); ... } In cross-endian mode, this iterates over an unvalidated nr_entry array using fixed-size indexing. Since debug_entry includes a variable-length name[] string inline, doesn't fixed-size array indexing read from and byte-swap completely incorrect offsets? [Severity: High] This is a pre-existing issue, but does jit_repipe_code_move() lack boundary checks for the JIT_CODE_MOVE total_size? tools/perf/util/jitdump.c:jit_repipe_code_move() { ... event->mmap2.start = jr->move.new_code_addr; event->mmap2.len = usize ? ALIGN_8(jr->move.code_size) + usize : jr->move.code_size; ... } Unlike the new validation added to JIT_CODE_LOAD in this patch, jit_repipe_code_move() lacks a check confirming that total_size is greater than or equal to sizeof(jr->move). Can malformed records cause the parser to read uninitialized heap or stale data from jd->buf into perf.data? [Severity: Medium] This is a pre-existing issue, but does the ALIGN_8 macro silently overflow into a negative value? tools/perf/util/jitdump.c:jit_repipe_code_load() { ... int ret, csize, usize; /* ... */ event->mmap2.len = usize ? ALIGN_8(csize) + usize : csize; ... } Since csize is typed as a signed int, does calculating ALIGN_8(csize) with a value near INT_MAX trigger signed integer overflow to a negative value, which is then sign-extended into a corrupted uint64_t length? [Severity: Medium] This is a pre-existing issue, but can the truncation of unwinding_mapped_size corrupt the mmap2 event length? tools/perf/util/jitdump.c:jit_repipe_code_move() { ... int usize; /* ... */ usize = jd->unwinding_mapped_size; /* ... */ event->mmap2.len = usize ? ALIGN_8(jr->move.code_size) + usize : jr->move.code_size; ... } The 64-bit jd->unwinding_mapped_size is truncated to a 32-bit signed integer (usize). Could a maliciously crafted mapped_size result in a negative or truncated value, corrupting the final event->mmap2.len? [Severity: High] This is a pre-existing issue, but does the lack of validation between buffer size and nr_entry lead to heap out-of-bounds reads and writes? tools/perf/util/jitdump.c:jit_repipe_debug_info() { ... jd->nr_debug_entries = jr->info.nr_entry; ... } The parser sets jd->nr_debug_entries from the unvalidated nr_entry file field without confirming the allocated sz bytes can accommodate them. When jit_process_debug_info() loops nr_entry times calling strlen on heap data, does this result in out-of-bounds heap writes? -- Sashiko AI review ยท https://sashiko.dev/#/patchset/[email protected]?part=2