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

            Bug ID: 34412
           Summary: libelf: heap-buffer-overflow read after elf_newscn
                    following elf_update
           Product: elfutils
           Version: unspecified
            Status: UNCONFIRMED
          Severity: normal
          Priority: P2
         Component: libelf
          Assignee: unassigned at sourceware dot org
          Reporter: karankurani3k at gmail dot com
                CC: elfutils-devel at sourceware dot org
  Target Milestone: ---

Created attachment 16852
  --> https://sourceware.org/bugzilla/attachment.cgi?id=16852&action=edit
Standalone reproducer, ASan proof, tested patch, after-patch validation, and
duplicate analysis for elf_newscn stale section-header cache OOB read

Reported by:
Karan Kurani <[email protected]>

I found a heap-buffer-overflow read in libelf when elf_newscn() is called after
an earlier successful elf_update(ELF_C_WRITE), followed by another
elf_update().

Tested version
--------------
Latest elfutils HEAD:

  01a933566e2606e17893a72121e70c81ba7558dc

The remote HEAD was rechecked on July 20, 2026 and was identical.

git status --porcelain printed no output before the evidence runs and again
after restoring the tested patch.

The ASan/UBSan build used:

  CFLAGS="-O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer"
  CXXFLAGS="-O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer"
  LDFLAGS="-fsanitize=address,undefined"

  ./configure --enable-maintainer-mode --enable-sanitize-address \
    --disable-debuginfod --disable-libdebuginfod --disable-nls

A temporary AArch64 build-only include replacement from <linux/uio.h> to
<sys/uio.h> was used while building backend-dependent tools. It was reverted
before all evidence runs and is not part of the patch.

Summary
-------
gelf_getshdr() can materialize a contiguous section-header cache sized for the
current number of sections.

An initial elf_update(ELF_C_WRITE) clears the ELF-wide dirty flag.

If elf_newscn() is then used to append a section, it marks only the newly
created section and its header dirty. It does not mark the whole Elf object
dirty.

A subsequent elf_update(ELF_C_WRITE) therefore reuses the old contiguous
section-header cache while using the new, larger section count.

For the tested ELF64 object:

  original section count: 22
  cached section-header allocation: 22 * 64 = 1,408 bytes
  section count after elf_newscn(): 23
  pwrite() read request: 23 * 64 = 1,472 bytes

This causes pwrite() to read exactly 64 bytes beyond the cached allocation.

Root cause
----------
File:

  libelf/elf_newscn.c

Function:

  elf_newscn()

Root-cause location:

  libelf/elf_newscn.c:156

After successfully adding a section, the function currently performs:

  result->flags |= ELF_F_DIRTY;

but does not also set:

  elf->flags |= ELF_F_DIRTY;

Adding a section changes the size of the section-header table, so the cached
whole-file serialization state must also be invalidated.

Vulnerable location
-------------------
File:

  libelf/elf32_updatefile.c

Function:

  __elf64_updatefile()

ASan reaches the vulnerable write path at:

  libelf/elf32_updatefile.c:835

