https://sourceware.org/bugzilla/show_bug.cgi?id=34372
Bug ID: 34372
Summary: objdump: CPU-exhaustion hang in dump_section() when
--section bypasses SEC_HAS_CONTENTS guard
(binutils/objdump.c:5089)
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 16827
--> https://sourceware.org/bugzilla/attachment.cgi?id=16827&action=edit
Minimal 1024-byte PE32 with .text VirtualSize=0x52000000 and no
SEC_HAS_CONTENTS; triggers CPU-exhaustion hang in dump_section
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
------------------------------------------------------------------------
dump_section() (binutils/objdump.c) dumps the raw hex content of an ELF
or PE section. It contains an early-exit guard that skips sections which
have no real on-disk content:
/* objdump.c:5086 – dump_section (simplified) */
static void
dump_section (bfd *abfd, asection *section, void *dummy)
{
...
if (only_list == NULL)
{
/* No --section filter: skip sections without content. */
if ((section->flags & SEC_HAS_CONTENTS) == 0)
return;
}
else if (!process_section_p (section))
return; /* --section filter: process_section_p decides; no
SEC_HAS_CONTENTS check here! */
...
}
When the user does NOT pass --section (only_list == NULL), the
SEC_HAS_CONTENTS flag is checked and sections with only a virtual size
(e.g. BSS or PE SectionVirtualSize without content) are skipped.
When --section=NAME IS passed (only_list != NULL), the code takes the
else branch. process_section_p() checks only whether the section name
matches — it does NOT check SEC_HAS_CONTENTS. A PE32 section that has
a large VirtualSize but carries no raw data (characteristics without the
IMAGE_SCN_CNT_INITIALIZED_DATA or IMAGE_SCN_CNT_CODE bits mapped by BFD
to SEC_HAS_CONTENTS) passes process_section_p() and execution falls
through to:
bfd_get_full_section_contents (abfd, section, &data)
BFD honours the VirtualSize for content allocation even when the section
has no file data. With a crafted PE32 whose .text VirtualSize is
0x52000000 (1.28 GB), bfd_malloc allocates 1.28 GB, then the hex-dump
loop at the bottom of dump_section() iterates over ~85 million 16-byte
lines, consuming enormous CPU and memory before outputting the entire
1.28 GB of zeroes.
A companion bug: bfd_section_size_insane() (bfd.h) is also supposed to
detect unreasonably large sections, but its check requires SEC_HAS_CONTENTS
to be set, so it also returns false (safe) for this section — adding a
second layer of bypass.
Trigger combination:
--section=.text (only_list != NULL, causes bypass)
--full-contents (activates dump_section via dump_bfd)
PE32 input where .text VirtualSize >> FileSize
------------------------------------------------------------------------
CRASH OUTPUT
------------------------------------------------------------------------
There is no crash; the process hangs printing hex output until killed.
Reproduction evidence:
$ timeout 3 objdump --disassemble=main -D --endian=big -F -M intel \
-d -j .text -m i386:x86-64 -s poc_dump_section_hang.exe 2>&1 | tail -3
Terminated
$ echo $?
143 (SIGTERM from timeout)
$ ulimit -t 3; objdump ... poc_dump_section_hang.exe >/dev/null
Killed
$ echo $?
137 (SIGKILL from CPU time limit — confirms CPU-bound loop)
In /proc/<PID>/status while running:
State: R (running) ← confirms purely CPU-bound
------------------------------------------------------------------------
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 PE32 input must:
1. Be a valid minimal PE32 file (MZ + PE header + optional header +
section table).
2. Contain a section named ".text" where:
VirtualSize (SizeOfRawData in the optional header) = 0x52000000
SizeOfRawData (actual on-disk bytes) = 0x00 or very small
Characteristics = 0x00020000
(IMAGE_SCN_MEM_EXECUTE without CNT_CODE/CNT_INIT_DATA, so BFD
does not set SEC_HAS_CONTENTS)
Reproduce:
./binutils/objdump \
--disassemble=main --insn-width=8 -D --endian=big -F \
-M intel -d -j .text -m i386:x86-64 -s poc_dump_section_hang.exe
The key flags are:
-j .text (equivalent to --section=.text — enables only_list path)
-s (equivalent to --full-contents — calls dump_section)
Without -j (i.e., without specifying --section), the SEC_HAS_CONTENTS
guard fires and dump_section returns immediately (no hang).
------------------------------------------------------------------------
SUGGESTED FIX
------------------------------------------------------------------------
The SEC_HAS_CONTENTS check must also apply when the --section filter is
active. Change the guard in dump_section() from an if/else into a
combined check:
--- a/binutils/objdump.c
+++ b/binutils/objdump.c
@@ -5084,10 +5084,9 @@ dump_section (bfd *abfd, asection *section, void
*dummy)
{
...
- if (only_list == NULL)
- {
- if ((section->flags & SEC_HAS_CONTENTS) == 0)
- return;
- }
- else if (!process_section_p (section))
+ if ((section->flags & SEC_HAS_CONTENTS) == 0)
return;
+ if (only_list != NULL && !process_section_p (section))
+ return;
...
}
This ensures that sections without on-disk content are always skipped,
regardless of whether a --section filter is specified.
Additionally, the bfd_section_size_insane() helper should be fixed to
not unconditionally return false when SEC_HAS_CONTENTS is absent, or a
redundant size cap should be added in dump_section() before calling
bfd_get_full_section_contents().
------------------------------------------------------------------------
IMPACT
------------------------------------------------------------------------
Denial of service (CPU and memory exhaustion) via crafted PE32 or
other BFD-handled binary. Any local or remote use of objdump --section
--full-contents on untrusted files (e.g. in automated analysis pipelines,
CI/CD build systems, or package-manager scripts) is affected. With
VirtualSize ≤ available RAM, the process also allocates 1+ GB of memory.
CWE-835 (Loop with Unreachable Exit Condition) / CWE-400 (Uncontrolled
Resource Consumption). No privileges required; a single crafted file
triggers the hang.
--
You are receiving this mail because:
You are on the CC list for the bug.