Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python314 for openSUSE:Factory checked in at 2026-09-23 14:32:22 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python314 (Old) and /work/SRC/openSUSE:Factory/.python314.new.383539 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python314" Wed Sep 23 14:32:22 2026 rev:40 rq:1379535 version:3.14.7 Changes: -------- --- /work/SRC/openSUSE:Factory/python314/python314.changes 2026-08-25 13:18:10.420907150 +0200 +++ /work/SRC/openSUSE:Factory/.python314.new.383539/python314.changes 2026-09-23 14:32:51.302080728 +0200 @@ -1,0 +2,22 @@ +Thu Sep 17 21:24:30 UTC 2026 - Matej Cepl <[email protected]> + +- CVE-2026-15310: bound zipfile decompression for + bzip2/LZMA/Zstandard (bsc#1277111, gh#python/cpython#156002) + CVE-2026-15310-bound-zipfile-decompression.patch + CVE-2026-15310-keep-through-3rd-party-decomp.patch + +------------------------------------------------------------------- +Tue Sep 15 10:29:32 UTC 2026 - Matej Cepl <[email protected]> + +- CVE-2026-19672: in tarfile, handle a member that leaves the + destination and comes back (bsc#1276227, gh#python/cpython#156000) + CVE-2026-19672-tarfile-outside-dirs.patch + +------------------------------------------------------------------- +Fri Sep 11 18:14:08 UTC 2026 - Matej Cepl <[email protected]> + +- CVE-2026-17084: Don't consider Unicode codepoint attributes + outside RFC 3454 (bsc#1276226) + CVE-2026-17084-stringprep-rfc3454.patch + +------------------------------------------------------------------- New: ---- CVE-2026-15310-bound-zipfile-decompression.patch CVE-2026-15310-keep-through-3rd-party-decomp.patch CVE-2026-17084-stringprep-rfc3454.patch CVE-2026-19672-tarfile-outside-dirs.patch ----------(New B)---------- New: bzip2/LZMA/Zstandard (bsc#1277111, gh#python/cpython#156002) CVE-2026-15310-bound-zipfile-decompression.patch CVE-2026-15310-keep-through-3rd-party-decomp.patch New: CVE-2026-15310-bound-zipfile-decompression.patch CVE-2026-15310-keep-through-3rd-party-decomp.patch New: outside RFC 3454 (bsc#1276226) CVE-2026-17084-stringprep-rfc3454.patch New: destination and comes back (bsc#1276227, gh#python/cpython#156000) CVE-2026-19672-tarfile-outside-dirs.patch ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python314.spec ++++++ --- /var/tmp/diff_new_pack.jqsCRE/_old 2026-09-23 14:32:52.532132150 +0200 +++ /var/tmp/diff_new_pack.jqsCRE/_new 2026-09-23 14:32:52.534132234 +0200 @@ -237,6 +237,17 @@ # PATCH-FIX-OPENSUSE bsc1260884-llvm21-support.patch bsc#1260884 [email protected] # update JIT builds to use LLVM 21 Patch57: bsc1260884-llvm21-support.patch +# PATCH-FIX-UPSTREAM CVE-2026-17084-stringprep-rfc3454.patch bsc#1276226 Matej Cepl <[email protected]> +# Don't consider Unicode codepoint attributes outside RFC 3454 +Patch58: CVE-2026-17084-stringprep-rfc3454.patch +# PATCH-FIX-UPSTREAM CVE-2026-19672-tarfile-outside-dirs.patch bsc#1276227 [email protected] +# in tarfile, handle a member that leaves the destination and comes back +Patch59: CVE-2026-19672-tarfile-outside-dirs.patch +# PATCH-FIX-UPSTREAM CVE-2026-15310-bound-zipfile-decompression.patch bsc#1277111 [email protected] +# Bound zipfile decompression for bzip2/LZMA/Zstandard (gh#python/cpython!156003) +Patch60: CVE-2026-15310-bound-zipfile-decompression.patch +# Keep reading through third-party zipfile decompressors (gh#python/cpython!157180) +Patch61: CVE-2026-15310-keep-through-3rd-party-decomp.patch #### Python 3.14 END OF PATCHES BuildRequires: autoconf-archive BuildRequires: automake ++++++ CVE-2026-15310-bound-zipfile-decompression.patch ++++++ >From 527ec6a0117b7651cffc13881636501788044fb6 Mon Sep 17 00:00:00 2001 From: Petr Viktorin <[email protected]> Date: Mon, 31 Aug 2026 21:04:04 +0200 Subject: [PATCH] [3.15] gh-156002: Bound zipfile decompression for bzip2/LZMA/Zstandard (GH-156003) (GH-156362) Patch by @tonghuaroot. zipfile.ZipExtFile._read1() bounds the output of each decompress() call for DEFLATE members by passing a max_length to zlib, but for bzip2, LZMA, and Zstandard members it called decompress() with no bound. A whole compressed chunk was therefore expanded into a single allocation before the data[:self._left] clip ran, so a consumer that deliberately reads in small chunks to limit memory (for example zf.open(name).read(8192)) was silently unprotected for non-DEFLATE members. A small, spec-conformant archive member declaring a large uncompressed size could drive multi-GB peak memory. _read1() now passes a per-call bound to the non-DEFLATE decompress() (mirroring the DEFLATE branch) and drains the decompressor's internal buffer across calls by checking needs_input before reading more compressed input. zipfile's LZMADecompressor wrapper forwards max_length and exposes needs_input so the bound also holds for LZMA members. (cherry picked from commit f897dbf2f36a5935700b7c2d94d4681d2136b7d4) (cherry picked from commit 1b424c0178a01e155fd0267dc28a8fc1159b33a8) Co-authored-by: Petr Viktorin <[email protected]> Co-authored-by: tonghuaroot <[email protected]> --- Lib/test/test_zipfile/test_core.py | 42 ++++++++++ Lib/zipfile/__init__.py | 38 +++++++-- Misc/NEWS.d/next/Security/2026-08-18-13-54-05.gh-issue-156002.CcWXPP.rst | 4 3 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2026-08-18-13-54-05.gh-issue-156002.CcWXPP.rst Index: Python-3.14.7/Lib/test/test_zipfile/test_core.py =================================================================== --- Python-3.14.7.orig/Lib/test/test_zipfile/test_core.py 2026-09-17 23:23:21.984631243 +0200 +++ Python-3.14.7/Lib/test/test_zipfile/test_core.py 2026-09-17 23:23:34.932484254 +0200 @@ -2723,6 +2723,48 @@ unlink(TESTFN2) +class AbstractBoundedDecompressTests: + # ZipExtFile._read1() bounds the output of each decompress() call so that a + # small member declaring a large uncompressed size cannot expand into one + # unbounded read. + def test_read1_output_is_bounded(self): + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w", compression=self.compression) as zf: + zf.writestr("big", b"\0" * (4 * 1024 * 1024)) + with zipfile.ZipFile(io.BytesIO(buf.getvalue())) as zf: + with zf.open("big") as f: + self.assertLessEqual(len(f._read1(100)), f.MIN_READ_SIZE) + + +class StoredBoundedDecompressTests(AbstractBoundedDecompressTests, + unittest.TestCase): + compression = zipfile.ZIP_STORED + + +@requires_zlib() +class DeflateBoundedDecompressTests(AbstractBoundedDecompressTests, + unittest.TestCase): + compression = zipfile.ZIP_DEFLATED + + +@requires_bz2() +class Bzip2BoundedDecompressTests(AbstractBoundedDecompressTests, + unittest.TestCase): + compression = zipfile.ZIP_BZIP2 + + +@requires_lzma() +class LzmaBoundedDecompressTests(AbstractBoundedDecompressTests, + unittest.TestCase): + compression = zipfile.ZIP_LZMA + + +@requires_zstd() +class ZstdBoundedDecompressTests(AbstractBoundedDecompressTests, + unittest.TestCase): + compression = zipfile.ZIP_ZSTANDARD + + class AbstractBadCrcTests: def test_testzip_with_bad_crc(self): """Tests that files with bad CRCs return their name from testzip.""" Index: Python-3.14.7/Lib/zipfile/__init__.py =================================================================== --- Python-3.14.7.orig/Lib/zipfile/__init__.py 2026-09-17 23:23:21.984631243 +0200 +++ Python-3.14.7/Lib/zipfile/__init__.py 2026-09-17 23:23:34.933081122 +0200 @@ -786,7 +786,16 @@ self._unconsumed = b'' self.eof = False - def decompress(self, data): + @property + def _needs_input(self): + # While the LZMA properties header is still being buffered, more input + # is required; afterwards defer to the wrapped decompressor so a bounded + # decompress() call can be drained across reads. + if self._decomp is None: + return True + return self._decomp.needs_input + + def decompress(self, data, max_length=-1): if self._decomp is None: self._unconsumed += data if len(self._unconsumed) <= 4: @@ -802,7 +811,7 @@ data = self._unconsumed[4 + psize:] del self._unconsumed - result = self._decomp.decompress(data) + result = self._decomp.decompress(data, max_length) self.eof = self._decomp.eof return result @@ -869,6 +878,13 @@ return None +def _decompressor_needs_input(decompressor): + # bz2/zstd expose the stdlib decompressor's public needs_input; the LZMA + # wrapper keeps it private (_needs_input) to avoid adding public API. + needs_input = getattr(decompressor, "needs_input", None) + return decompressor._needs_input if needs_input is None else needs_input + + def _get_decompressor(compress_type): _check_compression(compress_type) if compress_type == ZIP_STORED: @@ -1171,8 +1187,15 @@ data = self._decompressor.unconsumed_tail if n > len(data): data += self._read2(n - len(data)) - else: + elif self._compress_type == ZIP_STORED: data = self._read2(n) + else: + # bzip2/lzma/zstd: a bounded decompress() call may leave input + # buffered inside the decompressor; drain that before reading more. + if _decompressor_needs_input(self._decompressor): + data = self._read2(n) + else: + data = b'' if self._compress_type == ZIP_STORED: self._eof = self._compress_left <= 0 @@ -1185,8 +1208,13 @@ if self._eof: data += self._decompressor.flush() else: - data = self._decompressor.decompress(data) - self._eof = self._decompressor.eof or self._compress_left <= 0 + # Bound the output of a single decompress() call (mirroring the + # DEFLATE path above) so that a small compressed member cannot + # expand into one unbounded read. + data = self._decompressor.decompress(data, max(n, self.MIN_READ_SIZE)) + self._eof = (self._decompressor.eof or + self._compress_left <= 0 and + _decompressor_needs_input(self._decompressor)) data = data[:self._left] self._left -= len(data) Index: Python-3.14.7/Misc/NEWS.d/next/Security/2026-08-18-13-54-05.gh-issue-156002.CcWXPP.rst =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ Python-3.14.7/Misc/NEWS.d/next/Security/2026-08-18-13-54-05.gh-issue-156002.CcWXPP.rst 2026-09-17 23:23:34.933373464 +0200 @@ -0,0 +1,4 @@ +Bound the amount of data :mod:`zipfile` decompresses per read for members +compressed with bzip2, LZMA, or Zstandard, matching the existing limit for +deflate. A small archive member could previously expand into an unbounded +allocation even when read in small chunks. ++++++ CVE-2026-15310-keep-through-3rd-party-decomp.patch ++++++ >From 2cbf3222825aafb01050630847ef04fd548b1417 Mon Sep 17 00:00:00 2001 From: Petr Viktorin <[email protected]> Date: Tue, 15 Sep 2026 16:00:39 +0200 Subject: [PATCH] [3.15] gh-156002: Keep reading through monkey-patched zipfile decompressors (GH-157180) (GH-157268) (cherry picked from commit f507e6946a3194e83e1d7b8ee6e14567175e46de) Co-authored-by: Petr Viktorin <[email protected]> Co-authored-by: rasmusfaber <[email protected]> --- Lib/test/test_zipfile/test_core.py | 67 ++++++++++ Lib/zipfile/__init__.py | 19 +- Misc/NEWS.d/next/Library/2026-09-08-13-06-29.gh-issue-156002.vmOC8T.rst | 5 3 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-08-13-06-29.gh-issue-156002.vmOC8T.rst Index: Python-3.14.7/Lib/test/test_zipfile/test_core.py =================================================================== --- Python-3.14.7.orig/Lib/test/test_zipfile/test_core.py 2026-09-17 23:52:21.711598638 +0200 +++ Python-3.14.7/Lib/test/test_zipfile/test_core.py 2026-09-17 23:52:21.726829242 +0200 @@ -2765,6 +2765,73 @@ compression = zipfile.ZIP_ZSTANDARD +class MonkeypatchedDecompressorTests(unittest.TestCase): + # Some third-party projects monkey-patch _get_decompressor() to add + # additional compression schemes. This can break at any time as the + # internal compressor objects change. + # To protect users, we try to keep this case working. + # See also: GH-156002 and GH-113767. + COMPRESSION = 99 + + class Compressor: + """Compressor with only the original BZ2Compressor API""" + def compress(self, data): + return data.swapcase() + + def flush(self): + return b'' + + class Decompressor: + """Decompressor with only the 3.3+ BZ2Decompressor API""" + eof = False + + def decompress(self, data): + return data.swapcase() + + def setUp(self): + orig_check_compression = zipfile._check_compression + orig_get_compressor = zipfile._get_compressor + orig_get_decompressor = zipfile._get_decompressor + + def check_compression(compression): + if compression != self.COMPRESSION: + orig_check_compression(compression) + + def get_compressor(compress_type, compresslevel=None): + if compress_type == self.COMPRESSION: + return self.Compressor() + return orig_get_compressor(compress_type, compresslevel) + + def get_decompressor(compress_type): + if compress_type == self.COMPRESSION: + return self.Decompressor() + return orig_get_decompressor(compress_type) + + self.enterContext(mock.patch.object( + zipfile, '_check_compression', check_compression)) + self.enterContext(mock.patch.object( + zipfile, '_get_compressor', get_compressor)) + self.enterContext(mock.patch.object( + zipfile, '_get_decompressor', get_decompressor)) + + def test_roundtrip_monkeypatched_decompressor(self): + data = bytes(range(256)) * 8 + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w", compression=self.COMPRESSION) as zf: + zf.writestr("member", data) + self.assertIn(data.swapcase(), buf.getvalue()) + with zipfile.ZipFile(io.BytesIO(buf.getvalue())) as zf: + self.assertEqual(zf.read("member"), data) + with zf.open("member") as f: + self.assertEqual(f.read(100), data[:100]) + self.assertEqual(f.read1(100), data[100:200]) + f.seek(-100, os.SEEK_END) + self.assertEqual(f.read(), data[-100:]) + # Rewinding past the read buffer re-creates the decompressor. + f.seek(0) + self.assertEqual(f.read(), data) + + class AbstractBadCrcTests: def test_testzip_with_bad_crc(self): """Tests that files with bad CRCs return their name from testzip.""" Index: Python-3.14.7/Lib/zipfile/__init__.py =================================================================== --- Python-3.14.7.orig/Lib/zipfile/__init__.py 2026-09-17 23:52:21.712247993 +0200 +++ Python-3.14.7/Lib/zipfile/__init__.py 2026-09-17 23:52:21.727575940 +0200 @@ -787,7 +787,7 @@ self.eof = False @property - def _needs_input(self): + def needs_input(self): # While the LZMA properties header is still being buffered, more input # is required; afterwards defer to the wrapped decompressor so a bounded # decompress() call can be drained across reads. @@ -878,13 +878,6 @@ return None -def _decompressor_needs_input(decompressor): - # bz2/zstd expose the stdlib decompressor's public needs_input; the LZMA - # wrapper keeps it private (_needs_input) to avoid adding public API. - needs_input = getattr(decompressor, "needs_input", None) - return decompressor._needs_input if needs_input is None else needs_input - - def _get_decompressor(compress_type): _check_compression(compress_type) if compress_type == ZIP_STORED: @@ -1192,7 +1185,7 @@ else: # bzip2/lzma/zstd: a bounded decompress() call may leave input # buffered inside the decompressor; drain that before reading more. - if _decompressor_needs_input(self._decompressor): + if getattr(self._decompressor, "needs_input", True): data = self._read2(n) else: data = b'' @@ -1211,10 +1204,14 @@ # Bound the output of a single decompress() call (mirroring the # DEFLATE path above) so that a small compressed member cannot # expand into one unbounded read. - data = self._decompressor.decompress(data, max(n, self.MIN_READ_SIZE)) + try: + data = self._decompressor.decompress(data, max(n, self.MIN_READ_SIZE)) + except TypeError: + # See MonkeypatchedDecompressorTests in test_core.py + data = self._decompressor.decompress(data) self._eof = (self._decompressor.eof or self._compress_left <= 0 and - _decompressor_needs_input(self._decompressor)) + getattr(self._decompressor, "needs_input", True)) data = data[:self._left] self._left -= len(data) Index: Python-3.14.7/Misc/NEWS.d/next/Library/2026-09-08-13-06-29.gh-issue-156002.vmOC8T.rst =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ Python-3.14.7/Misc/NEWS.d/next/Library/2026-09-08-13-06-29.gh-issue-156002.vmOC8T.rst 2026-09-17 23:52:21.727890844 +0200 @@ -0,0 +1,5 @@ +:mod:`zipfile` again reads members through a third-party decompressor +installed by monkey-patching the private ``_get_decompressor()`` to return an +object that only implements old BZ2Decompressor API from Python 3.3. +Note that decompressors without ``needs_input`` and two-argument +``decompress()`` are vulnerable to :cve:`2026-15310`. ++++++ CVE-2026-17084-stringprep-rfc3454.patch ++++++ ++++ 721 lines (skipped) ++++++ CVE-2026-19672-tarfile-outside-dirs.patch ++++++ >From 8277dc17745107e800f09df2316b2641645e5238 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <[email protected]> Date: Wed, 19 Aug 2026 09:52:01 +0100 Subject: [PATCH] gh-155999: `tarfile`: handle a member that leaves the destination but comes back (GH-156000) (cherry picked from commit 97688346ada2df3e5b9c279348862c3d64ab0823) Co-authored-by: Stan Ulbrych <[email protected]> --- Doc/library/tarfile.rst | 8 +++++ Lib/tarfile.py | 7 +++++ Lib/test/test_tarfile.py | 14 ++++++++++ Misc/NEWS.d/next/Security/2026-08-13-13-08-11.gh-issue-155999.Xt4rWq.rst | 5 +++ 4 files changed, 34 insertions(+) create mode 100644 Misc/NEWS.d/next/Security/2026-08-13-13-08-11.gh-issue-155999.Xt4rWq.rst Index: Python-3.14.7/Doc/library/tarfile.rst =================================================================== --- Python-3.14.7.orig/Doc/library/tarfile.rst 2026-08-05 12:29:49.000000000 +0200 +++ Python-3.14.7/Doc/library/tarfile.rst 2026-09-15 12:47:27.127447441 +0200 @@ -1100,6 +1100,10 @@ paths (in case the name is absolute even after stripping slashes, e.g. ``C:/foo`` on Windows). This raises :class:`~tarfile.AbsolutePathError`. + - Normalize filenames (:attr:`TarInfo.name`) that contain ``..`` components + using :func:`os.path.normpath`. + Note that this removes internal ``..`` components, which may change the + meaning of the name if it traverses symbolic links. - :ref:`Refuse <tarfile-extraction-refuse>` to extract files whose absolute path (after following symlinks) would end up outside the destination. This raises :class:`~tarfile.OutsideDestinationError`. @@ -1108,6 +1112,10 @@ Return the modified ``TarInfo`` member. + .. versionchanged:: next + + Filenames containing ``..`` components are now normalized. + .. function:: data_filter(member, path) Implements the ``'data'`` filter. Index: Python-3.14.7/Lib/tarfile.py =================================================================== --- Python-3.14.7.orig/Lib/tarfile.py 2026-09-15 12:47:21.788385666 +0200 +++ Python-3.14.7/Lib/tarfile.py 2026-09-15 12:47:27.127839249 +0200 @@ -819,6 +819,13 @@ # For example, 'C:/foo' on Windows. raise AbsolutePathError(member) # Ensure we stay in the destination + if '..' in name.replace(os.sep, '/').split('/'): + # Directories are created from the name as given, so a name that + # leaves the destination part-way through would create them + # outside it even if the resolved path stays inside. + normalized = os.path.normpath(name) + if normalized != name: + name = new_attrs['name'] = normalized target_path = os.path.realpath(os.path.join(dest_path, name), strict=os.path.ALLOW_MISSING) if os.path.commonpath([target_path, dest_path]) != dest_path: Index: Python-3.14.7/Lib/test/test_tarfile.py =================================================================== --- Python-3.14.7.orig/Lib/test/test_tarfile.py 2026-09-15 12:47:23.958222775 +0200 +++ Python-3.14.7/Lib/test/test_tarfile.py 2026-09-15 12:47:27.128928873 +0200 @@ -3925,6 +3925,20 @@ tarfile.AbsolutePathError, """['"].*escaped.evil['"] has an absolute path""") + def test_parent_dir_out_and_back(self): + # Test a member that leaves the destination and comes back. + # The containment check looks at the resolved path, which stays + # inside, but the intermediate directories are created from the + # name as given, which does not. + with ArchiveMaker() as arc: + arc.add(f'../escaped.evil/../{self.destdir.name}/sub/file', + content='content') + + for filter in 'tar', 'data': + with self.subTest(filter): + with self.check_context(arc.open(), filter): + self.expect_file('sub/file', content='content') + @symlink_test def test_parent_symlink(self): # Test interplaying symlinks Index: Python-3.14.7/Misc/NEWS.d/next/Security/2026-08-13-13-08-11.gh-issue-155999.Xt4rWq.rst =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ Python-3.14.7/Misc/NEWS.d/next/Security/2026-08-13-13-08-11.gh-issue-155999.Xt4rWq.rst 2026-09-15 12:47:27.129836874 +0200 @@ -0,0 +1,5 @@ +Fix the :mod:`tarfile` ``tar`` and ``data`` extraction filters creating +directories outside the destination for members whose name leaves the +destination and returns to it, such as ``../evil/../dest/sub/file``. The +containment check used the resolved path, but intermediate directories were +created from the name as given. ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.jqsCRE/_old 2026-09-23 14:32:52.677138212 +0200 +++ /var/tmp/diff_new_pack.jqsCRE/_new 2026-09-23 14:32:52.679138296 +0200 @@ -1,6 +1,6 @@ -mtime: 1786226926 -commit: e06431a9366afc49ed25c82716104f583cc21924c0499233d64bd2860f6ac67d +mtime: 1789682398 +commit: 5c3c1c4d192713ec808c89ec22e988bdd9bf76267d1bf400a41516d3d8c761d7 url: https://src.opensuse.org/python-interpreters/python314 -revision: e06431a9366afc49ed25c82716104f583cc21924c0499233d64bd2860f6ac67d +revision: 5c3c1c4d192713ec808c89ec22e988bdd9bf76267d1bf400a41516d3d8c761d7 projectscmsync: https://src.opensuse.org/python-interpreters/_ObsPrj ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-09-17 23:59:58.000000000 +0200 @@ -0,0 +1,5 @@ +.osc +*.obscpio +_build.* +.pbuild +python314-*-build/
