https://sourceware.org/bugzilla/show_bug.cgi?id=34326

Paul Wang <lswang1112 at gmail dot com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |lswang1112 at gmail dot com

--- Comment #1 from Paul Wang <lswang1112 at gmail dot com> ---
Created attachment 16804
  --> https://sourceware.org/bugzilla/attachment.cgi?id=16804&action=edit
PoC files (5 ELF files) triggering five distinct crash sites from the integer
overflow in update_all_relocations

Additional analysis for bug #34326: integer overflow in update_all_relocations
triggers five distinct crash paths

Affected version: GNU Binutils 2.46.50.20260629 (git HEAD 0e73a13c)
Confirmed reproduced on latest upstream HEAD as of 2026-06-30.

I independently discovered this bug through fuzzing and found that the
integer overflow in update_all_relocations() is more exploitable than
initially reported -- it triggers five distinct crash sites across
different code paths. I am providing a full root cause analysis, ASan
traces, PoC files for each crash, and a concrete suggested fix, hoping
this helps move the issue toward resolution.

------------------------------------------------------------------------
ROOT CAUSE
------------------------------------------------------------------------

The root cause is an unchecked integer overflow in update_all_relocations()
(binutils/readelf.c:1830). The function computes the allocation size using
nentries, which is derived from the ELF section header field sh_size /
sh_entsize and is entirely attacker-controlled:

    static void
    update_all_relocations (size_t nentries)
    {
      size_t sz;

      if (!all_relocations_root)
        {
          sz = nentries * sizeof (elf_relocation);  /* no overflow check */
          all_relocations_root = xmalloc (sz);      /* too-small buffer  */
          all_relocations = all_relocations_root;
          all_relocations_count = nentries;         /* stores REAL count */
        }
      else
        {
          size_t orig_count = all_relocations_count;
          sz = (orig_count + nentries) * sizeof (elf_relocation); /* overflow
*/
          all_relocations_root = xrealloc (all_relocations_root, sz);
          all_relocations = all_relocations_root + orig_count;
          all_relocations_count += nentries;
        }
      memset (all_relocations, 0, nentries * sizeof (elf_relocation));
    }

With a large enough nentries, the size expression wraps to a small value,
xmalloc/xrealloc succeeds with a tiny allocation, but all_relocations_count
retains the full (huge) value. Every caller that later iterates over
all_relocations_count entries reads or writes beyond the small buffer.

------------------------------------------------------------------------
FIVE CRASH SITES (all triggered by the same root cause)
------------------------------------------------------------------------

[1] negative-size-param in memset (readelf.c:1852)
    Command: readelf --ctf=".ctf" --ctf-strings=".strtab" \
             --ctf-symbols=".symtab" --dwarf-depth=3 --dwarf-start=0 \
             -a -w poc_1_negative_size_param.elf
    ASan output:
      ERROR: AddressSanitizer: negative-size-param: (size=-256)
          #0 memset
          #1 update_all_relocations   binutils/readelf.c:1852
          #2 display_relocations      binutils/readelf.c:9970
          #3 process_relocs           binutils/readelf.c:10170
          #4 process_object           binutils/readelf.c:24955
      SUMMARY: AddressSanitizer: negative-size-param in update_all_relocations

[2] heap-buffer-overflow in elf_relocation_cmp / qsort (readelf.c:21462)
    process_got_section_contents calls qsort with all_relocations_count as
    the element count, but the buffer holds far fewer entries. The comparator
    receives pointers beyond the buffer end.

    Code path:
      qsort(all_relocations_root, all_relocations_count,   /* count >> size */
            sizeof(elf_relocation), elf_relocation_cmp);
      -> elf_relocation_cmp reads rp->r_offset out of bounds

    Command: readelf --no-recurse-limit --demangle=auto \
             --process-links --wide --all poc_2_elf_relocation_cmp_oob.elf
    ASan output:
      ERROR: AddressSanitizer: heap-buffer-overflow
      READ of size 8 at 0x50b0000002c8
          #0 elf_relocation_cmp             binutils/readelf.c:21462
          #1 qsort_r
          #2 process_got_section_contents   binutils/readelf.c:21560
          #3 process_object                 binutils/readelf.c:24981
      allocated by update_all_relocations  binutils/readelf.c:1840
      SUMMARY: AddressSanitizer: heap-buffer-overflow in elf_relocation_cmp

[3] heap-buffer-overflow in process_got_section_contents cleanup
(readelf.c:21690)
    The cleanup loop iterates all_relocations_count times reading r_symbol
    pointers, which exceeds the actual buffer size.

    Code:
      for (size_t j = 0; j < all_relocations_count; j++)
        free (all_relocations_root[j].r_symbol);  /* OOB when j >= actual */

    Command: readelf --ctf=".ctf" --ctf-strings=".strtab" \
             --ctf-symbols=".symtab" --dwarf-depth=3 --dwarf-start=0 \
             -a -w poc_3_got_cleanup_oob.elf
    ASan output:
      ERROR: AddressSanitizer: heap-buffer-overflow
      READ of size 8 at 0x506000000240
          #0 process_got_section_contents   binutils/readelf.c:21690
          #1 process_object                 binutils/readelf.c:24981
      allocated by update_all_relocations  binutils/readelf.c:1840
      SUMMARY: AddressSanitizer: heap-buffer-overflow in
