Skip to content

Security (3/5): generic.py parse limits & cycle guard — CVE-2026-27024, CVE-2026-31826, CVE-2026-33123#3

Merged
icanhasmath merged 3 commits into
1.28.6.xfrom
1.28.6-sec-generic
Jun 23, 2026
Merged

Security (3/5): generic.py parse limits & cycle guard — CVE-2026-27024, CVE-2026-31826, CVE-2026-33123#3
icanhasmath merged 3 commits into
1.28.6.xfrom
1.28.6-sec-generic

Conversation

@icanhasmath

Copy link
Copy Markdown
Collaborator

Part 3 of 5 of the PyPDF2 1.28.6 security backport, targeting 1.28.6.x. Scoped to PyPDF2/generic.py.

CVE Sev Fix
CVE-2026-27024 Mod TreeObject.children cyclic /Next guard
CVE-2026-31826 Mod Cap declared stream /Length (75 MB)
CVE-2026-33123 Mod Bound array-based ContentStream

Backported from upstream pypdf 6.7.1 / 6.8.0 / 6.9.1; Py2.7-safe. New tests: Tests/test_security_generic.py (7). Validated under Python 2.7.18 — no new failures vs baseline.

🤖 Generated with Claude Code

icanhasmath and others added 3 commits June 18, 2026 11:25
A crafted document outline whose /Next chain forms a cycle that never
reaches /Last made TreeObject.children() iterate forever (CWE-835).

Snapshot /Last once and track visited node ids; stop (with a warning) if
a node repeats. Mirrors upstream pypdf 6.7.1 (PR py-pdf#3645). 1.28.6 keeps the
PEP 479 StopIteration branch for old Pythons, so the guard returns/raises
the same way the existing terminations do.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A crafted stream dictionary with an enormous /Length (e.g. 2 GB) made
DictionaryObject.read_from_stream call stream.read(length), pre-allocating
a huge buffer and exhausting memory (CWE-770).

Reject a declared /Length above MAX_DECLARED_STREAM_LENGTH (75 MB) with
PdfReadError before the read. Mirrors upstream pypdf 6.8.0 (PR py-pdf#3675),
reusing PdfReadError instead of LimitReachedError.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A content stream supplied as an array of many stream objects (or whose
elements concatenate to an enormous size) made ContentStream.__init__
build the combined buffer with an unbounded `data += ...` loop, causing
CPU/memory exhaustion (CWE-400/CWE-407).

Cap the number of array elements (CONTENT_STREAM_ARRAY_MAX_LENGTH = 10000)
and the total concatenated size (MAX_ARRAY_BASED_STREAM_OUTPUT_LENGTH =
75 MB), raising PdfReadError when exceeded, and join via a list to avoid
the quadratic concatenation. Mirrors upstream pypdf 6.9.1 (PR py-pdf#3686).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@martinPavesio martinPavesio left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks Good!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Backports upstream pypdf security hardenings into PyPDF2/generic.py to mitigate parsing-related DoS vectors (cycle in outline traversal, oversized declared stream lengths, and array-based ContentStream exhaustion), plus adds targeted regression tests.

Changes:

  • Add global parse-limit constants and enforce a maximum declared stream /Length during dictionary/stream parsing.
  • Add cycle detection to TreeObject.children() to prevent infinite /Next chains.
  • Bound array-based ContentStream concatenation by element count and total output size; add regression tests covering all three CVEs.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
PyPDF2/generic.py Adds caps/guards for declared stream length, outline traversal cycles, and array-based content-stream concatenation.
Tests/test_security_generic.py Adds regression tests exercising the new limits and cycle guard behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread PyPDF2/generic.py
Comment thread Tests/test_security_generic.py
@icanhasmath icanhasmath merged commit 3ce06e2 into 1.28.6.x Jun 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants