Re: [PATCH 04/12] perf jitdump: Prevent integer underflow in debug info size calculation

[email protected] Thu, 06 Aug 2026 12:55:08 +0000
Newsgroups org.kernel.vger.linux-perf-users
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

Pre-existing issues:
- [High] Unclamped `snprintf` return values when formatting filenames cause integer underflow, inflating event size and leading to out-of-bounds heap writes.
- [High] Cross-endian byte-swapping of debug info entries uses incorrect fixed-size array indexing on a variable-length structure, causing severe heap corruption.
- [High] Unvalidated structural sizes for unwinding info lead to massive out-of-bounds heap reads and information disclosure during ELF generation.
- [High] Missing validation of `nr_entry` against the payload size in `jit_repipe_debug_info()` leads to heap out-of-bounds accesses during debug info processing.
--

--- Patch [4]: [PATCH 04/12] perf jitdump: Prevent integer underflow in debug info size calculation ---
commit 279da6780ef70ba142d7c4f7db07000972f0e035
Author: Arnaldo Carvalho de Melo <[email protected]>

perf jitdump: Prevent integer underflow in debug info size calculation

jit_repipe_debug_info() and jit_repipe_unwinding_info() compute payload
sizes by subtracting the fixed header size from total_size. This prevents
an underflow when total_size is smaller than the header struct.

> diff --git a/tools/perf/util/jitdump.c b/tools/perf/util/jitdump.c
> index 14bd23c8d1963..f79e9420c6bd7 100644
> --- a/tools/perf/util/jitdump.c
> +++ b/tools/perf/util/jitdump.c
> @@ -671,6 +671,10 @@ static int jit_repipe_debug_info(struct jit_buf_desc *jd, union jr_entry *jr)
>  	if (!(jd && jr))
>  		return -1;
>  
> +	/* total_size must cover at least the fixed header */
> +	if (jr->prefix.total_size < sizeof(jr->info))
> +		return -1;
> +
>  	sz  = jr->prefix.total_size - sizeof(jr->info);
>  	data = malloc(sz);
>  	if (!data)

[Severity: High]
This is a pre-existing issue, but does this function also need to validate
jr->info.nr_entry against the payload size?

It appears the unvalidated count is saved to jd->nr_debug_entries. Later,
in jit_process_debug_info(), a loop iterates nr_debug_entries times:

tools/perf/util/genelf_debug.c:jit_process_debug_info() {
[ ... ]
    for (i = 0; i < nr_debug_entries; i++) {
        ent->addr = ent->addr - code_addr;
        ent = debug_entry_next(ent);
    }
[ ... ]
}

Since debug_entry_next() computes pointer offsets by reading variable-length
strings, could an artificially inflated nr_entry force the loop to iterate
past the allocated debug_data buffer and cause an out-of-bounds heap write?

[ ... ]

> @@ -699,6 +703,10 @@ jit_repipe_unwinding_info(struct jit_buf_desc *jd, union jr_entry *jr)
>  	if (!(jd && jr))
>  		return -1;
>  
> +	/* total_size must cover at least the fixed header */
> +	if (jr->prefix.total_size < sizeof(jr->unwinding))
> +		return -1;
> +
>  	unwinding_data_size  = jr->prefix.total_size - sizeof(jr->unwinding);
>  	unwinding_data = malloc(unwinding_data_size);
>  	if (!unwinding_data)

[Severity: High]
This isn't a bug introduced by this patch, but could the structural sizes
extracted from the untrusted record cause out-of-bounds reads later?

In jit_repipe_unwinding_info(), these sizes are blindly extracted:

tools/perf/util/jitdump.c:jit_repipe_unwinding_info() {
[ ... ]
    jd->eh_frame_hdr_size = jr->unwinding.eh_frame_hdr_size;
    jd->unwinding_size = jr->unwinding.unwinding_size;
[ ... ]
}

Then they are used to configure libelf data sections:

tools/perf/util/genelf.c:jit_add_eh_frame_info() {
[ ... ]
    uint64_t unwinding_table_size = unwinding_size - unwinding_header_size;
[ ... ]
    d->d_buf = unwinding;
    d->d_size = unwinding_table_size;
[ ... ]
}

If jr->unwinding.unwinding_size is inflated beyond the actual payload size,
could libelf read past the end of the unwinding_data allocation and leak
arbitrary heap memory to the generated ELF file?


[Severity: High]
This is a pre-existing issue, but I also noticed a potential integer underflow
when formatting filenames in jit_repipe_code_move() and
jit_repipe_code_load().

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);
[ ... ]
}

If an untrusted jitdump file causes snprintf to truncate the filename (for
example by providing a long nspid or code_index), size will exceed PATH_MAX.
Would the subsequent calculation for event->mmap2.header.size undergo a double
integer underflow since both operands are unsigned size_t?

This mathematically reduces to sizeof(event->mmap2) - PATH_MAX + size + idr_size.
Because size > PATH_MAX, header.size strictly exceeds the size of the event
structure.

The id pointer is then calculated as event + header.size - idr_size, placing
it past the end of the dynamically allocated event buffer, which could lead to
an out-of-bounds heap write on id->pid and id->tid.


[Severity: High]
This isn't a bug introduced by this patch, but does cross-endian byte-swapping
of debug info entries incorrectly use fixed-size array indexing?

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);
        jr->info.entries[n].lineno  = bswap_32(jr->info.entries[n].lineno);
        jr->info.entries[n].discrim = bswap_32(jr->info.entries[n].discrim);
    }
[ ... ]
}

Since struct debug_entry contains a flexible array member for the name, each
entry is variable in length. Will using standard array indexing ignore the
interleaved variable-length strings and cause the byte-swap operations to
stride incorrectly across the buffer, potentially writing out-of-bounds on
the heap?

-- 
Sashiko AI review ยท https://sashiko.dev/#/patchset/[email protected]?part=4