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

            Bug ID: 34596
           Summary: eu-readelf: heap-buffer-overflow (OOB read) in
                    print_gdb_index_section (.gdb_index CU-vector count
                    read)
           Product: elfutils
           Version: unspecified
            Status: UNCONFIRMED
          Severity: normal
          Priority: P2
         Component: tools
          Assignee: unassigned at sourceware dot org
          Reporter: sujaltuladhar1231 at gmail dot com
                CC: elfutils-devel at sourceware dot org
  Target Milestone: ---

eu-readelf --debug-dump=gdb_index FILE (also reached via -w or -a) parses the
attacker-controlled .gdb_index section. In the symbol-table loop of
print_gdb_index_section (src/readelf.c) the constant-pool offset "vector" read
from the file is validated with only a strict less-than check before a fixed
4-byte read at that offset, so the read runs 4 bytes past the section when
vector equals the constant-pool size.

Confirmed on current git HEAD 947b2d9abbbd4cec45208088de7576e33c2869ca.

The code (src/readelf.c, symbol-table loop):

      const unsigned char *readcus = const_start + vector;
      if (unlikely ((size_t) (dataend - const_start) < vector))
        goto invalid_data;
      uint32_t cus = read_4ubyte_unaligned (dbg, readcus);   /* OOB read */
      while (cus--)
        {
          ...
          readcus += 4;
          if (unlikely (readcus + 4 > dataend))              /* correct guard
*/
            goto invalid_data;
          cu_kind = read_4ubyte_unaligned (dbg, readcus);

"dataend - const_start" is the size of the constant pool. The guard accepts
vector == dataend - const_start, which makes readcus == dataend. The following
read_4ubyte_unaligned then reads the 4 bytes [dataend, dataend+4), up to 4
bytes
past the end of the section. The read width is never accounted for. The inner
CU
loop five lines below already guards its own read correctly with
"if (readcus + 4 > dataend) goto invalid_data", and the DW_FORM_sec_offset and
str_offsets paths use the same (end - ptr) < width idiom, so this initial count
read is the only fixed-width read on the path that omits the width term.

Reachability. print_gdb_index_section is dispatched for .gdb_index under
--debug-dump=gdb_index, -w or -a on any user-supplied ELF opened with
elf_begin(..., ELF_C_READ_MMAP, ...). Inspecting an untrusted ELF is the
intended
use of eu-readelf, so a crafted .gdb_index reaches the read with no privileges.
A
minimal trigger is a 33-byte .gdb_index (version 4 to 9) whose header makes the
CU, TU and address lists empty, with one symbol slot {name = 0, vector =
constant_pool_size} and a single NUL byte in the constant pool so the name
memchr
check passes.

Proof. Built from this HEAD with -fsanitize=address, the real eu-readelf 0.196
on
such a file reaches and executes the count read. The output stops at the symbol
"CUs:" field, which is the line right after cus = read_4ubyte_unaligned, then
the
inner guard rejects the result as invalid data. Under eu-readelf's own section
buffer the 4 over-read bytes fall in adjacent mapped memory, so that run does
not
fault. Placing the section data in a buffer sized exactly to d_size, as the
OSS-Fuzz harness does through elf_memory, turns the same read into a hard
AddressSanitizer heap-buffer-overflow READ of size 4 located 0 bytes after the
region, in read_4ubyte_unaligned. Rebuilt with the fix below, the same input is
rejected as invalid data.

Impact. Out-of-bounds read of up to 4 bytes on untrusted input, a crash and
undefined-behaviour class. The over-read value becomes the cus loop counter and
the inner loop bails on its next iteration, so no over-read byte reaches the
output and there is no write.

Fix. Add the read-width term to the guard, matching the inner loop and the
file's
own (end - ptr) < width idiom:

      const unsigned char *readcus = const_start + vector;
      if (unlikely ((size_t) (dataend - const_start) < vector
                    || (size_t) (dataend - readcus) < sizeof (uint32_t)))
        goto invalid_data;
      uint32_t cus = read_4ubyte_unaligned (dbg, readcus);

The first clause already guarantees readcus <= dataend, so the added clause is
equivalent to readcus + 4 > dataend and cannot underflow. I can attach the
patch
in git format-patch form, the section generator, and a crafted ELF if useful.

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

Reply via email to