process_got_section_contents

[4] null-pointer dereference in dump_relr_relocations (readelf.c:2225)
    When the overflow produces sz=0, a code path leaves all_relocations NULL.
    dump_relr_relocations writes through it unconditionally.

    Code:
      if (do_got_section_contents)
        {
          all_relocations[r].r_offset = addr;  /* SIGSEGV: all_relocations NULL
*/
          all_relocations[r].r_name   = rtype;
          ...
        }

    Command: readelf -D -W -a -s poc_4_dump_relr_null_deref.elf
    ASan/UBSan output:
      runtime error: member access within null pointer of type 'struct
elf_relocation'
      ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
          #0 dump_relr_relocations   binutils/readelf.c:2225
          #1 process_relocs           binutils/readelf.c:10114
          #2 process_object           binutils/readelf.c:24955

[5] heap-buffer-overflow in dump_relocations (readelf.c:2889)
    dump_relocations writes entries one by one into all_relocations[i].
    Once i exceeds the actual (tiny) buffer size, writes go out of bounds.

    Code:
      if (do_got_section_contents)
        {
          all_relocations[i].r_offset = offset;  /* OOB when i >= actual size
*/
          all_relocations[i].r_name   = rtype ? rtype : "unknown";
          all_relocations[i].r_symbol = symbol_name;
          ...
        }

    Command: readelf --dyn-syms -A -D -T -a -s -u
poc_5_dump_relocations_oob.elf
    ASan output:
      ERROR: AddressSanitizer: heap-buffer-overflow
      READ of size 8 at 0x502000000150
          #0 dump_relocations       binutils/readelf.c:2889
          #1 process_relocs         binutils/readelf.c:10135
          #2 process_object         binutils/readelf.c:24955
      allocated by update_all_relocations  binutils/readelf.c:1840
      SUMMARY: AddressSanitizer: heap-buffer-overflow in dump_relocations

------------------------------------------------------------------------
HOW TO REPRODUCE
------------------------------------------------------------------------

Build with AddressSanitizer from latest HEAD:

    git clone --depth 1 https://sourceware.org/git/binutils-gdb.git
    cd binutils-gdb
    mkdir build && cd build
    ../configure --disable-gdb CFLAGS="-g -O1 -fsanitize=address,undefined"
    make -j$(nproc) all-binutils

Five PoC files are provided in the attached zip archive (poc_files.zip).
Extract them first:

    unzip poc_files.zip

Then run each with the command listed above under its crash site:

    poc_1_negative_size_param.elf
    poc_2_elf_relocation_cmp_oob.elf
    poc_3_got_cleanup_oob.elf
    poc_4_dump_relr_null_deref.elf
    poc_5_dump_relocations_oob.elf

All five also crash a plain (no-sanitizer) build with SIGSEGV or heap
corruption.

------------------------------------------------------------------------
Build & Platform
------------------------------------------------------------------------

binutils version: GNU Binutils 2.46.50.20260629 (git HEAD 0e73a13c)
component: readelf
OS: Ubuntu 24.04.4 LTS
arch: x86_64

------------------------------------------------------------------------
SUGGESTED FIX
------------------------------------------------------------------------

Add overflow guards before both size computations in update_all_relocations:

    static void
    update_all_relocations (size_t nentries)
    {
      size_t sz;

      if (!do_got_section_contents)
        return;

    + /* Guard: reject counts that would overflow the size calculation.  */
    + if (nentries > (size_t)-1 / sizeof (elf_relocation))
    +   return;

      if (!all_relocations_root)
        {
          sz = nentries * sizeof (elf_relocation);
          all_relocations_root = xmalloc (sz);
          all_relocations = all_relocations_root;
          all_relocations_count = nentries;
        }
      else
        {
          size_t orig_count = all_relocations_count;
    +     if (nentries > (size_t)-1 / sizeof (elf_relocation) - orig_count)
    +       return;
          sz = (orig_count + nentries) * sizeof (elf_relocation);
          all_relocations_root = xrealloc (all_relocations_root, sz);
          all_relocations = all_relocations_root + orig_count;
          all_relocations_count += nentries;
        }
      memset (all_relocations, 0, nentries * sizeof (elf_relocation));
    }

This is a minimal change: when a count is rejected the global
all_relocations state stays consistent (NULL), so all five crash sites
are protected implicitly without any change to their code.

------------------------------------------------------------------------
IMPACT
------------------------------------------------------------------------

A single integer overflow in update_all_relocations exposes five distinct
crash paths (CWE-190 leading to CWE-122 / CWE-476), all reachable with a
malformed ELF file and no special privileges. Impact is denial of service.

-- 
You are receiving this mail because:
You are on the CC list for the bug.

Reply via email to