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

            Bug ID: 34370
           Summary: objdump: null-pointer dereference in tg_start_block
                    (binutils/prdbg.c:2774)
           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 16825
  --> https://sourceware.org/bugzilla/attachment.cgi?id=16825&action=edit
Minimal 392-byte ELF with .stab N_FUN entry for a non-member function; triggers
SIGSEGV at prdbg.c:2774

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

tg_start_block() (binutils/prdbg.c) is the CTF/tag-file backend's
"start block" callback, invoked for each function body during stab
debug-info output. When a function with no method annotation is
processed, the function pops the type string off the type stack with
pop_type(), then immediately tries to access fields of the now-emptied
stack head:

    /* prdbg.c:2733 – tg_start_block */
    static bool
    tg_start_block (void *p, bfd_vma addr)
    {
      struct pr_handle *info = (struct pr_handle *) p;
      ...
      if (info->parameter > 0)
        {
          info->parameter = 0;
          ...
          /* line 2764: pop_type() sets info->stack = info->stack->next */
          t = pop_type (info);     /* pops the only element; info->stack = NULL
*/
          if (t == NULL)
            return false;
          fprintf (info->f, ";\"\tkind:%c\ttype:%s", kind, t);
          free (t);
          ...
          free (info->stack->method);   /* line 2774: info->stack is NULL →
SIGSEGV */
          info->stack->method = NULL;   /* line 2775 */
        }
      return true;
    }

pop_type() (prdbg.c:461):

    static char *
    pop_type (struct pr_handle *info)
    {
      struct pr_stack *o;
      char *ret;
      assert (info->stack != NULL);
      o = info->stack;
      info->stack = o->next;   /* last element: o->next == NULL → info->stack =
NULL */
      ret = o->type;
      free (o);
      return ret;
    }

When the function has exactly one entry on the type stack and the
else-branch (non-method, kind='f') is taken at line 2761, pop_type()
is called and removes that last element, setting info->stack to NULL.
Lines 2774–2775 then dereference info->stack unconditionally, crashing
with SIGSEGV (or UBSan "member access within null pointer of type
'struct pr_stack'").

The code assumes that a second stack element (the class/method context)
always remains after popping the type; this assumption is violated when
the stab data describes a top-level (non-member) function.

------------------------------------------------------------------------
CRASH OUTPUT
------------------------------------------------------------------------

gdb backtrace, plain (non-instrumented) build:

    Program received signal SIGSEGV, Segmentation fault.
    0x00005555555f4b9e in tg_start_block (p=0x7fffffffdba0, addr=16)
        at
/home/paul/Desktop/experiment/src/binutils-2.46.1/binutils/prdbg.c:2774
    2774          free (info->stack->method);
    #0  tg_start_block          prdbg.c:2774   <-- null deref
    #1  debug_write_block        debug.c:2869
    #2  debug_write_function     debug.c:2845
    #3  debug_write_name         debug.c:2390
    #4  debug_write              debug.c:2352
    #5  print_debugging_info     prdbg.c:296
    #6  dump_bfd                 objdump.c:5849
    #7  display_object_bfd       objdump.c:5900
    #8  display_any_bfd          objdump.c:5979
    #9  display_file             objdump.c:6000
    #10 main                     objdump.c:6427

UBSan / ASan output (instrumented build):

    /home/paul/Desktop/experiment/src/binutils-2.46.1/binutils/prdbg.c:2774:26:
    runtime error: member access within null pointer of type 'struct pr_stack'
    SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior prdbg.c:2774:26

------------------------------------------------------------------------
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 (requires a crafted ELF with a malformed .stab section that
describes a non-member function via the stab tag-output path):

    ./binutils/objdump -G -W -e -g poc_tg_start_block_null_deref.elf

Under gdb:

    gdb -q -batch -ex run -ex bt \
      --args ./binutils/objdump -G -W -e -g poc_tg_start_block_null_deref.elf

The -g flag instructs objdump to print stab debug information using the
pr_fns / tg_fns callback tables. A crafted .stab section containing an
'N_FUN' stab entry for a non-method function is sufficient to trigger
the bug; the crash occurs during the tag-file output pass
(print_debugging_info with as_tags=true).

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

Save the method string before calling pop_type(), or guard lines
2774–2775 with a NULL check:

    --- a/binutils/prdbg.c
    +++ b/binutils/prdbg.c
    @@ -2770,8 +2770,12 @@ tg_start_block (void *p, bfd_vma addr)
           if (partof)
             fprintf (info->f, "\tclass:%s", partof);
           fputc ('\n', info->f);
    -      free (info->stack->method);
    -      info->stack->method = NULL;
    +      if (info->stack != NULL)
    +        {
    +          free (info->stack->method);
    +          info->stack->method = NULL;
    +        }
         }

       return true;

Alternatively, the method/partof string should be saved to a local
variable before pop_type() is called, so the stack pointer is not
needed afterward.

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

Null-pointer dereference (CWE-476) in binutils/prdbg.c, triggered by
objdump -g on a crafted ELF file containing a malformed .stab section.
Impact is denial of service (SIGSEGV crash). The crash is reproducible
on both instrumented and plain builds and requires no special privileges
or elevated permissions.

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

Reply via email to