Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python310 for openSUSE:Factory 
checked in at 2026-08-01 18:34:12
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python310 (Old)
 and      /work/SRC/openSUSE:Factory/.python310.new.16738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python310"

Sat Aug  1 18:34:12 2026 rev:79 rq:1368874 version:3.10.20

Changes:
--------
--- /work/SRC/openSUSE:Factory/python310/python310.changes      2026-07-15 
17:08:52.289952650 +0200
+++ /work/SRC/openSUSE:Factory/.python310.new.16738/python310.changes   
2026-08-01 18:37:23.048466060 +0200
@@ -1,0 +2,48 @@
+Fri Jul 31 13:55:17 UTC 2026 - Matej Cepl <[email protected]>
+
+- CVE-2026-3276: Fix O(n^2) canonical ordering in
+  unicodedata.normalize() (bsc#1267581, gh#python/cpython#149079)
+CVE-2026-3276-On2-unicodedata-normalize.patch
+
+-------------------------------------------------------------------
+Fri Jul 31 13:42:27 UTC 2026 - Matej Cepl <[email protected]>
+
+- CVE-2026-7774: tarfile.data_filter: validate written link
+  target (bsc#1267821, gh#149486)
+  CVE-2026-7774-tarfile-data_filter-symlink.patch
+
+-------------------------------------------------------------------
+Fri Jul 31 13:35:09 UTC 2026 - Matej Cepl <[email protected]>
+
+- CVE-2026-0864: Normalize all line endings (CR, CRLF, and LF) in
+  configparser (bsc#1269066, gh#python/cpython#143927)
+  CVE-2026-0864-normalize-LFTAB-configparser.patch
+
+-------------------------------------------------------------------
+Fri Jul 31 13:22:45 UTC 2026 - Matej Cepl <[email protected]>
+
+- CVE-2026-11972: Make tarfile._Stream.seek break at EOF
+  (bsc#1269788, gh#python/cpython#151981)
+  CVE-2026-11972-tarfile-Stream-seek-EOF.patch
+
+-------------------------------------------------------------------
+Fri Jul 31 13:07:50 UTC 2026 - Matej Cepl <[email protected]>
+
+- CVE-2026-4360: Pass filter_function to TarFile._extract_one()
+  during .extract() (bsc#1269959, gh#python/cpython#151987)
+  CVE-2026-4360-filter_function-TarFile-extractone.patch
+
+-------------------------------------------------------------------
+Fri Jul 31 12:54:51 UTC 2026 - Matej Cepl <[email protected]>
+
+- CVE-2026-15308: Fix quadratic complexity in incremental parsing
+  in HTMLParser (bsc#1271192, gh#python/cpython#153030)
+  CVE-2026-15308-HTMLParser-CPU-exhaust.patch
+
+-------------------------------------------------------------------
+Thu Jul 23 04:58:44 UTC 2026 - Steve Kowalik <[email protected]>
+
+- Add patch support-sphinx-9.patch:
+  * Support changes required for Sphinx 9.
+
+-------------------------------------------------------------------

New:
----
  CVE-2026-0864-normalize-LFTAB-configparser.patch
  CVE-2026-11972-tarfile-Stream-seek-EOF.patch
  CVE-2026-15308-HTMLParser-CPU-exhaust.patch
  CVE-2026-3276-On2-unicodedata-normalize.patch
  CVE-2026-4360-filter_function-TarFile-extractone.patch
  CVE-2026-7774-tarfile-data_filter-symlink.patch
  support-sphinx-9.patch

----------(New B)----------
  New:  configparser (bsc#1269066, gh#python/cpython#143927)
  CVE-2026-0864-normalize-LFTAB-configparser.patch
  New:  (bsc#1269788, gh#python/cpython#151981)
  CVE-2026-11972-tarfile-Stream-seek-EOF.patch
  New:  in HTMLParser (bsc#1271192, gh#python/cpython#153030)
  CVE-2026-15308-HTMLParser-CPU-exhaust.patch
  New:  unicodedata.normalize() (bsc#1267581, gh#python/cpython#149079)
CVE-2026-3276-On2-unicodedata-normalize.patch
  New:  during .extract() (bsc#1269959, gh#python/cpython#151987)
  CVE-2026-4360-filter_function-TarFile-extractone.patch
  New:  target (bsc#1267821, gh#149486)
  CVE-2026-7774-tarfile-data_filter-symlink.patch
  New:
- Add patch support-sphinx-9.patch:
  * Support changes required for Sphinx 9.
----------(New E)----------

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

Other differences:
------------------
++++++ python310.spec ++++++
--- /var/tmp/diff_new_pack.fk7mtj/_old  2026-08-01 18:37:24.296509002 +0200
+++ /var/tmp/diff_new_pack.fk7mtj/_new  2026-08-01 18:37:24.300509139 +0200
@@ -256,6 +256,26 @@
 # PATCH-FIX-OPENSUSE test_UDPLITE_support.patch bsc#1268375 [email protected]
 # improve testing of the presence of IPPROTO_UDPLITE support
 Patch55:        test_UDPLITE_support.patch
+# PATCH-FIX-UPSTREAM Based on gh#python/cpython#142057 Support fixes required 
for Sphinx 9
+Patch56:        support-sphinx-9.patch
+# PATCH-FIX-UPSTREAM CVE-2026-15308-HTMLParser-CPU-exhaust.patch bsc#1271192 
[email protected]
+# Fix quadratic complexity in incremental parsing in HTMLParser
+Patch57:        CVE-2026-15308-HTMLParser-CPU-exhaust.patch
+# PATCH-FIX-UPSTREAM CVE-2026-4360-filter_function-TarFile-extractone.patch 
bsc#1269959 [email protected]
+# Pass filter_function to TarFile._extract_one() during .extract()
+Patch58:        CVE-2026-4360-filter_function-TarFile-extractone.patch
+# PATCH-FIX-UPSTREAM CVE-2026-11972-tarfile-Stream-seek-EOF.patch bsc#1269788 
[email protected]
+# Make tarfile._Stream.seek break at EOF
+Patch59:        CVE-2026-11972-tarfile-Stream-seek-EOF.patch
+# PATCH-FIX-UPSTREAM CVE-2026-0864-normalize-LFTAB-configparser.patch 
bsc#1269066 [email protected]
+# Normalize all line endings (CR, CRLF, and LF) in configparser
+Patch60:        CVE-2026-0864-normalize-LFTAB-configparser.patch
+# PATCH-FIX-UPSTREAM CVE-2026-7774-tarfile-data_filter-symlink.patch 
bsc#1267821 [email protected]
+# tarfile.data_filter: validate written link target
+Patch61:        CVE-2026-7774-tarfile-data_filter-symlink.patch
+# PATCH-FIX-UPSTREAM CVE-2026-3276-On2-unicodedata-normalize.patch bsc#1267581 
[email protected]
+# gh-149079: Fix O(n^2) canonical ordering in unicodedata.normalize()
+Patch62:        CVE-2026-3276-On2-unicodedata-normalize.patch
 ### END OF PATCHES                                                             
 
 BuildRequires:  autoconf-archive
 BuildRequires:  automake

++++++ CVE-2026-0864-normalize-LFTAB-configparser.patch ++++++
>From 6141c1576ae9c5e6581dec01a9a729423ad69a9b Mon Sep 17 00:00:00 2001
From: Seth Larson <[email protected]>
Date: Tue, 23 Jun 2026 08:33:51 -0500
Subject: [PATCH] gh-143927: Normalize all line endings (CR, CRLF, and LF) in
 configparser (GH-143929) (cherry picked from commit
 5858e42c539dac8394636a6e9b30472b8994851f)

Co-authored-by: Seth Larson <[email protected]>
---
 Lib/configparser.py                                                      |    
4 ++-
 Lib/test/test_configparser.py                                            |   
11 ++++++++++
 Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst |    
2 +
 3 files changed, 16 insertions(+), 1 deletion(-)
 create mode 100644 
Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst

Index: Python-3.10.20/Lib/configparser.py
===================================================================
--- Python-3.10.20.orig/Lib/configparser.py     2026-07-31 17:25:09.972229477 
+0200
+++ Python-3.10.20/Lib/configparser.py  2026-07-31 17:25:15.990119182 +0200
@@ -931,7 +931,9 @@
             value = self._interpolation.before_write(self, section_name, key,
                                                      value)
             if value is not None or not self._allow_no_value:
-                value = delimiter + str(value).replace('\n', '\n\t')
+                # Convert all possible line-endings into '\n\t'
+                value = (delimiter + str(value).replace('\r\n', '\n')
+                         .replace('\r', '\n').replace('\n', '\n\t'))
             else:
                 value = ""
             fp.write("{}{}\n".format(key, value))
Index: Python-3.10.20/Lib/test/test_configparser.py
===================================================================
--- Python-3.10.20.orig/Lib/test/test_configparser.py   2026-07-31 
17:25:12.197596399 +0200
+++ Python-3.10.20/Lib/test/test_configparser.py        2026-07-31 
17:25:15.990358974 +0200
@@ -528,6 +528,17 @@
             cf.get(self.default_section, "Foo"), "Bar",
             "could not locate option, expecting case-insensitive defaults")
 
+    def test_crlf_normalization(self):
+        cf = self.newconfig({"key1": "a\nb","key2": "a\rb", "key3": "a\r\nb", 
"key4": "a\r\nb"})
+        buf = io.StringIO()
+        cf.write(buf)
+        cf_str = buf.getvalue()
+        self.assertNotIn("\r", cf_str)
+        self.assertNotIn("\r\n", cf_str)
+        self.assertEqual(cf_str.count("\n"), 10)
+        self.assertEqual(cf_str.count("\n\t"), 4)
+        self.assertTrue(cf_str.endswith("\n\n"))
+
     def test_parse_errors(self):
         cf = self.newconfig()
         self.parse_error(cf, configparser.ParsingError,
Index: 
Python-3.10.20/Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst
===================================================================
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
+++ 
Python-3.10.20/Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst
     2026-07-31 17:25:15.990570894 +0200
@@ -0,0 +1,2 @@
+Normalize all line endings (CR, CRLF, and LF) to LF+TAB when writing
+multi-line configparser values.

++++++ CVE-2026-11972-tarfile-Stream-seek-EOF.patch ++++++
>From f038444a8a996a746e1f4058f9a4cb6ad1ac28cc Mon Sep 17 00:00:00 2001
From: Petr Viktorin <[email protected]>
Date: Tue, 23 Jun 2026 15:13:30 +0200
Subject: [PATCH] gh-151981: Make tarfile._Stream.seek break at EOF (GH-151982)
 (cherry picked from commit f50bf13566189c8d0ce5a814f33eff3d89951896)

Co-authored-by: Petr Viktorin <[email protected]>
Co-authored-by: Stan Ulbrych <[email protected]>
---
 Lib/tarfile.py                                                           |    
4 +-
 Lib/test/test_tarfile.py                                                 |   
16 ++++++++++
 Misc/NEWS.d/next/Security/2026-06-23-13-28-16.gh-issue-151981.xBHEcU.rst |    
2 +
 3 files changed, 21 insertions(+), 1 deletion(-)
 create mode 100644 
Misc/NEWS.d/next/Security/2026-06-23-13-28-16.gh-issue-151981.xBHEcU.rst

Index: Python-3.10.20/Lib/tarfile.py
===================================================================
--- Python-3.10.20.orig/Lib/tarfile.py  2026-07-31 17:28:42.692969214 +0200
+++ Python-3.10.20/Lib/tarfile.py       2026-07-31 17:28:42.707639106 +0200
@@ -513,7 +513,9 @@
         if pos - self.pos >= 0:
             blocks, remainder = divmod(pos - self.pos, self.bufsize)
             for i in range(blocks):
-                self.read(self.bufsize)
+                data = self.read(self.bufsize)
+                if not data:
+                    break
             self.read(remainder)
         else:
             raise StreamError("seeking backwards is not allowed")
Index: Python-3.10.20/Lib/test/test_tarfile.py
===================================================================
--- Python-3.10.20.orig/Lib/test/test_tarfile.py        2026-07-31 
17:28:42.693877226 +0200
+++ Python-3.10.20/Lib/test/test_tarfile.py     2026-07-31 17:28:42.708159177 
+0200
@@ -4405,6 +4405,22 @@
         with self.check_context(arc.open(errorlevel='boo!'), 
filtererror_filter):
             self.expect_exception(TypeError)  # errorlevel is not int
 
+    @support.subTests('format', [tarfile.GNU_FORMAT, tarfile.PAX_FORMAT])
+    def test_getmembers_big_size(self, format):
+        # gh-151981: A loop in seek() for streaming files tried to read the
+        # declared number of blocks even at EOF
+        tinfo = tarfile.TarInfo("huge-file")
+        tinfo.size = 1 << 64
+        bio = io.BytesIO()
+        # Write header without data
+        bio.write(tinfo.tobuf(format))
+
+        # Reset & try to get contents
+        bio.seek(0)
+        with tarfile.open(fileobj=bio, mode="r|") as tar:
+            with self.assertRaises(tarfile.ReadError):
+                tar.getmembers()
+
 
 class OffsetValidationTests(unittest.TestCase):
     tarname = tmpname
Index: 
Python-3.10.20/Misc/NEWS.d/next/Security/2026-06-23-13-28-16.gh-issue-151981.xBHEcU.rst
===================================================================
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
+++ 
Python-3.10.20/Misc/NEWS.d/next/Security/2026-06-23-13-28-16.gh-issue-151981.xBHEcU.rst
     2026-07-31 17:28:42.708562327 +0200
@@ -0,0 +1,2 @@
+In :mod:`tarfile`, seeking a stream now stops when end of the stream is
+reached.

++++++ CVE-2026-15308-HTMLParser-CPU-exhaust.patch ++++++
>From 0765ae4d3728db1e7d0870185d42e0025db6d2c5 Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <[email protected]>
Date: Sat, 4 Jul 2026 20:40:22 +0300
Subject: [PATCH] gh-153030: Fix quadratic complexity in incremental parsing in
 HTMLParser (GH-153031)

When an unterminated construct (e.g. a tag or comment) spanned many
feed() calls, rescanning the growing buffer and concatenating new data
onto it were both quadratic.  New data is now accumulated in a list and
only joined and parsed once enough has piled up.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
(cherry picked from commit bcf98ddbc40ec9b3ee87da0124a5660b19b7e606)
---
 Lib/html/parser.py                                                       |   
32 +++++++++-
 Lib/test/test_htmlparser.py                                              |   
20 ++++++
 Misc/NEWS.d/next/Security/2026-07-04-17-00-00.gh-issue-153030.RovkP6.rst |    
3 
 3 files changed, 53 insertions(+), 2 deletions(-)
 create mode 100644 
Misc/NEWS.d/next/Security/2026-07-04-17-00-00.gh-issue-153030.RovkP6.rst

Index: Python-3.10.20/Lib/html/parser.py
===================================================================
--- Python-3.10.20.orig/Lib/html/parser.py      2026-07-31 17:02:24.015719181 
+0200
+++ Python-3.10.20/Lib/html/parser.py   2026-07-31 17:02:32.281707169 +0200
@@ -137,6 +137,9 @@
         self.cdata_elem = None
         self._support_cdata = True
         self._escapable = True
+        self._pending = []
+        self._pending_len = 0
+        self._parse_threshold = 1
         _markupbase.ParserBase.reset(self)
 
     def feed(self, data):
@@ -145,11 +148,36 @@
         Call this as often as you want, with as little or as much text
         as you want (may include '\n').
         """
-        self.rawdata = self.rawdata + data
-        self.goahead(0)
+        # Accumulate new data in a list and only join and parse it once
+        # enough has piled up.  Rescanning an unparsed buffer (e.g. an
+        # unterminated tag) and concatenating onto it on every call would
+        # both be quadratic in the input size.
+        self._pending_len += len(data)
+        if self._pending_len < self._parse_threshold:
+            self._pending.append(data)
+        else:
+            if not self._pending:
+                self.rawdata += data
+            else:
+                self._pending.append(data)
+                self.rawdata += ''.join(self._pending)
+                self._pending.clear()
+            self._pending_len = 0
+            n = len(self.rawdata)
+            self.goahead(0)
+            if len(self.rawdata) < n:
+                # Some data was parsed; resume on the next call.
+                self._parse_threshold = 1
+            else:
+                # Nothing was parsed; wait until the buffer doubles.
+                self._parse_threshold = len(self.rawdata)
 
     def close(self):
         """Handle any buffered data."""
+        if self._pending:
+            self.rawdata += ''.join(self._pending)
+            self._pending.clear()
+            self._pending_len = 0
         self.goahead(1)
 
     __starttag_text = None
Index: Python-3.10.20/Lib/test/test_htmlparser.py
===================================================================
--- Python-3.10.20.orig/Lib/test/test_htmlparser.py     2026-07-31 
17:02:25.650388367 +0200
+++ Python-3.10.20/Lib/test/test_htmlparser.py  2026-07-31 17:02:32.281935820 
+0200
@@ -929,6 +929,26 @@
         check("<![CDATA[" * 9 * n)
         check("<!doctype" * 35 * n)
 
+    @support.requires_resource('cpu')
+    def test_incremental_no_quadratic_complexity(self):
+        # An unterminated construct fed in many small chunks used to take
+        # quadratic time, both to rescan and to concatenate the buffer.
+        # Now it takes a fraction of a second.
+        def check(prefix, chunk, suffix):
+            parser = html.parser.HTMLParser()
+            parser.feed(prefix)
+            for _ in range(200_000):
+                parser.feed(chunk)
+            parser.feed(suffix)
+            parser.close()
+        chunk = "a" * 64
+        check("<!--", chunk, "-->")       # comment
+        check("<?", chunk, ">")           # processing instruction
+        check("<!doctype ", chunk, ">")   # doctype
+        check("<![CDATA[", chunk, "]]>")  # CDATA section
+        check("<a href='", chunk, "'>")   # start tag
+        check("<script>", chunk, "</script>")  # RAWTEXT element
+
 
 class AttributesTestCase(TestCaseBase):
 
Index: 
Python-3.10.20/Misc/NEWS.d/next/Security/2026-07-04-17-00-00.gh-issue-153030.RovkP6.rst
===================================================================
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
+++ 
Python-3.10.20/Misc/NEWS.d/next/Security/2026-07-04-17-00-00.gh-issue-153030.RovkP6.rst
     2026-07-31 17:02:32.282127121 +0200
@@ -0,0 +1,3 @@
+Fixed quadratic complexity in incremental parsing of long unterminated
+constructs (such as tags or comments) in :class:`html.parser.HTMLParser`,
+which could be exploited for a denial of service.

++++++ CVE-2026-3276-On2-unicodedata-normalize.patch ++++++
>From 01a50e4548b6c37aebb9544f0d8f30d19536fc26 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <[email protected]>
Date: Tue, 2 Jun 2026 18:12:42 +0200
Subject: [PATCH] [3.10] gh-149079: Fix O(n^2) canonical ordering in
 unicodedata.normalize() (GH-149080)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Replace the insertion sort used for canonical ordering of combining
characters with a hybrid approach: insertion sort for short runs (< 20)
and counting sort for longer runs, reducing worst-case complexity from
O(n^2) to O(n). This prevents denial of service via crafted Unicode
strings with many combining characters in alternating CCC order.
(cherry picked from commit 991224b1e8311c85f198f6dd8208bf8cff7fc26f)

Co-authored-by: Seth Larson <[email protected]>
Co-authored-by: ch4n3-yoon <[email protected]>
Co-authored-by: Seokchan Yoon <[email protected]>
Co-authored-by: Stan Ulbrych <[email protected]>
Co-authored-by: Bénédikt Tran <[email protected]>
Co-authored-by: Serhiy Storchaka <[email protected]>
Co-authored-by: Maurycy Pawłowski-Wieroński <[email protected]>
---
 Lib/test/test_unicodedata.py                                             |   
28 +
 Misc/NEWS.d/next/Security/2026-04-27-16-36-11.gh-issue-149079.vKl-LM.rst |    
5 
 Modules/unicodedata.c                                                    |  
143 ++++++++--
 3 files changed, 150 insertions(+), 26 deletions(-)
 create mode 100644 
Misc/NEWS.d/next/Security/2026-04-27-16-36-11.gh-issue-149079.vKl-LM.rst

Index: Python-3.10.20/Lib/test/test_unicodedata.py
===================================================================
--- Python-3.10.20.orig/Lib/test/test_unicodedata.py    2026-07-31 
17:31:01.889020342 +0200
+++ Python-3.10.20/Lib/test/test_unicodedata.py 2026-07-31 17:31:06.054061998 
+0200
@@ -202,6 +202,34 @@
         b = 'C\u0338' * 20  + '\xC7'
         self.assertEqual(self.db.normalize('NFC', a), b)
 
+    def test_long_combining_mark_run(self):
+        # gh-149079: avoid quadratic canonical ordering.
+        payload = "a" + ("\u0300\u0327" * 32)
+        nfd = "a" + ("\u0327" * 32) + ("\u0300" * 32)
+        nfc = "\u00e0" + ("\u0327" * 32) + ("\u0300" * 31)
+
+        self.assertEqual(self.db.normalize("NFD", payload), nfd)
+        self.assertEqual(self.db.normalize("NFKD", payload), nfd)
+        self.assertEqual(self.db.normalize("NFC", payload), nfc)
+        self.assertEqual(self.db.normalize("NFKC", payload), nfc)
+
+    def test_combining_mark_run_fast_paths(self):
+        # gh-149079: cover short runs and already-sorted long runs.
+        short_payload = "a" + ("\u0300\u0327" * 9) + "\u0300"
+        short_nfd = "a" + ("\u0327" * 9) + ("\u0300" * 10)
+        short_nfc = "\u00e0" + ("\u0327" * 9) + ("\u0300" * 9)
+        long_sorted = "a" + ("\u0327" * 30) + ("\u0300" * 30)
+        long_sorted_nfc = "\u00e0" + ("\u0327" * 30) + ("\u0300" * 29)
+
+        self.assertEqual(self.db.normalize("NFD", short_payload), short_nfd)
+        self.assertEqual(self.db.normalize("NFKD", short_payload), short_nfd)
+        self.assertEqual(self.db.normalize("NFC", short_payload), short_nfc)
+        self.assertEqual(self.db.normalize("NFKC", short_payload), short_nfc)
+        self.assertEqual(self.db.normalize("NFD", long_sorted), long_sorted)
+        self.assertEqual(self.db.normalize("NFKD", long_sorted), long_sorted)
+        self.assertEqual(self.db.normalize("NFC", long_sorted), 
long_sorted_nfc)
+        self.assertEqual(self.db.normalize("NFKC", long_sorted), 
long_sorted_nfc)
+
     def test_issue29456(self):
         # Fix #29456
         u1176_str_a = '\u1100\u1176\u11a8'
Index: 
Python-3.10.20/Misc/NEWS.d/next/Security/2026-04-27-16-36-11.gh-issue-149079.vKl-LM.rst
===================================================================
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
+++ 
Python-3.10.20/Misc/NEWS.d/next/Security/2026-04-27-16-36-11.gh-issue-149079.vKl-LM.rst
     2026-07-31 17:31:06.054277183 +0200
@@ -0,0 +1,5 @@
+Fix a potential denial of service in :func:`unicodedata.normalize`. The
+canonical ordering step of Unicode normalization used a quadratic-time 
insertion
+sort for reordering combining characters, which could be exploited with
+crafted input containing many combining characters in non-canonical order.
+Replaced with a linear-time counting sort for long runs.
Index: Python-3.10.20/Modules/unicodedata.c
===================================================================
--- Python-3.10.20.orig/Modules/unicodedata.c   2026-03-03 01:49:35.000000000 
+0100
+++ Python-3.10.20/Modules/unicodedata.c        2026-07-31 17:31:06.054876273 
+0200
@@ -485,6 +485,64 @@
     (*index)++;
 }
 
+/* Small combining runs are usually cheaper with insertion sort. */
+#define CANONICAL_ORDERING_COUNTING_SORT_THRESHOLD 20
+
+static void
+canonical_ordering_sort_insertion(int kind, void *data,
+                                  Py_ssize_t start, Py_ssize_t end)
+{
+    for (Py_ssize_t i = start + 1; i < end; i++) {
+        Py_UCS4 code = PyUnicode_READ(kind, data, i);
+        unsigned char combining = _getrecord_ex(code)->combining;
+        Py_ssize_t j = i;
+
+        while (j > start) {
+            Py_UCS4 previous = PyUnicode_READ(kind, data, j - 1);
+            if (_getrecord_ex(previous)->combining <= combining) {
+                break;
+            }
+            PyUnicode_WRITE(kind, data, j, previous);
+            j--;
+        }
+        if (j != i) {
+            PyUnicode_WRITE(kind, data, j, code);
+        }
+    }
+}
+
+static void
+canonical_ordering_sort_counting(int kind, void *data,
+                                 Py_ssize_t start, Py_ssize_t end,
+                                 Py_UCS4 *sortbuf)
+{
+    Py_ssize_t counts[256] = {0};
+    Py_ssize_t run_length = end - start;
+    Py_ssize_t total = 0;
+
+    for (Py_ssize_t i = start; i < end; i++) {
+        Py_UCS4 code = PyUnicode_READ(kind, data, i);
+        unsigned char combining = _getrecord_ex(code)->combining;
+        counts[combining]++;
+    }
+
+    for (size_t i = 0; i < Py_ARRAY_LENGTH(counts); i++) {
+        Py_ssize_t count = counts[i];
+        counts[i] = total;
+        total += count;
+    }
+
+    /* Reuse counts[] as the next output slot for each CCC. */
+    for (Py_ssize_t i = start; i < end; i++) {
+        Py_UCS4 code = PyUnicode_READ(kind, data, i);
+        unsigned char combining = _getrecord_ex(code)->combining;
+        sortbuf[counts[combining]++] = code;
+    }
+    for (Py_ssize_t i = 0; i < run_length; i++) {
+        PyUnicode_WRITE(kind, data, start + i, sortbuf[i]);
+    }
+}
+
 #define SBase   0xAC00
 #define LBase   0x1100
 #define VBase   0x1161
@@ -501,13 +559,16 @@
     PyObject *result;
     Py_UCS4 *output;
     Py_ssize_t i, o, osize;
-    int kind;
-    const void *data;
+    int input_kind, result_kind;
+    const void *input_data;
+    void *result_data;
     /* Longest decomposition in Unicode 3.2: U+FDFA */
     Py_UCS4 stack[20];
     Py_ssize_t space, isize;
     int index, prefix, count, stackptr;
     unsigned char prev, cur;
+    Py_UCS4 *sortbuf = NULL;
+    Py_ssize_t sortbuflen = 0;
 
     stackptr = 0;
     isize = PyUnicode_GET_LENGTH(input);
@@ -527,11 +588,11 @@
         return NULL;
     }
     i = o = 0;
-    kind = PyUnicode_KIND(input);
-    data = PyUnicode_DATA(input);
+    input_kind = PyUnicode_KIND(input);
+    input_data = PyUnicode_DATA(input);
 
     while (i < isize) {
-        stack[stackptr++] = PyUnicode_READ(kind, data, i++);
+        stack[stackptr++] = PyUnicode_READ(input_kind, input_data, i++);
         while(stackptr) {
             Py_UCS4 code = stack[--stackptr];
             /* Hangul Decomposition adds three characters in
@@ -597,34 +658,64 @@
     if (!result)
         return NULL;
     /* result is guaranteed to be ready, as it is compact. */
-    kind = PyUnicode_KIND(result);
-    data = PyUnicode_DATA(result);
+    result_kind = PyUnicode_KIND(result);
+    result_data = PyUnicode_DATA(result);
 
-    /* Sort canonically. */
+    /* Sort each consecutive combining-character run canonically. */
     i = 0;
-    prev = _getrecord_ex(PyUnicode_READ(kind, data, i))->combining;
-    for (i++; i < PyUnicode_GET_LENGTH(result); i++) {
-        cur = _getrecord_ex(PyUnicode_READ(kind, data, i))->combining;
-        if (prev == 0 || cur == 0 || prev <= cur) {
-            prev = cur;
+    while (i < o) {
+        Py_ssize_t run_length, run_start;
+        int needs_sort = 0;
+
+        Py_UCS4 ch = PyUnicode_READ(result_kind, result_data, i);
+        prev = _getrecord_ex(ch)->combining;
+        if (prev == 0) {
+            i++;
             continue;
         }
-        /* Non-canonical order. Need to switch *i with previous. */
-        o = i - 1;
-        while (1) {
-            Py_UCS4 tmp = PyUnicode_READ(kind, data, o+1);
-            PyUnicode_WRITE(kind, data, o+1,
-                            PyUnicode_READ(kind, data, o));
-            PyUnicode_WRITE(kind, data, o, tmp);
-            o--;
-            if (o < 0)
-                break;
-            prev = _getrecord_ex(PyUnicode_READ(kind, data, o))->combining;
-            if (prev == 0 || prev <= cur)
+
+        run_start = i++;
+        while (i < o) {
+            Py_UCS4 ch = PyUnicode_READ(result_kind, result_data, i);
+            cur = _getrecord_ex(ch)->combining;
+            if (cur == 0) {
                 break;
+            }
+            if (prev > cur) {
+                needs_sort = 1;
+            }
+            prev = cur;
+            i++;
+        }
+        if (!needs_sort) {
+            continue;
+        }
+
+        run_length = i - run_start;
+        if (run_length < CANONICAL_ORDERING_COUNTING_SORT_THRESHOLD) {
+            canonical_ordering_sort_insertion(result_kind, result_data,
+                                              run_start, i);
+            continue;
         }
-        prev = _getrecord_ex(PyUnicode_READ(kind, data, i))->combining;
+
+        if (run_length > sortbuflen) {
+            Py_UCS4 *new_sortbuf = PyMem_Resize(sortbuf,
+                                                Py_UCS4,
+                                                run_length);
+            if (new_sortbuf == NULL) {
+                PyErr_NoMemory();
+                PyMem_Free(sortbuf);
+                Py_DECREF(result);
+                return NULL;
+            }
+            sortbuf = new_sortbuf;
+            sortbuflen = run_length;
+        }
+
+        canonical_ordering_sort_counting(result_kind, result_data,
+                                         run_start, i, sortbuf);
     }
+    PyMem_Free(sortbuf);
     return result;
 }
 

++++++ CVE-2026-4360-filter_function-TarFile-extractone.patch ++++++
>From 1924e21bf78f27a34a21d3c9c83310dbaa78be45 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <[email protected]>
Date: Mon, 29 Jun 2026 17:41:26 +0200
Subject: [PATCH 1/2] gh-151987: Pass filter_function to TarFile._extract_one()
 during .extract() (GH-151988)

(cherry picked from commit 7ccdbaba2c54250a70d7f25632152df7655a5e0a)

Co-authored-by: Petr Viktorin <[email protected]>
Co-authored-by: Seth Michael Larson <[email protected]>
---
 Lib/tarfile.py                                                           |    
3 
 Lib/test/test_tarfile.py                                                 |   
90 ++++++++++
 Misc/NEWS.d/next/Security/2026-06-23-14-19-30.gh-issue-151987.8mNIMf.rst |    
2 
 3 files changed, 94 insertions(+), 1 deletion(-)
 create mode 100644 
Misc/NEWS.d/next/Security/2026-06-23-14-19-30.gh-issue-151987.8mNIMf.rst

Index: Python-3.10.20/Lib/tarfile.py
===================================================================
--- Python-3.10.20.orig/Lib/tarfile.py  2026-07-31 17:22:30.939669742 +0200
+++ Python-3.10.20/Lib/tarfile.py       2026-07-31 17:22:31.027704182 +0200
@@ -2392,7 +2392,8 @@
         tarinfo, unfiltered = self._get_extract_tarinfo(
             member, filter_function, path)
         if tarinfo is not None:
-            self._extract_one(tarinfo, path, set_attrs, numeric_owner)
+            self._extract_one(tarinfo, path, set_attrs, numeric_owner,
+                              filter_function=filter_function)
 
     def _get_extract_tarinfo(self, member, filter_function, path):
         """Get (filtered, unfiltered) TarInfos from *member*
Index: Python-3.10.20/Lib/test/test_tarfile.py
===================================================================
--- Python-3.10.20.orig/Lib/test/test_tarfile.py        2026-07-31 
17:22:30.941669763 +0200
+++ Python-3.10.20/Lib/test/test_tarfile.py     2026-07-31 17:22:31.028402749 
+0200
@@ -3984,6 +3984,96 @@
                     st_mode = cc.outerdir.stat().st_mode
                     self.assertNotEqual(st_mode & 0o777, 0o777)
 
+    @unittest.skipUnless(hasattr(os, 'chown'), "missing os.chown")
+    @unittest.skipUnless(hasattr(os, 'lchown'), "missing os.lchown")
+    @unittest.skipUnless(hasattr(os, 'geteuid'), "missing os.geteuid")
+    @support.subTests('link_type', (tarfile.SYMTYPE, tarfile.LNKTYPE))
+    def test_chown_links_on_extract(self, link_type):
+        with ArchiveMaker() as arc:
+            arc.add("test.txt",
+                    uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
+            arc.add("link",
+                    type=link_type,
+                    linkname='test.txt',
+                    uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
+
+        with (
+            os_helper.temp_dir() as tmpdir,
+            arc.open() as tar,
+            unittest.mock.patch("os.chown") as mock_chown,
+            unittest.mock.patch("os.lchown") as mock_lchown,
+            unittest.mock.patch("os.geteuid") as mock_geteuid,
+        ):
+            # Set UID to 0 so chown() is attempted.
+            mock_geteuid.return_value = 0
+            tar.extract("link", path=tmpdir, filter='data')
+            extract_path = os.path.join(tmpdir, "link")
+
+            if link_type == tarfile.SYMTYPE:
+                mock_chown.assert_not_called()
+                mock_lchown.assert_called_once_with(extract_path, -1, -1)
+            else:
+                mock_chown.assert_has_calls([
+                    unittest.mock.call(extract_path, -1, -1),
+                    unittest.mock.call(extract_path, -1, -1)
+                ])
+                mock_lchown.assert_not_called()
+
+    @unittest.skipUnless(hasattr(os, 'chown'), "missing os.chown")
+    @unittest.skipUnless(hasattr(os, 'lchown'), "missing os.lchown")
+    @unittest.skipUnless(hasattr(os, 'geteuid'), "missing os.geteuid")
+    @support.subTests('link_type', (tarfile.SYMTYPE, tarfile.LNKTYPE))
+    def test_chown_links_on_extractall(self, link_type):
+        with ArchiveMaker() as arc:
+            arc.add("test.txt",
+                    uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
+            arc.add("link",
+                    type=link_type,
+                    linkname='test.txt',
+                    uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
+
+        with (
+            os_helper.temp_dir() as tmpdir,
+            arc.open() as tar,
+            unittest.mock.patch("os.chown") as mock_chown,
+            unittest.mock.patch("os.lchown") as mock_lchown,
+            unittest.mock.patch("os.geteuid") as mock_geteuid,
+        ):
+            # Set UID to 0 so chown() is attempted.
+            mock_geteuid.return_value = 0
+            tar.extractall(path=tmpdir, filter='data')
+            extract_link_path = os.path.join(tmpdir, "link")
+            extract_file_path = os.path.join(tmpdir, "test.txt")
+
+            if link_type == tarfile.SYMTYPE:
+                mock_chown.assert_called_once_with(extract_file_path, -1, -1)
+                mock_lchown.assert_called_once_with(extract_link_path, -1, -1)
+            else:
+                mock_chown.assert_has_calls([
+                    unittest.mock.call(extract_file_path, -1, -1),
+                    unittest.mock.call(extract_link_path, -1, -1)
+                ])
+                mock_lchown.assert_not_called()
+
+    def test_extract_filters_target(self):
+        # Test that when extract() falls back to extracting (rather than
+        # linking) a hardlink target, it filters the target.
+        with ArchiveMaker() as arc:
+            arc.add("target")
+            arc.add("link", hardlink_to="target")
+        def testing_filter(member, path):
+            if member.name == 'target':
+                # target: set read-only
+                return member.replace(mode=stat.S_IRUSR)
+            # link: don't overwrite the mode
+            return member.replace(mode=None)
+        tempdir = pathlib.Path(TEMPDIR) / 'extract'
+        with os_helper.temp_dir(tempdir), arc.open() as tar:
+            tar.extract("link", path=tempdir, filter=testing_filter)
+            path = tempdir / 'link'
+            if sys.platform != 'win32':
+                self.assertFalse(path.stat().st_mode & stat.S_IWUSR)
+
     def test_link_fallback_normalizes(self):
         # Make sure hardlink fallbacks work for non-normalized paths for all
         # filters
Index: 
Python-3.10.20/Misc/NEWS.d/next/Security/2026-06-23-14-19-30.gh-issue-151987.8mNIMf.rst
===================================================================
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
+++ 
Python-3.10.20/Misc/NEWS.d/next/Security/2026-06-23-14-19-30.gh-issue-151987.8mNIMf.rst
     2026-07-31 17:22:31.028929563 +0200
@@ -0,0 +1,2 @@
+The :meth:`tarfile.TarFile.extract` method now applies the given filter when
+it extracts a link target from the archive as a fallback.

++++++ CVE-2026-7774-tarfile-data_filter-symlink.patch ++++++
>From 6fd7650c9a5984e8914d79f4cc11e1e291aeeb74 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <[email protected]>
Date: Fri, 8 May 2026 14:16:06 +0200
Subject: [PATCH] [3.10] gh-149486: tarfile.data_filter: validate written link
 target (GH-149487)

The data filter rewrote linknames with normpath() but ran the
containment check against the un-normalised value, and computed a
symlink's directory before stripping trailing slashes.  Both let a
crafted archive create links pointing outside the destination.  Also
reject link members that resolve to the destination directory itself,
which could otherwise replace it with a symlink and redirect all
subsequent members.

(Patch by Greg; Petr's just reviewing & merging.)
(cherry picked from commit 578411982c16f753f4893532510099ef665117da)

Co-authored-by: Petr Viktorin <[email protected]>
Co-authored-by: Gregory P. Smith <[email protected]>
---
 Lib/tarfile.py                                                           |   
16 -
 Lib/test/test_tarfile.py                                                 |  
147 +++++++---
 Misc/NEWS.d/next/Security/2026-05-03-21-00-00.gh-issue-149486.tarflt.rst |    
5 
 3 files changed, 129 insertions(+), 39 deletions(-)
 create mode 100644 
Misc/NEWS.d/next/Security/2026-05-03-21-00-00.gh-issue-149486.tarflt.rst

Index: Python-3.10.20/Lib/tarfile.py
===================================================================
--- Python-3.10.20.orig/Lib/tarfile.py  2026-07-31 17:27:30.484847898 +0200
+++ Python-3.10.20/Lib/tarfile.py       2026-07-31 17:27:30.511720742 +0200
@@ -815,16 +815,22 @@
         if member.islnk() or member.issym():
             if os.path.isabs(member.linkname):
                 raise AbsoluteLinkError(member)
+            # A link member that resolves to the destination directory itself
+            # would replace it with a (sym)link, redirecting the destination
+            # for all subsequent members.
+            if target_path == dest_path:
+                raise OutsideDestinationError(member, target_path)
             normalized = os.path.normpath(member.linkname)
             if normalized != member.linkname:
                 new_attrs['linkname'] = normalized
             if member.issym():
-                target_path = os.path.join(dest_path,
-                                           os.path.dirname(name),
-                                           member.linkname)
+                # The symlink is created at `name` with trailing separators
+                # stripped, so its target is relative to the directory
+                # containing that path.
+                link_dir = os.path.dirname(name.rstrip('/' + os.sep))
+                target_path = os.path.join(dest_path, link_dir, normalized)
             else:
-                target_path = os.path.join(dest_path,
-                                           member.linkname)
+                target_path = os.path.join(dest_path, normalized)
             target_path = os.path.realpath(target_path,
                                            strict=os.path.ALLOW_MISSING)
             if os.path.commonpath([target_path, dest_path]) != dest_path:
Index: Python-3.10.20/Lib/test/test_tarfile.py
===================================================================
--- Python-3.10.20.orig/Lib/test/test_tarfile.py        2026-07-31 
17:27:30.485346378 +0200
+++ Python-3.10.20/Lib/test/test_tarfile.py     2026-07-31 17:27:30.512820955 
+0200
@@ -3357,6 +3357,39 @@
     # The destination for the extraction, within `outerdir`
     destdir = outerdir / 'dest'
 
+    @classmethod
+    def setUpClass(cls):
+        # Posix and Windows have different pathname resolution:
+        # either symlink or a '..' component resolve first.
+        # Let's see which we are on.
+        if os_helper.can_symlink():
+            testpath = os.path.join(TEMPDIR, 'resolution_test')
+            os.mkdir(testpath)
+
+            # testpath/current links to `.` which is all of:
+            #   - `testpath`
+            #   - `testpath/current`
+            #   - `testpath/current/current`
+            #   - etc.
+            os.symlink('.', os.path.join(testpath, 'current'))
+
+            # we'll test where `testpath/current/../file` ends up
+            with open(os.path.join(testpath, 'current', '..', 'file'), 'w'):
+                pass
+
+            if os.path.exists(os.path.join(testpath, 'file')):
+                # Windows collapses 'current\..' to '.' first, leaving
+                # 'testpath\file'
+                cls.dotdot_resolves_early = True
+            elif os.path.exists(os.path.join(testpath, '..', 'file')):
+                # Posix resolves 'current' to '.' first, leaving
+                # 'testpath/../file'
+                cls.dotdot_resolves_early = False
+            else:
+                raise AssertionError('Could not determine link resolution')
+        else:
+            cls.dotdot_resolves_early = False
+
     @contextmanager
     def check_context(self, tar, filter, *, check_flag=True, ignored_trees=()):
         """Extracts `tar` to `self.destdir` and allows checking the result
@@ -3538,10 +3571,19 @@
                     + "which is outside the destination")
 
             with self.check_context(arc.open(), 'data'):
-                self.expect_exception(
-                    tarfile.LinkOutsideDestinationError,
-                    """'parent' would link to ['"].*outerdir['"], """
-                    + "which is outside the destination")
+                if self.dotdot_resolves_early:
+                    # 'current/../..' normalises to '..', which is rejected.
+                    self.expect_exception(
+                        tarfile.LinkOutsideDestinationError,
+                        """'parent' would link to ['"].*outerdir['"], """
+                        + "which is outside the destination")
+                else:
+                    # 'current/..' normalises to '.'; the rewritten link is
+                    # created and 'parent/evil' lands harmlessly inside the
+                    # destination.
+                    self.expect_file('current', symlink_to='.')
+                    self.expect_file('parent', symlink_to='.')
+                    self.expect_file('evil')
 
         else:
             # No symlink support. The symlinks are ignored.
@@ -3628,35 +3670,6 @@
         # Test interplaying symlinks
         # Inspired by 'dirsymlink2b' in jwilk/traversal-archives
 
-        # Posix and Windows have different pathname resolution:
-        # either symlink or a '..' component resolve first.
-        # Let's see which we are on.
-        if os_helper.can_symlink():
-            testpath = os.path.join(TEMPDIR, 'resolution_test')
-            os.mkdir(testpath)
-
-            # testpath/current links to `.` which is all of:
-            #   - `testpath`
-            #   - `testpath/current`
-            #   - `testpath/current/current`
-            #   - etc.
-            os.symlink('.', os.path.join(testpath, 'current'))
-
-            # we'll test where `testpath/current/../file` ends up
-            with open(os.path.join(testpath, 'current', '..', 'file'), 'w'):
-                pass
-
-            if os.path.exists(os.path.join(testpath, 'file')):
-                # Windows collapses 'current\..' to '.' first, leaving
-                # 'testpath\file'
-                dotdot_resolves_early = True
-            elif os.path.exists(os.path.join(testpath, '..', 'file')):
-                # Posix resolves 'current' to '.' first, leaving
-                # 'testpath/../file'
-                dotdot_resolves_early = False
-            else:
-                raise AssertionError('Could not determine link resolution')
-
         with ArchiveMaker() as arc:
 
             # `current` links to `.` which is both the destination directory
@@ -3692,7 +3705,7 @@
 
         with self.check_context(arc.open(), 'data'):
             if os_helper.can_symlink():
-                if dotdot_resolves_early:
+                if self.dotdot_resolves_early:
                     # Fail when extracting a file outside destination
                     self.expect_exception(
                             tarfile.OutsideDestinationError,
@@ -3809,6 +3822,72 @@
                     + """['"].*moo['"], which is outside the """
                     + "destination")
 
+    @os_helper.skip_unless_symlink
+    def test_normpath_realpath_mismatch(self):
+        # The link-target check must validate the value that will actually
+        # be written to disk (the normalised linkname), not the original.
+        # Here 'a' is a symlink to a deep nonexistent path, so realpath()
+        # of 'a/../../...' stays inside the destination while normpath()
+        # collapses 'a/..' lexically and escapes.
+        depth = len(self.destdir.parts) + 5
+        deep = '/'.join(f'p{i}' for i in range(depth))
+        sneaky = 'a/' + '../' * depth + 'flag'
+        for kind in 'symlink_to', 'hardlink_to':
+            with self.subTest(kind):
+                with ArchiveMaker() as arc:
+                    arc.add('a', symlink_to=deep)
+                    arc.add('escape', **{kind: sneaky})
+                with self.check_context(arc.open(), 'data'):
+                    self.expect_exception(
+                        tarfile.LinkOutsideDestinationError)
+
+    @os_helper.skip_unless_symlink
+    def test_symlink_trailing_slash(self):
+        # A trailing slash on a symlink member's name must not cause the
+        # link target to be resolved relative to the wrong directory.
+        with ArchiveMaker() as arc:
+            t = tarfile.TarInfo('x/')
+            t.type = tarfile.SYMTYPE
+            t.linkname = '..'
+            arc.tar_w.addfile(t)
+            arc.add('x/escaped', content='hi')
+
+        with self.check_context(arc.open(), 'data'):
+            self.expect_exception(tarfile.LinkOutsideDestinationError)
+
+    @os_helper.skip_unless_symlink
+    def test_link_at_destination(self):
+        # A link member whose name resolves to the destination directory
+        # itself must be rejected: otherwise the destination is replaced
+        # by a symlink and later members can be redirected through it.
+        for name in '', '.', './':
+            with ArchiveMaker() as arc:
+                t = tarfile.TarInfo(name)
+                t.type = tarfile.SYMTYPE
+                t.linkname = '.'
+                arc.tar_w.addfile(t)
+
+            with self.check_context(arc.open(), 'data'):
+                self.expect_exception(tarfile.OutsideDestinationError)
+
+    @os_helper.skip_unless_symlink
+    def test_empty_name_symlink_chain(self):
+        # Regression test for a chain of empty-named symlinks that
+        # incrementally redirects the destination outwards.
+        with ArchiveMaker() as arc:
+            for name, target in [('', ''), ('a/', '..'),
+                                 ('', 'dummy'), ('', 'a'),
+                                 ('b/', '..'),
+                                 ('', 'dummy'), ('', 'a/b')]:
+                t = tarfile.TarInfo(name)
+                t.type = tarfile.SYMTYPE
+                t.linkname = target
+                arc.tar_w.addfile(t)
+            arc.add('escaped', content='hi')
+
+        with self.check_context(arc.open(), 'data'):
+            self.expect_exception(tarfile.FilterError)
+
     def test_deep_symlink(self):
         # Test that symlinks and hardlinks inside a directory
         # point to the correct file (`target` of size 3).
Index: 
Python-3.10.20/Misc/NEWS.d/next/Security/2026-05-03-21-00-00.gh-issue-149486.tarflt.rst
===================================================================
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
+++ 
Python-3.10.20/Misc/NEWS.d/next/Security/2026-05-03-21-00-00.gh-issue-149486.tarflt.rst
     2026-07-31 17:27:30.513348800 +0200
@@ -0,0 +1,5 @@
+:func:`tarfile.data_filter` now validates link targets using the same
+normalised value that is written to disk, strips trailing separators from
+the member name when resolving a symlink's directory, and rejects link
+members that would replace the destination directory itself. This closes
+several path-traversal bypasses of the ``data`` extraction filter.

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.fk7mtj/_old  2026-08-01 18:37:24.576518636 +0200
+++ /var/tmp/diff_new_pack.fk7mtj/_new  2026-08-01 18:37:24.576518636 +0200
@@ -1,6 +1,6 @@
-mtime: 1784043422
-commit: e7fbc1cb0a65ff2836b5d55d60cef3c526783db3d670d0f5cf7a8a8876a0ef3b
+mtime: 1785511873
+commit: 44151aa6dc4ed3d66728b146b9ab9c31e458b54ff070faedce2fd7334b4ac33c
 url: https://src.opensuse.org/python-interpreters/python310
-revision: e7fbc1cb0a65ff2836b5d55d60cef3c526783db3d670d0f5cf7a8a8876a0ef3b
+revision: 44151aa6dc4ed3d66728b146b9ab9c31e458b54ff070faedce2fd7334b4ac33c
 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-07-31 17:31:13.000000000 +0200
@@ -0,0 +1,7 @@
+.osc
+*.obscpio
+*.osc
+_build.*
+.pbuild
+*.rej
+python310*-build/


++++++ support-sphinx-9.patch ++++++
>From e7eedd63aa09cd59f13c017084a91ad3591b0e16 Mon Sep 17 00:00:00 2001
From: Adam Turner <[email protected]>
Date: Sat, 29 Nov 2025 00:49:46 +0000
Subject: [PATCH] Prepare for Sphinx 9

---
 Doc/howto/urllib2.rst                   |    2 +-
 Doc/library/socket.rst                  |    7 ++-----
 Doc/tools/extensions/glossary_search.py |    2 +-
 3 files changed, 4 insertions(+), 7 deletions(-)

Index: Python-3.10.20/Doc/howto/urllib2.rst
===================================================================
--- Python-3.10.20.orig/Doc/howto/urllib2.rst   2026-03-03 01:49:35.000000000 
+0100
+++ Python-3.10.20/Doc/howto/urllib2.rst        2026-07-27 22:39:02.469360986 
+0200
@@ -22,7 +22,7 @@
     You may also find useful the following article on fetching web resources
     with Python:
 
-    * `Basic Authentication 
<https://web.archive.org/web/20201215133350/http://www.voidspace.org.uk/python/articles/authentication.shtml>`_
+    * `Basic Authentication 
<https://web.archive.org/web/20201215133350/http://www.voidspace.org.uk/python/articles/authentication.shtml>`__
 
         A tutorial on *Basic Authentication*, with examples in Python.
 
Index: Python-3.10.20/Doc/library/socket.rst
===================================================================
--- Python-3.10.20.orig/Doc/library/socket.rst  2026-07-27 22:38:58.293999697 
+0200
+++ Python-3.10.20/Doc/library/socket.rst       2026-07-27 22:39:02.470643429 
+0200
@@ -1729,11 +1729,8 @@
       :attr:`socket.type`.
 
 
-.. method:: socket.setsockopt(level, optname, value: int)
-.. method:: socket.setsockopt(level, optname, value: buffer)
-   :noindex:
-.. method:: socket.setsockopt(level, optname, None, optlen: int)
-   :noindex:
+.. method:: socket.setsockopt(level, optname, value: int | Buffer)
+            socket.setsockopt(level, optname, None, optlen: int)
 
    .. index:: pair: module; struct
 
Index: Python-3.10.20/Doc/tools/extensions/glossary_search.py
===================================================================
--- Python-3.10.20.orig/Doc/tools/extensions/glossary_search.py 2026-03-03 
01:49:35.000000000 +0100
+++ Python-3.10.20/Doc/tools/extensions/glossary_search.py      2026-07-27 
22:39:48.150081477 +0200
@@ -33,7 +33,7 @@
             rendered = app.builder.render_partial(definition)
             terms[term] = {
                 'title': glossary_item[0].astext(),
-                'body': rendered['html_body']
+                'body': rendered['fragment']
             }
 
     if hasattr(app.env, 'glossary_terms'):

Reply via email to