From c98a51d2f9554964670a0ad7dac0c6188f18b146 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 6 Mar 2026 10:22:26 +1100 Subject: [PATCH] Raise an error if the trailer chain loops back on itself --- Tests/images/trailer_loop.pdf | Bin 0 -> 1818 bytes Tests/test_pdfparser.py | 5 +++++ src/PIL/PdfParser.py | 10 ++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 Tests/images/trailer_loop.pdf diff --git a/Tests/images/trailer_loop.pdf b/Tests/images/trailer_loop.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7bf27ca370ec635039e274ddb65685fa147a2abc GIT binary patch literal 1818 zcmd5*OK;Oa5O!&W%HFuZg~MJVl|#E;zmlLThd2bHQZ=NJKp+l|y-l|`-m>2G!KuF- znDL_tE}&kJn#A$$?9A+Z-#6154~DyZ&m%1wYY`cd(AhN|%QRJ&6wYX(<%Q71qc&Bu zv;BR-rq}0!vM@4Hs^)}^qq)eb59bro>xnD@H-g*W+zT(lLbc2c<%Au`B&VOLgJJZ` zDv&n=KOW7_L~IB@%BBpu4s2n}gj7>=gXVRSVRu}TD7 zz{=G)(hIy7aU9THi0-FR{B@LbYV;DahALeyvK;eH)Fr-qJq+(llaGZC)#6-bqQn5c zN*|v`G0-s(7cv%abaYMFJCV(?G~R*W+yJc$a4t~7d~t2M{DcNYP|(M zkJs!^H@1qnZLmLEvp=uw=>KBP4qJIx!l`0~B-fxu8@2B^xZTliOvgPnj~qmji%+n{4r zT}7hjt~mUL$%{{Y(`pkhC@YH_DEgk<1s<$YPo+r(-TCp;Qr6Nkmh%#7#pahP8^8$A zoxv-|b^_bAeTO@3-}?j}hbsB&;ceevX>meq+9qY4_)i1hcLRDtYa91qn2M9^*5-Ag zzJ@LE@A}yuaNO{-JMBBRwg=4Cenv+!g&9VfCrTNL!#shBlHUcC%0}6VqR3C7X>KBI z3LG7;OnW zadsX8PUnv}E9_1e2KZjsyES%1S4xcVn%I$ None: pdf = PdfParser("Tests/images/duplicate_xref_entry.pdf") assert pdf.xref_table.existing_entries[6][0] == 1197 pdf.close() + + +def test_trailer_loop() -> None: + with pytest.raises(PdfFormatError, match="trailer loop found"): + PdfParser("Tests/images/trailer_loop.pdf") diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 2c9031469ad..f7f3a46431d 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -685,7 +685,9 @@ def read_trailer(self) -> None: if b"Prev" in self.trailer_dict: self.read_prev_trailer(self.trailer_dict[b"Prev"]) - def read_prev_trailer(self, xref_section_offset: int) -> None: + def read_prev_trailer( + self, xref_section_offset: int, processed_offsets: list[int] = [] + ) -> None: assert self.buf is not None trailer_offset = self.read_xref_table(xref_section_offset=xref_section_offset) m = self.re_trailer_prev.search( @@ -700,7 +702,11 @@ def read_prev_trailer(self, xref_section_offset: int) -> None: ) trailer_dict = self.interpret_trailer(trailer_data) if b"Prev" in trailer_dict: - self.read_prev_trailer(trailer_dict[b"Prev"]) + processed_offsets.append(xref_section_offset) + check_format_condition( + trailer_dict[b"Prev"] not in processed_offsets, "trailer loop found" + ) + self.read_prev_trailer(trailer_dict[b"Prev"], processed_offsets) re_whitespace_optional = re.compile(whitespace_optional) re_name = re.compile(