From c418349b148c5b05ab5c20dea5a1471538cce47b Mon Sep 17 00:00:00 2001 From: damasiyachiranj Date: Sat, 14 Nov 2026 19:09:27 +0530 Subject: [PATCH 1/2] Fix: guard ArrayObject.read_from_stream against uncaught RecursionError A deeply nested array (e.g. "[[[[...]]]]") drives recursion depth in read_from_stream's recursive descent, not element count, so array_maximum_element_count never engages and an uncaught RecursionError escapes to the caller instead of a PdfReadError. Mirrors the existing guard in DictionaryObject.read_from_stream: catches (MemoryError, RecursionError) around the recursive read_object() call and re-raises as PdfReadError, while still letting LimitReachedError (the element-count/token budget) propagate unchanged. Fixes #4427. --- pypdf/generic/_data_structures.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pypdf/generic/_data_structures.py b/pypdf/generic/_data_structures.py index a3e66c01fe..98b9fc3540 100644 --- a/pypdf/generic/_data_structures.py +++ b/pypdf/generic/_data_structures.py @@ -300,7 +300,18 @@ def read_from_stream( stream.seek(-len(rest), 1) stream.seek(-1, 1) # read and append object - arr.append(read_object(stream, pdf, forced_encoding, parsing_budget=parsing_budget)) + try: + obj = read_object(stream, pdf, forced_encoding, parsing_budget=parsing_budget) + except LimitReachedError: + # Always propagate security issues. + raise + except (MemoryError, RecursionError) as exc: + # A deeply nested array (e.g. "[[[[...]]]]") blows the interpreter's + # recursion limit before the element-count budget above ever triggers, + # since nesting depth, not element count, drives the recursion here. + # Mirrors the guard DictionaryObject.read_from_stream already applies. + raise PdfReadError("Error reading PDF file.") from exc + arr.append(obj) return arr From 8cfafc7f738a515b7b3878a0f6e761d84f3b72ea Mon Sep 17 00:00:00 2001 From: damasiyachiranj Date: Sat, 14 Nov 2026 19:11:36 +0530 Subject: [PATCH 2/2] Test: regression test for deeply nested array RecursionError Adds test_array_object__read_from_stream__deeply_nested_array_raises_pdfreaderror, verifying a 5000-deep nested array now raises PdfReadError instead of an uncaught RecursionError. --- tests/generic/test_data_structures.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/generic/test_data_structures.py b/tests/generic/test_data_structures.py index f84fec3d68..d929364fda 100644 --- a/tests/generic/test_data_structures.py +++ b/tests/generic/test_data_structures.py @@ -807,6 +807,17 @@ def test_array_object__read_from_stream__maximum_element_count() -> None: ArrayObject.read_from_stream(BytesIO(data), pdf=pdf) # type: ignore[arg-type] +def test_array_object__read_from_stream__deeply_nested_array_raises_pdfreaderror() -> None: + # A single array nested thousands of levels deep drives recursion depth, + # not element count, so array_maximum_element_count never engages. + # Previously this raised an uncaught RecursionError instead of PdfReadError. + pdf = ReaderDummy() + depth = 5000 + data = b"[" * depth + b"]" * depth + with pytest.raises(PdfReadError, match=r"^Error reading PDF file\.$"): + ArrayObject.read_from_stream(BytesIO(data), pdf=pdf) # type: ignore[arg-type] + + def test_dictionary_object__read_from_stream__token_limit() -> None: pdf = ReaderDummy() data = b"<< /Key1 << /Key2 << /Key3 << /Key4 << /SomeName 42 >> >> >> >> /Key5 13 >>"