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

            Bug ID: 34341
           Summary: readelf: heap-buffer-overflow in
                    byte_get_little_endian via unchecked GOT section
                    sh_entsize (binutils/elfcomm.c:134)
           Product: binutils
           Version: 2.47 (HEAD)
            Status: UNCONFIRMED
          Severity: normal
          Priority: P2
         Component: binutils
          Assignee: unassigned at sourceware dot org
          Reporter: lswang1112 at gmail dot com
  Target Milestone: ---

Created attachment 16812
  --> https://sourceware.org/bugzilla/attachment.cgi?id=16812&action=edit
poc.elf (273 bytes) - minimal ELF triggering the heap-buffer-overflow, see "HOW
TO REPRODUCE"

Affected version: GNU Binutils 2.46.50.20260629 (git HEAD 18ca0215)
Confirmed reproduced on latest upstream HEAD as of 2026-07-02.

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

process_got_section_contents() (binutils/readelf.c) dumps every section
whose name starts with ".got". It derives the per-entry stride and the
entry count directly from the section header, with no sanity check
against the actual per-entry width the code below uses to walk the
buffer:

    /* binutils/readelf.c, in process_got_section_contents() */
    uint32_t entsz = section->sh_entsize;     /* fully attacker-controlled */
    if (entsz == 0)
      { /* only a real GOT entry size (4/8) is substituted when entsz==0 */ }

    entries = section->sh_size / entsz;       /* no upper/lower bound check */
    ...
    struct got64
      {
        unsigned char bytes[4];               /* NB: 4, not 8 -- see below */
      } *got;
    ...
    got = (struct got64 *) data;
    for (j = 0; j < entries; j++)
      {
        g = BYTE_GET (got[j].bytes);          /* byte_get(f, sizeof(f)) =
byte_get(f, 4) */
        ...
      }

`data` is a heap buffer holding exactly `section->sh_size` (+1, see
below) bytes, read straight from the file. `entsz` is `section->
sh_entsize`, read straight from the section header with no validation
that it matches a real GOT entry width (4 or 8 bytes) -- nothing stops a
crafted file from declaring `sh_entsize = 1`. When it does, `entries =
sh_size / entsz` massively overcounts how many actual (4-byte-strided,
see the struct note below) records the buffer can hold, and the loop
above reads `byte_get_little_endian(got[j].bytes, 4)` for `j` values that
walk `got` well past the end of `data`.

Secondary, related observation: `struct got64.bytes` is declared `[4]`,
not `[8]` -- it reuses the exact same 4-byte layout as `struct got32`
just above it, even though it's meant to represent a 64-bit GOT entry.
This isn't the primary trigger for this crash (the primary issue is the
unchecked `sh_entsize`), but it means `sizeof(struct got64) == 4`, so
even a "reasonable" `sh_entsize` doesn't guarantee the loop's actual
memory stride matches -- any fix needs to bound the loop by the real
struct stride (`sizeof(*got)`), not just by `entsz`.

Call chain:

    readelf -a poc.elf
      process_object                readelf.c:24981
      process_got_section_contents  readelf.c:21665
      byte_get_little_endian        elfcomm.c:134   <-- OOB read

------------------------------------------------------------------------
ASAN OUTPUT
------------------------------------------------------------------------

    ==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000132
    READ of size 1 at 0x502000000132 thread T0
        #0 byte_get_little_endian      binutils/elfcomm.c:134
        #1 process_got_section_contents binutils/readelf.c:21665
        #2 process_object              binutils/readelf.c:24981
        #3 process_file                binutils/readelf.c:25404
        #4 main                        binutils/readelf.c:25470

    0x502000000132 is located 0 bytes after 2-byte region [...130,...132)
    allocated by thread T0 here:
        #0 malloc
        #1 get_data                    binutils/readelf.c:555

    SUMMARY: AddressSanitizer: heap-buffer-overflow binutils/elfcomm.c:134
             in byte_get_little_endian

(The allocated region is 2 bytes, not 1, because get_data() allocates
`sh_size + 1` bytes so string-table sections can be NUL-terminated --
irrelevant here, just explains why the crash reads offset 2 rather than
offset 1.)

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

Build 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 -Wno-error=format-overflow"
    make -j$(nproc) all-binutils

The attached poc.elf is 273 bytes. Run with:

    ./binutils/readelf -a poc.elf

The file is a hand-crafted, minimal ELF64: a bare ELF header, a 3-entry
section header table (the mandatory NULL section, one SHT_PROGBITS
section named ".got" with sh_size=1 and sh_entsize=1, and a .shstrtab
holding the section name strings), followed by the 1 byte of ".got" data
and the string table bytes. No program headers, symbol tables, or
dynamic section are needed -- process_got_section_contents() only
depends on filedata->section_headers.

With sh_size=1 and sh_entsize=1, `entries = 1/1 = 1`, and the very first
loop iteration already reads 4 bytes from a region that only holds 1
real (+1 padding) byte -- the smallest possible trigger for this bug.

Plain (no-sanitizer) build was not tested in this environment (only an
ASan+UBSan build was available), but this is a genuine heap OOB read, so
a plain build should be expected to at minimum leak adjacent heap bytes
into the printed GOT dump, and may crash depending on heap layout.

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

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

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

Clamp the loop bound to what the buffer can actually supply at the
stride the code uses to walk it (`sizeof(*got)`), instead of trusting
`entries`/`n` as computed from the attacker-controlled `sh_entsize`:

    /* 32-bit branch */
    addr = section->sh_addr;
    got = (struct got32 *) data;
    + if ((uint64_t) n * sizeof (*got) > section->sh_size)
    +   n = section->sh_size / sizeof (*got);
    for (j = 0; j < n; j++)
      ...

    /* 64-bit branch */
    addr = section->sh_addr;
    got = (struct got64 *) data;
    + if (entries * sizeof (*got) > section->sh_size)
    +   entries = section->sh_size / sizeof (*got);
    for (j = 0; j < entries; j++)
      ...

Verified: with this patch applied and rebuilt, `readelf -a poc.elf` no
longer crashes (exit 0, no ASan report). The "Global Offset Table"
header line still prints the original (attacker-controlled) entry count
for information, but the dump loop itself is now bounded to 0 iterations
for this PoC (1 byte / 4-byte stride = 0), so it prints the header and
nothing unsafe after it.

Regression-tested against three well-formed ELF files with real GOT
sections (the freshly built readelf binary itself, /bin/ls, /bin/cat):
`readelf -a` output is byte-for-byte identical between the patched and
unpatched binaries on all three, confirming the clamp does not affect
normal files where sh_entsize is already correct.

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

Out-of-bounds heap read (CWE-125), triggered by any ELF file with a
section named ".got"/".got.plt" whose sh_entsize does not match the
size the code actually reads with. Reachable via the common `-a` flag
(and any other flag combination that sets do_got_section_contents).
Reproducible impact is denial of service and/or disclosure of adjacent
heap memory content through the printed GOT dump.

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

Reply via email to