From c98a51d2f9554964670a0ad7dac0c6188f18b146 Mon Sep 17 00:00:00 2001
From: Andrew Murray <radarhere@users.noreply.github.com>
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

Index: pillow-11.3.0/Tests/test_pdfparser.py
===================================================================
--- pillow-11.3.0.orig/Tests/test_pdfparser.py
+++ pillow-11.3.0/Tests/test_pdfparser.py
@@ -125,3 +125,8 @@ def test_duplicate_xref_entry() -> 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")
Index: pillow-11.3.0/src/PIL/PdfParser.py
===================================================================
--- pillow-11.3.0.orig/src/PIL/PdfParser.py
+++ pillow-11.3.0/src/PIL/PdfParser.py
@@ -684,7 +684,9 @@ class PdfParser:
         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
+    ) -> 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(
@@ -699,7 +701,13 @@ class PdfParser:
         )
         trailer_dict = self.interpret_trailer(trailer_data)
         if b"Prev" in trailer_dict:
-            self.read_prev_trailer(trailer_dict[b"Prev"])
+            if processed_offsets is None:
+                processed_offsets = []
+            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(
