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.