From 09104090266e05effc9464810c616d649654688d Mon Sep 17 00:00:00 2001 From: MSVSphere Packaging Team Date: Thu, 28 Mar 2024 18:14:34 +0300 Subject: [PATCH] import python3.11-3.11.7-1.el9 --- .gitignore | 2 +- .python3.11.metadata | 2 +- SOURCES/00329-fips.patch | 90 +-- SOURCES/00397-tarfile-filter.patch | 110 --- ...-addresses-in-email-parseaddr-111116.patch | 755 ++++++++++++++++++ SOURCES/Python-3.11.4.tar.xz.asc | 16 - SOURCES/Python-3.11.7.tar.xz.asc | 16 + SOURCES/check-pyc-timestamps.py | 6 +- SPECS/python3.11.spec | 41 +- 9 files changed, 859 insertions(+), 179 deletions(-) create mode 100644 SOURCES/00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch delete mode 100644 SOURCES/Python-3.11.4.tar.xz.asc create mode 100644 SOURCES/Python-3.11.7.tar.xz.asc diff --git a/.gitignore b/.gitignore index 245fd29..b16aaa9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/Python-3.11.4.tar.xz +SOURCES/Python-3.11.7.tar.xz diff --git a/.python3.11.metadata b/.python3.11.metadata index 6c4d744..7c0a04b 100644 --- a/.python3.11.metadata +++ b/.python3.11.metadata @@ -1 +1 @@ -413b3715d919a7b473281529ab91eeea5c82e632 SOURCES/Python-3.11.4.tar.xz +f2534d591121f3845388fbdd6a121b96dfe305a6 SOURCES/Python-3.11.7.tar.xz diff --git a/SOURCES/00329-fips.patch b/SOURCES/00329-fips.patch index b4763dd..256d214 100644 --- a/SOURCES/00329-fips.patch +++ b/SOURCES/00329-fips.patch @@ -1,4 +1,4 @@ -From c96f1bea2ffc5c0ca849d5406236c07ea229a64f Mon Sep 17 00:00:00 2001 +From ecc5137120f471c22ff6dcb1bd128561c31e023c Mon Sep 17 00:00:00 2001 From: Charalampos Stratakis Date: Thu, 12 Dec 2019 16:58:31 +0100 Subject: [PATCH 1/7] Expose blake2b and blake2s hashes from OpenSSL @@ -29,10 +29,10 @@ index 67becdd..6607ef7 100644 computed = m.hexdigest() if not shake else m.hexdigest(length) self.assertEqual( diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c -index 3c40f09..e819d02 100644 +index 57d64bd..d0c3b9e 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c -@@ -1077,6 +1077,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj, +@@ -1078,6 +1078,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj, } @@ -74,7 +74,7 @@ index 3c40f09..e819d02 100644 #ifdef PY_OPENSSL_HAS_SHA3 /*[clinic input] -@@ -2065,6 +2100,8 @@ static struct PyMethodDef EVP_functions[] = { +@@ -2066,6 +2101,8 @@ static struct PyMethodDef EVP_functions[] = { _HASHLIB_OPENSSL_SHA256_METHODDEF _HASHLIB_OPENSSL_SHA384_METHODDEF _HASHLIB_OPENSSL_SHA512_METHODDEF @@ -205,10 +205,10 @@ index 5d84f4a..011026a 100644 -/*[clinic end generated code: output=69f2374071bff707 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c6a9af5563972eda input=a9049054013a1b77]*/ -- -2.39.1 +2.43.0 -From 9a7e164840aa35602e1c6dddadd461fafc666a63 Mon Sep 17 00:00:00 2001 +From 0198d467525e79cb4be4418708719af3eaee7a40 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 1 Aug 2019 17:57:05 +0200 Subject: [PATCH 2/7] Use a stronger hash in multiprocessing handshake @@ -220,10 +220,10 @@ https://bugs.python.org/issue17258 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py -index b08144f..0497557 100644 +index 8b81f99..69c0b7e 100644 --- a/Lib/multiprocessing/connection.py +++ b/Lib/multiprocessing/connection.py -@@ -42,6 +42,10 @@ BUFSIZE = 8192 +@@ -43,6 +43,10 @@ BUFSIZE = 8192 # A very generous timeout when it comes to local connections... CONNECTION_TIMEOUT = 20. @@ -234,7 +234,7 @@ index b08144f..0497557 100644 _mmap_counter = itertools.count() default_family = 'AF_INET' -@@ -735,7 +739,7 @@ def deliver_challenge(connection, authkey): +@@ -752,7 +756,7 @@ def deliver_challenge(connection, authkey): "Authkey must be bytes, not {0!s}".format(type(authkey))) message = os.urandom(MESSAGE_LENGTH) connection.send_bytes(CHALLENGE + message) @@ -243,7 +243,7 @@ index b08144f..0497557 100644 response = connection.recv_bytes(256) # reject large message if response == digest: connection.send_bytes(WELCOME) -@@ -751,7 +755,7 @@ def answer_challenge(connection, authkey): +@@ -768,7 +772,7 @@ def answer_challenge(connection, authkey): message = connection.recv_bytes(256) # reject large message assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message message = message[len(CHALLENGE):] @@ -253,10 +253,10 @@ index b08144f..0497557 100644 response = connection.recv_bytes(256) # reject large message if response != WELCOME: -- -2.39.1 +2.43.0 -From 10b91783a2f22153738c5658a98daf7475ad9a8c Mon Sep 17 00:00:00 2001 +From a7822e2e1f21529e9730885bd8c9c6ab7c704d5b Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 25 Jul 2019 17:19:06 +0200 Subject: [PATCH 3/7] Disable Python's hash implementations in FIPS mode, @@ -359,7 +359,7 @@ index c2cac98..55b1677 100644 if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) diff --git a/Modules/_blake2/blake2module.c b/Modules/_blake2/blake2module.c -index 44d783b..d247e44 100644 +index 93478f5..e3a024d 100644 --- a/Modules/_blake2/blake2module.c +++ b/Modules/_blake2/blake2module.c @@ -13,6 +13,7 @@ @@ -370,7 +370,7 @@ index 44d783b..d247e44 100644 #include "blake2module.h" extern PyType_Spec blake2b_type_spec; -@@ -77,6 +78,7 @@ _blake2_free(void *module) +@@ -83,6 +84,7 @@ _blake2_free(void *module) static int blake2_exec(PyObject *m) { @@ -378,7 +378,7 @@ index 44d783b..d247e44 100644 Blake2State* st = blake2_get_state(m); st->blake2b_type = (PyTypeObject *)PyType_FromModuleAndSpec( -@@ -145,5 +147,6 @@ static struct PyModuleDef blake2_module = { +@@ -154,5 +156,6 @@ static struct PyModuleDef blake2_module = { PyMODINIT_FUNC PyInit__blake2(void) { @@ -446,10 +446,10 @@ index 56ae7a5..45fb403 100644 + if (_Py_hashlib_fips_error(exc, name)) return NULL; \ +} while (0) diff --git a/configure.ac b/configure.ac -index c62a565..861f7a0 100644 +index 52d5c1f..56aff78 100644 --- a/configure.ac +++ b/configure.ac -@@ -7044,7 +7044,8 @@ PY_STDLIB_MOD([_sha512], [test "$with_builtin_sha512" = yes]) +@@ -7069,7 +7069,8 @@ PY_STDLIB_MOD([_sha512], [test "$with_builtin_sha512" = yes]) PY_STDLIB_MOD([_sha3], [test "$with_builtin_sha3" = yes]) PY_STDLIB_MOD([_blake2], [test "$with_builtin_blake2" = yes], [], @@ -460,10 +460,10 @@ index c62a565..861f7a0 100644 PY_STDLIB_MOD([_crypt], [], [test "$ac_cv_crypt_crypt" = yes], -- -2.39.1 +2.43.0 -From e26066b1c05c9768e38cb6f45d6a01058de55b3f Mon Sep 17 00:00:00 2001 +From e9ce6d33544559172dbebbe0c0dfba2757c62331 Mon Sep 17 00:00:00 2001 From: Charalampos Stratakis Date: Fri, 29 Jan 2021 14:16:21 +0100 Subject: [PATCH 4/7] Use python's fall back crypto implementations only if we @@ -623,10 +623,10 @@ index 01d12f5..a7cdb07 100644 def test_pbkdf2_hmac_py(self): with warnings_helper.check_warnings(): -- -2.39.1 +2.43.0 -From 9ccbd22b8538fee379717c8b2916dc1ff8b96f07 Mon Sep 17 00:00:00 2001 +From 641c617775b6973ed84711a2602ba190fe064474 Mon Sep 17 00:00:00 2001 From: Charalampos Stratakis Date: Wed, 31 Jul 2019 15:43:43 +0200 Subject: [PATCH 5/7] Test equivalence of hashes for the various digests with @@ -783,10 +783,10 @@ index a7cdb07..c071f28 100644 class KDFTests(unittest.TestCase): -- -2.39.1 +2.43.0 -From c3b8d6ecc76c87e8b05fd2cb212d5dece50ce0b1 Mon Sep 17 00:00:00 2001 +From a706c8342f0f9307d44c43c203702e1476fe73b4 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 26 Aug 2019 19:39:48 +0200 Subject: [PATCH 6/7] Guard against Python HMAC in FIPS mode @@ -844,7 +844,7 @@ index 8b4f920..20ef96c 100644 digest_cons = digestmod elif isinstance(digestmod, str): diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py -index 7cf9973..a9e4e39 100644 +index a39a2c4..0742a1c 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -5,6 +5,7 @@ import hashlib @@ -875,7 +875,7 @@ index 7cf9973..a9e4e39 100644 with warnings.catch_warnings(): warnings.simplefilter('error', RuntimeWarning) with self.assertRaises(RuntimeWarning): -@@ -443,6 +450,7 @@ class ConstructorTestCase(unittest.TestCase): +@@ -453,6 +460,7 @@ class ConstructorTestCase(unittest.TestCase): with self.assertRaisesRegex(TypeError, "immutable type"): C_HMAC.value = None @@ -883,7 +883,7 @@ index 7cf9973..a9e4e39 100644 @unittest.skipUnless(sha256_module is not None, 'need _sha256') def test_with_sha256_module(self): h = hmac.HMAC(b"key", b"hash this!", digestmod=sha256_module.sha256) -@@ -471,6 +479,7 @@ class SanityTestCase(unittest.TestCase): +@@ -481,6 +489,7 @@ class SanityTestCase(unittest.TestCase): class CopyTestCase(unittest.TestCase): @@ -891,7 +891,7 @@ index 7cf9973..a9e4e39 100644 @hashlib_helper.requires_hashdigest('sha256') def test_attributes_old(self): # Testing if attributes are of same type. -@@ -482,6 +491,7 @@ class CopyTestCase(unittest.TestCase): +@@ -492,6 +501,7 @@ class CopyTestCase(unittest.TestCase): self.assertEqual(type(h1._outer), type(h2._outer), "Types of outer don't match.") @@ -900,10 +900,10 @@ index 7cf9973..a9e4e39 100644 def test_realcopy_old(self): # Testing if the copy method created a real copy. -- -2.39.1 +2.43.0 -From 2b06ee89344e8735cdc8435aadbdf83fe289e934 Mon Sep 17 00:00:00 2001 +From 03f1dedfe5d29af20fb3686d76b045384d41d8dd Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 25 Aug 2021 16:44:43 +0200 Subject: [PATCH 7/7] Disable hash-based PYCs in FIPS mode @@ -946,11 +946,11 @@ index db52725..5fca65e 100644 return PycInvalidationMode.CHECKED_HASH else: diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py -index c33f90d..7d40540 100644 +index dc7a6e6..646b328 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py -@@ -2225,6 +2225,20 @@ def requires_venv_with_pip(): - return unittest.skipUnless(ctypes, 'venv: pip requires ctypes') +@@ -2203,6 +2203,20 @@ def sleeping_retry(timeout, err_msg=None, /, + delay = min(delay * 2, max_delay) +def fails_in_fips_mode(expected_error): @@ -971,7 +971,7 @@ index c33f90d..7d40540 100644 def adjust_int_max_str_digits(max_digits): """Temporarily change the integer string conversion length limit.""" diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py -index 4dadbc0..7dc7e51 100644 +index 7fcd563..476b557 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -286,6 +286,7 @@ class CmdLineTest(unittest.TestCase): @@ -991,10 +991,10 @@ index 4dadbc0..7dc7e51 100644 with os_helper.temp_dir() as script_dir: script_name = _make_test_script(script_dir, '__main__') diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py -index 05154c8..c678d4a 100644 +index 9cd92ad..4ec29a1 100644 --- a/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py -@@ -800,14 +800,23 @@ class CommandLineTestsBase: +@@ -806,14 +806,23 @@ class CommandLineTestsBase: out = self.assertRunOK('badfilename') self.assertRegex(out, b"Can't list 'badfilename'") @@ -1020,10 +1020,10 @@ index 05154c8..c678d4a 100644 with open(pyc, 'rb') as fp: data = fp.read() diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py -index 4bb0390..ff62483 100644 +index 4062afd..6bc276d 100644 --- a/Lib/test/test_imp.py +++ b/Lib/test/test_imp.py -@@ -350,6 +350,7 @@ class ImportTests(unittest.TestCase): +@@ -352,6 +352,7 @@ class ImportTests(unittest.TestCase): import _frozen_importlib self.assertEqual(_frozen_importlib.__spec__.origin, "frozen") @@ -1031,7 +1031,7 @@ index 4bb0390..ff62483 100644 def test_source_hash(self): self.assertEqual(_imp.source_hash(42, b'hi'), b'\xfb\xd9G\x05\xaf$\x9b~') self.assertEqual(_imp.source_hash(43, b'hi'), b'\xd0/\x87C\xccC\xff\xe2') -@@ -369,6 +370,7 @@ class ImportTests(unittest.TestCase): +@@ -371,6 +372,7 @@ class ImportTests(unittest.TestCase): res = script_helper.assert_python_ok(*args) self.assertEqual(res.out.strip().decode('utf-8'), expected) @@ -1092,10 +1092,10 @@ index 378dcbe..7b223a1 100644 with util.create_modules('_temp') as mapping: bc_path = self.manipulate_bytecode( diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py -index e53f5d9..7266212 100644 +index 9b420d2..dd6460a 100644 --- a/Lib/test/test_py_compile.py +++ b/Lib/test/test_py_compile.py -@@ -141,13 +141,16 @@ class PyCompileTestsBase: +@@ -143,13 +143,16 @@ class PyCompileTestsBase: importlib.util.cache_from_source(bad_coding))) def test_source_date_epoch(self): @@ -1113,7 +1113,7 @@ index e53f5d9..7266212 100644 expected_flags = 0b11 else: expected_flags = 0b00 -@@ -178,7 +181,8 @@ class PyCompileTestsBase: +@@ -180,7 +183,8 @@ class PyCompileTestsBase: # Specifying optimized bytecode should lead to a path reflecting that. self.assertIn('opt-2', py_compile.compile(self.source_path, optimize=2)) @@ -1123,7 +1123,7 @@ index e53f5d9..7266212 100644 py_compile.compile( self.source_path, invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH, -@@ -187,6 +191,9 @@ class PyCompileTestsBase: +@@ -189,6 +193,9 @@ class PyCompileTestsBase: flags = importlib._bootstrap_external._classify_pyc( fp.read(), 'test', {}) self.assertEqual(flags, 0b11) @@ -1154,10 +1154,10 @@ index 59a5200..81fadb3 100644 def test_checked_hash_based_change_pyc(self): source = b"state = 'old'" diff --git a/Python/import.c b/Python/import.c -index 07a8b90..e97b47b 100644 +index 39144d3..b439059 100644 --- a/Python/import.c +++ b/Python/import.c -@@ -2437,6 +2437,26 @@ static PyObject * +@@ -2449,6 +2449,26 @@ static PyObject * _imp_source_hash_impl(PyObject *module, long key, Py_buffer *source) /*[clinic end generated code: output=edb292448cf399ea input=9aaad1e590089789]*/ { @@ -1185,5 +1185,5 @@ index 07a8b90..e97b47b 100644 uint64_t x; char data[sizeof(uint64_t)]; -- -2.39.1 +2.43.0 diff --git a/SOURCES/00397-tarfile-filter.patch b/SOURCES/00397-tarfile-filter.patch index bd8f98f..3c4ebf4 100644 --- a/SOURCES/00397-tarfile-filter.patch +++ b/SOURCES/00397-tarfile-filter.patch @@ -1,113 +1,3 @@ -From f36519078bde3cce4328c03fffccb846121fb5bc Mon Sep 17 00:00:00 2001 -From: Petr Viktorin -Date: Wed, 9 Aug 2023 20:23:03 +0200 -Subject: [PATCH] Fix symlink handling for tarfile.data_filter - ---- - Doc/library/tarfile.rst | 5 +++++ - Lib/tarfile.py | 9 ++++++++- - Lib/test/test_tarfile.py | 26 ++++++++++++++++++++++++-- - 3 files changed, 37 insertions(+), 3 deletions(-) - -diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst -index 00f3070324e..e0511bfeb64 100644 ---- a/Doc/library/tarfile.rst -+++ b/Doc/library/tarfile.rst -@@ -740,6 +740,11 @@ A ``TarInfo`` object has the following public data attributes: - Name of the target file name, which is only present in :class:`TarInfo` objects - of type :const:`LNKTYPE` and :const:`SYMTYPE`. - -+ For symbolic links (``SYMTYPE``), the linkname is relative to the directory -+ that contains the link. -+ For hard links (``LNKTYPE``), the linkname is relative to the root of -+ the archive. -+ - - .. attribute:: TarInfo.uid - :type: int -diff --git a/Lib/tarfile.py b/Lib/tarfile.py -index df4e41f7a0d..d62323715b4 100755 ---- a/Lib/tarfile.py -+++ b/Lib/tarfile.py -@@ -802,7 +802,14 @@ def _get_filtered_attrs(member, dest_path, for_data=True): - if member.islnk() or member.issym(): - if os.path.isabs(member.linkname): - raise AbsoluteLinkError(member) -- target_path = os.path.realpath(os.path.join(dest_path, member.linkname)) -+ if member.issym(): -+ target_path = os.path.join(dest_path, -+ os.path.dirname(name), -+ member.linkname) -+ else: -+ target_path = os.path.join(dest_path, -+ member.linkname) -+ target_path = os.path.realpath(target_path) - if os.path.commonpath([target_path, dest_path]) != dest_path: - raise LinkOutsideDestinationError(member, target_path) - return new_attrs -diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py -index 2eda7fc4cea..79fc35c2895 100644 ---- a/Lib/test/test_tarfile.py -+++ b/Lib/test/test_tarfile.py -@@ -3337,10 +3337,12 @@ def __exit__(self, *exc): - self.bio = None - - def add(self, name, *, type=None, symlink_to=None, hardlink_to=None, -- mode=None, **kwargs): -+ mode=None, size=None, **kwargs): - """Add a member to the test archive. Call within `with`.""" - name = str(name) - tarinfo = tarfile.TarInfo(name).replace(**kwargs) -+ if size is not None: -+ tarinfo.size = size - if mode: - tarinfo.mode = _filemode_to_int(mode) - if symlink_to is not None: -@@ -3416,7 +3418,8 @@ def check_context(self, tar, filter): - raise self.raised_exception - self.assertEqual(self.expected_paths, set()) - -- def expect_file(self, name, type=None, symlink_to=None, mode=None): -+ def expect_file(self, name, type=None, symlink_to=None, mode=None, -+ size=None): - """Check a single file. See check_context.""" - if self.raised_exception: - raise self.raised_exception -@@ -3445,6 +3448,8 @@ def expect_file(self, name, type=None, symlink_to=None, mode=None): - self.assertTrue(path.is_fifo()) - else: - raise NotImplementedError(type) -+ if size is not None: -+ self.assertEqual(path.stat().st_size, size) - for parent in path.parents: - self.expected_paths.discard(parent) - -@@ -3649,6 +3654,22 @@ def test_sly_relative2(self): - + """['"].*moo['"], which is outside the """ - + "destination") - -+ def test_deep_symlink(self): -+ with ArchiveMaker() as arc: -+ arc.add('targetdir/target', size=3) -+ arc.add('linkdir/hardlink', hardlink_to='targetdir/target') -+ arc.add('linkdir/symlink', symlink_to='../targetdir/target') -+ -+ for filter in 'tar', 'data', 'fully_trusted': -+ with self.check_context(arc.open(), filter): -+ self.expect_file('targetdir/target', size=3) -+ self.expect_file('linkdir/hardlink', size=3) -+ if os_helper.can_symlink(): -+ self.expect_file('linkdir/symlink', size=3, -+ symlink_to='../targetdir/target') -+ else: -+ self.expect_file('linkdir/symlink', size=3) -+ - def test_modes(self): - # Test how file modes are extracted - # (Note that the modes are ignored on platforms without working chmod) --- -2.41.0 - From 8b70605b594b3831331a9340ba764ff751871612 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 6 Mar 2023 17:24:24 +0100 diff --git a/SOURCES/00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch b/SOURCES/00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch new file mode 100644 index 0000000..73bf9b7 --- /dev/null +++ b/SOURCES/00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch @@ -0,0 +1,755 @@ +From d8b0fafb202bf884135a3f7f0ce0b086217a2da2 Mon Sep 17 00:00:00 2001 +From: Victor Stinner +Date: Fri, 15 Dec 2023 16:10:40 +0100 +Subject: [PATCH 1/2] 00415: [CVE-2023-27043] gh-102988: Reject malformed + addresses in email.parseaddr() (#111116) + +Detect email address parsing errors and return empty tuple to +indicate the parsing error (old API). Add an optional 'strict' +parameter to getaddresses() and parseaddr() functions. Patch by +Thomas Dwyer. + +Co-Authored-By: Thomas Dwyer +--- + Doc/library/email.utils.rst | 19 +- + Lib/email/utils.py | 151 ++++++++++++- + Lib/test/test_email/test_email.py | 204 +++++++++++++++++- + ...-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8 + + 4 files changed, 361 insertions(+), 21 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst + +diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst +index 0e266b6..6723dc4 100644 +--- a/Doc/library/email.utils.rst ++++ b/Doc/library/email.utils.rst +@@ -60,13 +60,18 @@ of the new API. + begins with angle brackets, they are stripped off. + + +-.. function:: parseaddr(address) ++.. function:: parseaddr(address, *, strict=True) + + Parse address -- which should be the value of some address-containing field such + as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and + *email address* parts. Returns a tuple of that information, unless the parse + fails, in which case a 2-tuple of ``('', '')`` is returned. + ++ If *strict* is true, use a strict parser which rejects malformed inputs. ++ ++ .. versionchanged:: 3.13 ++ Add *strict* optional parameter and reject malformed inputs by default. ++ + + .. function:: formataddr(pair, charset='utf-8') + +@@ -84,12 +89,15 @@ of the new API. + Added the *charset* option. + + +-.. function:: getaddresses(fieldvalues) ++.. function:: getaddresses(fieldvalues, *, strict=True) + + This method returns a list of 2-tuples of the form returned by ``parseaddr()``. + *fieldvalues* is a sequence of header field values as might be returned by +- :meth:`Message.get_all `. Here's a simple +- example that gets all the recipients of a message:: ++ :meth:`Message.get_all `. ++ ++ If *strict* is true, use a strict parser which rejects malformed inputs. ++ ++ Here's a simple example that gets all the recipients of a message:: + + from email.utils import getaddresses + +@@ -99,6 +107,9 @@ of the new API. + resent_ccs = msg.get_all('resent-cc', []) + all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) + ++ .. versionchanged:: 3.13 ++ Add *strict* optional parameter and reject malformed inputs by default. ++ + + .. function:: parsedate(date) + +diff --git a/Lib/email/utils.py b/Lib/email/utils.py +index cfdfeb3..9522341 100644 +--- a/Lib/email/utils.py ++++ b/Lib/email/utils.py +@@ -48,6 +48,7 @@ TICK = "'" + specialsre = re.compile(r'[][\\()<>@,:;".]') + escapesre = re.compile(r'[\\"]') + ++ + def _has_surrogates(s): + """Return True if s contains surrogate-escaped binary data.""" + # This check is based on the fact that unless there are surrogates, utf8 +@@ -106,12 +107,127 @@ def formataddr(pair, charset='utf-8'): + return address + + ++def _iter_escaped_chars(addr): ++ pos = 0 ++ escape = False ++ for pos, ch in enumerate(addr): ++ if escape: ++ yield (pos, '\\' + ch) ++ escape = False ++ elif ch == '\\': ++ escape = True ++ else: ++ yield (pos, ch) ++ if escape: ++ yield (pos, '\\') ++ ++ ++def _strip_quoted_realnames(addr): ++ """Strip real names between quotes.""" ++ if '"' not in addr: ++ # Fast path ++ return addr ++ ++ start = 0 ++ open_pos = None ++ result = [] ++ for pos, ch in _iter_escaped_chars(addr): ++ if ch == '"': ++ if open_pos is None: ++ open_pos = pos ++ else: ++ if start != open_pos: ++ result.append(addr[start:open_pos]) ++ start = pos + 1 ++ open_pos = None ++ ++ if start < len(addr): ++ result.append(addr[start:]) ++ ++ return ''.join(result) + +-def getaddresses(fieldvalues): +- """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" +- all = COMMASPACE.join(str(v) for v in fieldvalues) +- a = _AddressList(all) +- return a.addresslist ++ ++supports_strict_parsing = True ++ ++def getaddresses(fieldvalues, *, strict=True): ++ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. ++ ++ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in ++ its place. ++ ++ If strict is true, use a strict parser which rejects malformed inputs. ++ """ ++ ++ # If strict is true, if the resulting list of parsed addresses is greater ++ # than the number of fieldvalues in the input list, a parsing error has ++ # occurred and consequently a list containing a single empty 2-tuple [('', ++ # '')] is returned in its place. This is done to avoid invalid output. ++ # ++ # Malformed input: getaddresses(['alice@example.com ']) ++ # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')] ++ # Safe output: [('', '')] ++ ++ if not strict: ++ all = COMMASPACE.join(str(v) for v in fieldvalues) ++ a = _AddressList(all) ++ return a.addresslist ++ ++ fieldvalues = [str(v) for v in fieldvalues] ++ fieldvalues = _pre_parse_validation(fieldvalues) ++ addr = COMMASPACE.join(fieldvalues) ++ a = _AddressList(addr) ++ result = _post_parse_validation(a.addresslist) ++ ++ # Treat output as invalid if the number of addresses is not equal to the ++ # expected number of addresses. ++ n = 0 ++ for v in fieldvalues: ++ # When a comma is used in the Real Name part it is not a deliminator. ++ # So strip those out before counting the commas. ++ v = _strip_quoted_realnames(v) ++ # Expected number of addresses: 1 + number of commas ++ n += 1 + v.count(',') ++ if len(result) != n: ++ return [('', '')] ++ ++ return result ++ ++ ++def _check_parenthesis(addr): ++ # Ignore parenthesis in quoted real names. ++ addr = _strip_quoted_realnames(addr) ++ ++ opens = 0 ++ for pos, ch in _iter_escaped_chars(addr): ++ if ch == '(': ++ opens += 1 ++ elif ch == ')': ++ opens -= 1 ++ if opens < 0: ++ return False ++ return (opens == 0) ++ ++ ++def _pre_parse_validation(email_header_fields): ++ accepted_values = [] ++ for v in email_header_fields: ++ if not _check_parenthesis(v): ++ v = "('', '')" ++ accepted_values.append(v) ++ ++ return accepted_values ++ ++ ++def _post_parse_validation(parsed_email_header_tuples): ++ accepted_values = [] ++ # The parser would have parsed a correctly formatted domain-literal ++ # The existence of an [ after parsing indicates a parsing failure ++ for v in parsed_email_header_tuples: ++ if '[' in v[1]: ++ v = ('', '') ++ accepted_values.append(v) ++ ++ return accepted_values + + + def _format_timetuple_and_zone(timetuple, zone): +@@ -205,16 +321,33 @@ def parsedate_to_datetime(data): + tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) + + +-def parseaddr(addr): ++def parseaddr(addr, *, strict=True): + """ + Parse addr into its constituent realname and email address parts. + + Return a tuple of realname and email address, unless the parse fails, in + which case return a 2-tuple of ('', ''). ++ ++ If strict is True, use a strict parser which rejects malformed inputs. + """ +- addrs = _AddressList(addr).addresslist +- if not addrs: +- return '', '' ++ if not strict: ++ addrs = _AddressList(addr).addresslist ++ if not addrs: ++ return ('', '') ++ return addrs[0] ++ ++ if isinstance(addr, list): ++ addr = addr[0] ++ ++ if not isinstance(addr, str): ++ return ('', '') ++ ++ addr = _pre_parse_validation([addr])[0] ++ addrs = _post_parse_validation(_AddressList(addr).addresslist) ++ ++ if not addrs or len(addrs) > 1: ++ return ('', '') ++ + return addrs[0] + + +diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py +index 677f209..20b6779 100644 +--- a/Lib/test/test_email/test_email.py ++++ b/Lib/test/test_email/test_email.py +@@ -17,6 +17,7 @@ from unittest.mock import patch + + import email + import email.policy ++import email.utils + + from email.charset import Charset + from email.generator import Generator, DecodedGenerator, BytesGenerator +@@ -3321,15 +3322,154 @@ Foo + [('Al Person', 'aperson@dom.ain'), + ('Bud Person', 'bperson@dom.ain')]) + ++ def test_getaddresses_comma_in_name(self): ++ """GH-106669 regression test.""" ++ self.assertEqual( ++ utils.getaddresses( ++ [ ++ '"Bud, Person" ', ++ 'aperson@dom.ain (Al Person)', ++ '"Mariusz Felisiak" ', ++ ] ++ ), ++ [ ++ ('Bud, Person', 'bperson@dom.ain'), ++ ('Al Person', 'aperson@dom.ain'), ++ ('Mariusz Felisiak', 'to@example.com'), ++ ], ++ ) ++ ++ def test_parsing_errors(self): ++ """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056""" ++ alice = 'alice@example.org' ++ bob = 'bob@example.com' ++ empty = ('', '') ++ ++ # Test utils.getaddresses() and utils.parseaddr() on malformed email ++ # addresses: default behavior (strict=True) rejects malformed address, ++ # and strict=False which tolerates malformed address. ++ for invalid_separator, expected_non_strict in ( ++ ('(', [(f'<{bob}>', alice)]), ++ (')', [('', alice), empty, ('', bob)]), ++ ('<', [('', alice), empty, ('', bob), empty]), ++ ('>', [('', alice), empty, ('', bob)]), ++ ('[', [('', f'{alice}[<{bob}>]')]), ++ (']', [('', alice), empty, ('', bob)]), ++ ('@', [empty, empty, ('', bob)]), ++ (';', [('', alice), empty, ('', bob)]), ++ (':', [('', alice), ('', bob)]), ++ ('.', [('', alice + '.'), ('', bob)]), ++ ('"', [('', alice), ('', f'<{bob}>')]), ++ ): ++ address = f'{alice}{invalid_separator}<{bob}>' ++ with self.subTest(address=address): ++ self.assertEqual(utils.getaddresses([address]), ++ [empty]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ expected_non_strict) ++ ++ self.assertEqual(utils.parseaddr([address]), ++ empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Comma (',') is treated differently depending on strict parameter. ++ # Comma without quotes. ++ address = f'{alice},<{bob}>' ++ self.assertEqual(utils.getaddresses([address]), ++ [('', alice), ('', bob)]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ [('', alice), ('', bob)]) ++ self.assertEqual(utils.parseaddr([address]), ++ empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Real name between quotes containing comma. ++ address = '"Alice, alice@example.org" ' ++ expected_strict = ('Alice, alice@example.org', 'bob@example.com') ++ self.assertEqual(utils.getaddresses([address]), [expected_strict]) ++ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) ++ self.assertEqual(utils.parseaddr([address]), expected_strict) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Valid parenthesis in comments. ++ address = 'alice@example.org (Alice)' ++ expected_strict = ('Alice', 'alice@example.org') ++ self.assertEqual(utils.getaddresses([address]), [expected_strict]) ++ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) ++ self.assertEqual(utils.parseaddr([address]), expected_strict) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Invalid parenthesis in comments. ++ address = 'alice@example.org )Alice(' ++ self.assertEqual(utils.getaddresses([address]), [empty]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ [('', 'alice@example.org'), ('', ''), ('', 'Alice')]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Two addresses with quotes separated by comma. ++ address = '"Jane Doe" , "John Doe" ' ++ self.assertEqual(utils.getaddresses([address]), ++ [('Jane Doe', 'jane@example.net'), ++ ('John Doe', 'john@example.net')]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ [('Jane Doe', 'jane@example.net'), ++ ('John Doe', 'john@example.net')]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Test email.utils.supports_strict_parsing attribute ++ self.assertEqual(email.utils.supports_strict_parsing, True) ++ + def test_getaddresses_nasty(self): +- eq = self.assertEqual +- eq(utils.getaddresses(['foo: ;']), [('', '')]) +- eq(utils.getaddresses( +- ['[]*-- =~$']), +- [('', ''), ('', ''), ('', '*--')]) +- eq(utils.getaddresses( +- ['foo: ;', '"Jason R. Mastaler" ']), +- [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) ++ for addresses, expected in ( ++ (['"Sürname, Firstname" '], ++ [('Sürname, Firstname', 'to@example.com')]), ++ ++ (['foo: ;'], ++ [('', '')]), ++ ++ (['foo: ;', '"Jason R. Mastaler" '], ++ [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]), ++ ++ ([r'Pete(A nice \) chap) '], ++ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]), ++ ++ (['(Empty list)(start)Undisclosed recipients :(nobody(I know))'], ++ [('', '')]), ++ ++ (['Mary <@machine.tld:mary@example.net>, , jdoe@test . example'], ++ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]), ++ ++ (['John Doe '], ++ [('John Doe (comment)', 'jdoe@machine.example')]), ++ ++ (['"Mary Smith: Personal Account" '], ++ [('Mary Smith: Personal Account', 'smith@home.example')]), ++ ++ (['Undisclosed recipients:;'], ++ [('', '')]), ++ ++ ([r', "Giant; \"Big\" Box" '], ++ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]), ++ ): ++ with self.subTest(addresses=addresses): ++ self.assertEqual(utils.getaddresses(addresses), ++ expected) ++ self.assertEqual(utils.getaddresses(addresses, strict=False), ++ expected) ++ ++ addresses = ['[]*-- =~$'] ++ self.assertEqual(utils.getaddresses(addresses), ++ [('', '')]) ++ self.assertEqual(utils.getaddresses(addresses, strict=False), ++ [('', ''), ('', ''), ('', '*--')]) + + def test_getaddresses_embedded_comment(self): + """Test proper handling of a nested comment""" +@@ -3520,6 +3660,54 @@ multipart/report + m = cls(*constructor, policy=email.policy.default) + self.assertIs(m.policy, email.policy.default) + ++ def test_iter_escaped_chars(self): ++ self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')), ++ [(0, 'a'), ++ (2, '\\\\'), ++ (3, 'b'), ++ (5, '\\"'), ++ (6, 'c'), ++ (8, '\\\\'), ++ (9, '"'), ++ (10, 'd')]) ++ self.assertEqual(list(utils._iter_escaped_chars('a\\')), ++ [(0, 'a'), (1, '\\')]) ++ ++ def test_strip_quoted_realnames(self): ++ def check(addr, expected): ++ self.assertEqual(utils._strip_quoted_realnames(addr), expected) ++ ++ check('"Jane Doe" , "John Doe" ', ++ ' , ') ++ check(r'"Jane \"Doe\"." ', ++ ' ') ++ ++ # special cases ++ check(r'before"name"after', 'beforeafter') ++ check(r'before"name"', 'before') ++ check(r'b"name"', 'b') # single char ++ check(r'"name"after', 'after') ++ check(r'"name"a', 'a') # single char ++ check(r'"name"', '') ++ ++ # no change ++ for addr in ( ++ 'Jane Doe , John Doe ', ++ 'lone " quote', ++ ): ++ self.assertEqual(utils._strip_quoted_realnames(addr), addr) ++ ++ ++ def test_check_parenthesis(self): ++ addr = 'alice@example.net' ++ self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)')) ++ self.assertFalse(utils._check_parenthesis(f'{addr} )Alice(')) ++ self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))')) ++ self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)')) ++ ++ # Ignore real name between quotes ++ self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}')) ++ + + # Test the iterator/generators + class TestIterators(TestEmailBase): +diff --git a/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst +new file mode 100644 +index 0000000..3d0e9e4 +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst +@@ -0,0 +1,8 @@ ++:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now ++return ``('', '')`` 2-tuples in more situations where invalid email ++addresses are encountered instead of potentially inaccurate values. Add ++optional *strict* parameter to these two functions: use ``strict=False`` to ++get the old behavior, accept malformed inputs. ++``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to check ++if the *strict* paramater is available. Patch by Thomas Dwyer and Victor ++Stinner to improve the CVE-2023-27043 fix. +-- +2.43.0 + + +From 6c34f5b95da90bd494e29776c0e807af44689fae Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Wed, 10 Jan 2024 08:53:53 +0100 +Subject: [PATCH 2/2] Make it possible to disable strict parsing in email + module + +--- + Doc/library/email.utils.rst | 26 +++++++++++ + Lib/email/utils.py | 54 +++++++++++++++++++++- + Lib/test/test_email/test_email.py | 74 ++++++++++++++++++++++++++++++- + 3 files changed, 150 insertions(+), 4 deletions(-) + +diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst +index 6723dc4..c89602d 100644 +--- a/Doc/library/email.utils.rst ++++ b/Doc/library/email.utils.rst +@@ -69,6 +69,19 @@ of the new API. + + If *strict* is true, use a strict parser which rejects malformed inputs. + ++ The default setting for *strict* is set to ``True``, but you can override ++ it by setting the environment variable ``PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING`` ++ to non-empty string. ++ ++ Additionally, you can permanently set the default value for *strict* to ++ ``False`` by creating the configuration file ``/etc/python/email.cfg`` ++ with the following content: ++ ++ .. code-block:: ini ++ ++ [email_addr_parsing] ++ PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true ++ + .. versionchanged:: 3.13 + Add *strict* optional parameter and reject malformed inputs by default. + +@@ -97,6 +110,19 @@ of the new API. + + If *strict* is true, use a strict parser which rejects malformed inputs. + ++ The default setting for *strict* is set to ``True``, but you can override ++ it by setting the environment variable ``PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING`` ++ to non-empty string. ++ ++ Additionally, you can permanently set the default value for *strict* to ++ ``False`` by creating the configuration file ``/etc/python/email.cfg`` ++ with the following content: ++ ++ .. code-block:: ini ++ ++ [email_addr_parsing] ++ PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true ++ + Here's a simple example that gets all the recipients of a message:: + + from email.utils import getaddresses +diff --git a/Lib/email/utils.py b/Lib/email/utils.py +index 9522341..2e30e09 100644 +--- a/Lib/email/utils.py ++++ b/Lib/email/utils.py +@@ -48,6 +48,46 @@ TICK = "'" + specialsre = re.compile(r'[][\\()<>@,:;".]') + escapesre = re.compile(r'[\\"]') + ++_EMAIL_CONFIG_FILE = "/etc/python/email.cfg" ++_cached_strict_addr_parsing = None ++ ++ ++def _use_strict_email_parsing(): ++ """"Cache implementation for _cached_strict_addr_parsing""" ++ global _cached_strict_addr_parsing ++ if _cached_strict_addr_parsing is None: ++ _cached_strict_addr_parsing = _use_strict_email_parsing_impl() ++ return _cached_strict_addr_parsing ++ ++ ++def _use_strict_email_parsing_impl(): ++ """Returns True if strict email parsing is not disabled by ++ config file or env variable. ++ """ ++ disabled = bool(os.environ.get("PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING")) ++ if disabled: ++ return False ++ ++ try: ++ file = open(_EMAIL_CONFIG_FILE) ++ except FileNotFoundError: ++ pass ++ else: ++ with file: ++ import configparser ++ config = configparser.ConfigParser( ++ interpolation=None, ++ comment_prefixes=('#', ), ++ ++ ) ++ config.read_file(file) ++ disabled = config.getboolean('email_addr_parsing', "PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING", fallback=None) ++ ++ if disabled: ++ return False ++ ++ return True ++ + + def _has_surrogates(s): + """Return True if s contains surrogate-escaped binary data.""" +@@ -149,7 +189,7 @@ def _strip_quoted_realnames(addr): + + supports_strict_parsing = True + +-def getaddresses(fieldvalues, *, strict=True): ++def getaddresses(fieldvalues, *, strict=None): + """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. + + When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in +@@ -158,6 +198,11 @@ def getaddresses(fieldvalues, *, strict=True): + If strict is true, use a strict parser which rejects malformed inputs. + """ + ++ # If default is used, it's True unless disabled ++ # by env variable or config file. ++ if strict == None: ++ strict = _use_strict_email_parsing() ++ + # If strict is true, if the resulting list of parsed addresses is greater + # than the number of fieldvalues in the input list, a parsing error has + # occurred and consequently a list containing a single empty 2-tuple [('', +@@ -321,7 +366,7 @@ def parsedate_to_datetime(data): + tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) + + +-def parseaddr(addr, *, strict=True): ++def parseaddr(addr, *, strict=None): + """ + Parse addr into its constituent realname and email address parts. + +@@ -330,6 +375,11 @@ def parseaddr(addr, *, strict=True): + + If strict is True, use a strict parser which rejects malformed inputs. + """ ++ # If default is used, it's True unless disabled ++ # by env variable or config file. ++ if strict == None: ++ strict = _use_strict_email_parsing() ++ + if not strict: + addrs = _AddressList(addr).addresslist + if not addrs: +diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py +index 20b6779..d7d99f0 100644 +--- a/Lib/test/test_email/test_email.py ++++ b/Lib/test/test_email/test_email.py +@@ -8,6 +8,9 @@ import base64 + import unittest + import textwrap + import warnings ++import contextlib ++import tempfile ++import os + + from io import StringIO, BytesIO + from itertools import chain +@@ -41,8 +44,8 @@ from email import quoprimime + from email import utils + + from test import support +-from test.support import threading_helper +-from test.support.os_helper import unlink ++from test.support import threading_helper, swap_attr ++from test.support.os_helper import unlink, EnvironmentVarGuard + from test.test_email import openfile, TestEmailBase + + # These imports are documented to work, but we are testing them using a +@@ -3427,6 +3430,73 @@ Foo + # Test email.utils.supports_strict_parsing attribute + self.assertEqual(email.utils.supports_strict_parsing, True) + ++ def test_parsing_errors_strict_set_via_env_var(self): ++ address = 'alice@example.org )Alice(' ++ empty = ('', '') ++ ++ # Reset cached default value to make the function ++ # reload the config file provided below. ++ utils._cached_strict_addr_parsing = None ++ ++ # Strict disabled via env variable, old behavior expected ++ with EnvironmentVarGuard() as environ: ++ environ["PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING"] = "1" ++ ++ self.assertEqual(utils.getaddresses([address]), ++ [('', 'alice@example.org'), ('', ''), ('', 'Alice')]) ++ self.assertEqual(utils.parseaddr([address]), ('', address)) ++ ++ # Clear cache again ++ utils._cached_strict_addr_parsing = None ++ ++ # Default strict=True, empty result expected ++ self.assertEqual(utils.getaddresses([address]), [empty]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ ++ # Clear cache again ++ utils._cached_strict_addr_parsing = None ++ ++ # Empty string in env variable = strict parsing enabled (default) ++ with EnvironmentVarGuard() as environ: ++ environ["PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING"] = "" ++ ++ # Default strict=True, empty result expected ++ self.assertEqual(utils.getaddresses([address]), [empty]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ ++ @contextlib.contextmanager ++ def _email_strict_parsing_conf(self): ++ """Context for the given email strict parsing configured in config file""" ++ with tempfile.TemporaryDirectory() as tmpdirname: ++ filename = os.path.join(tmpdirname, 'conf.cfg') ++ with swap_attr(utils, "_EMAIL_CONFIG_FILE", filename): ++ with open(filename, 'w') as file: ++ file.write('[email_addr_parsing]\n') ++ file.write('PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true') ++ utils._EMAIL_CONFIG_FILE = filename ++ yield ++ ++ def test_parsing_errors_strict_disabled_via_config_file(self): ++ address = 'alice@example.org )Alice(' ++ empty = ('', '') ++ ++ # Reset cached default value to make the function ++ # reload the config file provided below. ++ utils._cached_strict_addr_parsing = None ++ ++ # Strict disabled via config file, old results expected ++ with self._email_strict_parsing_conf(): ++ self.assertEqual(utils.getaddresses([address]), ++ [('', 'alice@example.org'), ('', ''), ('', 'Alice')]) ++ self.assertEqual(utils.parseaddr([address]), ('', address)) ++ ++ # Clear cache again ++ utils._cached_strict_addr_parsing = None ++ ++ # Default strict=True, empty result expected ++ self.assertEqual(utils.getaddresses([address]), [empty]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ + def test_getaddresses_nasty(self): + for addresses, expected in ( + (['"Sürname, Firstname" '], +-- +2.43.0 + diff --git a/SOURCES/Python-3.11.4.tar.xz.asc b/SOURCES/Python-3.11.4.tar.xz.asc deleted file mode 100644 index bf39d4a..0000000 --- a/SOURCES/Python-3.11.4.tar.xz.asc +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PGP SIGNATURE----- - -iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmR/sHIACgkQ/+h0BBaL -2EfQDQ//eFWvcQ5ijhVd3r5lp7NTNUPK6xKR2iqzpNWlN2Z4QkGJ2+IworBaZoGA -tzmbT0j0LB9ZQ+ba3xnqXGXD8Ky+fHLg8GV5yshPlH/bD7tPuHtfDRxNcWplEVSS -MbMuLjAYavTIHhYEz/Rpx4jvZTI5lwplVqj9WxNI/8tNrL5M2bsCtv+IB6brohiw -rUOUlT/KDkZbrGfB1Fe033Ep8hay5MkKjhgr7O1dU7zMuDRG+HRsCYGs7a5x6KhH -3QNTEp+GEIAKEsip5nR7vl5KqL02lHa5sf36SV2wjRTwO+IhgV7lvtJEwOD12oE5 -c+TCQMFbmBXg2vVmNBN/Lwftw1SwT/+orFX6V4U93jq6QNUo4GvPqum6YzuayGYc -/JM4MNziqmfdNW2YjEHPPfzti3f40eTapys97YufOrmYjM2NY0Fs+kAErvyxiWqi -guVQtaZIYeLl/9KWqQ0F/Apy1N+fVDuWBkZlizwHrUsGips4Rp7Bh/iCrDdOj+1D -gRCio7+KvdtzHavZPZnU5dcpUiXZgsDzOTI138IyYaEtVUS59ELkA2qxI1yCb5mk -eLVG1L7r/J2tIaTcguQppp5Z+62UDTArlUbnRxda0buzA2r1aFiQCTMwp+kTRegw -T9Ht/CT/D4vpMdmSQTun9MkKifcK+2uGfSsS7Lz4fSWjQLqg36k= -=zSfJ ------END PGP SIGNATURE----- diff --git a/SOURCES/Python-3.11.7.tar.xz.asc b/SOURCES/Python-3.11.7.tar.xz.asc new file mode 100644 index 0000000..5d3dcbe --- /dev/null +++ b/SOURCES/Python-3.11.7.tar.xz.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP SIGNATURE----- + +iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmVuFigACgkQ/+h0BBaL +2EeHPg/+LU5xs2ZDrQogDcH+A1v8RyursiggypdM5hXTrsFsTCIk4iekcI9xkhG1 +ltNX4UuCe5PUEbTgtaWP0ncXARrUnPCoQaQ1sHVDTYoHegancsk+sXZc1JM7qr0p +Y4Ig6mKjuHFMXCInQSI2GaH4t5r4Z1jGk/PGrecIHOPJgqfA/6Z3TBF5N+y3jEvS +2QazMB298q4RDhh9m3REe8LwFPHDlfw9eRohv0MB8xygg9KtxhLZrN7gLBQZvKGD +ihNw6EgJj5OZ0dvwKCCXnlZuwknuJW7vAOPHhYeenPdVdYCGoRSyN7JdD07L+5AG +O14l2rqZrz5Eu28by+kAUrcPYAfAXekw1PmtT3HSd9U/nqnUiTkkJcjyGG/e3cjJ +sUDKMNCSBq0G7j5DB3bB6VHkZjVuz+T+iR5QdfJ4kI2pYSuE/rUj1rhkUXApYsHl +7Wff0QbOW6QT1wCtQcMpJSzkTDVJVYxiqrko/ihlOhphDHYLdOIGOrxWAUwc06x/ +BhJD6tM1kEVZvifoJp1OsNwDzZ/Ku6CUs05E1vWxdeNVeANyKAgCZ5hOVmhnv866 +11zfgo/znRsMzMIyJuy0bhO0C6omVLzzfhipAbZM2jDorn37xxV0v/I0pceNtLrp +YR7Tjs7+Ihe6/oItjW53j9T7ANdgQ1RVDg98lKlPFNL+hxfctwY= +=0Pkd +-----END PGP SIGNATURE----- diff --git a/SOURCES/check-pyc-timestamps.py b/SOURCES/check-pyc-timestamps.py index 91af4fd..1a0a288 100644 --- a/SOURCES/check-pyc-timestamps.py +++ b/SOURCES/check-pyc-timestamps.py @@ -16,9 +16,9 @@ LEVELS = (None, 1, 2) # list of globs of test and other files that we expect not to have bytecode not_compiled = [ '/usr/bin/*', - '*/test/bad_coding.py', - '*/test/bad_coding2.py', - '*/test/badsyntax_*.py', + '*/test/*/bad_coding.py', + '*/test/*/bad_coding2.py', + '*/test/*/badsyntax_*.py', '*/lib2to3/tests/data/bom.py', '*/lib2to3/tests/data/crlf.py', '*/lib2to3/tests/data/different_encoding.py', diff --git a/SPECS/python3.11.spec b/SPECS/python3.11.spec index e838321..b8143b0 100644 --- a/SPECS/python3.11.spec +++ b/SPECS/python3.11.spec @@ -16,11 +16,11 @@ URL: https://www.python.org/ # WARNING When rebasing to a new Python version, # remember to update the python3-docs package as well -%global general_version %{pybasever}.4 +%global general_version %{pybasever}.7 #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 3%{?dist} +Release: 1%{?dist} License: Python @@ -63,7 +63,7 @@ License: Python # If the rpmwheels condition is disabled, we use the bundled wheel packages # from Python with the versions below. # This needs to be manually updated when we update Python. -%global pip_version 23.1.2 +%global pip_version 23.2.1 %global setuptools_version 65.5.0 # Expensive optimizations (mainly, profile-guided optimizations) @@ -342,6 +342,20 @@ Patch371: 00371-revert-bpo-1596321-fix-threading-_shutdown-for-the-main-thread-g # - https://access.redhat.com/articles/7004769 Patch397: 00397-tarfile-filter.patch +# 00415 # +# [CVE-2023-27043] gh-102988: Reject malformed addresses in email.parseaddr() (#111116) +# +# Detect email address parsing errors and return empty tuple to +# indicate the parsing error (old API). Add an optional 'strict' +# parameter to getaddresses() and parseaddr() functions. Patch by +# Thomas Dwyer. +# +# Upstream PR: https://github.com/python/cpython/pull/111116 +# +# Second patch implmenets the possibility to restore the old behavior via +# config file or environment variable. +Patch415: 00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -937,6 +951,10 @@ for tool in pygettext msgfmt; do ln -s ${tool}%{pybasever}.py %{buildroot}%{_bindir}/${tool}3.py done +# Install missing test data +# Fixed upstream: https://github.com/python/cpython/pull/112784 +cp -rp Lib/test/regrtestdata/ %{buildroot}%{pylibdir}/test/ + # Switch all shebangs to refer to the specific Python version. # This currently only covers files matching ^[a-zA-Z0-9_]+\.py$, # so handle files named using other naming scheme separately. @@ -1095,10 +1113,14 @@ CheckPython() { # test_freeze_simple_script is skipped, because it fails when bundled wheels # are removed in Fedora. # upstream report: https://bugs.python.org/issue45783 + # test_check_probes is failing since it was introduced in 3.11.5, + # the test is skipped until it is fixed in upstream. + # see: https://github.com/python/cpython/issues/104280#issuecomment-1669249980 LD_LIBRARY_PATH=$ConfDir $ConfDir/python -m test.regrtest \ -wW --slowest -j0 --timeout=1800 \ -i test_freeze_simple_script \ + -i test_check_probes \ %if %{with bootstrap} -x test_distutils \ %endif @@ -1609,6 +1631,19 @@ CheckPython optimized # ====================================================== %changelog +* Mon Jan 22 2024 Charalampos Stratakis - 3.11.7-1 +- Rebase to 3.11.7 +Resolves: RHEL-20233 + +* Tue Jan 09 2024 Lumír Balhar - 3.11.5-2 +- Security fix for CVE-2023-27043 +Resolves: RHEL-21325 + +* Thu Sep 07 2023 Charalampos Stratakis - 3.11.5-1 +- Rebase to 3.11.5 +- Security fixes for CVE-2023-40217 and CVE-2023-41105 +Resolves: RHEL-3045, RHEL-3269 + * Wed Aug 09 2023 Petr Viktorin - 3.11.4-3 - Fix symlink handling in the fix for CVE-2023-24329 Resolves: rhbz#263261