https://sourceware.org/bugzilla/show_bug.cgi?id=34373

            Bug ID: 34373
           Summary: objdump: infinite loop in debug_type_samep() due to
                    missing cycle detection for INDIRECT type chains
                    (binutils/debug.c:3016)
           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 16828
  --> https://sourceware.org/bugzilla/attachment.cgi?id=16828&action=edit
Minimal 504-byte ELF with a crafted .stab section that creates a
self-referential INDIRECT type cycle; triggers infinite loop in
debug_type_samep() at debug.c:3016

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_type_samep() (binutils/debug.c) compares two debug type objects
for structural equality. It begins by "unwinding" any INDIRECT types —
forwarding pointers used by the stab parser to handle forward type
references — via two while loops:

    /* debug.c:3005 – debug_type_samep (simplified) */
    static bool
    debug_type_samep (struct debug_handle *info,
                      struct debug_type_s *t1,
                      struct debug_type_s *t2)
    {
      ...
      while (t1->kind == DEBUG_KIND_INDIRECT)
        {
          t1 = *t1->u.kindirect->slot;   /* follow the indirection */
          if (t1 == NULL)
            return false;
          /* ← NO cycle detection: if slot points back to t1, loops forever */
        }
      while (t2->kind == DEBUG_KIND_INDIRECT)
        {
          t2 = *t2->u.kindirect->slot;
          if (t2 == NULL)
            return false;
        }
      ...
    }

A crafted stab .stab section can create a circular INDIRECT chain:

    INDIRECT_A → slot = &slot_cell
    slot_cell  = INDIRECT_A          ← slot_cell points back to INDIRECT_A

In this case `*t1->u.kindirect->slot` always returns `INDIRECT_A` again,
so `t1` never changes, the NULL check never fires, and the loop runs
without bound.

The contrast with the rest of debug.c is instructive: debug_write_type()
uses a `type->mark` field specifically to detect and skip already-visited
types:

    /* debug.c:2428 */
    type->mark = info->mark;   /* stamp; re-entering with same mark → bail */

debug_type_samep() has no equivalent visited-set or mark check; it
silently relies on the assumption that INDIRECT chains are acyclic.

Trigger: objdump -g on a crafted ELF whose .stab section encodes a type
number that resolves through an INDIRECT slot that points to itself.

------------------------------------------------------------------------
HANG OUTPUT
------------------------------------------------------------------------

There is no crash; the process loops forever.

Reproduction evidence:

    $ timeout 3 objdump -g poc_debug_type_samep_infinite_loop.elf 2>&1 | tail
-3
    Terminated
    $ echo $?
    124    (SIGTERM from timeout)

    $ objdump -g poc_debug_type_samep_infinite_loop.elf &
    PID=$!; sleep 1
    $ cat /proc/$PID/status | grep State
    State: R (running)    ← purely CPU-bound, never progresses
    $ kill $PID

    CPU ticks consumed per second: ~100 (one full core), confirmed
    by reading /proc/<PID>/stat with a 1-second sampling interval.

The hang is inside debug_type_samep(), which is called from
debug_set_class_id() when two struct types with the same tag name
are compared for structural equality during stab debug-info output.

------------------------------------------------------------------------
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

