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.

Reply via email to