https://sourceware.org/bugzilla/show_bug.cgi?id=34326
Paul Wang <lswang1112 at gmail dot com> changed:
What |Removed |Added
----------------------------------------------------------------------------
CC| |lswang1112 at gmail dot com
--- Comment #1 from Paul Wang <lswang1112 at gmail dot com> ---
Created attachment 16804
--> https://sourceware.org/bugzilla/attachment.cgi?id=16804&action=edit
PoC files (5 ELF files) triggering five distinct crash sites from the integer
overflow in update_all_relocations
Additional analysis for bug #34326: integer overflow in update_all_relocations
triggers five distinct crash paths
Affected version: GNU Binutils 2.46.50.20260629 (git HEAD 0e73a13c)
Confirmed reproduced on latest upstream HEAD as of 2026-06-30.
I independently discovered this bug through fuzzing and found that the
integer overflow in update_all_relocations() is more exploitable than
initially reported -- it triggers five distinct crash sites across
different code paths. I am providing a full root cause analysis, ASan
traces, PoC files for each crash, and a concrete suggested fix, hoping
this helps move the issue toward resolution.
------------------------------------------------------------------------
ROOT CAUSE
------------------------------------------------------------------------
The root cause is an unchecked integer overflow in update_all_relocations()
(binutils/readelf.c:1830). The function computes the allocation size using
nentries, which is derived from the ELF section header field sh_size /
sh_entsize and is entirely attacker-controlled:
static void
update_all_relocations (size_t nentries)
{
size_t sz;
if (!all_relocations_root)
{
sz = nentries * sizeof (elf_relocation); /* no overflow check */
all_relocations_root = xmalloc (sz); /* too-small buffer */
all_relocations = all_relocations_root;
all_relocations_count = nentries; /* stores REAL count */
}
else
{
size_t orig_count = all_relocations_count;
sz = (orig_count + nentries) * sizeof (elf_relocation); /* overflow
*/
all_relocations_root = xrealloc (all_relocations_root, sz);
all_relocations = all_relocations_root + orig_count;
all_relocations_count += nentries;
}
memset (all_relocations, 0, nentries * sizeof (elf_relocation));
}
With a large enough nentries, the size expression wraps to a small value,
xmalloc/xrealloc succeeds with a tiny allocation, but all_relocations_count
retains the full (huge) value. Every caller that later iterates over
all_relocations_count entries reads or writes beyond the small buffer.
------------------------------------------------------------------------
FIVE CRASH SITES (all triggered by the same root cause)
------------------------------------------------------------------------
[1] negative-size-param in memset (readelf.c:1852)
Command: readelf --ctf=".ctf" --ctf-strings=".strtab" \
--ctf-symbols=".symtab" --dwarf-depth=3 --dwarf-start=0 \
-a -w poc_1_negative_size_param.elf
ASan output:
ERROR: AddressSanitizer: negative-size-param: (size=-256)
#0 memset
#1 update_all_relocations binutils/readelf.c:1852
#2 display_relocations binutils/readelf.c:9970
#3 process_relocs binutils/readelf.c:10170
#4 process_object binutils/readelf.c:24955
SUMMARY: AddressSanitizer: negative-size-param in update_all_relocations
[2] heap-buffer-overflow in elf_relocation_cmp / qsort (readelf.c:21462)
process_got_section_contents calls qsort with all_relocations_count as
the element count, but the buffer holds far fewer entries. The comparator
receives pointers beyond the buffer end.
Code path:
qsort(all_relocations_root, all_relocations_count, /* count >> size */
sizeof(elf_relocation), elf_relocation_cmp);
-> elf_relocation_cmp reads rp->r_offset out of bounds
Command: readelf --no-recurse-limit --demangle=auto \
--process-links --wide --all poc_2_elf_relocation_cmp_oob.elf
ASan output:
ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 8 at 0x50b0000002c8
#0 elf_relocation_cmp binutils/readelf.c:21462
#1 qsort_r
#2 process_got_section_contents binutils/readelf.c:21560
#3 process_object binutils/readelf.c:24981
allocated by update_all_relocations binutils/readelf.c:1840
SUMMARY: AddressSanitizer: heap-buffer-overflow in elf_relocation_cmp
[3] heap-buffer-overflow in process_got_section_contents cleanup
(readelf.c:21690)
The cleanup loop iterates all_relocations_count times reading r_symbol
pointers, which exceeds the actual buffer size.
Code:
for (size_t j = 0; j < all_relocations_count; j++)
free (all_relocations_root[j].r_symbol); /* OOB when j >= actual */
Command: readelf --ctf=".ctf" --ctf-strings=".strtab" \
--ctf-symbols=".symtab" --dwarf-depth=3 --dwarf-start=0 \
-a -w poc_3_got_cleanup_oob.elf
ASan output:
ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 8 at 0x506000000240
#0 process_got_section_contents binutils/readelf.c:21690
#1 process_object binutils/readelf.c:24981
allocated by update_all_relocations binutils/readelf.c:1840
SUMMARY: AddressSanitizer: heap-buffer-overflow in
process_got_section_contents
[4] null-pointer dereference in dump_relr_relocations (readelf.c:2225)
When the overflow produces sz=0, a code path leaves all_relocations NULL.
dump_relr_relocations writes through it unconditionally.
Code:
if (do_got_section_contents)
{
all_relocations[r].r_offset = addr; /* SIGSEGV: all_relocations NULL
*/
all_relocations[r].r_name = rtype;
...
}
Command: readelf -D -W -a -s poc_4_dump_relr_null_deref.elf
ASan/UBSan output:
runtime error: member access within null pointer of type 'struct
elf_relocation'
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
#0 dump_relr_relocations binutils/readelf.c:2225
#1 process_relocs binutils/readelf.c:10114
#2 process_object binutils/readelf.c:24955
[5] heap-buffer-overflow in dump_relocations (readelf.c:2889)
dump_relocations writes entries one by one into all_relocations[i].
Once i exceeds the actual (tiny) buffer size, writes go out of bounds.
Code:
if (do_got_section_contents)
{
all_relocations[i].r_offset = offset; /* OOB when i >= actual size
*/
all_relocations[i].r_name = rtype ? rtype : "unknown";
all_relocations[i].r_symbol = symbol_name;
...
}
Command: readelf --dyn-syms -A -D -T -a -s -u
poc_5_dump_relocations_oob.elf
ASan output:
ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 8 at 0x502000000150
#0 dump_relocations binutils/readelf.c:2889
#1 process_relocs binutils/readelf.c:10135
#2 process_object binutils/readelf.c:24955
allocated by update_all_relocations binutils/readelf.c:1840
SUMMARY: AddressSanitizer: heap-buffer-overflow in dump_relocations
------------------------------------------------------------------------
HOW TO REPRODUCE
------------------------------------------------------------------------
Build with AddressSanitizer 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"
make -j$(nproc) all-binutils
Five PoC files are provided in the attached zip archive (poc_files.zip).
Extract them first:
unzip poc_files.zip
Then run each with the command listed above under its crash site:
poc_1_negative_size_param.elf
poc_2_elf_relocation_cmp_oob.elf
poc_3_got_cleanup_oob.elf
poc_4_dump_relr_null_deref.elf
poc_5_dump_relocations_oob.elf
All five also crash a plain (no-sanitizer) build with SIGSEGV or heap
corruption.
------------------------------------------------------------------------
Build & Platform
------------------------------------------------------------------------
binutils version: GNU Binutils 2.46.50.20260629 (git HEAD 0e73a13c)
component: readelf
OS: Ubuntu 24.04.4 LTS
arch: x86_64
------------------------------------------------------------------------
SUGGESTED FIX
------------------------------------------------------------------------
Add overflow guards before both size computations in update_all_relocations:
static void
update_all_relocations (size_t nentries)
{
size_t sz;
if (!do_got_section_contents)
return;
+ /* Guard: reject counts that would overflow the size calculation. */
+ if (nentries > (size_t)-1 / sizeof (elf_relocation))
+ return;
if (!all_relocations_root)
{
sz = nentries * sizeof (elf_relocation);
all_relocations_root = xmalloc (sz);
all_relocations = all_relocations_root;
all_relocations_count = nentries;
}
else
{
size_t orig_count = all_relocations_count;
+ if (nentries > (size_t)-1 / sizeof (elf_relocation) - orig_count)
+ return;
sz = (orig_count + nentries) * sizeof (elf_relocation);
all_relocations_root = xrealloc (all_relocations_root, sz);
all_relocations = all_relocations_root + orig_count;
all_relocations_count += nentries;
}
memset (all_relocations, 0, nentries * sizeof (elf_relocation));
}
This is a minimal change: when a count is rejected the global
all_relocations state stays consistent (NULL), so all five crash sites
are protected implicitly without any change to their code.
------------------------------------------------------------------------
IMPACT
------------------------------------------------------------------------
A single integer overflow in update_all_relocations exposes five distinct
crash paths (CWE-190 leading to CWE-122 / CWE-476), all reachable with a
malformed ELF file and no special privileges. Impact is denial of service.
--
You are receiving this mail because:
You are on the CC list for the bug.