pypdf possible Infinite Loop when PdfWriter(clone_from) is used with a PDF
Description
pypdf is a free and open-source pure-python PDF library. An attacker who uses a vulnerability present in versions 3.7.0 through 3.16.4 can craft a PDF which leads to an infinite loop. This infinite loop blocks the current process and can utilize a single core of the CPU by 100%. It does not affect memory usage. That is, for example, the case when the pypdf-user manipulates an incoming malicious PDF e.g. by merging it with another PDF or by adding annotations. The issue was fixed in version 3.17.0. As a workaround, apply the patch manually by modifying pypdf/generic/_data_structures.py.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
pypdfPyPI | >= 3.7.0, < 3.17.0 | 3.17.0 |
Affected products
1Patches
19b23ac3c9619SEC: Infinite recursion when using PdfWriter(clone_from=reader) (#2264)
1 file changed · +19 −5
pypdf/generic/_data_structures.py+19 −5 modified@@ -40,6 +40,7 @@ List, Optional, Sequence, + Set, Tuple, Union, cast, @@ -187,14 +188,15 @@ def clone( except Exception: pass + visited: Set[Tuple[int, int]] = set() d__ = cast( "DictionaryObject", self._reference_clone(self.__class__(), pdf_dest, force_duplicate), ) if ignore_fields is None: ignore_fields = [] if len(d__.keys()) == 0: - d__._clone(self, pdf_dest, force_duplicate, ignore_fields) + d__._clone(self, pdf_dest, force_duplicate, ignore_fields, visited) return d__ def _clone( @@ -203,6 +205,7 @@ def _clone( pdf_dest: PdfWriterProtocol, force_duplicate: bool, ignore_fields: Optional[Sequence[Union[str, int]]], + visited: Set[Tuple[int, int]], ) -> None: """ Update the object from src. @@ -270,6 +273,14 @@ def _clone( cur_obj.__class__(), pdf_dest, force_duplicate ), ) + # check to see if we've previously processed our item + if clon.indirect_reference is not None: + idnum = clon.indirect_reference.idnum + generation = clon.indirect_reference.generation + if (idnum, generation) in visited: + cur_obj = None + break + visited.add((idnum, generation)) objs.append((cur_obj, clon)) assert prev_obj is not None prev_obj[NameObject(k)] = clon.indirect_reference @@ -282,7 +293,7 @@ def _clone( except Exception: cur_obj = None for s, c in objs: - c._clone(s, pdf_dest, force_duplicate, ignore_fields) + c._clone(s, pdf_dest, force_duplicate, ignore_fields, visited) for k, v in src.items(): if k not in ignore_fields: @@ -798,6 +809,7 @@ def _clone( pdf_dest: PdfWriterProtocol, force_duplicate: bool, ignore_fields: Optional[Sequence[Union[str, int]]], + visited: Set[Tuple[int, int]], ) -> None: """ Update the object from src. @@ -820,7 +832,7 @@ def _clone( ) except Exception: pass - super()._clone(src, pdf_dest, force_duplicate, ignore_fields) + super()._clone(src, pdf_dest, force_duplicate, ignore_fields, visited) def get_data(self) -> Union[bytes, str]: return self._data @@ -1048,6 +1060,7 @@ def clone( except Exception: pass + visited: Set[Tuple[int, int]] = set() d__ = cast( "ContentStream", self._reference_clone( @@ -1056,7 +1069,7 @@ def clone( ) if ignore_fields is None: ignore_fields = [] - d__._clone(self, pdf_dest, force_duplicate, ignore_fields) + d__._clone(self, pdf_dest, force_duplicate, ignore_fields, visited) return d__ def _clone( @@ -1065,6 +1078,7 @@ def _clone( pdf_dest: PdfWriterProtocol, force_duplicate: bool, ignore_fields: Optional[Sequence[Union[str, int]]], + visited: Set[Tuple[int, int]], ) -> None: """ Update the object from src. @@ -1081,7 +1095,7 @@ def _clone( self._operations = list(src_cs._operations) self.forced_encoding = src_cs.forced_encoding # no need to call DictionaryObjection or anything - # like super(DictionaryObject,self)._clone(src, pdf_dest, force_duplicate, ignore_fields) + # like super(DictionaryObject,self)._clone(src, pdf_dest, force_duplicate, ignore_fields, visited) def _parse_content_stream(self, stream: StreamType) -> None: # 7.8.2 Content Streams
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-wjcc-cq79-p63fghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-46250ghsaADVISORY
- github.com/py-pdf/pypdf/commit/9b23ac3c9619492570011d551d521690de9a3e2dghsax_refsource_MISCWEB
- github.com/py-pdf/pypdf/pull/2264ghsax_refsource_MISCWEB
- github.com/py-pdf/pypdf/security/advisories/GHSA-wjcc-cq79-p63fghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.