The relevant code begins:

  && unlikely ((size_t) pwrite_retry (elf->fildes, shdr_data,
                                      sizeof (Elf64_Shdr) * shnum,
                                      shdr_offset)

The source is shared with the ELF64 implementation.

Public API path
---------------
The issue is reached entirely through public libelf APIs:

  elf_begin(fd, ELF_C_RDWR, NULL)
  -> elf_getshdrnum() / elf_getscn() / gelf_getshdr()
  -> elf_update(ELF_C_WRITE)
  -> elf_newscn()
  -> gelf_update_shdr()
  -> elf_newdata()
  -> elf_update(ELF_C_WRITE)
  -> __elf64_updatefile()
  -> pwrite()

The one-byte caller-owned section-data buffer remains allocated and valid until
after elf_end(). No ownership or lifetime requirement is violated.

Input and output layout
-----------------------
Seed:

  valid GCC-generated ELF64 ET_REL object
  file size: 4,720 bytes
  section-header offset: 3,312
  section count: 22
  section-header entry size: 64 bytes
  section-header table ends at seed EOF

After the first elf_update():

  file size: 4,744 bytes

Mutation:

  append one unnamed, one-byte SHT_PROGBITS section

Second update layout:

  new section data offset: 0xd05
  section-header table offset: 3,336
  section count: 23
  section-header entry size: 64 bytes
  expected output EOF: 4,808 bytes

Memory-size mismatch:

  cached allocation: 22 * 64 = 1,408 bytes
  pwrite() request: 23 * 64 = 1,472 bytes
  out-of-bounds read: 64 bytes

Runtime proof
-------------
The issue reproduced three out of three times on the clean audited HEAD.

Each vulnerable run exited with status 134 and reported:

  ERROR: AddressSanitizer: heap-buffer-overflow
  READ of size 1472

      #1 pwrite_retry
         lib/system.h:192

      #2 __elf64_updatefile
         libelf/elf32_updatefile.c:835

      #3 write_file / elf_update
         libelf/elf_update.c:132

  0 bytes after 1408-byte region

  allocated by:

      #1 load_shdr_wrlock
         libelf/elf32_getshdr.c:69

      #3 gelf_getshdr
         libelf/gelf_getshdr.c:86

Reproducer hashes
-----------------
Seed ELF:

  7f648bf6e39624c40041a6b056a021b77fd0c83a5ec00acbc22895634a5630db

Reproducer source:

  1da08071f55cfc1545d9a6a9eb5c9e573bab95ece564389343c157fa14863efa

Vulnerable harness binary:

  63ad2ec36b90b25c135f78c2d638c04d0eb3ebf895b534327f654fea123b9725

Before-patch partial output:

  24aa02f5762ea75fd7b94b3cf4cc90307c1d69db51cbf563f712437f0c4b2806

Complete ASan trace:

  58f569476dc1714fb7452d2fed6d07f0bdfd8b7449d70539315ff3392bc17e5e

Patch:

  3e434895fe75ea578cd16700103fea11544d1bc4d76c0acf9a4c4ccc80f0730c

Patched harness binary:

  f487ffab19feaadfbf063b88985f216e1abd0f2a30e3146daeb39ce9a3e85c53

Patched output:

  d7830304d5ad42d25b3566aea4a3d0c34c3c1997718f8b3294ffc5de62025321

Impact
------
A valid public libelf mutation sequence can cause a heap out-of-bounds read
during ELF serialization.

Under ASan, the immediate result is process abort and denial of service.

Without instrumentation, pwrite() may consume bytes beyond libelf's cached
section-header allocation and include adjacent heap contents in the generated
ELF output. This creates a potential information-disclosure condition, although
I have not independently demonstrated recovery of sensitive data.

I am not claiming code execution.

Suggested fix
-------------
Mark the whole Elf object dirty after elf_newscn() successfully adds a section,
because the section-header table size has changed.

This forces the section-header serialization state to be regenerated before the
next elf_update().

Patch diff tested
-----------------
  diff --git a/libelf/elf_newscn.c b/libelf/elf_newscn.c
  --- a/libelf/elf_newscn.c
  +++ b/libelf/elf_newscn.c
  @@ -154,6 +154,8 @@ elf_newscn (Elf *elf)
       result->flags |= ELF_F_DIRTY;
  +    /* Adding a section changes the size of the section header table.  */
  +    elf->flags |= ELF_F_DIRTY;

After-patch validation
----------------------
After rebuilding with the patch, the identical reproducer completed three out
of three times with exit status 0 and empty ASan/UBSan logs.

Each resulting object:

  reopened successfully through public libelf APIs
  contained 23 sections
  passed eu-elflint with:

    No errors

Relevant ASan/UBSan regression tests also passed:

  tests/newscn
  tests/update1
  tests/update2
  tests/addsections 1
  tests/addsections --mmap 1

The addsections tests exercised ELF_C_RDWR and ELF_C_RDWR_MMAP.

Not a duplicate
---------------
Sourceware Bugzilla searches for the following terms did not identify a
matching report:

  elf_newscn
  elf_update
  stale section
  ELF_F_DIRTY
  section header array
  heap-buffer-overflow

The elfutils-devel archive search for elf_newscn returned documentation patches
only.

The closest public reports are not duplicates:

Bug 33967 concerns a section-zero NULL dereference only when the section count
reaches 65,280 or more, in elf32_updatenull.c.

Bug 25077 concerns an incorrect eu-unstrip data size under ELF_F_LAYOUT, where
the oversized buffer originated from adjust_relocs.

This finding:

  uses only 22 ordinary sections
  does not use ELF_F_LAYOUT
  is triggered by repeated public libelf updates
  overreads libelf's own stale cached section-header allocation

It is also unrelated to the previously fixed read-side issues:

  Bug 34382: .debug_macro
  Bug 34383: .debug_str_offsets
  Bug 34385: .debug_rnglists
  Bug 34386: .debug_loclists
  Bug 34387: .debug_frame CIE augmentation
  Bug 34392: .debug_pubnames
  Bug 34401: elf_getarsym truncated /SYM64/ archive index

Please include the following credit if this is fixed:

  Reported-by: Karan Kurani <[email protected]>

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

Reply via email to