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

            Bug ID: 34332
           Summary: readelf: heap-buffer-overflow in search_modent_by_name
                    (libctf/ctf-archive.c:335)
           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 16806
  --> https://sourceware.org/bugzilla/attachment.cgi?id=16806&action=edit
Minimal 604-byte PoC ELF with out-of-range CTF archive name_offset

readelf: heap-buffer-overflow in search_modent_by_name
(libctf/ctf-archive.c:335)

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

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

search_modent_by_name() is the bsearch_r comparator used to locate a named
dictionary in a CTF archive. It dereferences name_offset from the modent
without any bounds check against the actual name table length:

    /* libctf/ctf-archive.c:329-336 */
    static int
    search_modent_by_name (const void *key, const void *ent, void *arg)
    {
      const char *k = key;
      const struct ctf_archive_modent *v = ent;
      const char *search_nametbl = arg;

      return strcmp (k, &search_nametbl[le64toh (v->name_offset)]);  /* line
335 */
    }

search_nametbl is the archive's name table, whose length is implicitly
defined as (ctfa_ctfs - ctfa_names) -- the byte range between the two
offsets in the archive header. v->name_offset is a 64-bit value read
directly from each modent in the archive. If an attacker-controlled
name_offset exceeds this computed length, strcmp reads beyond the end of
the heap-allocated name table.

Call chain:

    readelf --ctf=.ctf ...
      dump_section_as_ctf         binutils/readelf.c:17641
      ctf_dict_open               libctf/ctf-archive.c:649
      ctf_dict_open_sections      libctf/ctf-archive.c:606
      ctf_dict_open_internal      libctf/ctf-archive.c:569
      bsearch_r (comparator)      libiberty/bsearch_r.c:84
      search_modent_by_name       libctf/ctf-archive.c:335  <-- OOB read

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

    ERROR: AddressSanitizer: heap-buffer-overflow on address 0x5060000003b8
    READ of size 1 at 0x5060000003b8 thread T0
        #0 search_modent_by_name     libctf/ctf-archive.c:335
        #1 bsearch_r                 libiberty/bsearch_r.c:84
        #2 ctf_dict_open_internal    libctf/ctf-archive.c:569
        #3 ctf_dict_open_sections    libctf/ctf-archive.c:606
        #4 ctf_dict_open             libctf/ctf-archive.c:649
        #5 dump_section_as_ctf       binutils/readelf.c:17641
        #6 process_section_contents  binutils/readelf.c:18244
        #7 process_object            binutils/readelf.c:24978
        #8 process_file              binutils/readelf.c:25404
        #9 main                      binutils/readelf.c:25470
    SUMMARY: AddressSanitizer: heap-buffer-overflow libctf/ctf-archive.c:335
             in search_modent_by_name

------------------------------------------------------------------------
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 604 bytes. Run with:

    ./binutils/readelf --ctf=.ctf --ctf-parent=.ctf \
      --ctf-strings=.strtab --ctf-symbols=.symtab poc.elf

The .ctf section contains a CTF archive whose modent holds a name_offset
of 0x79, which exceeds the actual name table length of 0x33. strcmp at
ctf-archive.c:335 therefore reads 70 bytes past the end of the
heap-allocated name table.

Plain build (no sanitizer) also crashes with SIGSEGV.

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

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

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

Validate all name_offset values in ctf_dict_open_internal()
(libctf/ctf-archive.c) before calling bsearch_r. The name table length
can be derived from the archive header as ctfa_ctfs - ctfa_names (the
name table occupies the region between the two offsets):

    search_nametbl = (const char *) arc + le64toh (arc->ctfa_names);

    /* Validate all name_offsets before searching to prevent OOB reads. */
    {
      uint64_t names_len = le64toh (arc->ctfa_ctfs) - le64toh
(arc->ctfa_names);
      size_t i;
      ctf_archive_modent_t *mptr = (ctf_archive_modent_t *) ((char *) arc
                                        + sizeof (struct ctf_archive));
      for (i = 0; i < le64toh (arc->ctfa_ndicts); i++)
        if (le64toh (mptr[i].name_offset) >= names_len)
          {
            if (errp)
              *errp = ECTF_CORRUPT;
            return NULL;
          }
    }

    modent = bsearch_r (name, modent, ...);

With this patch applied, the PoC is rejected with "CTF open failure:
File data structure corruption detected." and readelf exits cleanly
with code 0.

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

Out-of-bounds read (CWE-125) in libctf, triggered when readelf (or any
other CTF consumer such as objdump) opens a crafted ELF file with a
malformed CTF archive. Reproducible impact is denial of service (SIGSEGV).
The OOB read also leaks bytes from the heap region adjacent to the name
table before the crash, which may disclose heap metadata.

Note: libctf is also linked into ld. If a crafted input can reach
search_modent_by_name via ld's CTF-merging code path, this may qualify
as a security bug under the Binutils security policy.

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

Reply via email to