All of lore.kernel.org
 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.