Re: [PATCH 04/12] perf jitdump: Bounds-check debug entry byte-swap loop

[email protected] Wed, 05 Aug 2026 21:40:44 +0000
Newsgroups org.kernel.vger.linux-perf-users
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] The newly added bounds-checking and string NUL-termination validation for debug entries are isolated within the `if (jd->needs_bswap)` block. For native-endian files, validation is bypassed, leaving out-of-bounds heap read/write vulnerabilities unmitigated.
--

--- Patch [4]: [PATCH 04/12] perf jitdump: Bounds-check debug entry byte-swap loop ---
commit b0b40e7a47c677d680f97fb1312ed129f617d651
Author: Arnaldo Carvalho de Melo <[email protected]>

perf jitdump: Bounds-check debug entry byte-swap loop

This commit replaces array indexing with pointer arithmetic for debug_entry
structures in the JIT_CODE_DEBUG_INFO byte-swap loop to handle
variable-length names. It also adds bounds checking against total_size to
prevent out-of-bounds reads.

> diff --git a/tools/perf/util/jitdump.c b/tools/perf/util/jitdump.c
> index 787f8a03dae87..078d3304d2b7e 100644
> --- a/tools/perf/util/jitdump.c
> +++ b/tools/perf/util/jitdump.c

[ ... ]

> @@ -318,14 +318,32 @@ jit_get_next_entry(struct jit_buf_desc *jd)
>  	switch(id) {
>  	case JIT_CODE_DEBUG_INFO:
>  		if (jd->needs_bswap) {
> +			void *end = (void *)jr + jr->prefix.total_size;
> +			struct debug_entry *ent;
>  			uint64_t n;
> +
>  			jr->info.code_addr = bswap_64(jr->info.code_addr);
>  			jr->info.nr_entry  = bswap_64(jr->info.nr_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);
> +
> +			/*
> +			 * debug_entry has a variable-length name[], so array
> +			 * indexing would compute wrong offsets — use
> +			 * debug_entry_next() and bounds-check each entry.
> +			 */
> +			ent = &jr->info.entries[0];
> +			for (n = 0; n < jr->info.nr_entry; n++) {
> +				if ((void *)ent + sizeof(*ent) > end)
> +					break;
> +				/* name must be NUL-terminated within the record */
> +				if (!memchr(ent->name, '\0', (char *)end - ent->name))
> +					break;
> +				ent->addr    = bswap_64(ent->addr);
> +				ent->lineno  = bswap_32(ent->lineno);
> +				ent->discrim = bswap_32(ent->discrim);
> +				ent = debug_entry_next(ent);
>  			}
> +			/* clamp so downstream consumers don't overrun */
> +			jr->info.nr_entry = n;
>  		}
>  		break;

[Severity: High]
This is a pre-existing issue, but by keeping the new bounds-checking and
clamping logic exclusively inside the if (jd->needs_bswap) block, doesn't
this leave native-endian files unvalidated?

For a native-endian file, jd->needs_bswap is false, so jr->info.nr_entry
bypasses this clamping in jit_get_next_entry(). Later in
tools/perf/util/jitdump.c, it is assigned directly:

jit_repipe_debug_info() {
    ...
    jd->nr_debug_entries = jr->info.nr_entry;
    ...
}

This unvalidated count is then used in 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);
    }
    ...
}

If the number of entries is artificially inflated, or if NUL terminators are
missing, could this lead to out-of-bounds reads and writes in
jit_process_debug_info() when processing a native-endian jitdump file?

Should the entry validation and clamping logic be pulled out of the
jd->needs_bswap block so it applies to all files regardless of endianness?

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