Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python311 for openSUSE:Factory 
checked in at 2026-09-24 22:55:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python311 (Old)
 and      /work/SRC/openSUSE:Factory/.python311.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python311"

Thu Sep 24 22:55:18 2026 rev:74 rq:1379773 version:3.11.16

Changes:
--------
--- /work/SRC/openSUSE:Factory/python311/python311.changes      2026-09-11 
19:00:07.746612212 +0200
+++ /work/SRC/openSUSE:Factory/.python311.new.383539/python311.changes  
2026-09-24 22:55:20.894915530 +0200
@@ -1,0 +2,27 @@
+Thu Sep 17 23:49:43 UTC 2026 - Matej Cepl <[email protected]>
+
+- CVE-2026-15310: bound zipfile decompression for
+  bzip2/LZMA (bsc#1277111, gh#python/cpython#156002)
+  CVE-2026-15310-bound-zipfile-decompression.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 19:59:21 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
+-------------------------------------------------------------------
+Thu Sep 10 18:29:11 UTC 2026 - Matej Cepl <[email protected]>
+
+- CVE-2026-15806: Scope HTTPPasswordMgr credentials by URL scheme
+  (bsc#1276223)
+  * Add CVE-2026-15806-HTTPPasswordMgr-scheme.patch
+
+-------------------------------------------------------------------

New:
----
  CVE-2026-15310-bound-zipfile-decompression.patch
  CVE-2026-15806-HTTPPasswordMgr-scheme.patch
  CVE-2026-17084-stringprep-rfc3454.patch
  CVE-2026-19672-tarfile-outside-dirs.patch

----------(New B)----------
  New:  bzip2/LZMA (bsc#1277111, gh#python/cpython#156002)
  CVE-2026-15310-bound-zipfile-decompression.patch
  New:  (bsc#1276223)
  * Add CVE-2026-15806-HTTPPasswordMgr-scheme.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:
------------------
++++++ python311.spec ++++++
--- /var/tmp/diff_new_pack.Xp0R1x/_old  2026-09-24 22:55:22.066964510 +0200
+++ /var/tmp/diff_new_pack.Xp0R1x/_new  2026-09-24 22:55:22.068964594 +0200
@@ -35,13 +35,11 @@
 %bcond_with base
 %bcond_without general
 %endif
-
 %if 0%{?do_profiling} && !0%{?want_reproducible_builds}
 %bcond_without profileopt
 %else
 %bcond_with profileopt
 %endif
-
 # Only for Tumbleweed
 # https://en.opensuse.org/openSUSE:Python:Externally_managed
 %if 0%{?suse_version} > 1600
@@ -49,14 +47,12 @@
 %else
 %bcond_with externally_managed
 %endif
-
 %define         python_pkg_name python311
 %if "%{python_pkg_name}" == "%{primary_python}"
 %define primary_interpreter 1
 %else
 %define primary_interpreter 0
 %endif
-
 # Setting up variables
 %define _version %(c=%{version}; echo ${c/[a-z]*/})
 %define tar_suffix %(c=%{_version}; echo ${c#%{_version}})
@@ -210,6 +206,18 @@
 # PATCH-FIX-UPSTREAM bsc1263083-http-cookies-atob-utf8.patch bsc#1263083 
[email protected]
 # Use decodeURIComponent() for UTF-8 support in js_output()
 Patch58:        bsc1263083-http-cookies-atob-utf8.patch
+# PATCH-FIX-UPSTREAM CVE-2026-15806-HTTPPasswordMgr-scheme.patch bsc#1276223 
[email protected]
+# Scope HTTPPasswordMgr credentials by URL scheme
+Patch59:        CVE-2026-15806-HTTPPasswordMgr-scheme.patch
+# PATCH-FIX-UPSTREAM CVE-2026-17084-stringprep-rfc3454.patch bsc#1276226 
[email protected]
+# Don't consider Unicode codepoint attributes outside RFC 3454
+Patch60:        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
+Patch61:        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 (gh#python/cpython!156003)
+Patch62:        CVE-2026-15310-bound-zipfile-decompression.patch
 ### END OF PATCHES
 BuildRequires:  autoconf-archive
 BuildRequires:  automake
@@ -227,6 +235,7 @@
 BuildRequires:  pkgconfig(uuid)
 BuildRequires:  pkgconfig(zlib)
 #!BuildIgnore:  gdk-pixbuf-loader-rsvg
+%{?suse_build_hwcaps_libs}
 %if 0%{?suse_version} >= 1550 && %{without base}
 # Skip for the base flavor: rpm-build-python requires python3-base, which
 # creates an unresolvable dependency loop when building python3xx-base itself.
@@ -256,13 +265,12 @@
 BuildRequires:  pkgconfig(tk)
 BuildRequires:  pkgconfig(x11)
 Requires:       %{python_pkg_name}-base = %{version}
-Provides:       %{python_pkg_name}-readline
-Provides:       %{python_pkg_name}-sqlite3
 Recommends:     %{python_pkg_name}-curses
 Recommends:     %{python_pkg_name}-dbm
 Recommends:     %{python_pkg_name}-pip
+Provides:       %{python_pkg_name}-readline
+Provides:       %{python_pkg_name}-sqlite3
 %endif
-%{?suse_build_hwcaps_libs}
 
 %description
 Python 3 is modern interpreted, object-oriented programming language,

++++++ CVE-2026-15310-bound-zipfile-decompression.patch ++++++
>From 7a2b7388233ea0c647168c5792dc31f3051fe177 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <[email protected]>
Date: Mon, 31 Aug 2026 21:04:04 +0200
Subject: [PATCH 1/3] [3.15] gh-156002: Bound zipfile decompression for
 bzip2/LZMA (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,
 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.py                                       |  104 
++++++++++
 Lib/zipfile.py                                                  |   35 ++-
 Misc/NEWS.d/next/Library/2026-09-08-13-06-29.gh-issue-156002.vmOC8T.rst  |    5
 Misc/NEWS.d/next/Security/2026-08-18-13-54-05.gh-issue-156002.CcWXPP.rst |    4
 4 files changed, 143 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.11.16/Lib/test/test_zipfile.py
===================================================================
--- Python-3.11.16.orig/Lib/test/test_zipfile.py        2026-09-18 
02:13:30.182873716 +0200
+++ Python-3.11.16/Lib/test/test_zipfile.py     2026-09-18 02:13:32.565920004 
+0200
@@ -2590,6 +2590,110 @@
         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
+
+
+
+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.11.16/Lib/zipfile.py
===================================================================
--- Python-3.11.16.orig/Lib/zipfile.py  2026-09-18 02:13:30.518309790 +0200
+++ Python-3.11.16/Lib/zipfile.py       2026-09-18 02:13:32.567802775 +0200
@@ -698,7 +698,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:
@@ -714,7 +723,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

@@ -1074,8 +1083,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 getattr(self._decompressor, "needs_input", True):
+                data = self._read2(n)
+            else:
+                data = b''

         if self._compress_type == ZIP_STORED:
             self._eof = self._compress_left <= 0
@@ -1088,8 +1104,17 @@
             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.
+            try:
+                data = self._decompressor.decompress(data, max(n, 
self.MIN_READ_SIZE))
+            except TypeError:
+                # See MonkeypatchedDecompressorTests in test_zipfile.py
+                data = self._decompressor.decompress(data)
+            self._eof = (self._decompressor.eof or
+                         self._compress_left <= 0 and
+                         getattr(self._decompressor, "needs_input", True))

         data = data[:self._left]
         self._left -= len(data)
Index: 
Python-3.11.16/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.11.16/Misc/NEWS.d/next/Library/2026-09-08-13-06-29.gh-issue-156002.vmOC8T.rst
      2026-09-18 02:13:32.568427063 +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`.
Index: 
Python-3.11.16/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.11.16/Misc/NEWS.d/next/Security/2026-08-18-13-54-05.gh-issue-156002.CcWXPP.rst
     2026-09-18 02:13:32.568144339 +0200
@@ -0,0 +1,4 @@
+Bound the amount of data :mod:`zipfile` decompresses per read for members
+compressed with bzip2 or LZMA, 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-15806-HTTPPasswordMgr-scheme.patch ++++++
>From 95e57c3cfe9b6f9a9e70d067afeeff16aaa503b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=81ukasz?= <[email protected]>
Date: Mon, 17 Aug 2026 21:39:16 +0200
Subject: [PATCH 1/3] gh-155694: Scope HTTPPasswordMgr credentials by URL
 scheme (GH-155696)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Credentials stored for an https:// URI were also matched against the
corresponding http:// URI, since `reduce_uri()` discards the scheme.

`HTTPPasswordMgr` and `HTTPPasswordMgrWithPriorAuth` now compare the scheme
too; URIs registered without a scheme still match any scheme.
(cherry picked from commit a7bb524fef61f77ede01f660ffbd591e1d5837ce)

Co-authored-by: Ɓukasz <[email protected]>
---
 Doc/library/urllib.request.rst                | 10 +++-
 Lib/test/test_urllib2.py                      | 56 +++++++++++++++++++
 Lib/urllib/request.py                         | 25 +++++++--
 ...-07-31-16-20-17.gh-issue-155694.SsxlKG.rst |  4 ++
 4 files changed, 87 insertions(+), 8 deletions(-)
 create mode 100644 
Misc/NEWS.d/next/Security/2026-07-31-16-20-17.gh-issue-155694.SsxlKG.rst

Index: Python-3.11.16/Doc/library/urllib.request.rst
===================================================================
--- Python-3.11.16.orig/Doc/library/urllib.request.rst  2026-08-13 
01:03:19.000000000 +0200
+++ Python-3.11.16/Doc/library/urllib.request.rst       2026-09-10 
20:27:18.832162876 +0200
@@ -939,8 +939,14 @@
 
    *uri* can be either a single URI, or a sequence of URIs. *realm*, *user* and
    *passwd* must be strings. This causes ``(user, passwd)`` to be used as
-   authentication tokens when authentication for *realm* and a super-URI of 
any of
-   the given URIs is given.
+   authentication tokens when authentication for *realm* and a super-URI of any
+   of the given URIs is given. If a URI includes a scheme, its credentials only
+   match authentication URIs with the same scheme or no scheme. A URI without a
+   scheme matches authentication URIs with any scheme.
+
+   .. versionchanged:: next
+      Authentication credentials for URIs with a scheme are now scoped by
+      that scheme.
 
 
 .. method:: HTTPPasswordMgr.find_user_password(realm, authuri)
Index: Python-3.11.16/Lib/test/test_urllib2.py
===================================================================
--- Python-3.11.16.orig/Lib/test/test_urllib2.py        2026-09-10 
20:26:40.010207640 +0200
+++ Python-3.11.16/Lib/test/test_urllib2.py     2026-09-10 20:27:18.832540748 
+0200
@@ -271,6 +271,50 @@
         self.assertEqual(find_user_pass("i", "http://j.example.com:80";),
                          (None, None))
 
+    def test_password_manager_scheme(self):
+        mgr = urllib.request.HTTPPasswordMgr()
+        mgr.add_password(
+            "realm", "https://example.com/";, "user", "password")
+
+        self.assertEqual(
+            mgr.find_user_password("realm", "https://example.com/";),
+            ("user", "password"))
+        self.assertEqual(
+            mgr.find_user_password("realm", "http://example.com/";),
+            (None, None))
+        # Support an authority without a scheme.
+        self.assertEqual(
+            mgr.find_user_password("realm", "example.com"),
+            ("user", "password"))
+        # An authority without a scheme continues to match any scheme.
+        mgr.add_password(
+            "realm", "schemeless.example.com", "user", "password")
+        for scheme in "http", "https":
+            with self.subTest(scheme=scheme):
+                self.assertEqual(
+                    mgr.find_user_password(
+                        "realm", f"{scheme}://schemeless.example.com/"),
+                    ("user", "password"))
+
+        # A network-path reference also has no scheme.
+        mgr.add_password(
+            "realm", "//network-path.example.com/", "user", "password")
+        self.assertEqual(
+            mgr.find_user_password(
+                "realm", "https://network-path.example.com/";),
+            ("user", "password"))
+
+    def test_password_manager_reduced_uri(self):
+        mgr = urllib.request.HTTPPasswordMgr()
+
+        self.assertEqual(
+            mgr.reduce_uri("http://example.com/path";),
+            ("example.com:80", "/path"))
+        self.assertTrue(
+            mgr.is_suburi(
+                ("example.com", "/path"),
+                ("example.com", "/path/subpath")))
+
 
 class MockOpener:
     addheaders = []
@@ -1714,6 +1758,18 @@
         # expect request to be sent with auth header
         self.assertTrue(http_handler.has_auth_header)
 
+    def test_basic_prior_auth_different_scheme(self):
+        pwd_manager = HTTPPasswordMgrWithPriorAuth()
+        auth_handler = HTTPBasicAuthHandler(pwd_manager)
+        auth_handler.add_password(
+            None, "https://example.com/";, "user", "password",
+            is_authenticated=True)
+
+        request = Request("http://example.com/";)
+        auth_handler.http_request(request)
+
+        self.assertFalse(request.has_header("Authorization"))
+
     def test_basic_prior_auth_send_after_first_success(self):
         # Auto send auth header after authentication is successful once
 
Index: Python-3.11.16/Lib/urllib/request.py
===================================================================
--- Python-3.11.16.orig/Lib/urllib/request.py   2026-09-10 20:26:40.450781742 
+0200
+++ Python-3.11.16/Lib/urllib/request.py        2026-09-10 20:27:18.832947575 
+0200
@@ -844,16 +844,17 @@
             self.passwd[realm] = {}
         for default_port in True, False:
             reduced_uri = tuple(
-                self.reduce_uri(u, default_port) for u in uri)
+                self._reduce_uri_with_scheme(u, default_port) for u in uri)
             self.passwd[realm][reduced_uri] = (user, passwd)
 
     def find_user_password(self, realm, authuri):
         domains = self.passwd.get(realm, {})
         for default_port in True, False:
-            reduced_authuri = self.reduce_uri(authuri, default_port)
+            reduced_authuri = self._reduce_uri_with_scheme(
+                authuri, default_port)
             for uris, authinfo in domains.items():
                 for uri in uris:
-                    if self.is_suburi(uri, reduced_authuri):
+                    if self._is_suburi_with_scheme(uri, reduced_authuri):
                         return authinfo
         return None, None
 
@@ -880,6 +881,17 @@
                 authority = "%s:%d" % (host, dport)
         return authority, path
 
+    def _reduce_uri_with_scheme(self, uri, default_port=True):
+        parts = urlsplit(uri)
+        scheme = parts[0] if parts[1] else None
+        return (scheme or None, *self.reduce_uri(uri, default_port))
+
+    def _is_suburi_with_scheme(self, base, test):
+        if (base[0] is not None and test[0] is not None and
+                base[0] != test[0]):
+            return False
+        return self.is_suburi(base[1:], test[1:])
+
     def is_suburi(self, base, test):
         """Check if test is below base in a URI tree
 
@@ -925,14 +937,15 @@
 
         for default_port in True, False:
             for u in uri:
-                reduced_uri = self.reduce_uri(u, default_port)
+                reduced_uri = self._reduce_uri_with_scheme(u, default_port)
                 self.authenticated[reduced_uri] = is_authenticated
 
     def is_authenticated(self, authuri):
         for default_port in True, False:
-            reduced_authuri = self.reduce_uri(authuri, default_port)
+            reduced_authuri = self._reduce_uri_with_scheme(
+                authuri, default_port)
             for uri in self.authenticated:
-                if self.is_suburi(uri, reduced_authuri):
+                if self._is_suburi_with_scheme(uri, reduced_authuri):
                     return self.authenticated[uri]
 
 
Index: 
Python-3.11.16/Misc/NEWS.d/next/Security/2026-07-31-16-20-17.gh-issue-155694.SsxlKG.rst
===================================================================
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
+++ 
Python-3.11.16/Misc/NEWS.d/next/Security/2026-07-31-16-20-17.gh-issue-155694.SsxlKG.rst
     2026-09-10 20:27:18.833718347 +0200
@@ -0,0 +1,4 @@
+Fix `CVE-2026-15806 <https://www.cve.org/CVERecord?id=CVE-2026-15806>`_ by 
scoping :class:`~urllib.request.HTTPPasswordMgr`
+credentials to the URL scheme, preventing credentials stored for an HTTPS
+URL from being used for a matching HTTP URL, while URIs without a scheme
+continue to match any scheme.

++++++ CVE-2026-17084-stringprep-rfc3454.patch ++++++
++++ 959 lines (skipped)

++++++ CVE-2026-19672-tarfile-outside-dirs.patch ++++++
>From 3ee68fa9516b9780d62cc17f68b0f359551c5ac1 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.11.16/Doc/library/tarfile.rst
===================================================================
--- Python-3.11.16.orig/Doc/library/tarfile.rst 2026-08-13 01:03:19.000000000 
+0200
+++ Python-3.11.16/Doc/library/tarfile.rst      2026-09-15 15:53:34.389577345 
+0200
@@ -1033,6 +1033,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`.
@@ -1041,6 +1045,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.11.16/Lib/tarfile.py
===================================================================
--- Python-3.11.16.orig/Lib/tarfile.py  2026-09-15 15:53:29.552151439 +0200
+++ Python-3.11.16/Lib/tarfile.py       2026-09-15 15:53:34.389860590 +0200
@@ -777,6 +777,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.11.16/Lib/test/test_tarfile.py
===================================================================
--- Python-3.11.16.orig/Lib/test/test_tarfile.py        2026-09-15 
15:53:31.262667996 +0200
+++ Python-3.11.16/Lib/test/test_tarfile.py     2026-09-15 15:53:34.390386453 
+0200
@@ -3615,6 +3615,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.11.16/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.11.16/Misc/NEWS.d/next/Security/2026-08-13-13-08-11.gh-issue-155999.Xt4rWq.rst
     2026-09-15 15:53:34.390762022 +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.Xp0R1x/_old  2026-09-24 22:55:22.217970821 +0200
+++ /var/tmp/diff_new_pack.Xp0R1x/_new  2026-09-24 22:55:22.220970946 +0200
@@ -1,6 +1,6 @@
-mtime: 1788802751
-commit: 095976ce39ed3b0443d0b4bba207d5564941f83019da2e8eef3782b3ff79f113
+mtime: 1789690797
+commit: 5b7c5d747e0961cb68cf4ec3b1d41113884019b2b05eabc835b500919d7302a0
 url: https://src.opensuse.org/python-interpreters/python311
-revision: 095976ce39ed3b0443d0b4bba207d5564941f83019da2e8eef3782b3ff79f113
+revision: 5b7c5d747e0961cb68cf4ec3b1d41113884019b2b05eabc835b500919d7302a0
 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-18 02:19:57.000000000 +0200
@@ -0,0 +1,7 @@
+_build.*
+*.obscpio
+*.osc
+.osc
+.pbuild
+python311*-build/
+*.rej

Reply via email to