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.