Repository navigation
Conversation
pdf_repr and the PdfArray/PdfDict __bytes__ methods recursed with no depth bound, while parsing has had a max_nesting guard since python-pillow#10125. A nested value the parser accepts (up to max_nesting = 900) could exhaust the stack when re-serialized during Image.save(append=True). Thread a depth counter through pdf_repr and raise PdfFormatError past MAX_NESTING = 100, mirroring the parse-side guard; the bound is kept conservative because serialization runs several frames per level at arbitrary call depth. PdfArray.__bytes__ and PdfDict.__bytes__ now route through pdf_repr so the bound applies to bytes() as well. Output for values within the bound is byte-identical.
read_indirect looked up the object id in the xref table without checking and leaked a bare KeyError for malformed documents, and rewrite_pages assumed every page carried a resolvable /Parent chain when appending to an existing document. Validate the reference through check_format_condition, and skip unresolvable parent references instead of crashing. Regression tests cover the dangling /Kids and dangling /Parent cases.
The serialization bound guards a different operation from the max_nesting defaults on get_value/read_indirect, so make the separation explicit and document both. Add release note entries for the serialization bound and the KeyError to PdfFormatError change.
|
The two limits guard different operations, so I've made the separation explicit instead of sharing one number. Reading keeps the (The three failing checks also fail on current main — the CodSpeed job (Python 3.15), run 40194988170, and the Docker jobs, run 40194988114 — so they don't look related to either PR.) |
Systematic hardening of
PdfParser, closing the serialization half of themax_nestingwork (#10023, #10093, #10125, #10127, #10185). Those changes bounded the document-graph traversals and the parse-side recursion (get_value/read_indirectmax_nesting), but the serialize path (pdf_repr,PdfArray.__bytes__,PdfDict.__bytes__) still recursed with no depth bound, and two reachable lookups inread_indirect/rewrite_pagescould still raise bareKeyErrors on malformed documents.Changes (2 commits)
pdf_repr— thread a depth counter throughpdf_reprand raisePdfFormatErrorpast a conservativeMAX_NESTING = 100, mirroring the parse-sidemax_nestingguard.PdfArray.__bytes__/PdfDict.__bytes__route throughpdf_reprso the bound applies tobytes()as well. The bound is kept conservative because serialization runs several Python frames per nesting level at arbitrary call depth (for example insideImage.save(..., append=True)), and it keeps the stack far below Python's recursion limit. Output for values within the bound is byte-identical — the existingtest_pdf_reprbyte-exact assertions are unchanged.PdfFormatErrorfor dangling object references —read_indirectnow validates the object id against the xref table viacheck_format_conditioninstead of leaking a bareKeyError, andrewrite_pagesskips unresolvable/Parentreferences when rewriting an existing document, matching thecheck_format_conditionidiom used throughout the parser.Tests
3 regression tests: the serialization depth guard (
pdf_reprandbytes(), at and just pastMAX_NESTING), a dangling/Kidsreference raisingPdfFormatErrorinstead ofKeyError, and a page with a dangling/Parentsurvivingim.save(..., append=True)without an exception. The full test suite passes.