https://sourceware.org/bugzilla/show_bug.cgi?id=34333
Bug ID: 34333
Summary: readelf: unbounded recursion in ctf_decl_push
(libctf/ctf-decl.c) causes stack overflow
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 16807
--> https://sourceware.org/bugzilla/attachment.cgi?id=16807&action=edit
PoC ELF (16736 bytes) with self-referential CTF_K_FUNCTION type triggering
ctf_decl_push infinite recursion
Affected version: GNU Binutils 2.46.50.20260629 (git HEAD 0e73a13c)
Confirmed reproduced on latest upstream HEAD as of 2026-06-30.
------------------------------------------------------------------------
SUMMARY
------------------------------------------------------------------------
A crafted ELF file with a .ctf section that encodes a self-referential
CTF type chain (e.g. a function or pointer type whose ctt_type field
points back to itself) causes ctf_decl_push() (libctf/ctf-decl.c:75)
to recurse without bound, exhausting the call stack. readelf --ctf or
objdump --ctf crashes with SIGSEGV (stack overflow) regardless of file
size or any other flag. No recursion limit or cycle-detection is
implemented for CTF type traversal.
Unlike the Rust demangler's stack overflow (which is explicitly
documented, with --no-recurse-limit required to disable the 2048-frame
guard), this recursion is entirely inside libctf's internal type-name
resolution and has no user-controllable limit at all.
------------------------------------------------------------------------
ROOT CAUSE
------------------------------------------------------------------------
ctf_decl_push() builds the type-name declaration stack for CTF type
printing. For composite types it recurses into their ctt_type member:
/* libctf/ctf-decl.c:75 */
static void
ctf_decl_push (ctf_decl_t *cd, ctf_dict_t *fp, ctf_id_t type)
{
...
switch (kind = LCTF_INFO_KIND (fp, tp->ctt_info))
{
case CTF_K_ARRAY:
ctf_decl_push (cd, fp, ar.ctr_contents); /* recurse */
break;
case CTF_K_TYPEDEF:
ctf_decl_push (cd, fp, tp->ctt_type); /* recurse */
return;
case CTF_K_FUNCTION:
ctf_decl_push (cd, fp, tp->ctt_type); /* recurse */
break;
case CTF_K_POINTER:
ctf_decl_push (cd, fp, tp->ctt_type); /* recurse */
break;
case CTF_K_SLICE:
ctf_decl_push (cd, fp, ctf_type_reference (fp, type));
return;
case CTF_K_VOLATILE:
case CTF_K_CONST:
case CTF_K_RESTRICT:
ctf_decl_push (cd, fp, tp->ctt_type); /* recurse */
break;
...
}
}
If a CTF type's ctt_type field points back to itself (or to any cycle
in the type graph), ctf_decl_push calls itself infinitely. There is no
visited-type set, depth counter, or any other cycle-detection mechanism.
------------------------------------------------------------------------
ASAN OUTPUT
------------------------------------------------------------------------
AddressSanitizer detects the crash as stack-overflow after ~250 frames:
SUMMARY: AddressSanitizer: stack-overflow in ctf_decl_push
#0 ctf_lookup_by_id libctf/ctf-lookup.c:331
#1 ctf_decl_push libctf/ctf-decl.c:85
#2 ctf_decl_push libctf/ctf-decl.c:119
... [~250 identical ctf_decl_push frames]
GDB on a plain build confirms ~74 829 frames before SIGSEGV:
Program received signal SIGSEGV, Segmentation fault.
#0 ctf_lookup_by_id (...) libctf/ctf-lookup.c:331
#1 ctf_decl_push (cd=..., type=8) libctf/ctf-decl.c:85
#2 ctf_decl_push (cd=..., type=8) libctf/ctf-decl.c:119
#3 ctf_decl_push (cd=..., type=8) libctf/ctf-decl.c:119
... [74829 identical frames]
Call chain:
readelf --ctf=.ctf ...
dump_section_as_ctf readelf.c:17641
dump_ctf_archive_member
ctf_variable_iter
ctf_type_aname libctf/ctf-type.c
ctf_decl_push libctf/ctf-decl.c:75 <- infinite recursion
------------------------------------------------------------------------
HOW TO REPRODUCE
------------------------------------------------------------------------
Build from latest HEAD (plain build -- no sanitizers needed; stack
overflow crashes any build):
git clone --depth 1 https://sourceware.org/git/binutils-gdb.git
cd binutils-gdb
mkdir build && cd build
../configure --disable-gdb CFLAGS="-g -O2"
make -j$(nproc) all-binutils
The attached poc.elf is 16 736 bytes. Run with:
./binutils/readelf --ctf=.ctf --ctf-parent=.ctf \
--ctf-strings=.strtab --ctf-symbols=.symtab poc.elf
# Segmentation fault (~74 000 recursive ctf_decl_push frames)
The PoC contains a .ctf section with a CTF archive whose type dictionary
encodes a self-referential CTF_K_FUNCTION type with ctt_type pointing
back to itself. When ctf_type_aname resolves this type for printing,
ctf_decl_push recurses indefinitely.
Note: objdump --ctf=.ctf poc.elf triggers the same crash via an
identical call chain.
------------------------------------------------------------------------
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
------------------------------------------------------------------------
Add a depth counter to ctf_decl_t (libctf/ctf-impl.h) and a guard at
the top of ctf_decl_push. The counter must be decremented at every
return path to track actual recursion depth, not total calls.
Step 1: add cd_depth to struct ctf_decl in libctf/ctf-impl.h:
typedef struct ctf_decl
{
...
int cd_err; /* Saved error value. */
int cd_enomem; /* Nonzero if OOM during printing. */
+ int cd_depth; /* Current recursion depth. */
} ctf_decl_t;
/* cd_depth is zero-initialised by ctf_decl_init() via memset. */
Step 2: add depth guard in ctf_decl_push (libctf/ctf-decl.c):
static void
ctf_decl_push (ctf_decl_t *cd, ctf_dict_t *fp, ctf_id_t type)
{
+ #define CTF_DECL_PUSH_MAX_DEPTH 512
+ if (cd->cd_depth >= CTF_DECL_PUSH_MAX_DEPTH)
+ { cd->cd_err = ECTF_CORRUPT; return; }
+ cd->cd_depth++;
if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
{
cd->cd_err = fp->ctf_errno;
+ cd->cd_depth--;
return;
}
switch (kind = LCTF_INFO_KIND (fp, tp->ctt_info))
{
case CTF_K_TYPEDEF: /* early return */
ctf_decl_push (cd, fp, tp->ctt_type);
+ cd->cd_depth--;
return;
case CTF_K_SLICE: /* early return */
ctf_decl_push (cd, fp, ctf_type_reference (fp, type));
+ cd->cd_depth--;
return;
...
}
if ((cdp = malloc (sizeof (ctf_decl_node_t))) == NULL)
{
cd->cd_err = EAGAIN;
+ cd->cd_depth--;
return;
}
/* ... append cdp to cd_nodes ... */
+ cd->cd_depth--;
}
With this patch, the PoC is handled gracefully: self-referential types
are reported as "cannot format name dumping type 0xN" and readelf exits
cleanly with code 0 instead of crashing.
Alternatively, maintain a ctf_id_t visited-set within ctf_decl_t and
return ECTF_CORRUPT when a type ID is seen for the second time (proper
cycle detection without a fixed depth limit).
------------------------------------------------------------------------
DIFFERENCE FROM RUST DEMANGLER STACK OVERFLOW
------------------------------------------------------------------------
The Rust demangler in libiberty also has a stack overflow reachable from
readelf, but it is explicitly documented: --no-recurse-limit must be
passed to disable the 2048-frame default limit, and binutils SECURITY.txt
states that reports with that flag disabled are rejected.
ctf_decl_push has no equivalent limit. There is no option to enable
depth-limiting in libctf's type declaration printer, so the crash is
triggered with default flags and a plain --ctf=.ctf invocation.
------------------------------------------------------------------------
IMPACT
------------------------------------------------------------------------
Unbounded recursive call (CWE-674) in libctf triggered by a
self-referential CTF type in any ELF file. Stack exhaustion causes
SIGSEGV. Because libctf is also used internally by the linker (ld)
during CTF type merging, a crafted object file could trigger this crash
during a link operation, which may qualify as a security-relevant denial
of service under the Binutils security policy.
--
You are receiving this mail because:
You are on the CC list for the bug.