Linux Perf Users
 help / color / mirror / Atom feed
From: Arnaldo Carvalho de Melo <acme@kernel.org>
To: sashiko-reviews@lists.linux.dev
Cc: linux-perf-users@vger.kernel.org
Subject: Re: [PATCH 05/12] perf jitdump: Bounds-check debug entry byte-swap loop
Date: Thu, 6 Aug 2026 12:21:37 -0300	[thread overview]
Message-ID: <anSmgfNw3KdJcs_b@x1> (raw)
In-Reply-To: <20260806125156.ADDF61F00A3A@smtp.kernel.org>

On Thu, Aug 06, 2026 at 12:51:56PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
> 
> New issues:
> - [Medium] Replacing array indexing with sequential pointer arithmetic (`debug_entry_next`) on unpadded, variable-length structures results in unaligned struct pointers, causing undefined behavior and SIGBUS crashes on strict-alignment architectures.

This is something bigger, the proper fix needs research, the TODO entry
#154 notes we must confirm the JDK/Mono/V8 jitdump writers actually pad
entries to 8-byte boundaries before choosing between get_unaligned and
ALIGN() — that's next-series work.

- Arnaldo
 
> Pre-existing issues:
> - [Critical] The newly added bounds checking and `nr_entry` clamping for `JIT_CODE_DEBUG_INFO` records are scoped exclusively inside the `if (jd->needs_bswap)` block, leaving native-endian files completely vulnerable to a massive heap buffer overflow.
> - [High] Missing validation of `unwinding_size` against `unwinding_data_size` in `JIT_CODE_UNWINDING_INFO` leads to an out-of-bounds read during ELF generation.
> - [Medium] The `event` buffer is leaked on all return paths in `jit_repipe_code_move()`.
> - [Medium] `jit_repipe_code_move` fails to validate that the record's `total_size` is large enough to contain the `jr_code_move` payload, leading to the processing of uninitialized memory.
> --
> 
> --- Patch [5]: [PATCH 05/12] perf jitdump: Bounds-check debug entry byte-swap loop ---
> commit 14beafbf2609e41d04f4020f064d84fdbb15c5a7
> Author: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> perf jitdump: Bounds-check debug entry byte-swap loop
>     
> This commit replaces array indexing with pointer arithmetic and adds bounds
> checking for JIT_CODE_DEBUG_INFO records during byte-swapping to prevent
> out-of-bounds reads.
> 
> > diff --git a/tools/perf/util/jitdump.c b/tools/perf/util/jitdump.c
> > index f79e9420c6bd7..7efbaa07f1ba7 100644
> > --- a/tools/perf/util/jitdump.c
> > +++ b/tools/perf/util/jitdump.c
> > @@ -320,14 +320,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;
> 
> [Severity: Critical]
> This is a pre-existing issue, but by placing the bounds checking and
> nr_entry clamping exclusively inside the if (jd->needs_bswap) block,
> native-endian files bypass this validation completely.
> 
> If a maliciously crafted JIT dump file with a native endianness matching the
> host provides an arbitrarily large untrusted nr_entry count for a
> JIT_CODE_DEBUG_INFO record, this path is skipped.
> 
> Later in the processing, this unvalidated value is assigned directly:
> 
> tools/perf/util/jitdump.c:jit_repipe_debug_info()
>     jd->nr_debug_entries = jr->info.nr_entry;
> 
> And then blindly iterated over on the heap:
> 
> 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);
>     }
> 
> Could we move the bounds validation outside of the byte-swapping block so all
> inputs are checked?
> 
> > +				/* 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);
> 
> [Severity: Medium]
> Does this unpadded sequential pointer arithmetic lead to unaligned memory
> accesses?
> 
> struct debug_entry contains 64-bit and 32-bit fields, requiring 8-byte
> alignment, but it is not __packed. The debug_entry_next() helper calculates
> the next entry's address by adding the length of the string:
> 
> tools/perf/util/jitdump.h:debug_entry_next()
>     void *a = ent + 1;
>     size_t l = strlen(ent->name) + 1;
>     return a + l;
> 
> This leaves the pointer unaligned. When the byte-swapping loop uses
> ent = debug_entry_next(ent) and accesses ent->addr = bswap_64(ent->addr),
> the compiler generates aligned load/store instructions for an unaligned
> address, leading to SIGBUS crashes on strict-alignment architectures.
> 
> >  			}
> > +			/* clamp so downstream consumers don't overrun */
> > +			jr->info.nr_entry = n;
> >  		}
> >  		break;
> >  	case JIT_CODE_UNWINDING_INFO:
> >  		if (jd->needs_bswap) {
> >  			jr->unwinding.unwinding_size = bswap_64(jr->unwinding.unwinding_size);
> 
> [Severity: High]
> This is a pre-existing issue, but is the unwinding_size validated against the
> actual payload size?
> 
> In jit_repipe_unwinding_info(), the unwinding size is saved directly from the
> payload without validation against the allocated heap buffer size:
> 
> tools/perf/util/jitdump.c:jit_repipe_unwinding_info()
>     jd->unwinding_size = jr->unwinding.unwinding_size;
> 
> This unvalidated size is later passed to libelf:
> 
> tools/perf/util/genelf.c:jit_add_eh_frame_info()
>     d->d_size = unwinding_table_size;
> 
> A crafted record with a small total_size but a huge unwinding_size could
> cause libelf to read out-of-bounds heap memory and embed it into the output
> ELF, leading to an information leak.
> 
> >  			jr->unwinding.eh_frame_hdr_size = bswap_64(jr->unwinding.eh_frame_hdr_size);
> >  			jr->unwinding.mapped_size = bswap_64(jr->unwinding.mapped_size);
> >  		}
> >  		break;
> 
> While reviewing this patch, I also noticed a couple of pre-existing issues
> in the handling of JIT_CODE_MOVE records:
> 
> tools/perf/util/jitdump.c:jit_repipe_code_move()
> [ ... ]
> > 	nspid = jr->load.pid;
> > 	pid   = jr_entry_pid(jd, jr);
> 
> [Severity: Medium]
> This isn't a bug introduced by this patch, but does jit_repipe_code_move()
> validate that the record's total_size is large enough to contain the
> jr_code_move payload?
> 
> If a malformed record is provided with a truncated total_size, fread() will
> read only a partial record into jd->buf. The remaining fields will be
> uninitialized or contain leftover data, which are then used to construct
> the perf event.
> 
> [ ... ]
> > 	event = calloc(1, sizeof(*event) + 16);
> > 	if (!event)
> > 		return -1;
> [ ... ]
> > 	ret = perf_event__process_mmap2(tool, event, &sample, jd->machine);
> > 	if (ret)
> > 		goto out;
> > 
> > 	ret = jit_inject_event(jd, event);
> > 	if (!ret)
> > 		build_id__mark_dso_hit(tool, event, &sample, jd->machine);
> > out:
> > 	perf_sample__exit(&sample);
> > 	return ret;
> 
> [Severity: Medium]
> This is a pre-existing issue, but does this leak the event buffer?
> 
> The memory for event is allocated via calloc() and the function returns at
> the out: label without calling free(event). Neither of the processing
> functions take ownership of the memory, so the buffer appears to be leaked on
> every JIT_CODE_MOVE record.
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260806123604.271277-1-acme@kernel.org?part=5

  reply	other threads:[~2026-08-06 15:21 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-06 12:35 [PATCHES v3 0/12] perf jitdump: Input validation hardening Arnaldo Carvalho de Melo
2026-08-06 12:35 ` [PATCH 01/12] perf jitdump: Fix extended header read that always fails Arnaldo Carvalho de Melo
2026-08-06 13:04   ` sashiko-bot
2026-08-06 12:35 ` [PATCH 02/12] perf jitdump: Fix debug_data and unwinding_data leaks Arnaldo Carvalho de Melo
2026-08-06 12:52   ` sashiko-bot
2026-08-06 12:35 ` [PATCH 03/12] perf jitdump: Validate code_size against total_size in code load Arnaldo Carvalho de Melo
2026-08-06 12:56   ` sashiko-bot
2026-08-06 12:35 ` [PATCH 04/12] perf jitdump: Prevent integer underflow in debug info size calculation Arnaldo Carvalho de Melo
2026-08-06 12:55   ` sashiko-bot
2026-08-06 12:35 ` [PATCH 05/12] perf jitdump: Bounds-check debug entry byte-swap loop Arnaldo Carvalho de Melo
2026-08-06 12:51   ` sashiko-bot
2026-08-06 15:21     ` Arnaldo Carvalho de Melo [this message]
2026-08-06 12:35 ` [PATCH 06/12] perf jitdump: Check snprintf return before computing header size Arnaldo Carvalho de Melo
2026-08-06 12:52   ` sashiko-bot
2026-08-06 12:35 ` [PATCH 07/12] perf jitdump: Fix funlockfile on unlocked stream in jit_open() error path Arnaldo Carvalho de Melo
2026-08-06 12:51   ` sashiko-bot
2026-08-06 12:35 ` [PATCH 08/12] perf jitdump: Free event in jit_repipe_code_move() Arnaldo Carvalho de Melo
2026-08-06 12:49   ` sashiko-bot
2026-08-06 12:36 ` [PATCH 09/12] perf jitdump: Use dirname() return value in jit_open() Arnaldo Carvalho de Melo
2026-08-06 12:36 ` [PATCH 10/12] perf jitdump: Validate debug entries on native (non-swap) path Arnaldo Carvalho de Melo
2026-08-06 13:10   ` sashiko-bot
2026-08-06 12:36 ` [PATCH 11/12] perf jitdump: Validate sym string NUL-termination in code load Arnaldo Carvalho de Melo
2026-08-06 13:03   ` sashiko-bot
2026-08-06 12:36 ` [PATCH 12/12] perf jitdump: Validate unwinding sizes against record payload Arnaldo Carvalho de Melo
2026-08-06 13:06   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=anSmgfNw3KdJcs_b@x1 \
    --to=acme@kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox