https://sourceware.org/bugzilla/show_bug.cgi?id=34371
Bug ID: 34371
Summary: objdump: invalid-pointer dereference in
debug_write_type (binutils/debug.c:2428)
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 16826
--> https://sourceware.org/bugzilla/attachment.cgi?id=16826&action=edit
Minimal 416-byte ELF with malformed C++ stab method descriptor "##2;:PR;2;.;;";
triggers SIGSEGV at debug.c:2428
Affected version: GNU Binutils 2.46.1 (also confirmed on 2.46.50.20260707 git
HEAD)
Confirmed reproduced on 2.46.1 release.
------------------------------------------------------------------------
ROOT CAUSE
------------------------------------------------------------------------
debug_write_type() (binutils/debug.c) is the recursive type-printing
function used by objdump -g to emit stab debug information. At line 2428
it unconditionally stamps the type's mark field to prevent infinite
recursion on cyclic types:
/* debug.c:2415 – debug_write_type (simplified) */
static bool
debug_write_type (struct debug_handle *info,
const struct debug_write_fns *fns, void *fhandle,
struct debug_type_s *type, struct debug_name *name)
{
...
if (type == DEBUG_TYPE_NULL)
return (*fns->empty_type) (fhandle);
type->mark = info->mark; /* line 2428: writes through type pointer */
...
}
The type pointer passed at the crash site is 0x32007fffffffd4d0 —
a value that is non-NULL (so the NULL check at the top is bypassed) but
is not a valid heap or stack address. Writing through it at line 2428
triggers SIGSEGV.
The invalid pointer originates from a silent failure in the stab
demangler. The crafted ELF contains a malformed C++ method type
descriptor that causes the demangler to enter stab_demangle_fund_type()
with an empty string (pp points to '\0'). The case for that condition
does not propagate the error:
/* stabs.c – stab_demangle_fund_type (simplified) */
static bool
stab_demangle_fund_type (struct stab_demangle_info *minfo,
const char **pp, debug_type *ptype)
{
...
switch (**pp)
{
case '\0':
case '_':
/* cplus_demangle permits this, but I don't know what it means. */
stab_bad_demangle (orig);
break; /* ← BUG: should be "return false" */
...
}
return true; /* ← returns success even though *ptype was never set
*/
}
The caller (stab_demangle_type, case 'R') checks the return value and,
seeing true, calls debug_make_reference_type() with the uninitialized
or stale *ptype value — passing a garbage address as the referenced type.
debug_make_reference_type() stores any non-NULL value verbatim in the
type node. When debug_write_type() later dereferences this garbage
pointer, SIGSEGV results.
Observed call chain (gdb on plain build):
objdump -g poc.elf
debug_write_type debug.c:2428 <-- crash
(type=0x32007fffffffd4d0)
debug_write_type debug.c:2583 (traversing class member)
debug_write_type debug.c:2561
debug_write_class_type debug.c:2781 (writing class fields)
debug_write_type debug.c:2555
... (several more frames)
debug_write_name debug.c:2390
debug_write debug.c:2352
print_debugging_info prdbg.c:296
dump_bfd objdump.c:5849
main objdump.c:6427
------------------------------------------------------------------------
CRASH OUTPUT
------------------------------------------------------------------------
gdb backtrace, plain build:
Program received signal SIGSEGV, Segmentation fault.
0x00005555555fbbd5 in debug_write_type (info=0x5555558d27f8,
fns=0x555555805740 <pr_fns>, fhandle=0x7fffffffdbc0,
type=0x32007fffffffd4d0, name=0x0)
at binutils/debug.c:2428
2428 type->mark = info->mark;
#0 debug_write_type debug.c:2428 <-- invalid ptr deref
#1 debug_write_type debug.c:2583
#2 debug_write_type debug.c:2561
#3 debug_write_type debug.c:2626
#4 debug_write_class_type debug.c:2781
#5 debug_write_type debug.c:2555
#6 debug_write_type debug.c:2653
#7 debug_write_type debug.c:2617
#8 debug_write_class_type debug.c:2781
#9 debug_write_type debug.c:2555
#10 debug_write_name debug.c:2390
#11 debug_write debug.c:2352
#12 print_debugging_info prdbg.c:296
#13 dump_bfd objdump.c:5849
#14 display_object_bfd objdump.c:5900
#15 display_any_bfd objdump.c:5979
#16 display_file objdump.c:6000
#17 main objdump.c:6427
(gdb) print/x type
$1 = 0x32007fffffffd4d0 <-- not a valid heap address
Key diagnostic output before the crash (stab parser warnings):
poc.elf: .stab: stab entry 1 is corrupt, strx = 0x41414141, type = 65
Warning: const/volatile indicator missing: bar::##2;:PR;2;.;;
Warning: member function type missing: bar::##2;:PR;2;.;;
------------------------------------------------------------------------
HOW TO REPRODUCE
------------------------------------------------------------------------
Download the source:
# Release tarball (recommended):
wget https://ftp.gnu.org/gnu/binutils/binutils-2.46.1.tar.xz
# Or from git:
git clone git://sourceware.org/git/binutils-gdb.git
cd binutils-gdb && git checkout binutils-2_46_1
Build from the tarball:
tar xf binutils-2.46.1.tar.xz && cd binutils-2.46.1
mkdir build && cd build
../configure --disable-gdb --disable-gdbserver \
--disable-gdbsupport --disable-sim CFLAGS="-g -O0"
make -j$(nproc) all-binutils
Reproduce:
./binutils/objdump -g poc_debug_write_type_invalid_ptr.elf
Under gdb:
gdb -q -batch -ex run -ex bt \
--args ./binutils/objdump -g poc_debug_write_type_invalid_ptr.elf
The -g flag activates the stab debug-info printer. The crash is
triggered by a crafted .stab section containing a type descriptor that
encodes a C++ reference type (& modifier, stab type code 'P' or '#')
whose referent type number refers to a corrupt or out-of-range slot.
The stab parser calls debug_make_reference_type() with the slot value
masquerading as a pointer; debug_write_type() later crashes when it
attempts to mark the referenced type.
------------------------------------------------------------------------
SUGGESTED FIX
------------------------------------------------------------------------
The fix belongs in stab_demangle_fund_type() in binutils/stabs.c. The
case for an end-of-string condition (case '\0') must signal failure
instead of falling through to "return true":
--- a/binutils/stabs.c
+++ b/binutils/stabs.c
@@ -4987,7 +4987,7 @@ stab_demangle_fund_type (struct stab_demangle_info
*minfo,
case '\0':
case '_':
/* cplus_demangle permits this, but I don't know what it means. */
stab_bad_demangle (orig);
- break;
+ return false;
With this change the error propagates back through stab_demangle_type
(case 'R') → stab_demangle_args → stab_demangle_argtypes →
parse_stab_argtypes → the caller, which checks for DEBUG_TYPE_NULL and
takes the goto-fail path cleanly. The garbage pointer is never created.
------------------------------------------------------------------------
IMPACT
------------------------------------------------------------------------
Invalid-pointer dereference (CWE-822 / CWE-476) in binutils/debug.c,
triggered by objdump -g on a crafted ELF file with a malformed .stab
section. Impact is denial of service (SIGSEGV crash). Both the
instrumented and plain builds crash. No special privileges are required.
--
You are receiving this mail because:
You are on the CC list for the bug.