DEV Community

CVE Reports
CVE Reports

Posted on Originally published at cvereports.com

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

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

Vulnerability ID: CVE-2026-119209
CVSS Score: 5.5
Published: 2026-10-30

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.

TL;DR

Versions of pypdf prior to 6.21.3 fail to restrict the cumulative number of cross-reference stream entries across chained /Prev streams and allow negative integer counts in /Index arrays to bypass per-stream validation clamps. Processing a crafted PDF causes uncontrolled memory allocation and application crash.


⚠️ Exploit Status: POC

Technical Details

  • Vulnerability Type: Uncontrolled Resource Consumption (CWE-400 / CWE-770)
  • Affected Component: pypdf._reader.PdfReader (_read_pdf15_xref_stream)
  • CVSS Score: 5.5 Medium (NVD v3.1) / 7.1 High (CNA v4.0)
  • Attack Vector: Crafted PDF with chained /Prev XRef streams or negative /Index counts
  • Security Impact: Denial of Service (DoS) via Memory Exhaustion (OOM)
  • EPSS Score: 0.32% (24.86th percentile)
  • Exploit Status: Proof-of-Concept (Unit test generator in PR #4269)
  • Patched Version: pypdf 6.21.3

Affected Systems

  • pypdf (< 6.21.3)
  • pypdf: < 6.21.3 (Fixed in: 6.21.3)

Code Analysis

Commit: a93fe3a

Enforce global xref_stream_maximum_entries limit across XRef streams and reject/clamp negative /Index counts in _reader.py

--- a/pypdf/_reader.py
+++ b/pypdf/_reader.py
@@ -116,6 +116,8 @@ class PdfReader(PdfDocCommon):
         root_object_recovery_limit: The maximum number of objects to query
             for recovering the Root object in non-strict mode. To disable
             this security measure, pass ``None``.
+        xref_stream_maximum_entries: The maximum number of entries which are
+            allowed inside a stream-based cross-reference stream.

     """

@@ -129,6 +131,7 @@ def __init__(
         password: Union[str, bytes, None] = None,
         *,
         root_object_recovery_limit: Optional[int] = 10_000,
+        xref_stream_maximum_entries: int = 2_000_000,
     ) -> None:
         self.strict = strict
         self.flattened_pages: Optional[list[PageObject]] = None
@@ -147,6 +150,8 @@ def __init__(
         self._root_object_recovery_limit = (
             root_object_recovery_limit if isinstance(root_object_recovery_limit, int) else sys.maxsize
         )
+        self._xref_stream_maximum_entries = xref_stream_maximum_entries
+        self._xref_stream_entry_count = 0
         apply_legacy_configuration()

         # Map page indirect_reference number to page number
@@ -1194,6 +1199,7 @@ def _read_xref_tables_and_trailers(
         self.xref = {}
         self.xref_free_entry = {}
         self.xref_objStm = {}
+        self._xref_stream_entry_count = 0
         self.trailer = DictionaryObject()
         visited_xref_offsets: set[int] = set()
         while startxref is not None:
@@ -1351,6 +1357,16 @@ def _sanitize_pdf15_xref_stream_index_pairs(
             # `index_pairs` has the format `[start0, count0, start1, count1, ...]`
             # Only modify the counts here, but keep the start values.
             if index % 2 == 1:
+                if pair_value_int < 0:
+                    if self.strict:
+                        raise PdfStreamError(f"Negative XRef count {pair_value_int}.")
+                    logger_warning(
+                        "Negative XRef count %(count)d, using 0.",
+                        source=__name__,
+                        count=pair_value_int,
+                    )
+                    pair_value_int = 0
+
                 if total + pair_value_int > max_entries:
                     if self.strict:
                         raise LimitReachedError(
@@ -1395,6 +1411,13 @@ def _read_pdf15_xref_stream(self, stream: StreamType) -> StreamObject:
             index_pairs=index_pairs, entry_sizes=entry_sizes, xref_stream=xref_stream
         )

+        self._xref_stream_entry_count += sum(index_pairs[1::2])
+        if self._xref_stream_entry_count > self._xref_stream_maximum_entries:
+            raise LimitReachedError(
+                f"Maximum XRef stream entry limit reached: "
+                f"{self._xref_stream_entry_count} > {self._xref_stream_maximum_entries}."
+            )
+
         stream_data = BytesIO(xref_stream.get_data())

         def get_entry(i: int) -> Union[int, tuple[int, ...]]:
Enter fullscreen mode Exit fullscreen mode

Exploit Details

Mitigation Strategies

  • Upgrade pypdf to version 6.21.3 or later to enforce document-wide XRef stream entry limits and negative count validation.
  • Configure custom lower thresholds using the xref_stream_maximum_entries parameter in PdfReader for memory-constrained environments.
  • Enable strict parsing mode (strict=True) in PdfReader to immediately raise exceptions on malformed PDF structures.
  • Isolate PDF parsing workloads within memory-constrained worker processes or containers using cgroups or resource.setrlimit.

Remediation Steps:

  1. Audit project dependencies (requirements.txt, pyproject.toml, Pipfile.lock, or poetry.lock) for pypdf versions prior to 6.21.3.
  2. Run pip install --upgrade "pypdf>=6.21.3" across all affected build pipelines and runtime environments.
  3. Review all call sites of pypdf.PdfReader() that process untrusted inputs and pass an appropriate xref_stream_maximum_entries value (e.g., xref_stream_maximum_entries=100_000).
  4. Ensure exception handlers around PdfReader catch pypdf.errors.LimitReachedError and pypdf.errors.PdfStreamError to prevent unhandled 500 Internal Server Errors.

References


Read the full report for CVE-2026-119209 on our website for more details including interactive diagrams and full exploit analysis.

Top comments (0)