Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-PyPDF2 for openSUSE:Factory 
checked in at 2026-02-23 16:13:22
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-PyPDF2 (Old)
 and      /work/SRC/openSUSE:Factory/.python-PyPDF2.new.1977 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-PyPDF2"

Mon Feb 23 16:13:22 2026 rev:13 rq:1334483 version:2.11.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-PyPDF2/python-PyPDF2.changes      
2025-05-12 16:55:51.346218714 +0200
+++ /work/SRC/openSUSE:Factory/.python-PyPDF2.new.1977/python-PyPDF2.changes    
2026-02-23 16:15:21.268273115 +0100
@@ -1,0 +2,9 @@
+Mon Feb 23 10:42:53 UTC 2026 - Markéta Machová <[email protected]>
+
+- Add security patches:
+  * CVE-2025-55197.patch (bsc#1248089)
+  * CVE-2026-27024.patch (bsc#1258691)
+  * CVE-2026-27025.patch (bsc#1258692)
+  * CVE-2026-27026.patch (bsc#1258693) 
+
+-------------------------------------------------------------------

New:
----
  CVE-2025-55197.patch
  CVE-2026-27024.patch
  CVE-2026-27025.patch
  CVE-2026-27026.patch

----------(New B)----------
  New:- Add security patches:
  * CVE-2025-55197.patch (bsc#1248089)
  * CVE-2026-27024.patch (bsc#1258691)
  New:  * CVE-2025-55197.patch (bsc#1248089)
  * CVE-2026-27024.patch (bsc#1258691)
  * CVE-2026-27025.patch (bsc#1258692)
  New:  * CVE-2026-27024.patch (bsc#1258691)
  * CVE-2026-27025.patch (bsc#1258692)
  * CVE-2026-27026.patch (bsc#1258693) 
  New:  * CVE-2026-27025.patch (bsc#1258692)
  * CVE-2026-27026.patch (bsc#1258693) 
----------(New E)----------

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-PyPDF2.spec ++++++
--- /var/tmp/diff_new_pack.0qmZPm/_old  2026-02-23 16:15:21.776294061 +0100
+++ /var/tmp/diff_new_pack.0qmZPm/_new  2026-02-23 16:15:21.780294226 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-PyPDF2
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -25,6 +25,14 @@
 Group:          Development/Languages/Python
 URL:            https://github.com/py-pdf/PyPDF2
 Source:         
https://github.com/py-pdf/PyPDF2/archive/refs/tags/%{version}.tar.gz
+# PATCH-FIX-UPSTREAM CVE-2025-55197.patch bsc#1248089
+Patch0:         CVE-2025-55197.patch
+# PATCH-FIX-UPSTREAM CVE-2026-27024.patch bsc#1258691
+Patch1:         CVE-2026-27024.patch
+# PATCH-FIX-UPSTREAM CVE-2026-27025.patch bsc#1258692
+Patch2:         CVE-2026-27025.patch
+# PATCH-FIX-UPSTREAM CVE-2026-27026.patch bsc#1258693
+Patch3:         CVE-2026-27026.patch
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  %{python_module wheel}
@@ -49,7 +57,7 @@
 It is therefore a useful tool for websites that manage or manipulate PDFs.
 
 %prep
-%setup -q -n PyPDF2-%{version}
+%autosetup -p1 -n PyPDF2-%{version}
 #remove unwanted shebang
 sed -i '/^#!/ d' PyPDF2/pagerange.py
 
@@ -61,6 +69,8 @@
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
 chmod a-x CHANGELOG.md LICENSE README.md
 
+# many tests need internet connection, cannot be run on OBS
+
 %files %{python_files}
 %license LICENSE
 %doc CHANGELOG.md README.md

++++++ CVE-2025-55197.patch ++++++
>From bb3a69030fde7da545229438ff327b8c971cef49 Mon Sep 17 00:00:00 2001
From: Stefan <[email protected]>
Date: Mon, 11 Aug 2025 16:10:25 +0200
Subject: [PATCH] SEC: Limit decompressed size for FlateDecode filter (#3430)

Closes #3429.
---
 PyPDF2/_reader.py         | 10 ++++-
 PyPDF2/errors.py          |  4 ++
 PyPDF2/filters.py         | 89 +++++++++++++++++++++++++---------------
 tests/example_files.yaml |  2 +
 tests/test_filters.py    | 41 ++++++++++++++----
 tests/test_reader.py     | 15 +++++++
 6 files changed, 119 insertions(+), 42 deletions(-)

Index: PyPDF2-2.11.1/PyPDF2/_reader.py
===================================================================
--- PyPDF2-2.11.1.orig/PyPDF2/_reader.py
+++ PyPDF2-2.11.1/PyPDF2/_reader.py
@@ -1681,15 +1681,20 @@ class PdfReader:
         xrefstream = cast(ContentStream, read_object(stream, self))
         assert cast(str, xrefstream["/Type"]) == "/XRef"
         self.cache_indirect_object(generation, idnum, xrefstream)
-        stream_data = BytesIO(b_(xrefstream.get_data()))
+
         # Index pairs specify the subsections in the dictionary. If
         # none create one subsection that spans everything.
-        idx_pairs = xrefstream.get("/Index", [0, xrefstream.get("/Size")])
+        if "/Size" not in xrefstream:
+            # According to table 17 of the PDF 2.0 specification, this key is 
required.
+            raise PdfReadError(f"Size missing from XRef stream 
{xrefstream!r}!")
+        idx_pairs = xrefstream.get("/Index", [0, xrefstream["/Size"]])
         entry_sizes = cast(Dict[Any, Any], xrefstream.get("/W"))
         assert len(entry_sizes) >= 3
         if self.strict and len(entry_sizes) > 3:
             raise PdfReadError(f"Too many entry sizes: {entry_sizes}")
 
+        stream_data = BytesIO(xrefstream.get_data())
+
         def get_entry(i: int) -> Union[int, Tuple[int, ...]]:
             # Reads the correct number of bytes for each entry. See the
             # discussion of the W parameter in PDF spec table 17.
Index: PyPDF2-2.11.1/PyPDF2/errors.py
===================================================================
--- PyPDF2-2.11.1.orig/PyPDF2/errors.py
+++ PyPDF2-2.11.1/PyPDF2/errors.py
@@ -46,3 +46,7 @@ class EmptyFileError(PdfReadError):
 
 
 STREAM_TRUNCATED_PREMATURELY = "Stream has ended unexpectedly"
+
+
+class LimitReachedError(PyPdfError):
+    """Raised when a limit is reached."""
Index: PyPDF2-2.11.1/PyPDF2/filters.py
===================================================================
--- PyPDF2-2.11.1.orig/PyPDF2/filters.py
+++ PyPDF2-2.11.1/PyPDF2/filters.py
@@ -58,12 +58,12 @@ from .constants import GraphicsStatePara
 from .constants import ImageAttributes as IA
 from .constants import LzwFilterParameters as LZW
 from .constants import StreamAttributes as SA
-from .errors import PdfReadError, PdfStreamError
+from .errors import LimitReachedError, PdfReadError, PdfStreamError
 
 
 def decompress(data: bytes) -> bytes:
     try:
-        return zlib.decompress(data)
+        return _decompress_with_limit(data)
     except zlib.error:
         d = zlib.decompressobj(zlib.MAX_WBITS | 32)
         result_str = b""
@@ -74,6 +74,18 @@ def decompress(data: bytes) -> bytes:
                 pass
         return result_str
 
+ZLIB_MAX_OUTPUT_LENGTH = 75_000_000
+
+
+def _decompress_with_limit(data: bytes) -> bytes:
+    decompressor = zlib.decompressobj()
+    result = decompressor.decompress(data, max_length=ZLIB_MAX_OUTPUT_LENGTH)
+    if decompressor.unconsumed_tail:
+        raise LimitReachedError(
+            f"Limit reached while decompressing. 
{len(decompressor.unconsumed_tail)} bytes remaining."
+        )
+    return result
+
 
 class FlateDecode:
     @staticmethod
Index: PyPDF2-2.11.1/tests/test_filters.py
===================================================================
--- PyPDF2-2.11.1.orig/tests/test_filters.py
+++ PyPDF2-2.11.1/tests/test_filters.py
@@ -1,4 +1,5 @@
 import string
+import zlib
 from io import BytesIO
 from itertools import product as cartesian_product
 from unittest.mock import patch
@@ -6,13 +7,14 @@ from unittest.mock import patch
 import pytest
 
 from PyPDF2 import PdfReader
-from PyPDF2.errors import PdfReadError, PdfStreamError
+from PyPDF2.errors import LimitReachedError, PdfReadError, PdfStreamError
 from PyPDF2.filters import (
     ASCII85Decode,
     ASCIIHexDecode,
     CCITParameters,
     CCITTFaxDecode,
     FlateDecode,
+    decompress,
 )
 from PyPDF2.generic import ArrayObject, DictionaryObject, NumberObject
 

++++++ CVE-2026-27024.patch ++++++
>From bd2f6d052fe5941e85e37082c2a43453d48d1295 Mon Sep 17 00:00:00 2001
From: Stefan <[email protected]>
Date: Tue, 17 Feb 2026 17:51:28 +0100
Subject: [PATCH] SEC: Detect cyclic references when accessing
 TreeObject.children (#3645)

---
 PyPDF2/generic/_data_structures.py     | 11 ++++++++++-
 tests/generic/test_data_structures.py | 18 ++++++++++++++++--
 2 files changed, 26 insertions(+), 3 deletions(-)

Index: PyPDF2-2.11.1/PyPDF2/generic/_data_structures.py
===================================================================
--- PyPDF2-2.11.1.orig/PyPDF2/generic/_data_structures.py
+++ PyPDF2-2.11.1/PyPDF2/generic/_data_structures.py
@@ -372,10 +372,19 @@ class TreeObject(DictionaryObject):
             return
 
         child_ref = self[NameObject("/First")]
+        last = self[NameObject("/Last")]
         child = child_ref.get_object()
+        visited: set[int] = set()
         while True:
+            child_id = id(child)
+            if child_id in visited:
+                logger_warning(f"Detected cycle in outline structure for 
{child}", __name__)
+                return
+            visited.add(child_id)
+
             yield child
-            if child == self[NameObject("/Last")]:
+
+            if child == last:
                 return
             child_ref = child.get(NameObject("/Next"))  # type: ignore
             if child_ref is None:
Index: PyPDF2-2.11.1/tests/test_generic.py
===================================================================
--- PyPDF2-2.11.1.orig/tests/test_generic.py
+++ PyPDF2-2.11.1/tests/test_generic.py
@@ -918,3 +918,17 @@ def test_create_string_object_force():
 )
 def test_float_object_decimal_to_string(value, expected):
     assert repr(FloatObject(value)) == expected
+
+
+def test_tree_object__cyclic_reference(caplog):
+    writer = PdfWriter()
+    child1 = writer._add_object(DictionaryObject())
+    child2 = writer._add_object(DictionaryObject({NameObject("/Next"): 
child1}))
+    child3 = writer._add_object(DictionaryObject({NameObject("/Next"): 
child2}))
+    child1.get_object()[NameObject("/Next")] = child3
+    tree = TreeObject()
+    tree[NameObject("/First")] = child2
+    tree[NameObject("/Last")] = writer._add_object(DictionaryObject())
+
+    assert list(tree.children()) == [child2.get_object(), child1.get_object(), 
child3.get_object()]
+    assert "Detected cycle in outline structure for " in caplog.text

++++++ CVE-2026-27025.patch ++++++
>From 77d7b8d7cfbe8dd179858dfa42666f73fc6e57a2 Mon Sep 17 00:00:00 2001
From: Stefan <[email protected]>
Date: Tue, 17 Feb 2026 17:46:56 +0100
Subject: [PATCH] SEC: Limit size of `/ToUnicode` entries (#3646)

---
 PyPDF2/_cmap.py     | 20 ++++++++++
 tests/test_cmap.py | 91 +++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 109 insertions(+), 2 deletions(-)

Index: PyPDF2-2.11.1/PyPDF2/_cmap.py
===================================================================
--- PyPDF2-2.11.1.orig/PyPDF2/_cmap.py
+++ PyPDF2-2.11.1/PyPDF2/_cmap.py
@@ -5,7 +5,7 @@ from typing import Any, Dict, List, Tupl
 
 from ._codecs import adobe_glyphs, charset_encoding
 from ._utils import logger_warning
-from .errors import PdfReadWarning
+from .errors import PdfReadWarning, LimitReachedError
 from .generic import DecodedStreamObject, DictionaryObject
 
 
@@ -262,6 +262,15 @@ def process_cm_line(
     return process_rg, process_char, multiline_rg
 
 
+# Usual values should be up to 65_536.
+MAPPING_DICTIONARY_SIZE_LIMIT = 100_000
+
+
+def _check_mapping_size(size: int) -> None:
+    if size > MAPPING_DICTIONARY_SIZE_LIMIT:
+        raise LimitReachedError(f"Maximum /ToUnicode size limit reached: 
{size} > {MAPPING_DICTIONARY_SIZE_LIMIT}.")
+
+
 def parse_bfrange(
     l: bytes,
     map_dict: Dict[Any, Any],
@@ -273,6 +282,8 @@ def parse_bfrange(
     nbi = max(len(lst[0]), len(lst[1]))
     map_dict[-1] = ceil(nbi / 2)
     fmt = b"%%0%dX" % (map_dict[-1] * 2)
+    entry_count = len(int_entry)
+    _check_mapping_size(entry_count)
     if multiline_rg is not None:
         a = multiline_rg[0]  # a, b not in the current line
         b = multiline_rg[1]
@@ -280,6 +291,8 @@ def parse_bfrange(
             if sq == b"]":
                 closure_found = True
                 break
+            entry_count += 1
+            _check_mapping_size(entry_count)
             map_dict[
                 unhexlify(fmt % a).decode(
                     "charmap" if map_dict[-1] == 1 else "utf-16-be",
@@ -296,6 +309,8 @@ def parse_bfrange(
                 if sq == b"]":
                     closure_found = True
                     break
+                entry_count += 1
+                _check_mapping_size(entry_count)
                 map_dict[
                     unhexlify(fmt % a).decode(
                         "charmap" if map_dict[-1] == 1 else "utf-16-be",
@@ -308,6 +323,8 @@ def parse_bfrange(
             c = int(lst[2], 16)
             fmt2 = b"%%0%dX" % max(4, len(lst[2]))
             closure_found = True
+            range_size = max(0, b - a + 1)
+            _check_mapping_size(entry_count + range_size)  # This can be 
checked beforehand.
             while a <= b:
                 map_dict[
                     unhexlify(fmt % a).decode(
@@ -323,6 +340,8 @@ def parse_bfrange(
 
 def parse_bfchar(l: bytes, map_dict: Dict[Any, Any], int_entry: List[int]) -> 
None:
     lst = [x for x in l.split(b" ") if x]
+    new_count = len(lst) // 2
+    _check_mapping_size(len(int_entry) + new_count)  # This can be checked 
beforehand.
     map_dict[-1] = len(lst[0]) // 2
     while len(lst) > 1:
         map_to = ""
Index: PyPDF2-2.11.1/tests/test_cmap.py
===================================================================
--- PyPDF2-2.11.1.orig/tests/test_cmap.py
+++ PyPDF2-2.11.1/tests/test_cmap.py
@@ -3,7 +3,9 @@ from io import BytesIO
 import pytest
 
 from PyPDF2 import PdfReader
-from PyPDF2.errors import PdfReadWarning
+from PyPDF2._cmap import parse_bfchar, parse_bfrange
+from PyPDF2.errors import PdfReadWarning, LimitReachedError
+from PyPDF2.generic import StreamObject
 
 from . import get_pdf_from_url
 
@@ -91,3 +93,89 @@ def test_iss1379():
     name = "02voc.pdf"
     reader = PdfReader(BytesIO(get_pdf_from_url(url, name=name)))
     reader.pages[2].extract_text()
+
+
+def test_parse_bfrange__iteration_limit():
+    writer = PdfWriter()
+
+    to_unicode = StreamObject()
+    to_unicode.set_data(
+        b"beginbfrange\n"
+        b"<00000000> <001FFFFF> <00000000>\n"
+        b"endbfrange\n"
+    )
+    font = writer._add_object(DictionaryObject({
+        NameObject("/Type"): NameObject("/Font"),
+        NameObject("/Subtype"): NameObject("/Type1"),
+        NameObject("/BaseFont"): NameObject("/Helvetica"),
+        NameObject("/ToUnicode"): to_unicode,
+    }))
+
+    page = writer.add_blank_page(width=100, height=100)
+    page[NameObject("/Resources")] = DictionaryObject({
+        NameObject("/Font"): DictionaryObject({
+            NameObject("/F1"): font.indirect_reference,
+        })
+    })
+
+    # Case without list, exceeding list directly.
+    with pytest.raises(
+            expected_exception=LimitReachedError, match=r"^Maximum /ToUnicode 
size limit reached: 2097152 > 100000\.$"
+    ):
+        _ = page.extract_text()
+
+    # Use a pre-filled dummy list to simulate multiple calls where the upper 
bound does
+    # not overflow, but the overall size does. Case without list.
+    int_entry = [0] * 99_999
+    map_dict = {}
+    with pytest.raises(
+            expected_exception=LimitReachedError, match=r"^Maximum /ToUnicode 
size limit reached: 165535 > 100000\.$"
+    ):
+        _ = parse_bfrange(line=b"0000 FFFF 0000", map_dict=map_dict, 
int_entry=int_entry, multiline_rg=None)
+    assert map_dict == {-1: 2}
+
+    # Exceeding from previous call.
+    int_entry.append(1)
+    map_dict = {}
+    with pytest.raises(
+            expected_exception=LimitReachedError, match=r"^Maximum /ToUnicode 
size limit reached: 100001 > 100000\.$"
+    ):
+        _ = parse_bfrange(line=b"00000000 00000000 00000000", 
map_dict=map_dict, int_entry=int_entry, multiline_rg=None)
+    assert map_dict == {-1: 4}
+
+    # multiline_rg
+    int_entry = [0] * 99_995
+    map_dict = {-1: 1}
+    with pytest.raises(
+            expected_exception=LimitReachedError, match=r"^Maximum /ToUnicode 
size limit reached: 100001 > 100000\.$"
+    ):
+        _ = parse_bfrange(
+            line=b"0020  0021  0022  0023  0024  0025  0026  2019",
+            map_dict=map_dict, int_entry=int_entry, multiline_rg=(32, 251)
+        )
+    assert map_dict == {-1: 1, " ": " ", "!": "!", '"': '"', "#": "#", "$": 
"$"}
+
+    # No multiline_rg, but list.
+    int_entry = [0] * 99_995
+    map_dict = {}
+    with pytest.raises(
+            expected_exception=LimitReachedError, match=r"^Maximum /ToUnicode 
size limit reached: 100001 > 100000\.$"
+    ):
+        _ = parse_bfrange(
+            line=b"01 8A [ FFFD FFFD FFFD FFFF FFAB AAAA BBBB",
+            map_dict=map_dict, int_entry=int_entry, multiline_rg=None
+        )
+    assert map_dict == {-1: 1, "\x01": "�", "\x02": "�", "\x03": "�", "\x04": 
"\uffff", "\x05": "ᆱ"}
+
+
+def test_parse_bfchar__iteration_limit():
+    int_entry = [0] * 99_995
+    map_dict = {}
+    with pytest.raises(
+            expected_exception=LimitReachedError, match=r"^Maximum /ToUnicode 
size limit reached: 100002 > 100000\.$"
+    ):
+        parse_bfchar(
+            line=b"0003   0020   0008   0025   0009   0026   000A   0027   
000B   0028   000C   0029   000D   002A",
+            map_dict=map_dict, int_entry=int_entry,
+        )
+    assert map_dict == {}

++++++ CVE-2026-27026.patch ++++++
>From 7905842d833f899f1d3228af7e7467ad80277016 Mon Sep 17 00:00:00 2001
From: Stefan <[email protected]>
Date: Tue, 17 Feb 2026 17:38:44 +0100
Subject: [PATCH] SEC: Limit FlateDecode recovery attempts (#3644)

---
 docs/user/security.md |  3 +++
 PyPDF2/filters.py      | 16 ++++++++++++----
 tests/test_filters.py | 13 ++++++++++---
 3 files changed, 25 insertions(+), 7 deletions(-)

Index: PyPDF2-2.11.1/PyPDF2/filters.py
===================================================================
--- PyPDF2-2.11.1.orig/PyPDF2/filters.py
+++ PyPDF2-2.11.1/PyPDF2/filters.py
@@ -60,6 +60,11 @@ from .constants import LzwFilterParamete
 from .constants import StreamAttributes as SA
 from .errors import LimitReachedError, PdfReadError, PdfStreamError
 
+ZLIB_MAX_RECOVERY_INPUT_LENGTH = 5_000_000
+
+# Reuse cached 1-byte values in the fallback loop to avoid per-byte 
allocations.
+_SINGLE_BYTES = tuple(bytes((i,)) for i in range(256))
+
 
 def decompress(data: bytes) -> bytes:
     try:
@@ -67,10 +72,22 @@ def decompress(data: bytes) -> bytes:
     except zlib.error:
         d = zlib.decompressobj(zlib.MAX_WBITS | 32)
         result_str = b""
-        for b in [data[i : i + 1] for i in range(len(data))]:
+        remaining_limit = ZLIB_MAX_OUTPUT_LENGTH
+        for index in range(len(data)):
+            chunk = _SINGLE_BYTES[data[index]]
             try:
-                result_str += d.decompress(b)
+                decompressed = d.decompress(chunk)
+                result_str += decompressed
+                remaining_limit -= len(decompressed)
+                if remaining_limit <= 0:
+                    raise LimitReachedError(
+                        f"Limit reached while decompressing. {len(data) - 
index} bytes remaining."
+                    )
             except zlib.error:
+                if index > ZLIB_MAX_RECOVERY_INPUT_LENGTH:
+                    raise LimitReachedError(
+                        f"Recovery limit reached while decompressing. 
{data_length - index} bytes remaining."
+                    )
                 pass
         return result_str
 

Reply via email to