diff --git a/Python-3.11.6.tar.xz b/Python-3.11.10.tar.xz similarity index 68% rename from Python-3.11.6.tar.xz rename to Python-3.11.10.tar.xz index eac8cb2617fee97e8f1c10ea36a6b95a1b35384d..2d484f18583d49884eb5609a12e6d2b63be6a13c 100644 Binary files a/Python-3.11.6.tar.xz and b/Python-3.11.10.tar.xz differ diff --git a/backport-3.11-gh-115133-Fix-tests-for-XMLPullParser-with-Expa.patch b/backport-3.11-gh-115133-Fix-tests-for-XMLPullParser-with-Expa.patch deleted file mode 100644 index 94fde4b50bbf1fb198888cd51a8eabd8c8d23902..0000000000000000000000000000000000000000 --- a/backport-3.11-gh-115133-Fix-tests-for-XMLPullParser-with-Expa.patch +++ /dev/null @@ -1,112 +0,0 @@ -From 3501eca89e27873f6037abcb39e5031dfbce7077 Mon Sep 17 00:00:00 2001 -From: "Miss Islington (bot)" - <31488909+miss-islington@users.noreply.github.com> -Date: Sun, 11 Feb 2024 11:38:04 +0100 -Subject: [PATCH] [3.11] gh-115133: Fix tests for XMLPullParser with Expat - 2.6.0 (GH-115164) (GH-115289) - -Feeding the parser by too small chunks defers parsing to prevent -CVE-2023-52425. Future versions of Expat may be more reactive. -(cherry picked from commit 4a08e7b3431cd32a0daf22a33421cd3035343dc4) - -Co-authored-by: Serhiy Storchaka ---- - Lib/test/test_xml_etree.py | 58 ++++++++++++------- - ...-02-08-14-21-28.gh-issue-115133.ycl4ko.rst | 2 + - 2 files changed, 38 insertions(+), 22 deletions(-) - create mode 100644 Misc/NEWS.d/next/Library/2024-02-08-14-21-28.gh-issue-115133.ycl4ko.rst - -diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py -index 267982a823..fa03f381fa 100644 ---- a/Lib/test/test_xml_etree.py -+++ b/Lib/test/test_xml_etree.py -@@ -13,6 +13,7 @@ - import operator - import os - import pickle -+import pyexpat - import sys - import textwrap - import types -@@ -120,6 +121,10 @@ - - """ - -+fails_with_expat_2_6_0 = (unittest.expectedFailure -+ if pyexpat.version_info >= (2, 6, 0) else -+ lambda test: test) -+ - def checkwarnings(*filters, quiet=False): - def decorator(test): - def newtest(*args, **kwargs): -@@ -1400,28 +1405,37 @@ def assert_event_tags(self, parser, expected, max_events=None): - self.assertEqual([(action, elem.tag) for action, elem in events], - expected) - -- def test_simple_xml(self): -- for chunk_size in (None, 1, 5): -- with self.subTest(chunk_size=chunk_size): -- parser = ET.XMLPullParser() -- self.assert_event_tags(parser, []) -- self._feed(parser, "\n", chunk_size) -- self.assert_event_tags(parser, []) -- self._feed(parser, -- "\n text\n", chunk_size) -- self.assert_event_tags(parser, [('end', 'element')]) -- self._feed(parser, "texttail\n", chunk_size) -- self._feed(parser, "\n", chunk_size) -- self.assert_event_tags(parser, [ -- ('end', 'element'), -- ('end', 'empty-element'), -- ]) -- self._feed(parser, "\n", chunk_size) -- self.assert_event_tags(parser, [('end', 'root')]) -- self.assertIsNone(parser.close()) -+ def test_simple_xml(self, chunk_size=None): -+ parser = ET.XMLPullParser() -+ self.assert_event_tags(parser, []) -+ self._feed(parser, "\n", chunk_size) -+ self.assert_event_tags(parser, []) -+ self._feed(parser, -+ "\n text\n", chunk_size) -+ self.assert_event_tags(parser, [('end', 'element')]) -+ self._feed(parser, "texttail\n", chunk_size) -+ self._feed(parser, "\n", chunk_size) -+ self.assert_event_tags(parser, [ -+ ('end', 'element'), -+ ('end', 'empty-element'), -+ ]) -+ self._feed(parser, "\n", chunk_size) -+ self.assert_event_tags(parser, [('end', 'root')]) -+ self.assertIsNone(parser.close()) -+ -+ @fails_with_expat_2_6_0 -+ def test_simple_xml_chunk_1(self): -+ self.test_simple_xml(chunk_size=1) -+ -+ @fails_with_expat_2_6_0 -+ def test_simple_xml_chunk_5(self): -+ self.test_simple_xml(chunk_size=5) -+ -+ def test_simple_xml_chunk_22(self): -+ self.test_simple_xml(chunk_size=22) - - def test_feed_while_iterating(self): - parser = ET.XMLPullParser() -diff --git a/Misc/NEWS.d/next/Library/2024-02-08-14-21-28.gh-issue-115133.ycl4ko.rst b/Misc/NEWS.d/next/Library/2024-02-08-14-21-28.gh-issue-115133.ycl4ko.rst -new file mode 100644 -index 0000000000..6f1015235c ---- /dev/null -+++ b/Misc/NEWS.d/next/Library/2024-02-08-14-21-28.gh-issue-115133.ycl4ko.rst -@@ -0,0 +1,2 @@ -+Fix tests for :class:`~xml.etree.ElementTree.XMLPullParser` with Expat -+2.6.0. --- -2.27.0 - diff --git a/backport-CVE-2023-6597-gh-91133-tempfile.TemporaryDirectory-fix-symlin.patch b/backport-CVE-2023-6597-gh-91133-tempfile.TemporaryDirectory-fix-symlin.patch deleted file mode 100644 index d931cac779ce6ca8880c22c5963d1b9bee77ea4a..0000000000000000000000000000000000000000 --- a/backport-CVE-2023-6597-gh-91133-tempfile.TemporaryDirectory-fix-symlin.patch +++ /dev/null @@ -1,218 +0,0 @@ -From 5585334d772b253a01a6730e8202ffb1607c3d25 Mon Sep 17 00:00:00 2001 -From: Serhiy Storchaka -Date: Thu, 7 Dec 2023 18:37:10 +0200 -Subject: [PATCH] [3.11] gh-91133: tempfile.TemporaryDirectory: fix symlink bug - in cleanup (GH-99930) (GH-112839) -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -(cherry picked from commit 81c16cd94ec38d61aa478b9a452436dc3b1b524d) - -Co-authored-by: Søren Løvborg ---- - Lib/tempfile.py | 27 +++-- - Lib/test/test_tempfile.py | 111 +++++++++++++++++- - ...2-12-01-16-57-44.gh-issue-91133.LKMVCV.rst | 2 + - 3 files changed, 125 insertions(+), 15 deletions(-) - create mode 100644 Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst - -diff --git a/Lib/tempfile.py b/Lib/tempfile.py -index aace11fa7b1..f59a63a7b45 100644 ---- a/Lib/tempfile.py -+++ b/Lib/tempfile.py -@@ -270,6 +270,22 @@ def _mkstemp_inner(dir, pre, suf, flags, output_type): - raise FileExistsError(_errno.EEXIST, - "No usable temporary file name found") - -+def _dont_follow_symlinks(func, path, *args): -+ # Pass follow_symlinks=False, unless not supported on this platform. -+ if func in _os.supports_follow_symlinks: -+ func(path, *args, follow_symlinks=False) -+ elif _os.name == 'nt' or not _os.path.islink(path): -+ func(path, *args) -+ -+def _resetperms(path): -+ try: -+ chflags = _os.chflags -+ except AttributeError: -+ pass -+ else: -+ _dont_follow_symlinks(chflags, path, 0) -+ _dont_follow_symlinks(_os.chmod, path, 0o700) -+ - - # User visible interfaces. - -@@ -863,17 +879,10 @@ def __init__(self, suffix=None, prefix=None, dir=None, - def _rmtree(cls, name, ignore_errors=False): - def onerror(func, path, exc_info): - if issubclass(exc_info[0], PermissionError): -- def resetperms(path): -- try: -- _os.chflags(path, 0) -- except AttributeError: -- pass -- _os.chmod(path, 0o700) -- - try: - if path != name: -- resetperms(_os.path.dirname(path)) -- resetperms(path) -+ _resetperms(_os.path.dirname(path)) -+ _resetperms(path) - - try: - _os.unlink(path) -diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py -index 1242ec7e3cc..675edc8de9c 100644 ---- a/Lib/test/test_tempfile.py -+++ b/Lib/test/test_tempfile.py -@@ -1565,6 +1565,103 @@ def test_cleanup_with_symlink_to_a_directory(self): - "were deleted") - d2.cleanup() - -+ @os_helper.skip_unless_symlink -+ def test_cleanup_with_symlink_modes(self): -+ # cleanup() should not follow symlinks when fixing mode bits (#91133) -+ with self.do_create(recurse=0) as d2: -+ file1 = os.path.join(d2, 'file1') -+ open(file1, 'wb').close() -+ dir1 = os.path.join(d2, 'dir1') -+ os.mkdir(dir1) -+ for mode in range(8): -+ mode <<= 6 -+ with self.subTest(mode=format(mode, '03o')): -+ def test(target, target_is_directory): -+ d1 = self.do_create(recurse=0) -+ symlink = os.path.join(d1.name, 'symlink') -+ os.symlink(target, symlink, -+ target_is_directory=target_is_directory) -+ try: -+ os.chmod(symlink, mode, follow_symlinks=False) -+ except NotImplementedError: -+ pass -+ try: -+ os.chmod(symlink, mode) -+ except FileNotFoundError: -+ pass -+ os.chmod(d1.name, mode) -+ d1.cleanup() -+ self.assertFalse(os.path.exists(d1.name)) -+ -+ with self.subTest('nonexisting file'): -+ test('nonexisting', target_is_directory=False) -+ with self.subTest('nonexisting dir'): -+ test('nonexisting', target_is_directory=True) -+ -+ with self.subTest('existing file'): -+ os.chmod(file1, mode) -+ old_mode = os.stat(file1).st_mode -+ test(file1, target_is_directory=False) -+ new_mode = os.stat(file1).st_mode -+ self.assertEqual(new_mode, old_mode, -+ '%03o != %03o' % (new_mode, old_mode)) -+ -+ with self.subTest('existing dir'): -+ os.chmod(dir1, mode) -+ old_mode = os.stat(dir1).st_mode -+ test(dir1, target_is_directory=True) -+ new_mode = os.stat(dir1).st_mode -+ self.assertEqual(new_mode, old_mode, -+ '%03o != %03o' % (new_mode, old_mode)) -+ -+ @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags') -+ @os_helper.skip_unless_symlink -+ def test_cleanup_with_symlink_flags(self): -+ # cleanup() should not follow symlinks when fixing flags (#91133) -+ flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK -+ self.check_flags(flags) -+ -+ with self.do_create(recurse=0) as d2: -+ file1 = os.path.join(d2, 'file1') -+ open(file1, 'wb').close() -+ dir1 = os.path.join(d2, 'dir1') -+ os.mkdir(dir1) -+ def test(target, target_is_directory): -+ d1 = self.do_create(recurse=0) -+ symlink = os.path.join(d1.name, 'symlink') -+ os.symlink(target, symlink, -+ target_is_directory=target_is_directory) -+ try: -+ os.chflags(symlink, flags, follow_symlinks=False) -+ except NotImplementedError: -+ pass -+ try: -+ os.chflags(symlink, flags) -+ except FileNotFoundError: -+ pass -+ os.chflags(d1.name, flags) -+ d1.cleanup() -+ self.assertFalse(os.path.exists(d1.name)) -+ -+ with self.subTest('nonexisting file'): -+ test('nonexisting', target_is_directory=False) -+ with self.subTest('nonexisting dir'): -+ test('nonexisting', target_is_directory=True) -+ -+ with self.subTest('existing file'): -+ os.chflags(file1, flags) -+ old_flags = os.stat(file1).st_flags -+ test(file1, target_is_directory=False) -+ new_flags = os.stat(file1).st_flags -+ self.assertEqual(new_flags, old_flags) -+ -+ with self.subTest('existing dir'): -+ os.chflags(dir1, flags) -+ old_flags = os.stat(dir1).st_flags -+ test(dir1, target_is_directory=True) -+ new_flags = os.stat(dir1).st_flags -+ self.assertEqual(new_flags, old_flags) -+ - @support.cpython_only - def test_del_on_collection(self): - # A TemporaryDirectory is deleted when garbage collected -@@ -1737,10 +1834,7 @@ def test_modes(self): - d.cleanup() - self.assertFalse(os.path.exists(d.name)) - -- @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags') -- def test_flags(self): -- flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK -- -+ def check_flags(self, flags): - # skip the test if these flags are not supported (ex: FreeBSD 13) - filename = os_helper.TESTFN - try: -@@ -1749,13 +1843,18 @@ def test_flags(self): - os.chflags(filename, flags) - except OSError as exc: - # "OSError: [Errno 45] Operation not supported" -- self.skipTest(f"chflags() doesn't support " -- f"UF_IMMUTABLE|UF_NOUNLINK: {exc}") -+ self.skipTest(f"chflags() doesn't support flags " -+ f"{flags:#b}: {exc}") - else: - os.chflags(filename, 0) - finally: - os_helper.unlink(filename) - -+ @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags') -+ def test_flags(self): -+ flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK -+ self.check_flags(flags) -+ - d = self.do_create(recurse=3, dirs=2, files=2) - with d: - # Change files and directories flags recursively. -diff --git a/Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst b/Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst -new file mode 100644 -index 00000000000..7991048fc48 ---- /dev/null -+++ b/Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst -@@ -0,0 +1,2 @@ -+Fix a bug in :class:`tempfile.TemporaryDirectory` cleanup, which now no longer -+dereferences symlinks when working around file system permission errors. --- -2.33.0 - diff --git a/backport-CVE-2024-0397-gh-114572-Fix-locking-in-cert_store_stats-and-g.patch b/backport-CVE-2024-0397-gh-114572-Fix-locking-in-cert_store_stats-and-g.patch deleted file mode 100644 index c24367bfb28a917f597be38a00e9fc54ab73082f..0000000000000000000000000000000000000000 --- a/backport-CVE-2024-0397-gh-114572-Fix-locking-in-cert_store_stats-and-g.patch +++ /dev/null @@ -1,156 +0,0 @@ -From 01c37f1d0714f5822d34063ca7180b595abf589d Mon Sep 17 00:00:00 2001 -From: "Miss Islington (bot)" - <31488909+miss-islington@users.noreply.github.com> -Date: Tue, 20 Feb 2024 17:34:44 +0100 -Subject: [PATCH] [3.11] gh-114572: Fix locking in cert_store_stats and - get_ca_certs (GH-114573) (#115549) - -gh-114572: Fix locking in cert_store_stats and get_ca_certs (GH-114573) - -* gh-114572: Fix locking in cert_store_stats and get_ca_certs - -cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with -X509_STORE_get0_objects, but reading the result requires a lock. See -https://github.com/openssl/openssl/pull/23224 for details. - -Instead, use X509_STORE_get1_objects, newly added in that PR. -X509_STORE_get1_objects does not exist in current OpenSSLs, but we can -polyfill it with X509_STORE_lock and X509_STORE_unlock. - -* Work around const-correctness problem - -* Add missing X509_STORE_get1_objects failure check - -* Add blurb -(cherry picked from commit bce693111bff906ccf9281c22371331aaff766ab) - -Co-authored-by: David Benjamin ---- - ...-01-26-22-14-09.gh-issue-114572.t1QMQD.rst | 4 ++ - Modules/_ssl.c | 65 +++++++++++++++++-- - 2 files changed, 64 insertions(+), 5 deletions(-) - create mode 100644 Misc/NEWS.d/next/Security/2024-01-26-22-14-09.gh-issue-114572.t1QMQD.rst - -diff --git a/Misc/NEWS.d/next/Security/2024-01-26-22-14-09.gh-issue-114572.t1QMQD.rst b/Misc/NEWS.d/next/Security/2024-01-26-22-14-09.gh-issue-114572.t1QMQD.rst -new file mode 100644 -index 0000000000..b4f9fe64db ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2024-01-26-22-14-09.gh-issue-114572.t1QMQD.rst -@@ -0,0 +1,4 @@ -+:meth:`ssl.SSLContext.cert_store_stats` and -+:meth:`ssl.SSLContext.get_ca_certs` now correctly lock access to the -+certificate store, when the :class:`ssl.SSLContext` is shared across -+multiple threads. -diff --git a/Modules/_ssl.c b/Modules/_ssl.c -index 67ce6e97af..81d36a6f11 100644 ---- a/Modules/_ssl.c -+++ b/Modules/_ssl.c -@@ -4529,6 +4529,50 @@ set_sni_callback(PySSLContext *self, PyObject *arg, void *c) - return 0; - } - -+#if OPENSSL_VERSION_NUMBER < 0x30300000L -+static X509_OBJECT *x509_object_dup(const X509_OBJECT *obj) -+{ -+ int ok; -+ X509_OBJECT *ret = X509_OBJECT_new(); -+ if (ret == NULL) { -+ return NULL; -+ } -+ switch (X509_OBJECT_get_type(obj)) { -+ case X509_LU_X509: -+ ok = X509_OBJECT_set1_X509(ret, X509_OBJECT_get0_X509(obj)); -+ break; -+ case X509_LU_CRL: -+ /* X509_OBJECT_get0_X509_CRL was not const-correct prior to 3.0.*/ -+ ok = X509_OBJECT_set1_X509_CRL( -+ ret, X509_OBJECT_get0_X509_CRL((X509_OBJECT *)obj)); -+ break; -+ default: -+ /* We cannot duplicate unrecognized types in a polyfill, but it is -+ * safe to leave an empty object. The caller will ignore it. */ -+ ok = 1; -+ break; -+ } -+ if (!ok) { -+ X509_OBJECT_free(ret); -+ return NULL; -+ } -+ return ret; -+} -+ -+static STACK_OF(X509_OBJECT) * -+X509_STORE_get1_objects(X509_STORE *store) -+{ -+ STACK_OF(X509_OBJECT) *ret; -+ if (!X509_STORE_lock(store)) { -+ return NULL; -+ } -+ ret = sk_X509_OBJECT_deep_copy(X509_STORE_get0_objects(store), -+ x509_object_dup, X509_OBJECT_free); -+ X509_STORE_unlock(store); -+ return ret; -+} -+#endif -+ - PyDoc_STRVAR(PySSLContext_sni_callback_doc, - "Set a callback that will be called when a server name is provided by the SSL/TLS client in the SNI extension.\n\ - \n\ -@@ -4558,7 +4602,12 @@ _ssl__SSLContext_cert_store_stats_impl(PySSLContext *self) - int x509 = 0, crl = 0, ca = 0, i; - - store = SSL_CTX_get_cert_store(self->ctx); -- objs = X509_STORE_get0_objects(store); -+ objs = X509_STORE_get1_objects(store); -+ if (objs == NULL) { -+ PyErr_SetString(PyExc_MemoryError, "failed to query cert store"); -+ return NULL; -+ } -+ - for (i = 0; i < sk_X509_OBJECT_num(objs); i++) { - obj = sk_X509_OBJECT_value(objs, i); - switch (X509_OBJECT_get_type(obj)) { -@@ -4572,12 +4621,11 @@ _ssl__SSLContext_cert_store_stats_impl(PySSLContext *self) - crl++; - break; - default: -- /* Ignore X509_LU_FAIL, X509_LU_RETRY, X509_LU_PKEY. -- * As far as I can tell they are internal states and never -- * stored in a cert store */ -+ /* Ignore unrecognized types. */ - break; - } - } -+ sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free); - return Py_BuildValue("{sisisi}", "x509", x509, "crl", crl, - "x509_ca", ca); - } -@@ -4609,7 +4657,12 @@ _ssl__SSLContext_get_ca_certs_impl(PySSLContext *self, int binary_form) - } - - store = SSL_CTX_get_cert_store(self->ctx); -- objs = X509_STORE_get0_objects(store); -+ objs = X509_STORE_get1_objects(store); -+ if (objs == NULL) { -+ PyErr_SetString(PyExc_MemoryError, "failed to query cert store"); -+ goto error; -+ } -+ - for (i = 0; i < sk_X509_OBJECT_num(objs); i++) { - X509_OBJECT *obj; - X509 *cert; -@@ -4637,9 +4690,11 @@ _ssl__SSLContext_get_ca_certs_impl(PySSLContext *self, int binary_form) - } - Py_CLEAR(ci); - } -+ sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free); - return rlist; - - error: -+ sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free); - Py_XDECREF(ci); - Py_XDECREF(rlist); - return NULL; --- -2.27.0 - diff --git a/backport-CVE-2024-0450-gh-109858-Protect-zipfile-from-quoted-overlap-z.patch b/backport-CVE-2024-0450-gh-109858-Protect-zipfile-from-quoted-overlap-z.patch deleted file mode 100644 index 03e5cc10774dff1aaa095b35b2164f3ac6635a91..0000000000000000000000000000000000000000 --- a/backport-CVE-2024-0450-gh-109858-Protect-zipfile-from-quoted-overlap-z.patch +++ /dev/null @@ -1,146 +0,0 @@ -From a956e510f6336d5ae111ba429a61c3ade30a7549 Mon Sep 17 00:00:00 2001 -From: "Miss Islington (bot)" - <31488909+miss-islington@users.noreply.github.com> -Date: Thu, 11 Jan 2024 10:24:47 +0100 -Subject: [PATCH] [3.11] gh-109858: Protect zipfile from "quoted-overlap" - zipbomb (GH-110016) (GH-113913) - -Raise BadZipFile when try to read an entry that overlaps with other entry or -central directory. -(cherry picked from commit 66363b9a7b9fe7c99eba3a185b74c5fdbf842eba) - -Co-authored-by: Serhiy Storchaka ---- - Lib/test/test_zipfile.py | 60 +++++++++++++++++++ - Lib/zipfile.py | 12 ++++ - ...-09-28-13-15-51.gh-issue-109858.43e2dg.rst | 3 + - 3 files changed, 75 insertions(+) - create mode 100644 Misc/NEWS.d/next/Library/2023-09-28-13-15-51.gh-issue-109858.43e2dg.rst - -diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py -index c8e0159765e..9354ab74faa 100644 ---- a/Lib/test/test_zipfile.py -+++ b/Lib/test/test_zipfile.py -@@ -2216,6 +2216,66 @@ def test_decompress_without_3rd_party_library(self): - with zipfile.ZipFile(zip_file) as zf: - self.assertRaises(RuntimeError, zf.extract, 'a.txt') - -+ @requires_zlib() -+ def test_full_overlap(self): -+ data = ( -+ b'PK\x03\x04\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e' -+ b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00a\xed' -+ b'\xc0\x81\x08\x00\x00\x00\xc00\xd6\xfbK\\d\x0b`P' -+ b'K\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2' -+ b'\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00' -+ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00aPK' -+ b'\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e' -+ b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00\x00' -+ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00bPK\x05' -+ b'\x06\x00\x00\x00\x00\x02\x00\x02\x00^\x00\x00\x00/\x00\x00' -+ b'\x00\x00\x00' -+ ) -+ with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf: -+ self.assertEqual(zipf.namelist(), ['a', 'b']) -+ zi = zipf.getinfo('a') -+ self.assertEqual(zi.header_offset, 0) -+ self.assertEqual(zi.compress_size, 16) -+ self.assertEqual(zi.file_size, 1033) -+ zi = zipf.getinfo('b') -+ self.assertEqual(zi.header_offset, 0) -+ self.assertEqual(zi.compress_size, 16) -+ self.assertEqual(zi.file_size, 1033) -+ self.assertEqual(len(zipf.read('a')), 1033) -+ with self.assertRaisesRegex(zipfile.BadZipFile, 'File name.*differ'): -+ zipf.read('b') -+ -+ @requires_zlib() -+ def test_quoted_overlap(self): -+ data = ( -+ b'PK\x03\x04\x14\x00\x00\x00\x08\x00\xa0lH\x05Y\xfc' -+ b'8\x044\x00\x00\x00(\x04\x00\x00\x01\x00\x00\x00a\x00' -+ b'\x1f\x00\xe0\xffPK\x03\x04\x14\x00\x00\x00\x08\x00\xa0l' -+ b'H\x05\xe2\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00' -+ b'\x00\x00b\xed\xc0\x81\x08\x00\x00\x00\xc00\xd6\xfbK\\' -+ b'd\x0b`PK\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0' -+ b'lH\x05Y\xfc8\x044\x00\x00\x00(\x04\x00\x00\x01' -+ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' -+ b'\x00aPK\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0l' -+ b'H\x05\xe2\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00' -+ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00\x00\x00' -+ b'bPK\x05\x06\x00\x00\x00\x00\x02\x00\x02\x00^\x00\x00' -+ b'\x00S\x00\x00\x00\x00\x00' -+ ) -+ with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf: -+ self.assertEqual(zipf.namelist(), ['a', 'b']) -+ zi = zipf.getinfo('a') -+ self.assertEqual(zi.header_offset, 0) -+ self.assertEqual(zi.compress_size, 52) -+ self.assertEqual(zi.file_size, 1064) -+ zi = zipf.getinfo('b') -+ self.assertEqual(zi.header_offset, 36) -+ self.assertEqual(zi.compress_size, 16) -+ self.assertEqual(zi.file_size, 1033) -+ with self.assertRaisesRegex(zipfile.BadZipFile, 'Overlapped entries'): -+ zipf.read('a') -+ self.assertEqual(len(zipf.read('b')), 1033) -+ - def tearDown(self): - unlink(TESTFN) - unlink(TESTFN2) -diff --git a/Lib/zipfile.py b/Lib/zipfile.py -index 6189db5e3e4..058d7163ea1 100644 ---- a/Lib/zipfile.py -+++ b/Lib/zipfile.py -@@ -367,6 +367,7 @@ class ZipInfo (object): - 'compress_size', - 'file_size', - '_raw_time', -+ '_end_offset', - ) - - def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): -@@ -408,6 +409,7 @@ def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): - self.external_attr = 0 # External file attributes - self.compress_size = 0 # Size of the compressed file - self.file_size = 0 # Size of the uncompressed file -+ self._end_offset = None # Start of the next local header or central directory - # Other attributes are set by class ZipFile: - # header_offset Byte offset to the file header - # CRC CRC-32 of the uncompressed file -@@ -1437,6 +1439,12 @@ def _RealGetContents(self): - if self.debug > 2: - print("total", total) - -+ end_offset = self.start_dir -+ for zinfo in sorted(self.filelist, -+ key=lambda zinfo: zinfo.header_offset, -+ reverse=True): -+ zinfo._end_offset = end_offset -+ end_offset = zinfo.header_offset - - def namelist(self): - """Return a list of file names in the archive.""" -@@ -1590,6 +1598,10 @@ def open(self, name, mode="r", pwd=None, *, force_zip64=False): - 'File name in directory %r and header %r differ.' - % (zinfo.orig_filename, fname)) - -+ if (zinfo._end_offset is not None and -+ zef_file.tell() + zinfo.compress_size > zinfo._end_offset): -+ raise BadZipFile(f"Overlapped entries: {zinfo.orig_filename!r} (possible zip bomb)") -+ - # check for encrypted flag & handle password - is_encrypted = zinfo.flag_bits & _MASK_ENCRYPTED - if is_encrypted: -diff --git a/Misc/NEWS.d/next/Library/2023-09-28-13-15-51.gh-issue-109858.43e2dg.rst b/Misc/NEWS.d/next/Library/2023-09-28-13-15-51.gh-issue-109858.43e2dg.rst -new file mode 100644 -index 00000000000..be279caffc4 ---- /dev/null -+++ b/Misc/NEWS.d/next/Library/2023-09-28-13-15-51.gh-issue-109858.43e2dg.rst -@@ -0,0 +1,3 @@ -+Protect :mod:`zipfile` from "quoted-overlap" zipbomb. It now raises -+BadZipFile when try to read an entry that overlaps with other entry or -+central directory. --- -2.33.0 - diff --git a/backport-CVE-2024-3219-1-gh-122133-Authenticate-socket-connection-for-so.patch b/backport-CVE-2024-3219-1-gh-122133-Authenticate-socket-connection-for-so.patch deleted file mode 100644 index 2911f7bdd9f1430ce8a95f0509485f82cd0ed7ed..0000000000000000000000000000000000000000 --- a/backport-CVE-2024-3219-1-gh-122133-Authenticate-socket-connection-for-so.patch +++ /dev/null @@ -1,218 +0,0 @@ -From 5f90abaa786f994db3907fc31e2ee00ea2cf0929 Mon Sep 17 00:00:00 2001 -From: "Miss Islington (bot)" - <31488909+miss-islington@users.noreply.github.com> -Date: Tue, 30 Jul 2024 14:43:45 +0200 -Subject: [PATCH] [3.11] gh-122133: Authenticate socket connection for - `socket.socketpair()` fallback (GH-122134) (#122426) - -Authenticate socket connection for `socket.socketpair()` fallback when the platform does not have a native `socketpair` C API. We authenticate in-process using `getsocketname` and `getpeername` (thanks to Nathaniel J Smith for that suggestion). - -(cherry picked from commit 78df1043dbdce5c989600616f9f87b4ee72944e5) - -Co-authored-by: Seth Michael Larson -Co-authored-by: Gregory P. Smith ---- - Lib/socket.py | 17 +++ - Lib/test/test_socket.py | 128 +++++++++++++++++- - ...-07-22-13-11-28.gh-issue-122133.0mPeta.rst | 5 + - 3 files changed, 147 insertions(+), 3 deletions(-) - create mode 100644 Misc/NEWS.d/next/Security/2024-07-22-13-11-28.gh-issue-122133.0mPeta.rst - -diff --git a/Lib/socket.py b/Lib/socket.py -index a0567b76bcf..591d4739a64 100644 ---- a/Lib/socket.py -+++ b/Lib/socket.py -@@ -648,6 +648,23 @@ def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0): - raise - finally: - lsock.close() -+ -+ # Authenticating avoids using a connection from something else -+ # able to connect to {host}:{port} instead of us. -+ # We expect only AF_INET and AF_INET6 families. -+ try: -+ if ( -+ ssock.getsockname() != csock.getpeername() -+ or csock.getsockname() != ssock.getpeername() -+ ): -+ raise ConnectionError("Unexpected peer connection") -+ except: -+ # getsockname() and getpeername() can fail -+ # if either socket isn't connected. -+ ssock.close() -+ csock.close() -+ raise -+ - return (ssock, csock) - __all__.append("socketpair") - -diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py -index 42adc573ecc..a60eb436c7b 100644 ---- a/Lib/test/test_socket.py -+++ b/Lib/test/test_socket.py -@@ -542,19 +542,27 @@ class SocketPairTest(unittest.TestCase, ThreadableTest): - def __init__(self, methodName='runTest'): - unittest.TestCase.__init__(self, methodName=methodName) - ThreadableTest.__init__(self) -+ self.cli = None -+ self.serv = None -+ -+ def socketpair(self): -+ # To be overridden by some child classes. -+ return socket.socketpair() - - def setUp(self): -- self.serv, self.cli = socket.socketpair() -+ self.serv, self.cli = self.socketpair() - - def tearDown(self): -- self.serv.close() -+ if self.serv: -+ self.serv.close() - self.serv = None - - def clientSetUp(self): - pass - - def clientTearDown(self): -- self.cli.close() -+ if self.cli: -+ self.cli.close() - self.cli = None - ThreadableTest.clientTearDown(self) - -@@ -4667,6 +4675,120 @@ def _testSend(self): - self.assertEqual(msg, MSG) - - -+class PurePythonSocketPairTest(SocketPairTest): -+ -+ # Explicitly use socketpair AF_INET or AF_INET6 to ensure that is the -+ # code path we're using regardless platform is the pure python one where -+ # `_socket.socketpair` does not exist. (AF_INET does not work with -+ # _socket.socketpair on many platforms). -+ def socketpair(self): -+ # called by super().setUp(). -+ try: -+ return socket.socketpair(socket.AF_INET6) -+ except OSError: -+ return socket.socketpair(socket.AF_INET) -+ -+ # Local imports in this class make for easy security fix backporting. -+ -+ def setUp(self): -+ import _socket -+ self._orig_sp = getattr(_socket, 'socketpair', None) -+ if self._orig_sp is not None: -+ # This forces the version using the non-OS provided socketpair -+ # emulation via an AF_INET socket in Lib/socket.py. -+ del _socket.socketpair -+ import importlib -+ global socket -+ socket = importlib.reload(socket) -+ else: -+ pass # This platform already uses the non-OS provided version. -+ super().setUp() -+ -+ def tearDown(self): -+ super().tearDown() -+ import _socket -+ if self._orig_sp is not None: -+ # Restore the default socket.socketpair definition. -+ _socket.socketpair = self._orig_sp -+ import importlib -+ global socket -+ socket = importlib.reload(socket) -+ -+ def test_recv(self): -+ msg = self.serv.recv(1024) -+ self.assertEqual(msg, MSG) -+ -+ def _test_recv(self): -+ self.cli.send(MSG) -+ -+ def test_send(self): -+ self.serv.send(MSG) -+ -+ def _test_send(self): -+ msg = self.cli.recv(1024) -+ self.assertEqual(msg, MSG) -+ -+ def test_ipv4(self): -+ cli, srv = socket.socketpair(socket.AF_INET) -+ cli.close() -+ srv.close() -+ -+ def _test_ipv4(self): -+ pass -+ -+ @unittest.skipIf(not hasattr(_socket, 'IPPROTO_IPV6') or -+ not hasattr(_socket, 'IPV6_V6ONLY'), -+ "IPV6_V6ONLY option not supported") -+ @unittest.skipUnless(socket_helper.IPV6_ENABLED, 'IPv6 required for this test') -+ def test_ipv6(self): -+ cli, srv = socket.socketpair(socket.AF_INET6) -+ cli.close() -+ srv.close() -+ -+ def _test_ipv6(self): -+ pass -+ -+ def test_injected_authentication_failure(self): -+ orig_getsockname = socket.socket.getsockname -+ inject_sock = None -+ -+ def inject_getsocketname(self): -+ nonlocal inject_sock -+ sockname = orig_getsockname(self) -+ # Connect to the listening socket ahead of the -+ # client socket. -+ if inject_sock is None: -+ inject_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -+ inject_sock.setblocking(False) -+ try: -+ inject_sock.connect(sockname[:2]) -+ except (BlockingIOError, InterruptedError): -+ pass -+ inject_sock.setblocking(True) -+ return sockname -+ -+ sock1 = sock2 = None -+ try: -+ socket.socket.getsockname = inject_getsocketname -+ with self.assertRaises(OSError): -+ sock1, sock2 = socket.socketpair() -+ finally: -+ socket.socket.getsockname = orig_getsockname -+ if inject_sock: -+ inject_sock.close() -+ if sock1: # This cleanup isn't needed on a successful test. -+ sock1.close() -+ if sock2: -+ sock2.close() -+ -+ def _test_injected_authentication_failure(self): -+ # No-op. Exists for base class threading infrastructure to call. -+ # We could refactor this test into its own lesser class along with the -+ # setUp and tearDown code to construct an ideal; it is simpler to keep -+ # it here and live with extra overhead one this _one_ failure test. -+ pass -+ -+ - class NonBlockingTCPTests(ThreadedTCPSocketTest): - - def __init__(self, methodName='runTest'): -diff --git a/Misc/NEWS.d/next/Security/2024-07-22-13-11-28.gh-issue-122133.0mPeta.rst b/Misc/NEWS.d/next/Security/2024-07-22-13-11-28.gh-issue-122133.0mPeta.rst -new file mode 100644 -index 00000000000..3544eb3824d ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2024-07-22-13-11-28.gh-issue-122133.0mPeta.rst -@@ -0,0 +1,5 @@ -+Authenticate the socket connection for the ``socket.socketpair()`` fallback -+on platforms where ``AF_UNIX`` is not available like Windows. -+ -+Patch by Gregory P. Smith and Seth Larson . Reported by Ellie -+ --- -2.33.0 - diff --git a/backport-CVE-2024-3219-2-gh-122133-Rework-pure-Python-socketpair-tests-t.patch b/backport-CVE-2024-3219-2-gh-122133-Rework-pure-Python-socketpair-tests-t.patch deleted file mode 100644 index 3c696cdb41fc264e2212eeb94cfcbb97ce4e8cff..0000000000000000000000000000000000000000 --- a/backport-CVE-2024-3219-2-gh-122133-Rework-pure-Python-socketpair-tests-t.patch +++ /dev/null @@ -1,207 +0,0 @@ -From c5655aa6ad120d2ed7f255bebd6e8b71a9c07dde Mon Sep 17 00:00:00 2001 -From: "Miss Islington (bot)" - <31488909+miss-islington@users.noreply.github.com> -Date: Fri, 2 Aug 2024 15:09:45 +0200 -Subject: [PATCH] [3.11] gh-122133: Rework pure Python socketpair tests to - avoid use of importlib.reload. (GH-122493) (GH-122506) - -(cherry picked from commit f071f01b7b7e19d7d6b3a4b0ec62f820ecb14660) - -Co-authored-by: Russell Keith-Magee -Co-authored-by: Gregory P. Smith ---- - Lib/socket.py | 121 +++++++++++++++++++--------------------- - Lib/test/test_socket.py | 20 ++----- - 2 files changed, 64 insertions(+), 77 deletions(-) - -diff --git a/Lib/socket.py b/Lib/socket.py -index 591d4739a64..f386241abfb 100644 ---- a/Lib/socket.py -+++ b/Lib/socket.py -@@ -590,16 +590,65 @@ def fromshare(info): - return socket(0, 0, 0, info) - __all__.append("fromshare") - --if hasattr(_socket, "socketpair"): -+# Origin: https://gist.github.com/4325783, by Geert Jansen. Public domain. -+# This is used if _socket doesn't natively provide socketpair. It's -+# always defined so that it can be patched in for testing purposes. -+def _fallback_socketpair(family=AF_INET, type=SOCK_STREAM, proto=0): -+ if family == AF_INET: -+ host = _LOCALHOST -+ elif family == AF_INET6: -+ host = _LOCALHOST_V6 -+ else: -+ raise ValueError("Only AF_INET and AF_INET6 socket address families " -+ "are supported") -+ if type != SOCK_STREAM: -+ raise ValueError("Only SOCK_STREAM socket type is supported") -+ if proto != 0: -+ raise ValueError("Only protocol zero is supported") -+ -+ # We create a connected TCP socket. Note the trick with -+ # setblocking(False) that prevents us from having to create a thread. -+ lsock = socket(family, type, proto) -+ try: -+ lsock.bind((host, 0)) -+ lsock.listen() -+ # On IPv6, ignore flow_info and scope_id -+ addr, port = lsock.getsockname()[:2] -+ csock = socket(family, type, proto) -+ try: -+ csock.setblocking(False) -+ try: -+ csock.connect((addr, port)) -+ except (BlockingIOError, InterruptedError): -+ pass -+ csock.setblocking(True) -+ ssock, _ = lsock.accept() -+ except: -+ csock.close() -+ raise -+ finally: -+ lsock.close() - -- def socketpair(family=None, type=SOCK_STREAM, proto=0): -- """socketpair([family[, type[, proto]]]) -> (socket object, socket object) -+ # Authenticating avoids using a connection from something else -+ # able to connect to {host}:{port} instead of us. -+ # We expect only AF_INET and AF_INET6 families. -+ try: -+ if ( -+ ssock.getsockname() != csock.getpeername() -+ or csock.getsockname() != ssock.getpeername() -+ ): -+ raise ConnectionError("Unexpected peer connection") -+ except: -+ # getsockname() and getpeername() can fail -+ # if either socket isn't connected. -+ ssock.close() -+ csock.close() -+ raise - -- Create a pair of socket objects from the sockets returned by the platform -- socketpair() function. -- The arguments are the same as for socket() except the default family is -- AF_UNIX if defined on the platform; otherwise, the default is AF_INET. -- """ -+ return (ssock, csock) -+ -+if hasattr(_socket, "socketpair"): -+ def socketpair(family=None, type=SOCK_STREAM, proto=0): - if family is None: - try: - family = AF_UNIX -@@ -611,61 +660,7 @@ def socketpair(family=None, type=SOCK_STREAM, proto=0): - return a, b - - else: -- -- # Origin: https://gist.github.com/4325783, by Geert Jansen. Public domain. -- def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0): -- if family == AF_INET: -- host = _LOCALHOST -- elif family == AF_INET6: -- host = _LOCALHOST_V6 -- else: -- raise ValueError("Only AF_INET and AF_INET6 socket address families " -- "are supported") -- if type != SOCK_STREAM: -- raise ValueError("Only SOCK_STREAM socket type is supported") -- if proto != 0: -- raise ValueError("Only protocol zero is supported") -- -- # We create a connected TCP socket. Note the trick with -- # setblocking(False) that prevents us from having to create a thread. -- lsock = socket(family, type, proto) -- try: -- lsock.bind((host, 0)) -- lsock.listen() -- # On IPv6, ignore flow_info and scope_id -- addr, port = lsock.getsockname()[:2] -- csock = socket(family, type, proto) -- try: -- csock.setblocking(False) -- try: -- csock.connect((addr, port)) -- except (BlockingIOError, InterruptedError): -- pass -- csock.setblocking(True) -- ssock, _ = lsock.accept() -- except: -- csock.close() -- raise -- finally: -- lsock.close() -- -- # Authenticating avoids using a connection from something else -- # able to connect to {host}:{port} instead of us. -- # We expect only AF_INET and AF_INET6 families. -- try: -- if ( -- ssock.getsockname() != csock.getpeername() -- or csock.getsockname() != ssock.getpeername() -- ): -- raise ConnectionError("Unexpected peer connection") -- except: -- # getsockname() and getpeername() can fail -- # if either socket isn't connected. -- ssock.close() -- csock.close() -- raise -- -- return (ssock, csock) -+ socketpair = _fallback_socketpair - __all__.append("socketpair") - - socketpair.__doc__ = """socketpair([family[, type[, proto]]]) -> (socket object, socket object) -diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py -index a60eb436c7b..cc803d8753b 100644 ---- a/Lib/test/test_socket.py -+++ b/Lib/test/test_socket.py -@@ -4676,7 +4676,6 @@ def _testSend(self): - - - class PurePythonSocketPairTest(SocketPairTest): -- - # Explicitly use socketpair AF_INET or AF_INET6 to ensure that is the - # code path we're using regardless platform is the pure python one where - # `_socket.socketpair` does not exist. (AF_INET does not work with -@@ -4691,28 +4690,21 @@ def socketpair(self): - # Local imports in this class make for easy security fix backporting. - - def setUp(self): -- import _socket -- self._orig_sp = getattr(_socket, 'socketpair', None) -- if self._orig_sp is not None: -+ if hasattr(_socket, "socketpair"): -+ self._orig_sp = socket.socketpair - # This forces the version using the non-OS provided socketpair - # emulation via an AF_INET socket in Lib/socket.py. -- del _socket.socketpair -- import importlib -- global socket -- socket = importlib.reload(socket) -+ socket.socketpair = socket._fallback_socketpair - else: -- pass # This platform already uses the non-OS provided version. -+ # This platform already uses the non-OS provided version. -+ self._orig_sp = None - super().setUp() - - def tearDown(self): - super().tearDown() -- import _socket - if self._orig_sp is not None: - # Restore the default socket.socketpair definition. -- _socket.socketpair = self._orig_sp -- import importlib -- global socket -- socket = importlib.reload(socket) -+ socket.socketpair = self._orig_sp - - def test_recv(self): - msg = self.serv.recv(1024) --- -2.33.0 - diff --git a/backport-CVE-2024-4032-gh-113171-gh-65056-Fix-private-non-global-IP-ad.patch b/backport-CVE-2024-4032-gh-113171-gh-65056-Fix-private-non-global-IP-ad.patch deleted file mode 100644 index 7cb3cfbb22f9d023f0a29b12009bfc1d1c919a07..0000000000000000000000000000000000000000 --- a/backport-CVE-2024-4032-gh-113171-gh-65056-Fix-private-non-global-IP-ad.patch +++ /dev/null @@ -1,346 +0,0 @@ -From ba431579efdcbaed7a96f2ac4ea0775879a332fb Mon Sep 17 00:00:00 2001 -From: Petr Viktorin -Date: Thu, 25 Apr 2024 14:45:48 +0200 -Subject: [PATCH] [3.11] gh-113171: gh-65056: Fix "private" (non-global) IP - address ranges (GH-113179) (GH-113186) (GH-118177) (#118227) - ---- - Doc/library/ipaddress.rst | 43 +++++++- - Doc/whatsnew/3.11.rst | 9 ++ - Lib/ipaddress.py | 99 +++++++++++++++---- - Lib/test/test_ipaddress.py | 21 +++- - ...-03-14-01-38-44.gh-issue-113171.VFnObz.rst | 9 ++ - 5 files changed, 157 insertions(+), 24 deletions(-) - create mode 100644 Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst - -diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst -index 03dc956cd1..f57fa15aa5 100644 ---- a/Doc/library/ipaddress.rst -+++ b/Doc/library/ipaddress.rst -@@ -178,18 +178,53 @@ write code that handles both IP versions correctly. Address objects are - - .. attribute:: is_private - -- ``True`` if the address is allocated for private networks. See -+ ``True`` if the address is defined as not globally reachable by - iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -- (for IPv6). -+ (for IPv6) with the following exceptions: -+ -+ * ``is_private`` is ``False`` for the shared address space (``100.64.0.0/10``) -+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: -+ -+ address.is_private == address.ipv4_mapped.is_private -+ -+ ``is_private`` has value opposite to :attr:`is_global`, except for the shared address space -+ (``100.64.0.0/10`` range) where they are both ``False``. -+ -+ .. versionchanged:: 3.11.10 -+ -+ Fixed some false positives and false negatives. -+ -+ * ``192.0.0.0/24`` is considered private with the exception of ``192.0.0.9/32`` and -+ ``192.0.0.10/32`` (previously: only the ``192.0.0.0/29`` sub-range was considered private). -+ * ``64:ff9b:1::/48`` is considered private. -+ * ``2002::/16`` is considered private. -+ * There are exceptions within ``2001::/23`` (otherwise considered private): ``2001:1::1/128``, -+ ``2001:1::2/128``, ``2001:3::/32``, ``2001:4:112::/48``, ``2001:20::/28``, ``2001:30::/28``. -+ The exceptions are not considered private. - - .. attribute:: is_global - -- ``True`` if the address is allocated for public networks. See -+ ``True`` if the address is defined as globally reachable by - iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -- (for IPv6). -+ (for IPv6) with the following exception: -+ -+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: -+ -+ address.is_global == address.ipv4_mapped.is_global -+ -+ ``is_global`` has value opposite to :attr:`is_private`, except for the shared address space -+ (``100.64.0.0/10`` range) where they are both ``False``. - - .. versionadded:: 3.4 - -+ .. versionchanged:: 3.11.10 -+ -+ Fixed some false positives and false negatives, see :attr:`is_private` for details. -+ - .. attribute:: is_unspecified - - ``True`` if the address is unspecified. See :RFC:`5735` (for IPv4) -diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst -index f670fa1f09..42b61c75c7 100644 ---- a/Doc/whatsnew/3.11.rst -+++ b/Doc/whatsnew/3.11.rst -@@ -2727,3 +2727,12 @@ OpenSSL - * Windows builds and macOS installers from python.org now use OpenSSL 3.0. - - .. _libb2: https://www.blake2.net/ -+ -+Notable changes in 3.11.10 -+========================== -+ -+ipaddress -+--------- -+ -+* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``, -+ ``IPv6Address``, ``IPv4Network`` and ``IPv6Network``. -diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py -index 16ba16cd7d..567beb37e0 100644 ---- a/Lib/ipaddress.py -+++ b/Lib/ipaddress.py -@@ -1086,7 +1086,11 @@ def is_private(self): - """ - return any(self.network_address in priv_network and - self.broadcast_address in priv_network -- for priv_network in self._constants._private_networks) -+ for priv_network in self._constants._private_networks) and all( -+ self.network_address not in network and -+ self.broadcast_address not in network -+ for network in self._constants._private_networks_exceptions -+ ) - - @property - def is_global(self): -@@ -1333,18 +1337,41 @@ def is_reserved(self): - @property - @functools.lru_cache() - def is_private(self): -- """Test if this address is allocated for private networks. -+ """``True`` if the address is defined as not globally reachable by -+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -+ (for IPv6) with the following exceptions: - -- Returns: -- A boolean, True if the address is reserved per -- iana-ipv4-special-registry. -+ * ``is_private`` is ``False`` for ``100.64.0.0/10`` -+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: - -+ address.is_private == address.ipv4_mapped.is_private -+ -+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10`` -+ IPv4 range where they are both ``False``. - """ -- return any(self in net for net in self._constants._private_networks) -+ return ( -+ any(self in net for net in self._constants._private_networks) -+ and all(self not in net for net in self._constants._private_networks_exceptions) -+ ) - - @property - @functools.lru_cache() - def is_global(self): -+ """``True`` if the address is defined as globally reachable by -+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -+ (for IPv6) with the following exception: -+ -+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: -+ -+ address.is_global == address.ipv4_mapped.is_global -+ -+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10`` -+ IPv4 range where they are both ``False``. -+ """ - return self not in self._constants._public_network and not self.is_private - - @property -@@ -1548,13 +1575,15 @@ class _IPv4Constants: - - _public_network = IPv4Network('100.64.0.0/10') - -+ # Not globally reachable address blocks listed on -+ # https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml - _private_networks = [ - IPv4Network('0.0.0.0/8'), - IPv4Network('10.0.0.0/8'), - IPv4Network('127.0.0.0/8'), - IPv4Network('169.254.0.0/16'), - IPv4Network('172.16.0.0/12'), -- IPv4Network('192.0.0.0/29'), -+ IPv4Network('192.0.0.0/24'), - IPv4Network('192.0.0.170/31'), - IPv4Network('192.0.2.0/24'), - IPv4Network('192.168.0.0/16'), -@@ -1565,6 +1594,11 @@ class _IPv4Constants: - IPv4Network('255.255.255.255/32'), - ] - -+ _private_networks_exceptions = [ -+ IPv4Network('192.0.0.9/32'), -+ IPv4Network('192.0.0.10/32'), -+ ] -+ - _reserved_network = IPv4Network('240.0.0.0/4') - - _unspecified_address = IPv4Address('0.0.0.0') -@@ -2010,27 +2044,42 @@ def is_site_local(self): - @property - @functools.lru_cache() - def is_private(self): -- """Test if this address is allocated for private networks. -+ """``True`` if the address is defined as not globally reachable by -+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -+ (for IPv6) with the following exceptions: - -- Returns: -- A boolean, True if the address is reserved per -- iana-ipv6-special-registry, or is ipv4_mapped and is -- reserved in the iana-ipv4-special-registry. -+ * ``is_private`` is ``False`` for ``100.64.0.0/10`` -+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: - -+ address.is_private == address.ipv4_mapped.is_private -+ -+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10`` -+ IPv4 range where they are both ``False``. - """ - ipv4_mapped = self.ipv4_mapped - if ipv4_mapped is not None: - return ipv4_mapped.is_private -- return any(self in net for net in self._constants._private_networks) -+ return ( -+ any(self in net for net in self._constants._private_networks) -+ and all(self not in net for net in self._constants._private_networks_exceptions) -+ ) - - @property - def is_global(self): -- """Test if this address is allocated for public networks. -+ """``True`` if the address is defined as globally reachable by -+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -+ (for IPv6) with the following exception: - -- Returns: -- A boolean, true if the address is not reserved per -- iana-ipv6-special-registry. -+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: -+ -+ address.is_global == address.ipv4_mapped.is_global - -+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10`` -+ IPv4 range where they are both ``False``. - """ - return not self.is_private - -@@ -2271,19 +2320,31 @@ class _IPv6Constants: - - _multicast_network = IPv6Network('ff00::/8') - -+ # Not globally reachable address blocks listed on -+ # https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml - _private_networks = [ - IPv6Network('::1/128'), - IPv6Network('::/128'), - IPv6Network('::ffff:0:0/96'), -+ IPv6Network('64:ff9b:1::/48'), - IPv6Network('100::/64'), - IPv6Network('2001::/23'), -- IPv6Network('2001:2::/48'), - IPv6Network('2001:db8::/32'), -- IPv6Network('2001:10::/28'), -+ # IANA says N/A, let's consider it not globally reachable to be safe -+ IPv6Network('2002::/16'), - IPv6Network('fc00::/7'), - IPv6Network('fe80::/10'), - ] - -+ _private_networks_exceptions = [ -+ IPv6Network('2001:1::1/128'), -+ IPv6Network('2001:1::2/128'), -+ IPv6Network('2001:3::/32'), -+ IPv6Network('2001:4:112::/48'), -+ IPv6Network('2001:20::/28'), -+ IPv6Network('2001:30::/28'), -+ ] -+ - _reserved_networks = [ - IPv6Network('::/8'), IPv6Network('100::/8'), - IPv6Network('200::/7'), IPv6Network('400::/6'), -diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py -index fc27628af1..16c34163a0 100644 ---- a/Lib/test/test_ipaddress.py -+++ b/Lib/test/test_ipaddress.py -@@ -2269,6 +2269,10 @@ def testReservedIpv4(self): - self.assertEqual(True, ipaddress.ip_address( - '172.31.255.255').is_private) - self.assertEqual(False, ipaddress.ip_address('172.32.0.0').is_private) -+ self.assertFalse(ipaddress.ip_address('192.0.0.0').is_global) -+ self.assertTrue(ipaddress.ip_address('192.0.0.9').is_global) -+ self.assertTrue(ipaddress.ip_address('192.0.0.10').is_global) -+ self.assertFalse(ipaddress.ip_address('192.0.0.255').is_global) - - self.assertEqual(True, - ipaddress.ip_address('169.254.100.200').is_link_local) -@@ -2294,6 +2298,7 @@ def testPrivateNetworks(self): - self.assertEqual(True, ipaddress.ip_network("169.254.0.0/16").is_private) - self.assertEqual(True, ipaddress.ip_network("172.16.0.0/12").is_private) - self.assertEqual(True, ipaddress.ip_network("192.0.0.0/29").is_private) -+ self.assertEqual(False, ipaddress.ip_network("192.0.0.9/32").is_private) - self.assertEqual(True, ipaddress.ip_network("192.0.0.170/31").is_private) - self.assertEqual(True, ipaddress.ip_network("192.0.2.0/24").is_private) - self.assertEqual(True, ipaddress.ip_network("192.168.0.0/16").is_private) -@@ -2310,8 +2315,8 @@ def testPrivateNetworks(self): - self.assertEqual(True, ipaddress.ip_network("::/128").is_private) - self.assertEqual(True, ipaddress.ip_network("::ffff:0:0/96").is_private) - self.assertEqual(True, ipaddress.ip_network("100::/64").is_private) -- self.assertEqual(True, ipaddress.ip_network("2001::/23").is_private) - self.assertEqual(True, ipaddress.ip_network("2001:2::/48").is_private) -+ self.assertEqual(False, ipaddress.ip_network("2001:3::/48").is_private) - self.assertEqual(True, ipaddress.ip_network("2001:db8::/32").is_private) - self.assertEqual(True, ipaddress.ip_network("2001:10::/28").is_private) - self.assertEqual(True, ipaddress.ip_network("fc00::/7").is_private) -@@ -2390,6 +2395,20 @@ def testReservedIpv6(self): - self.assertEqual(True, ipaddress.ip_address('0::0').is_unspecified) - self.assertEqual(False, ipaddress.ip_address('::1').is_unspecified) - -+ self.assertFalse(ipaddress.ip_address('64:ff9b:1::').is_global) -+ self.assertFalse(ipaddress.ip_address('2001::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:1::1').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:1::2').is_global) -+ self.assertFalse(ipaddress.ip_address('2001:2::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:3::').is_global) -+ self.assertFalse(ipaddress.ip_address('2001:4::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:4:112::').is_global) -+ self.assertFalse(ipaddress.ip_address('2001:10::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:20::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:30::').is_global) -+ self.assertFalse(ipaddress.ip_address('2001:40::').is_global) -+ self.assertFalse(ipaddress.ip_address('2002::').is_global) -+ - # some generic IETF reserved addresses - self.assertEqual(True, ipaddress.ip_address('100::').is_reserved) - self.assertEqual(True, ipaddress.ip_network('4000::1/128').is_reserved) -diff --git a/Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst b/Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst -new file mode 100644 -index 0000000000..f9a72473be ---- /dev/null -+++ b/Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst -@@ -0,0 +1,9 @@ -+Fixed various false positives and false negatives in -+ -+* :attr:`ipaddress.IPv4Address.is_private` (see these docs for details) -+* :attr:`ipaddress.IPv4Address.is_global` -+* :attr:`ipaddress.IPv6Address.is_private` -+* :attr:`ipaddress.IPv6Address.is_global` -+ -+Also in the corresponding :class:`ipaddress.IPv4Network` and :class:`ipaddress.IPv6Network` -+attributes. --- -2.27.0 - diff --git a/backport-CVE-2024-6232-gh-121285-Remove-backtracking-when-parsing-tarf.patch b/backport-CVE-2024-6232-gh-121285-Remove-backtracking-when-parsing-tarf.patch deleted file mode 100644 index 0e3db4eacdf89a5c64f62dc5ac9bcfc7105c11ad..0000000000000000000000000000000000000000 --- a/backport-CVE-2024-6232-gh-121285-Remove-backtracking-when-parsing-tarf.patch +++ /dev/null @@ -1,247 +0,0 @@ -From d449caf8a179e3b954268b3a88eb9170be3c8fbf Mon Sep 17 00:00:00 2001 -From: Seth Michael Larson -Date: Tue, 3 Sep 2024 10:07:13 -0500 -Subject: [PATCH] [3.11] gh-121285: Remove backtracking when parsing tarfile - headers (GH-121286) (#123639) - -* Remove backtracking when parsing tarfile headers -* Rewrite PAX header parsing to be stricter -* Optimize parsing of GNU extended sparse headers v0.0 - -(cherry picked from commit 34ddb64d088dd7ccc321f6103d23153256caa5d4) - -Co-authored-by: Kirill Podoprigora -Co-authored-by: Gregory P. Smith ---- - Lib/tarfile.py | 105 +++++++++++------- - Lib/test/test_tarfile.py | 42 +++++++ - ...-07-02-13-39-20.gh-issue-121285.hrl-yI.rst | 2 + - 3 files changed, 111 insertions(+), 38 deletions(-) - create mode 100644 Misc/NEWS.d/next/Security/2024-07-02-13-39-20.gh-issue-121285.hrl-yI.rst - -diff --git a/Lib/tarfile.py b/Lib/tarfile.py -index 612217b1ad0..0d6b925533b 100755 ---- a/Lib/tarfile.py -+++ b/Lib/tarfile.py -@@ -842,6 +842,9 @@ def data_filter(member, dest_path): - # Sentinel for replace() defaults, meaning "don't change the attribute" - _KEEP = object() - -+# Header length is digits followed by a space. -+_header_length_prefix_re = re.compile(br"([0-9]{1,20}) ") -+ - class TarInfo(object): - """Informational class which holds the details about an - archive member given by a tar header block. -@@ -1411,41 +1414,59 @@ def _proc_pax(self, tarfile): - else: - pax_headers = tarfile.pax_headers.copy() - -- # Check if the pax header contains a hdrcharset field. This tells us -- # the encoding of the path, linkpath, uname and gname fields. Normally, -- # these fields are UTF-8 encoded but since POSIX.1-2008 tar -- # implementations are allowed to store them as raw binary strings if -- # the translation to UTF-8 fails. -- match = re.search(br"\d+ hdrcharset=([^\n]+)\n", buf) -- if match is not None: -- pax_headers["hdrcharset"] = match.group(1).decode("utf-8") -- -- # For the time being, we don't care about anything other than "BINARY". -- # The only other value that is currently allowed by the standard is -- # "ISO-IR 10646 2000 UTF-8" in other words UTF-8. -- hdrcharset = pax_headers.get("hdrcharset") -- if hdrcharset == "BINARY": -- encoding = tarfile.encoding -- else: -- encoding = "utf-8" -- - # Parse pax header information. A record looks like that: - # "%d %s=%s\n" % (length, keyword, value). length is the size - # of the complete record including the length field itself and -- # the newline. keyword and value are both UTF-8 encoded strings. -- regex = re.compile(br"(\d+) ([^=]+)=") -+ # the newline. - pos = 0 -- while True: -- match = regex.match(buf, pos) -- if not match: -- break -+ encoding = None -+ raw_headers = [] -+ while len(buf) > pos and buf[pos] != 0x00: -+ if not (match := _header_length_prefix_re.match(buf, pos)): -+ raise InvalidHeaderError("invalid header") -+ try: -+ length = int(match.group(1)) -+ except ValueError: -+ raise InvalidHeaderError("invalid header") -+ # Headers must be at least 5 bytes, shortest being '5 x=\n'. -+ # Value is allowed to be empty. -+ if length < 5: -+ raise InvalidHeaderError("invalid header") -+ if pos + length > len(buf): -+ raise InvalidHeaderError("invalid header") - -- length, keyword = match.groups() -- length = int(length) -- if length == 0: -+ header_value_end_offset = match.start(1) + length - 1 # Last byte of the header -+ keyword_and_value = buf[match.end(1) + 1:header_value_end_offset] -+ raw_keyword, equals, raw_value = keyword_and_value.partition(b"=") -+ -+ # Check the framing of the header. The last character must be '\n' (0x0A) -+ if not raw_keyword or equals != b"=" or buf[header_value_end_offset] != 0x0A: - raise InvalidHeaderError("invalid header") -- value = buf[match.end(2) + 1:match.start(1) + length - 1] -+ raw_headers.append((length, raw_keyword, raw_value)) -+ -+ # Check if the pax header contains a hdrcharset field. This tells us -+ # the encoding of the path, linkpath, uname and gname fields. Normally, -+ # these fields are UTF-8 encoded but since POSIX.1-2008 tar -+ # implementations are allowed to store them as raw binary strings if -+ # the translation to UTF-8 fails. For the time being, we don't care about -+ # anything other than "BINARY". The only other value that is currently -+ # allowed by the standard is "ISO-IR 10646 2000 UTF-8" in other words UTF-8. -+ # Note that we only follow the initial 'hdrcharset' setting to preserve -+ # the initial behavior of the 'tarfile' module. -+ if raw_keyword == b"hdrcharset" and encoding is None: -+ if raw_value == b"BINARY": -+ encoding = tarfile.encoding -+ else: # This branch ensures only the first 'hdrcharset' header is used. -+ encoding = "utf-8" -+ -+ pos += length - -+ # If no explicit hdrcharset is set, we use UTF-8 as a default. -+ if encoding is None: -+ encoding = "utf-8" -+ -+ # After parsing the raw headers we can decode them to text. -+ for length, raw_keyword, raw_value in raw_headers: - # Normally, we could just use "utf-8" as the encoding and "strict" - # as the error handler, but we better not take the risk. For - # example, GNU tar <= 1.23 is known to store filenames it cannot -@@ -1453,17 +1474,16 @@ def _proc_pax(self, tarfile): - # hdrcharset=BINARY header). - # We first try the strict standard encoding, and if that fails we - # fall back on the user's encoding and error handler. -- keyword = self._decode_pax_field(keyword, "utf-8", "utf-8", -+ keyword = self._decode_pax_field(raw_keyword, "utf-8", "utf-8", - tarfile.errors) - if keyword in PAX_NAME_FIELDS: -- value = self._decode_pax_field(value, encoding, tarfile.encoding, -+ value = self._decode_pax_field(raw_value, encoding, tarfile.encoding, - tarfile.errors) - else: -- value = self._decode_pax_field(value, "utf-8", "utf-8", -+ value = self._decode_pax_field(raw_value, "utf-8", "utf-8", - tarfile.errors) - - pax_headers[keyword] = value -- pos += length - - # Fetch the next header. - try: -@@ -1478,7 +1498,7 @@ def _proc_pax(self, tarfile): - - elif "GNU.sparse.size" in pax_headers: - # GNU extended sparse format version 0.0. -- self._proc_gnusparse_00(next, pax_headers, buf) -+ self._proc_gnusparse_00(next, raw_headers) - - elif pax_headers.get("GNU.sparse.major") == "1" and pax_headers.get("GNU.sparse.minor") == "0": - # GNU extended sparse format version 1.0. -@@ -1500,15 +1520,24 @@ def _proc_pax(self, tarfile): - - return next - -- def _proc_gnusparse_00(self, next, pax_headers, buf): -+ def _proc_gnusparse_00(self, next, raw_headers): - """Process a GNU tar extended sparse header, version 0.0. - """ - offsets = [] -- for match in re.finditer(br"\d+ GNU.sparse.offset=(\d+)\n", buf): -- offsets.append(int(match.group(1))) - numbytes = [] -- for match in re.finditer(br"\d+ GNU.sparse.numbytes=(\d+)\n", buf): -- numbytes.append(int(match.group(1))) -+ for _, keyword, value in raw_headers: -+ if keyword == b"GNU.sparse.offset": -+ try: -+ offsets.append(int(value.decode())) -+ except ValueError: -+ raise InvalidHeaderError("invalid header") -+ -+ elif keyword == b"GNU.sparse.numbytes": -+ try: -+ numbytes.append(int(value.decode())) -+ except ValueError: -+ raise InvalidHeaderError("invalid header") -+ - next.sparse = list(zip(offsets, numbytes)) - - def _proc_gnusparse_01(self, next, pax_headers): -diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py -index 389da7be3a3..c99c88ce93a 100644 ---- a/Lib/test/test_tarfile.py -+++ b/Lib/test/test_tarfile.py -@@ -1208,6 +1208,48 @@ def test_pax_number_fields(self): - finally: - tar.close() - -+ def test_pax_header_bad_formats(self): -+ # The fields from the pax header have priority over the -+ # TarInfo. -+ pax_header_replacements = ( -+ b" foo=bar\n", -+ b"0 \n", -+ b"1 \n", -+ b"2 \n", -+ b"3 =\n", -+ b"4 =a\n", -+ b"1000000 foo=bar\n", -+ b"0 foo=bar\n", -+ b"-12 foo=bar\n", -+ b"000000000000000000000000036 foo=bar\n", -+ ) -+ pax_headers = {"foo": "bar"} -+ -+ for replacement in pax_header_replacements: -+ with self.subTest(header=replacement): -+ tar = tarfile.open(tmpname, "w", format=tarfile.PAX_FORMAT, -+ encoding="iso8859-1") -+ try: -+ t = tarfile.TarInfo() -+ t.name = "pax" # non-ASCII -+ t.uid = 1 -+ t.pax_headers = pax_headers -+ tar.addfile(t) -+ finally: -+ tar.close() -+ -+ with open(tmpname, "rb") as f: -+ data = f.read() -+ self.assertIn(b"11 foo=bar\n", data) -+ data = data.replace(b"11 foo=bar\n", replacement) -+ -+ with open(tmpname, "wb") as f: -+ f.truncate() -+ f.write(data) -+ -+ with self.assertRaisesRegex(tarfile.ReadError, r"method tar: ReadError\('invalid header'\)"): -+ tarfile.open(tmpname, encoding="iso8859-1") -+ - - class WriteTestBase(TarTest): - # Put all write tests in here that are supposed to be tested -diff --git a/Misc/NEWS.d/next/Security/2024-07-02-13-39-20.gh-issue-121285.hrl-yI.rst b/Misc/NEWS.d/next/Security/2024-07-02-13-39-20.gh-issue-121285.hrl-yI.rst -new file mode 100644 -index 00000000000..81f918bfe2b ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2024-07-02-13-39-20.gh-issue-121285.hrl-yI.rst -@@ -0,0 +1,2 @@ -+Remove backtracking from tarfile header parsing for ``hdrcharset``, PAX, and -+GNU sparse headers. --- -2.33.0 - diff --git a/backport-CVE-2024-6923-gh-121650-Encode-newlines-in-headers-and-verify-head.patch b/backport-CVE-2024-6923-gh-121650-Encode-newlines-in-headers-and-verify-head.patch deleted file mode 100644 index 59209aad70982da65414e8178d8eb4edc14548e3..0000000000000000000000000000000000000000 --- a/backport-CVE-2024-6923-gh-121650-Encode-newlines-in-headers-and-verify-head.patch +++ /dev/null @@ -1,348 +0,0 @@ -From 67067f7cca97cc980d31c5769f621b58ef876b7e Mon Sep 17 00:00:00 2001 -From: xinsheng -Date: Tue, 3 Sep 2024 21:17:26 +0800 -Subject: [PATCH] gh-121650: Encode newlines in headers, and verify headers are - sound (GH-122233) - ---- - Doc/library/email.errors.rst | 6 ++ - Doc/library/email.policy.rst | 18 ++++++ - Doc/whatsnew/3.11.rst | 13 ++++ - Lib/email/_header_value_parser.py | 12 +++- - Lib/email/_policybase.py | 8 +++ - Lib/email/errors.py | 4 ++ - Lib/email/generator.py | 13 +++- - Lib/test/test_email/test_generator.py | 62 +++++++++++++++++++ - Lib/test/test_email/test_policy.py | 26 ++++++++ - ...-07-27-16-10-41.gh-issue-121650.nf6oc9.rst | 5 ++ - 10 files changed, 163 insertions(+), 4 deletions(-) - create mode 100644 Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst - -diff --git a/Doc/library/email.errors.rst b/Doc/library/email.errors.rst -index 194a986..f737f02 100644 ---- a/Doc/library/email.errors.rst -+++ b/Doc/library/email.errors.rst -@@ -59,6 +59,12 @@ The following exception classes are defined in the :mod:`email.errors` module: - :class:`~email.mime.image.MIMEImage`). - - -+.. exception:: HeaderWriteError() -+ -+ Raised when an error occurs when the :mod:`~email.generator` outputs -+ headers. -+ -+ - Here is the list of the defects that the :class:`~email.parser.FeedParser` - can find while parsing messages. Note that the defects are added to the message - where the problem was found, so for example, if a message nested inside a -diff --git a/Doc/library/email.policy.rst b/Doc/library/email.policy.rst -index bf53b95..79b1786 100644 ---- a/Doc/library/email.policy.rst -+++ b/Doc/library/email.policy.rst -@@ -229,6 +229,24 @@ added matters. To illustrate:: - - .. versionadded:: 3.6 - -+ -+ .. attribute:: verify_generated_headers -+ -+ If ``True`` (the default), the generator will raise -+ :exc:`~email.errors.HeaderWriteError` instead of writing a header -+ that is improperly folded or delimited, such that it would -+ be parsed as multiple headers or joined with adjacent data. -+ Such headers can be generated by custom header classes or bugs -+ in the ``email`` module. -+ -+ As it's a security feature, this defaults to ``True`` even in the -+ :class:`~email.policy.Compat32` policy. -+ For backwards compatible, but unsafe, behavior, it must be set to -+ ``False`` explicitly. -+ -+ .. versionadded:: 3.11.10 -+ -+ - The following :class:`Policy` method is intended to be called by code using - the email library to create policy instances with custom settings: - -diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst -index c47c895..d46df98 100644 ---- a/Doc/whatsnew/3.11.rst -+++ b/Doc/whatsnew/3.11.rst -@@ -2727,6 +2727,7 @@ OpenSSL - - .. _libb2: https://www.blake2.net/ - -+ - Notable changes in 3.11.10 - ========================== - -@@ -2735,3 +2736,15 @@ ipaddress - - * Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``, - ``IPv6Address``, ``IPv4Network`` and ``IPv6Network``. -+ -+email -+----- -+ -+* Headers with embedded newlines are now quoted on output. -+ -+ The :mod:`~email.generator` will now refuse to serialize (write) headers -+ that are improperly folded or delimited, such that they would be parsed as -+ multiple headers or joined with adjacent data. -+ If you need to turn this safety feature off, -+ set :attr:`~email.policy.Policy.verify_generated_headers`. -+ (Contributed by Bas Bloemsaat and Petr Viktorin in :gh:`121650`.) -\ No newline at end of file -diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py -index e637e6d..e1b99d5 100644 ---- a/Lib/email/_header_value_parser.py -+++ b/Lib/email/_header_value_parser.py -@@ -92,6 +92,8 @@ TOKEN_ENDS = TSPECIALS | WSP - ASPECIALS = TSPECIALS | set("*'%") - ATTRIBUTE_ENDS = ASPECIALS | WSP - EXTENDED_ATTRIBUTE_ENDS = ATTRIBUTE_ENDS - set('%') -+NLSET = {'\n', '\r'} -+SPECIALSNL = SPECIALS | NLSET - - def quote_string(value): - return '"'+str(value).replace('\\', '\\\\').replace('"', r'\"')+'"' -@@ -2778,9 +2780,13 @@ def _refold_parse_tree(parse_tree, *, policy): - wrap_as_ew_blocked -= 1 - continue - tstr = str(part) -- if part.token_type == 'ptext' and set(tstr) & SPECIALS: -- # Encode if tstr contains special characters. -- want_encoding = True -+ if not want_encoding: -+ if part.token_type == 'ptext': -+ # Encode if tstr contains special characters. -+ want_encoding = not SPECIALSNL.isdisjoint(tstr) -+ else: -+ # Encode if tstr contains newlines. -+ want_encoding = not NLSET.isdisjoint(tstr) - try: - tstr.encode(encoding) - charset = encoding -diff --git a/Lib/email/_policybase.py b/Lib/email/_policybase.py -index c9cbadd..d1f4821 100644 ---- a/Lib/email/_policybase.py -+++ b/Lib/email/_policybase.py -@@ -157,6 +157,13 @@ class Policy(_PolicyBase, metaclass=abc.ABCMeta): - message_factory -- the class to use to create new message objects. - If the value is None, the default is Message. - -+ verify_generated_headers -+ -- if true, the generator verifies that each header -+ they are properly folded, so that a parser won't -+ treat it as multiple headers, start-of-body, or -+ part of another header. -+ This is a check against custom Header & fold() -+ implementations. - """ - - raise_on_defect = False -@@ -165,6 +172,7 @@ class Policy(_PolicyBase, metaclass=abc.ABCMeta): - max_line_length = 78 - mangle_from_ = False - message_factory = None -+ verify_generated_headers = True - - def handle_defect(self, obj, defect): - """Based on policy, either raise defect or call register_defect. -diff --git a/Lib/email/errors.py b/Lib/email/errors.py -index 3ad0056..02aa5ec 100644 ---- a/Lib/email/errors.py -+++ b/Lib/email/errors.py -@@ -29,6 +29,10 @@ class CharsetError(MessageError): - """An illegal charset was given.""" - - -+class HeaderWriteError(MessageError): -+ """Error while writing headers.""" -+ -+ - # These are parsing defects which the parser was able to work around. - class MessageDefect(ValueError): - """Base class for a message defect.""" -diff --git a/Lib/email/generator.py b/Lib/email/generator.py -index b8c1091..9cdd95f 100644 ---- a/Lib/email/generator.py -+++ b/Lib/email/generator.py -@@ -14,12 +14,14 @@ import random - from copy import deepcopy - from io import StringIO, BytesIO - from email.utils import _has_surrogates -+from email.errors import HeaderWriteError - - UNDERSCORE = '_' - NL = '\n' # XXX: no longer used by the code below. - - NLCRE = re.compile(r'\r\n|\r|\n') - fcre = re.compile(r'^From ', re.MULTILINE) -+NEWLINE_WITHOUT_FWSP = re.compile(r'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]') - - - class Generator: -@@ -222,7 +224,16 @@ class Generator: - - def _write_headers(self, msg): - for h, v in msg.raw_items(): -- self.write(self.policy.fold(h, v)) -+ folded = self.policy.fold(h, v) -+ if self.policy.verify_generated_headers: -+ linesep = self.policy.linesep -+ if not folded.endswith(self.policy.linesep): -+ raise HeaderWriteError( -+ f'folded header does not end with {linesep!r}: {folded!r}') -+ if NEWLINE_WITHOUT_FWSP.search(folded.removesuffix(linesep)): -+ raise HeaderWriteError( -+ f'folded header contains newline: {folded!r}') -+ self.write(folded) - # A blank line always separates headers from body - self.write(self._NL) - -diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py -index 89e7ede..d29400f 100644 ---- a/Lib/test/test_email/test_generator.py -+++ b/Lib/test/test_email/test_generator.py -@@ -6,6 +6,7 @@ from email.message import EmailMessage - from email.generator import Generator, BytesGenerator - from email.headerregistry import Address - from email import policy -+import email.errors - from test.test_email import TestEmailBase, parameterize - - -@@ -216,6 +217,44 @@ class TestGeneratorBase: - g.flatten(msg) - self.assertEqual(s.getvalue(), self.typ(expected)) - -+ def test_keep_encoded_newlines(self): -+ msg = self.msgmaker(self.typ(textwrap.dedent("""\ -+ To: nobody -+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com -+ -+ None -+ """))) -+ expected = textwrap.dedent("""\ -+ To: nobody -+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com -+ -+ None -+ """) -+ s = self.ioclass() -+ g = self.genclass(s, policy=self.policy.clone(max_line_length=80)) -+ g.flatten(msg) -+ self.assertEqual(s.getvalue(), self.typ(expected)) -+ -+ def test_keep_long_encoded_newlines(self): -+ msg = self.msgmaker(self.typ(textwrap.dedent("""\ -+ To: nobody -+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com -+ -+ None -+ """))) -+ expected = textwrap.dedent("""\ -+ To: nobody -+ Subject: Bad subject -+ =?utf-8?q?=0A?=Bcc: -+ injection@example.com -+ -+ None -+ """) -+ s = self.ioclass() -+ g = self.genclass(s, policy=self.policy.clone(max_line_length=30)) -+ g.flatten(msg) -+ self.assertEqual(s.getvalue(), self.typ(expected)) -+ - - class TestGenerator(TestGeneratorBase, TestEmailBase): - -@@ -224,6 +263,29 @@ class TestGenerator(TestGeneratorBase, TestEmailBase): - ioclass = io.StringIO - typ = str - -+ def test_verify_generated_headers(self): -+ """gh-121650: by default the generator prevents header injection""" -+ class LiteralHeader(str): -+ name = 'Header' -+ def fold(self, **kwargs): -+ return self -+ -+ for text in ( -+ 'Value\r\nBad Injection\r\n', -+ 'NoNewLine' -+ ): -+ with self.subTest(text=text): -+ message = message_from_string( -+ "Header: Value\r\n\r\nBody", -+ policy=self.policy, -+ ) -+ -+ del message['Header'] -+ message['Header'] = LiteralHeader(text) -+ -+ with self.assertRaises(email.errors.HeaderWriteError): -+ message.as_string() -+ - - class TestBytesGenerator(TestGeneratorBase, TestEmailBase): - -diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py -index e87c275..ff1ddf7 100644 ---- a/Lib/test/test_email/test_policy.py -+++ b/Lib/test/test_email/test_policy.py -@@ -26,6 +26,7 @@ class PolicyAPITests(unittest.TestCase): - 'raise_on_defect': False, - 'mangle_from_': True, - 'message_factory': None, -+ 'verify_generated_headers': True, - } - # These default values are the ones set on email.policy.default. - # If any of these defaults change, the docs must be updated. -@@ -277,6 +278,31 @@ class PolicyAPITests(unittest.TestCase): - with self.assertRaises(email.errors.HeaderParseError): - policy.fold("Subject", subject) - -+ def test_verify_generated_headers(self): -+ """Turning protection off allows header injection""" -+ policy = email.policy.default.clone(verify_generated_headers=False) -+ for text in ( -+ 'Header: Value\r\nBad: Injection\r\n', -+ 'Header: NoNewLine' -+ ): -+ with self.subTest(text=text): -+ message = email.message_from_string( -+ "Header: Value\r\n\r\nBody", -+ policy=policy, -+ ) -+ class LiteralHeader(str): -+ name = 'Header' -+ def fold(self, **kwargs): -+ return self -+ -+ del message['Header'] -+ message['Header'] = LiteralHeader(text) -+ -+ self.assertEqual( -+ message.as_string(), -+ f"{text}\nBody", -+ ) -+ - # XXX: Need subclassing tests. - # For adding subclassed objects, make sure the usual rules apply (subclass - # wins), but that the order still works (right overrides left). -diff --git a/Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst b/Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst -new file mode 100644 -index 0000000..83dd28d ---- /dev/null -+++ b/Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst -@@ -0,0 +1,5 @@ -+:mod:`email` headers with embedded newlines are now quoted on output. The -+:mod:`~email.generator` will now refuse to serialize (write) headers that -+are unsafely folded or delimited; see -+:attr:`~email.policy.Policy.verify_generated_headers`. (Contributed by Bas -+Bloemsaat and Petr Viktorin in :gh:`121650`.) --- -2.43.0 - diff --git a/backport-CVE-2024-7592-gh-123067-Fix-quadratic-complexity-in-parsing-quoted.patch b/backport-CVE-2024-7592-gh-123067-Fix-quadratic-complexity-in-parsing-quoted.patch deleted file mode 100644 index 100550b4624b054ce5dd494e6b0a0d7185fd2ce5..0000000000000000000000000000000000000000 --- a/backport-CVE-2024-7592-gh-123067-Fix-quadratic-complexity-in-parsing-quoted.patch +++ /dev/null @@ -1,131 +0,0 @@ -From cc22eb02afab967715da592536fc5eeab7d7c765 Mon Sep 17 00:00:00 2001 -From: xinsheng -Date: Tue, 3 Sep 2024 14:58:02 +0800 -Subject: [PATCH] gh-123067: Fix quadratic complexity in parsing ^Cuoted cookie - values with backslashes - ---- - Lib/http/cookies.py | 34 ++++------------- - Lib/test/test_http_cookies.py | 38 +++++++++++++++++++ - ...-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst | 1 + - 3 files changed, 47 insertions(+), 26 deletions(-) - create mode 100644 Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst - -diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py -index 35ac2dc..2c1f021 100644 ---- a/Lib/http/cookies.py -+++ b/Lib/http/cookies.py -@@ -184,8 +184,13 @@ def _quote(str): - return '"' + str.translate(_Translator) + '"' - - --_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") --_QuotePatt = re.compile(r"[\\].") -+_unquote_sub = re.compile(r'\\(?:([0-3][0-7][0-7])|(.))').sub -+ -+def _unquote_replace(m): -+ if m[1]: -+ return chr(int(m[1], 8)) -+ else: -+ return m[2] - - def _unquote(str): - # If there aren't any doublequotes, -@@ -205,30 +210,7 @@ def _unquote(str): - # \012 --> \n - # \" --> " - # -- i = 0 -- n = len(str) -- res = [] -- while 0 <= i < n: -- o_match = _OctalPatt.search(str, i) -- q_match = _QuotePatt.search(str, i) -- if not o_match and not q_match: # Neither matched -- res.append(str[i:]) -- break -- # else: -- j = k = -1 -- if o_match: -- j = o_match.start(0) -- if q_match: -- k = q_match.start(0) -- if q_match and (not o_match or k < j): # QuotePatt matched -- res.append(str[i:k]) -- res.append(str[k+1]) -- i = k + 2 -- else: # OctalPatt matched -- res.append(str[i:j]) -- res.append(chr(int(str[j+1:j+4], 8))) -- i = j + 4 -- return _nulljoin(res) -+ return _unquote_sub(_unquote_replace, str) - - # The _getdate() routine is used to set the expiration time in the cookie's HTTP - # header. By default, _getdate() returns the current time in the appropriate -diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py -index 925c869..8879902 100644 ---- a/Lib/test/test_http_cookies.py -+++ b/Lib/test/test_http_cookies.py -@@ -5,6 +5,7 @@ import unittest - import doctest - from http import cookies - import pickle -+from test import support - - - class CookieTests(unittest.TestCase): -@@ -58,6 +59,43 @@ class CookieTests(unittest.TestCase): - for k, v in sorted(case['dict'].items()): - self.assertEqual(C[k].value, v) - -+ def test_unquote(self): -+ cases = [ -+ (r'a="b=\""', 'b="'), -+ (r'a="b=\\"', 'b=\\'), -+ (r'a="b=\="', 'b=='), -+ (r'a="b=\n"', 'b=n'), -+ (r'a="b=\042"', 'b="'), -+ (r'a="b=\134"', 'b=\\'), -+ (r'a="b=\377"', 'b=\xff'), -+ (r'a="b=\400"', 'b=400'), -+ (r'a="b=\42"', 'b=42'), -+ (r'a="b=\\042"', 'b=\\042'), -+ (r'a="b=\\134"', 'b=\\134'), -+ (r'a="b=\\\""', 'b=\\"'), -+ (r'a="b=\\\042"', 'b=\\"'), -+ (r'a="b=\134\""', 'b=\\"'), -+ (r'a="b=\134\042"', 'b=\\"'), -+ ] -+ for encoded, decoded in cases: -+ with self.subTest(encoded): -+ C = cookies.SimpleCookie() -+ C.load(encoded) -+ self.assertEqual(C['a'].value, decoded) -+ -+ @support.requires_resource('cpu') -+ def test_unquote_large(self): -+ n = 10**6 -+ for encoded in r'\\', r'\134': -+ with self.subTest(encoded): -+ data = 'a="b=' + encoded*n + ';"' -+ C = cookies.SimpleCookie() -+ C.load(data) -+ value = C['a'].value -+ self.assertEqual(value[:3], 'b=\\') -+ self.assertEqual(value[-2:], '\\;') -+ self.assertEqual(len(value), n + 3) -+ - def test_load(self): - C = cookies.SimpleCookie() - C.load('Customer="WILE_E_COYOTE"; Version=1; Path=/acme') -diff --git a/Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst b/Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst -new file mode 100644 -index 0000000..6a23456 ---- /dev/null -+++ b/Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst -@@ -0,0 +1 @@ -+Fix quadratic complexity in parsing ``"``-quoted cookie values with backslashes by :mod:`http.cookies`. --- -2.43.0 - diff --git a/backport-CVE-2024-8088-gh-123270-Replaced-SanitizedNames-with-a-more-surgic.patch b/backport-CVE-2024-8088-gh-123270-Replaced-SanitizedNames-with-a-more-surgic.patch deleted file mode 100644 index e40560093a0c2209ed3e228303819f5e2e66efd1..0000000000000000000000000000000000000000 --- a/backport-CVE-2024-8088-gh-123270-Replaced-SanitizedNames-with-a-more-surgic.patch +++ /dev/null @@ -1,150 +0,0 @@ -From 148c957b6f1fe22ed5465f4b05adb87db361fb0f Mon Sep 17 00:00:00 2001 -From: xinsheng -Date: Wed, 4 Sep 2024 19:21:41 +0800 -Subject: [PATCH] gh-123270: Replaced SanitizedNames with a more surgical fix. - (GH-123354) - ---- - Lib/test/test_zipfile.py | 77 +++++++++++++++++++ - Lib/zipfile.py | 10 ++- - ...-08-26-13-45-20.gh-issue-123270.gXHvNJ.rst | 3 + - 3 files changed, 88 insertions(+), 2 deletions(-) - create mode 100644 Misc/NEWS.d/next/Library/2024-08-26-13-45-20.gh-issue-123270.gXHvNJ.rst - -diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py -index c8e0159..669e494 100644 ---- a/Lib/test/test_zipfile.py -+++ b/Lib/test/test_zipfile.py -@@ -3512,6 +3512,83 @@ with zipfile.ZipFile(io.BytesIO(), "w") as zf: - zipfile.Path(zf) - zf.extractall(source_path.parent) - -+ def test_malformed_paths(self): -+ """ -+ Path should handle malformed paths gracefully. -+ -+ Paths with leading slashes are not visible. -+ -+ Paths with dots are treated like regular files. -+ """ -+ data = io.BytesIO() -+ zf = zipfile.ZipFile(data, "w") -+ zf.writestr("/one-slash.txt", b"content") -+ zf.writestr("//two-slash.txt", b"content") -+ zf.writestr("../parent.txt", b"content") -+ zf.filename = '' -+ root = zipfile.Path(zf) -+ assert list(map(str, root.iterdir())) == ['../'] -+ assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content' -+ -+ def test_unsupported_names(self): -+ """ -+ Path segments with special characters are readable. -+ -+ On some platforms or file systems, characters like -+ ``:`` and ``?`` are not allowed, but they are valid -+ in the zip file. -+ """ -+ data = io.BytesIO() -+ zf = zipfile.ZipFile(data, "w") -+ zf.writestr("path?", b"content") -+ zf.writestr("V: NMS.flac", b"fLaC...") -+ zf.filename = '' -+ root = zipfile.Path(zf) -+ contents = root.iterdir() -+ assert next(contents).name == 'path?' -+ assert next(contents).name == 'V: NMS.flac' -+ assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..." -+ -+ def test_backslash_not_separator(self): -+ """ -+ In a zip file, backslashes are not separators. -+ """ -+ data = io.BytesIO() -+ zf = zipfile.ZipFile(data, "w") -+ zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content") -+ zf.filename = '' -+ root = zipfile.Path(zf) -+ (first,) = root.iterdir() -+ assert not first.is_dir() -+ assert first.name == 'foo\\bar' -+ -+ -+class DirtyZipInfo(zipfile.ZipInfo): -+ """ -+ Bypass name sanitization. -+ """ -+ -+ def __init__(self, filename, *args, **kwargs): -+ super().__init__(filename, *args, **kwargs) -+ self.filename = filename -+ -+ @classmethod -+ def for_name(cls, name, archive): -+ """ -+ Construct the same way that ZipFile.writestr does. -+ -+ TODO: extract this functionality and re-use -+ """ -+ self = cls(filename=name, date_time=time.localtime(time.time())[:6]) -+ self.compress_type = archive.compression -+ self.compress_level = archive.compresslevel -+ if self.filename.endswith('/'): # pragma: no cover -+ self.external_attr = 0o40775 << 16 # drwxrwxr-x -+ self.external_attr |= 0x10 # MS-DOS directory flag -+ else: -+ self.external_attr = 0o600 << 16 # ?rw------- -+ return self -+ - - class EncodedMetadataTests(unittest.TestCase): - file_names = ['\u4e00', '\u4e8c', '\u4e09'] # Han 'one', 'two', 'three' -diff --git a/Lib/zipfile.py b/Lib/zipfile.py -index 6189db5..622a3ee 100644 ---- a/Lib/zipfile.py -+++ b/Lib/zipfile.py -@@ -9,6 +9,7 @@ import io - import itertools - import os - import posixpath -+import re - import shutil - import stat - import struct -@@ -2192,7 +2193,7 @@ def _parents(path): - def _ancestry(path): - """ - Given a path with elements separated by -- posixpath.sep, generate all elements of that path -+ posixpath.sep, generate all elements of that path. - - >>> list(_ancestry('b/d')) - ['b/d', 'b'] -@@ -2204,9 +2205,14 @@ def _ancestry(path): - ['b'] - >>> list(_ancestry('')) - [] -+ -+ Multiple separators are treated like a single. -+ -+ >>> list(_ancestry('//b//d///f//')) -+ ['//b//d///f', '//b//d', '//b'] - """ - path = path.rstrip(posixpath.sep) -- while path and path != posixpath.sep: -+ while path.rstrip(posixpath.sep): - yield path - path, tail = posixpath.split(path) - -diff --git a/Misc/NEWS.d/next/Library/2024-08-26-13-45-20.gh-issue-123270.gXHvNJ.rst b/Misc/NEWS.d/next/Library/2024-08-26-13-45-20.gh-issue-123270.gXHvNJ.rst -new file mode 100644 -index 0000000..ee9fde6 ---- /dev/null -+++ b/Misc/NEWS.d/next/Library/2024-08-26-13-45-20.gh-issue-123270.gXHvNJ.rst -@@ -0,0 +1,3 @@ -+Applied a more surgical fix for malformed payloads in :class:`zipfile.Path` -+causing infinite loops (gh-122905) without breaking contents using -+legitimate characters. --- -2.43.0 - diff --git a/fix-check-error-with-CVEfixed-expat-2.5.0.patch b/fix-check-error-with-CVEfixed-expat-2.5.0.patch deleted file mode 100644 index 4b1cf3439280d8baf8fe3912112d8c8792db4ee4..0000000000000000000000000000000000000000 --- a/fix-check-error-with-CVEfixed-expat-2.5.0.patch +++ /dev/null @@ -1,28 +0,0 @@ -diff -ur a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py ---- a/Lib/test/test_xml_etree.py 2024-06-25 03:33:22.925676272 +0000 -+++ b/Lib/test/test_xml_etree.py 2024-06-25 03:34:30.900338499 +0000 -@@ -121,8 +121,8 @@ - - """ - --fails_with_expat_2_6_0 = (unittest.expectedFailure -- if pyexpat.version_info >= (2, 6, 0) else -+fails_with_expat_2_5_0 = (unittest.expectedFailure -+ if pyexpat.version_info >= (2, 5, 0) else - lambda test: test) - - def checkwarnings(*filters, quiet=False): -@@ -1424,11 +1424,11 @@ - self.assert_event_tags(parser, [('end', 'root')]) - self.assertIsNone(parser.close()) - -- @fails_with_expat_2_6_0 -+ @fails_with_expat_2_5_0 - def test_simple_xml_chunk_1(self): - self.test_simple_xml(chunk_size=1) - -- @fails_with_expat_2_6_0 -+ @fails_with_expat_2_5_0 - def test_simple_xml_chunk_5(self): - self.test_simple_xml(chunk_size=5) - diff --git a/python3.spec b/python3.spec index 907d7b22b4e3f703bdb03b258e218ce705ad4fe3..0200dd9c104d158f3f97c1f8f33599fbcdb1359c 100644 --- a/python3.spec +++ b/python3.spec @@ -2,8 +2,8 @@ Name: python3 Summary: Interpreter of the Python3 programming language URL: https://www.python.org/ -Version: 3.11.6 -Release: 8 +Version: 3.11.10 +Release: 1 License: Python-2.0 %global branchversion 3.11 @@ -92,21 +92,8 @@ Source1: pyconfig.h Patch1: 00001-rpath.patch Patch251: 00251-change-user-install-location.patch -Patch6000: backport-CVE-2024-0397-gh-114572-Fix-locking-in-cert_store_stats-and-g.patch -Patch6001: backport-CVE-2024-4032-gh-113171-gh-65056-Fix-private-non-global-IP-ad.patch -Patch6002: backport-3.11-gh-115133-Fix-tests-for-XMLPullParser-with-Expa.patch -Patch6003: backport-CVE-2024-6923-gh-121650-Encode-newlines-in-headers-and-verify-head.patch -Patch6004: backport-CVE-2024-7592-gh-123067-Fix-quadratic-complexity-in-parsing-quoted.patch -Patch6005: backport-CVE-2024-8088-gh-123270-Replaced-SanitizedNames-with-a-more-surgic.patch -Patch6006: backport-CVE-2024-6232-gh-121285-Remove-backtracking-when-parsing-tarf.patch -Patch6007: backport-CVE-2024-3219-1-gh-122133-Authenticate-socket-connection-for-so.patch -Patch6008: backport-CVE-2024-3219-2-gh-122133-Rework-pure-Python-socketpair-tests-t.patch -Patch6009: backport-CVE-2023-6597-gh-91133-tempfile.TemporaryDirectory-fix-symlin.patch -Patch6010: backport-CVE-2024-0450-gh-109858-Protect-zipfile-from-quoted-overlap-z.patch - Patch9000: add-the-sm3-method-for-obtaining-the-salt-value.patch Patch9001: 0001-add-loongarch64-support-for-python.patch -Patch9002: fix-check-error-with-CVEfixed-expat-2.5.0.patch Provides: python%{branchversion} = %{version}-%{release} Provides: python(abi) = %{branchversion} @@ -193,31 +180,12 @@ extension modules. %package_help %prep -%setup -q -n Python-%{version} +%autosetup -n Python-%{version} -p1 find -name '*.exe' -print -delete rm -r Modules/expat rm Lib/ensurepip/_bundled/*.whl rm configure pyconfig.h.in -%patch1 -p1 -%patch251 -p1 - -%patch6000 -p1 -%patch6001 -p1 -%patch6002 -p1 -%patch6003 -p1 -%patch6004 -p1 -%patch6005 -p1 -%patch6006 -p1 -%patch6007 -p1 -%patch6008 -p1 -%patch6009 -p1 -%patch6010 -p1 - -%patch9000 -p1 -%patch9001 -p1 -%patch9002 -p1 - %build autoconf autoheader @@ -658,7 +626,8 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP" %dir %{pylibdir}/importlib/metadata/ %dir %{pylibdir}/importlib/metadata/__pycache__/ -%{pylibdir}/importlib/metadata/ +%{pylibdir}/importlib/metadata/*.py +%{pylibdir}/importlib/metadata/__pycache__/*%{bytecode_suffixes} %dir %{pylibdir}/importlib/resources/ %dir %{pylibdir}/importlib/resources/__pycache__/ @@ -878,6 +847,9 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP" %{_mandir}/*/* %changelog +* Sat Oct 19 2024 Funda Wang - 3.11.10-1 +- update to 3.11.10 + * Tue Sep 24 2024 xinsheng - 3.11.6-8 - Type:CVE - CVE:CVE-2024-6232,CVE-2024-3219,CVE-2024-0450,CVE-2023-6597 diff --git a/python3.yaml b/python3.yaml index 4f544b01c2d2a83e0fe515aae3018f62f5a015e4..1da67044576d884c7033037e755a2db881eef38c 100644 --- a/python3.yaml +++ b/python3.yaml @@ -1,4 +1,4 @@ version_control: github src_repo: python/cpython tag_prefix: ^v -seperator: . \ No newline at end of file +separator: .