VAITP Dataset

← Back to the dataset

CVE-2026-59935

pypdf: An unterminated inline image in a PDF causes an infinite loop.

  • CVSS 8.7
  • CWE-835
  • Resource Management
  • Remote

pypdf is a free and open-source pure-python PDF library. Prior to 6.14.2, an attacker can craft a PDF with a page content stream containing a not terminated inline image that uses the ASCII85 or ASCIIHex filters, causing an infinite loop during parsing such as when extracting page text. This issue is fixed in version 6.14.2.

CVSS base score
8.7
Published
2026-07-08
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Resource Management
Subcategory
Insecure Parsing or Deserialization
Accessibility scope
Remote
Impact
Denial of Service (DoS)
Affected component
pypdf
Fixed by upgrading
Yes

Solution

“` pip install –upgrade pypdf>=6.14.2 “`

Vulnerable code sample

import io

def vulnerable_pypdf_parser(stream):
    """
    This function is a conceptual representation of the vulnerable parsing
    logic. It is NOT the actual source code from the pypdf library.

    It simulates finding an inline image's data stream ('ID' keyword) and
    then entering a loop to find the ASCII85 terminator ('~>'). If the
    terminator is missing, the loop becomes infinite.
    """
    # Simulate reading lines until the start of inline image data ('ID')
    line = stream.readline()
    while line and b'ID' not in line:
        line = stream.readline()

    # If 'ID' was found, start the vulnerable parsing logic
    if line:
        # A real parser would read from the stream in chunks. To simulate the
        # infinite loop on a finite stream, we read all remaining data.
        # A more complex real-world parser might hang differently, but the
        # logical flaw is the same: assuming a terminator will always exist.
        remaining_content = stream.read()
        
        # VULNERABILITY: This loop will never terminate if '~>' is not present
        # in the remaining_content, causing the program to hang and consume
        # 100% of a CPU core.
        while b'~>' not in remaining_content:
            # The parser is stuck, expecting a terminator that never comes.
            pass

    # This part of the code is never reached with the malicious input.
    return "Parsing complete."


# Craft a fake PDF content stream that is missing the '~>' terminator.
# BI = Begin Image, /A85 = ASCII85Decode filter, ID = Image Data
malicious_content = b"""
/P << >> BDC q 1 0 0 1 0 0 cm
BI
 /W 10
 /H 10
 /CS /G
 /F /A85
ID
87cURD_*#TDfTZ)i53i< % Some valid ASCII85 data...
% ...but the required '~>' terminator is deliberately omitted.
EI
Q EMC
"""

# Use BytesIO to simulate reading from a file
malicious_stream = io.BytesIO(malicious_content)

# Calling the function with the crafted stream will trigger the infinite loop.
# The script will hang here and will not exit.
vulnerable_pypdf_parser(malicious_stream)

Patched code sample

import io
from typing import IO

class PdfStreamError(Exception):
    """A custom exception for PDF stream parsing errors."""
    pass

def read_inline_image_data(stream: IO[bytes]) -> bytes:
    """
    Reads data for an inline image from a stream until the 'EI' marker.

    This function represents the logic used to fix an infinite loop
    vulnerability. The vulnerability occurs when a PDF stream for an inline
    image is not properly terminated with an 'EI' (End Image) marker.
    """
    data = b""
    while b"EI" not in data:
        # Read one byte at a time.
        char = stream.read(1)

        # THE FIX: Check if the end of the stream was reached.
        # If 'char' is an empty byte string, it means there's no more data to
        # read. The original vulnerable code lacked this check, causing an
        # infinite loop on a malformed stream that was missing the 'EI' marker.
        # By raising an error, we prevent the infinite loop.
        if not char:
            raise PdfStreamError(
                "Stream ended before 'EI' marker was found for an inline image."
            )

        data += char
    
    # Once the marker is found, return the data preceding it.
    image_data, _ = data.split(b"EI", 1)
    return image_data

# Example of how the fixed function prevents the infinite loop.

# 1. A malformed stream without the required 'EI' end marker.
# In a vulnerable version, this would cause an infinite loop.
malformed_stream = io.BytesIO(b"some unterminated image data")

# 2. A well-formed stream for comparison.
well_formed_stream = io.BytesIO(b"some image data EI and other content")


# Demonstrate the fix
try:
    print("Attempting to read from malformed stream...")
    # This call will not loop forever; it will raise an exception.
    read_inline_image_data(malformed_stream)
except PdfStreamError as e:
    # The exception is caught, preventing the application from hanging.
    print(f"Success: Caught expected error: {e}")

# Demonstrate normal operation
try:
    print("\nAttempting to read from well-formed stream...")
    content = read_inline_image_data(well_formed_stream)
    print(f"Success: Read content: {content}")
except PdfStreamError as e:
    print(f"Caught unexpected error: {e}")

Payload

%PDF-1.7
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 600 800] /Contents 4 0 R >>
endobj
4 0 obj
<< /Length 20 >>
stream
BI /F /A85 /W 1 ID s
endstream
endobj
xref
0 5
0000000000 65535 f
0000000009 00000 n
0000000059 00000 n
0000000125 00000 n
0000000215 00000 n
trailer
<< /Size 5 /Root 1 0 R >>
startxref
267
%%EOF

Cite this entry

@misc{vaitp:cve202659935,
  title        = {{pypdf: An unterminated inline image in a PDF causes an infinite loop.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-59935},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-59935/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::