Sep 2, 2026·6 min read·3 visits
Infinite loop (CWE-835) in pypdf < 6.16.0 allows unauthenticated attackers to cause complete CPU exhaustion (DoS) via crafted PDF structures containing cyclic tree relationships.
An infinite loop vulnerability in pypdf versions prior to 6.16.0 allows attackers to trigger computational resource exhaustion and complete thread locking by supplying a malformed PDF with a cyclic tree structure. When modifying or rewriting document outlines containing circular references, the library endlessly traverses /Next pointers, resulting in application denial of service.
The vulnerability resides in pypdf (formerly PyPDF2), a popular and widely integrated pure-Python library designed for PDF file parsing, generation, and structure modification. The specific component affected is the TreeObject class, implemented within pypdf/generic/_data_structures.py. This class processes hierarchical elements such as the document's outline bookmark systems and internal page tree directories.
Many web-based software ecosystems and microservices utilize pypdf to accept document uploads from untrusted clients, perform automated updates, merge PDF pages, or modify structural attributes before saving or rendering the output. These processing operations expose a technical attack surface by executing code paths that traverse, validate, or update hierarchical tree elements in the target PDF document.
By supplying a crafted PDF stream containing a recursive, loop-configured tree structure, an attacker can manipulate execution flow. When a vulnerable writing mechanism (such as TreeObject.insert_child) interacts with the tree, it enters an infinite loop. This vulnerability is classified under CWE-835: Loop with Unreachable Exit Condition, and results in application-level denial of service.
In standard PDF files, hierarchical node systems like document outlines are stored as structured dictionary objects linked via structural parameters. The standard dictates the use of designated keys, specifically /First, /Last, /Next, /Prev, and /Parent, to map sibling and parental relationships. Software libraries rely on these parameters to walk linear paths through the document tree structures.
When pypdf performs actions like adding structural markers or reassembling metadata layers, it invokes the TreeObject.insert_child method. The function is designed to iterate sequentially starting from the terminal node /Last (assigned to variable prev) and traverse forward by repeatedly evaluating the /Next key relationships of successive elements.
In vulnerable versions of the library, the traversal loop compares the target traversal node's reference to a tracking marker variable called before. The loop terminates only when this exact target is matched, or when a node is parsed that completely lacks a /Next pointer. An attacker can break this termination assumption by defining circular sibling nodes (e.g., Sibling A /Next -> Sibling B /Next -> Sibling A). Under this condition, the traversal path forms a closed ring, bypassing the exit criteria and resulting in a thread-blocking loop.
An analysis of the patch shows the modification introduced in the file pypdf/generic/_data_structures.py to fix this loop. Below is a comparative illustration of the code before and after the application of the official security patch.
# Vulnerable implementation in pypdf < 6.16.0
prev = cast("DictionaryObject", self["/Last"])
while prev.indirect_reference != before:
if "/Next" in prev:
prev = cast("TreeObject", prev["/Next"])
else: # append at the end
prev[NameObject("/Next")] = cast("TreeObject", child_reference)
# ... [omitted logic to update child relationships] ...
return child_referenceIn the vulnerable implementation, prev is assigned dynamically, but the iteration does not record which memory structures have already been parsed. The loop runs blindly across identical references, consuming processing memory and thread execution indefinitely.
# Patched implementation in pypdf >= 6.16.0 (Commit c9ba557d565d57c53a0b3a0be06c0a4c29b0559b)
prev = cast("DictionaryObject", self["/Last"])
visited: set[int] = set()
while prev.indirect_reference != before:
prev_id = id(prev)
if prev_id in visited:
raise LimitReachedError("Detected cycle in tree structure.")
visited.add(prev_id)
if "/Next" in prev:
prev = cast("TreeObject", prev["/Next"])
continue
# append at the end
prev[NameObject("/Next")] = cast("TreeObject", child_reference)
# ... [omitted logic to update child relationships] ...
return child_referenceThe patched version instantiates an internal state set tracking object memory IDs using id(prev). Because memory addresses of instantiated Python classes remain static during runtime processing, id(prev) functions as an effective tracking key. The loop checks each node against this set; if an ID is already present, it indicates a structural cycle and raises a LimitReachedError to abort execution.
Exploitation requires the attacker to construct a cyclic tree graph within the outline bookmarks or name-tree dictionaries of a PDF structure. This can be accomplished programmatically by modifying standard PDF references. The exploit payload relies on standard binary parsing; there are no memory corruption components, shellcode payloads, or platform-specific compilation constraints needed.
Once the malformed PDF is constructed, the target server or application must process the file through a routine that invokes structural updates on the outlines. Common triggers include page insertion functions (PdfWriter.append or merge), metadata extraction utilities, and bookmark reorganization tasks.
When the processing execution reaches the insert_child block, the thread enters the unconstrained loop. This results in the utilization of 100% of the active CPU thread. In microservices processing PDFs synchronously, this completely freezes the application worker, allowing an attacker to deny service to all other clients by uploading multiple instances of the document.
The impact of CVE-2026-84309 is classified as High for system availability. In common production configurations, Python web workers (such as Gunicorn, Celery, or uWSGI) are configured with a limited pool of synchronous execution threads. A single malformed PDF file can permanently freeze a single worker thread; sending a small quantity of concurrent exploit files can deplete the entire worker pool, resulting in a total denial of service.
The vulnerability has been evaluated with CVSS v4.0 metrics and assigned a base score of 6.9, with a Local Attack Vector (AV:L). Although classified as local, many cloud-native and serverless architectures process PDFs automatically via API gateways or upload portals, effectively extending this vulnerability to remote, unauthenticated actors.
Currently, there is no threat intelligence indicating active exploitation of this vulnerability by ransomware campaigns or advanced persistent threat groups, and the vulnerability is not listed in the CISA KEV catalog. The risk profile is primarily operational, impacting backend processing integrity and service availability.
The recommended remediation is upgrading the pypdf dependency to version 6.16.0 or later. The update introduces the cycle-detection mechanism within the core library code. If an immediate upgrade is impossible, systems administrators can implement execution timeouts to terminate runaway processing threads.
Administrators can implement process timeouts within server configurations. For example, setting strict runtime limits inside Celery tasks or setting lower Gunicorn worker lifetimes ensures that blocked threads are automatically terminated and restarted. Additionally, resource monitoring rules should flag any worker process exhibiting persistent 100% CPU utilization.
To identify vulnerable Python environments, systems security teams can run static detection scans. The following YARA signature can be utilized to scan for vulnerable packages within Python site-packages directories:
rule Detect_Vulnerable_PyPDF_Loop {
meta:
description = "Detects vulnerable pypdf installations lacking cyclic tree checks"
cve = "CVE-2026-84309"
strings:
$pkg = "pypdf"
$func = "def insert_child("
$prev = "prev = cast(\"DictionaryObject\", self[\"/Last\"])"
$patch = "visited: set[int] = set()"
condition:
$pkg and $func and $prev and not $patch
} CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
pypdf py-pdf | < 6.16.0 | 6.16.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-835 |
| Attack Vector | Local (AV:L) / Indirectly Network |
| CVSS v4.0 Score | 6.9 |
| Exploit Status | poc |
| CISA KEV Status | Not Listed |
| Impact | Denial of Service (CPU Exhaustion) |
The program contains a loop that can only exit when a specific condition is met, but a path exists where that condition is unreachable, causing the loop to run indefinitely.
An algorithmic complexity vulnerability in the python sqlparse library versions before 0.6.0 allows an attacker to cause high CPU usage and denial of service via a crafted SQL statement during formatting.
CVE-2026-84311 (GHSA-763m-79hh-57f2) is an algorithmic complexity Denial of Service (DoS) vulnerability in the pypdf library. Prior to version 6.16.1, the library does not place limits on iterations during the parsing of PDF document outlines and recursive Form XObject (XForm) expansions. An attacker can craft a malicious, highly compressed PDF document containing nested structures which, when parsed, trigger exponential iteration paths, resulting in severe CPU and memory exhaustion.
An algorithmic complexity vulnerability in the pypdf library before version 6.16.1 allows remote or local attackers to cause an application denial of service. The flaw is triggered via maliciously crafted PDF documents that utilize either deeply nested outlines or exponential Directed Acyclic Graph (DAG) structures in Form XObjects.
An authentication bypass vulnerability exists in Filament's app-based (TOTP/authenticator) multi-factor authentication (MFA) system when recovery codes are enabled. This allow attackers possessing primary credentials to bypass the second-factor authentication check entirely by manipulating the Livewire state during the challenge-form validation lifecycle.
An authentication oracle vulnerability exists in Filament before 4.12.5 and 5.7.5. The application initiates MFA challenge workflows prior to verifying user authorization policies, allowing unauthenticated attackers to validate guessed credentials.
A multi-factor authentication bypass vulnerability exists in Filament (Laravel full-stack framework panels) due to improper time-step tracking of Time-Based One-Time Password (TOTP) codes. By submitting valid TOTP codes from an older time window within the drift allowance, an attacker with a user's password can bypass the single-use MFA guarantee and obtain unauthorized account access.