The crafted ELF (poc_debug_type_samep_infinite_loop.elf, 504 bytes)
requires a .stab section with five entries that create a self-
referential INDIRECT type cycle in three steps:

  Step 1 — plant the forward-reference slot:
    N_LSYM  int:f(0,1)=9;(0,1)
    The 'f' case calls parse_stab_type for the function's return type.
    Parsing "(0,1)=9" triggers a look-up of type number 9 via
    stab_find_type([0,9]): slot[9] is empty, so an INDIRECT object
    (INDIRECT_9) is created with slot = &slot[9].  The result is
    stored: slot[1] = INDIRECT_9.  Crucially, slot[9] itself remains
    NULL at this point; INDIRECT_9 does not yet point to itself.

  Step 2 — create two struct definitions with the same tag:
    N_LSYM  A:T(0,2)=s4x:(0,1),0,32;;   (first definition)
    N_LSYM  A:T(0,2)=s4x:(0,1),0,32;;   (second definition)
    Each struct field `x` looks up type (0,1) via stab_find_type,
    which now returns INDIRECT_9 (slot[1] is non-NULL).  Both field
    objects therefore store INDIRECT_9 as their type.

  Step 3 — close the cycle (nameless typedef):
    N_LSYM  :t(0,9)=1(0,1)
    The empty name prevents debug_name_type() from wrapping the type
    in a NAMED node and from overwriting the slot.  parse_stab_type
    resolves "1" as type-number [0,1], looks up slot[1] = INDIRECT_9,
    and calls stab_record_type([0,9], INDIRECT_9).  This stores
    INDIRECT_9 into slot[9].  Now:
        INDIRECT_9->slot  = &slot[9]
        *INDIRECT_9->slot = slot[9] = INDIRECT_9   ← cycle!

  Trigger:
    After all stabs are parsed, debug_write() outputs the debug info.
    When it processes the second "A" struct type, debug_set_class_id()
    finds the first "A" struct in id_list and calls:
        debug_type_samep(info, struct1, struct2)
    → debug_class_type_samep() compares field types:
        debug_get_real_type(INDIRECT_9)
            → *slot == INDIRECT_9 == type → cycle detected → returns INDIRECT_9
        debug_type_samep(info, INDIRECT_9, INDIRECT_9)
            while (t1->kind == INDIRECT):
                t1 = *t1->u.kindirect->slot = slot[9] = INDIRECT_9
                (never NULL, loop never exits)  ← INFINITE LOOP

Reproduce:

    ./binutils/objdump -g poc_debug_type_samep_infinite_loop.elf

The -g flag activates the stab debug-info printer, which internally
calls debug_type_samep() during the type-equality checks in
debug_set_class_id() when two struct types share a tag name.

------------------------------------------------------------------------
SUGGESTED FIX
------------------------------------------------------------------------

Limit the unwinding depth for each INDIRECT chain independently.
Each loop gets its own counter so calls do not share state:

    --- a/binutils/debug.c
    +++ b/binutils/debug.c
    @@ -3016,11 +3016,21 @@ debug_type_samep (struct debug_handle *info,
    -  while (t1->kind == DEBUG_KIND_INDIRECT)
    -    {
    -      t1 = *t1->u.kindirect->slot;
    -      if (t1 == NULL)
    -        return false;
    -    }
    -  while (t2->kind == DEBUG_KIND_INDIRECT)
    -    {
    -      t2 = *t2->u.kindirect->slot;
    -      if (t2 == NULL)
    -        return false;
    -    }
    +  {
    +    unsigned int depth = 0;
    +    while (t1->kind == DEBUG_KIND_INDIRECT)
    +      {
    +        if (++depth > 1000)
    +          return false;
    +        t1 = *t1->u.kindirect->slot;
    +        if (t1 == NULL)
    +          return false;
    +      }
    +  }
    +  {
    +    unsigned int depth = 0;
    +    while (t2->kind == DEBUG_KIND_INDIRECT)
    +      {
    +        if (++depth > 1000)
    +          return false;
    +        t2 = *t2->u.kindirect->slot;
    +        if (t2 == NULL)
    +          return false;
    +      }
    +  }

Note: the info->mark field (used by debug_write_type() for its own
cycle detection) must NOT be reused here. debug_write_type() stamps
every visited type — including INDIRECT nodes — with info->mark before
processing them (debug.c:2428). Since debug_set_class_id() is called
from within debug_write_type(), INDIRECT nodes in field-type chains may
already carry info->mark, causing a spurious "cycle detected" return
even for non-cyclic chains. The depth limit avoids this: each
debug_type_samep() call maintains its own independent counter, and
INDIRECT chains deeper than 1000 cannot occur in valid debug info.

------------------------------------------------------------------------
IMPACT
------------------------------------------------------------------------

Denial of service (CPU exhaustion / infinite loop) via a crafted ELF
file with a malformed .stab section. Any invocation of objdump -g on an
untrusted file is affected. The process consumes 100% of one CPU core
and never terminates without an external kill signal.

CWE-835 (Loop with Unreachable Exit Condition). No privileges required;
a single crafted file triggers the hang. Automated analysis pipelines
that call objdump -g on submitted binaries are especially exposed.

-- 
You are receiving this mail because:
You are on the CC list for the bug.

Reply via email to