diff --git a/.gitignore b/.gitignore index 59b16ab..6c44485 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/Python-3.9.10.tar.xz +SOURCES/Python-3.9.14.tar.xz diff --git a/.python3.9.metadata b/.python3.9.metadata index 2556089..ff6c602 100644 --- a/.python3.9.metadata +++ b/.python3.9.metadata @@ -1 +1 @@ -936fc25ac4e1b482a0cefa82dd6092a0c6b575e6 SOURCES/Python-3.9.10.tar.xz +fa48bd60aee6abf2d41aafb273ebf9fb6b790458 SOURCES/Python-3.9.14.tar.xz diff --git a/SOURCES/00189-use-rpm-wheels.patch b/SOURCES/00189-use-rpm-wheels.patch index e5cf8f7..9a180c4 100644 --- a/SOURCES/00189-use-rpm-wheels.patch +++ b/SOURCES/00189-use-rpm-wheels.patch @@ -1,4 +1,4 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From 12b919396f3fd24521b5ded51e18beb55973f0ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Wed, 15 Aug 2018 15:36:29 +0200 Subject: [PATCH] 00189: Instead of bundled wheels, use our RPM packaged wheels @@ -12,7 +12,7 @@ We might eventually pursuit upstream support, but it's low prio 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py -index 2a140a2624..5bd16a6c59 100644 +index e510cc7..5bd16a6 100644 --- a/Lib/ensurepip/__init__.py +++ b/Lib/ensurepip/__init__.py @@ -1,3 +1,5 @@ @@ -31,7 +31,7 @@ index 2a140a2624..5bd16a6c59 100644 __all__ = ["version", "bootstrap"] -_SETUPTOOLS_VERSION = "58.1.0" --_PIP_VERSION = "21.2.4" +-_PIP_VERSION = "22.0.4" + +_WHEEL_DIR = "/usr/share/python-wheels/" + @@ -73,3 +73,6 @@ index 2a140a2624..5bd16a6c59 100644 additional_paths.append(os.path.join(tmpdir, wheel_name)) +-- +2.35.3 + diff --git a/SOURCES/00329-fips.patch b/SOURCES/00329-fips.patch index 0548005..d6c0a63 100644 --- a/SOURCES/00329-fips.patch +++ b/SOURCES/00329-fips.patch @@ -1,47 +1,7 @@ -From 355e975a386b60d787b98cc4cd08b98f876ff858 Mon Sep 17 00:00:00 2001 -From: "Miss Islington (bot)" - <31488909+miss-islington@users.noreply.github.com> -Date: Mon, 7 Feb 2022 00:08:10 -0800 -Subject: [PATCH 01/11] bpo-40479: Fix undefined behavior in - Modules/_hashopenssl.c (GH-31153) - -va_end() must be called before returning. -(cherry picked from commit 59e004af63742361b67d1e1ae70229ff0db1059d) - -Co-authored-by: Zackery Spytz ---- - .../Core and Builtins/2022-02-06-23-08-30.bpo-40479.zED3Zu.rst | 1 + - Modules/_hashopenssl.c | 1 + - 2 files changed, 2 insertions(+) - create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-02-06-23-08-30.bpo-40479.zED3Zu.rst - -diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-02-06-23-08-30.bpo-40479.zED3Zu.rst b/Misc/NEWS.d/next/Core and Builtins/2022-02-06-23-08-30.bpo-40479.zED3Zu.rst -new file mode 100644 -index 00000000000..52701d53d8f ---- /dev/null -+++ b/Misc/NEWS.d/next/Core and Builtins/2022-02-06-23-08-30.bpo-40479.zED3Zu.rst -@@ -0,0 +1 @@ -+Add a missing call to ``va_end()`` in ``Modules/_hashopenssl.c``. -diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c -index a4889450821..4873bb11aa0 100644 ---- a/Modules/_hashopenssl.c -+++ b/Modules/_hashopenssl.c -@@ -311,6 +311,7 @@ _setException(PyObject *exc, const char* altmsg, ...) - } else { - PyErr_FormatV(exc, altmsg, vargs); - } -+ va_end(vargs); - return NULL; - } - va_end(vargs); --- -2.34.1 - - -From b906713972396823c9e2e04421f9dbcfdc6a6c94 Mon Sep 17 00:00:00 2001 +From a350f1e323977baffc6d709c0dc877c7f3faba73 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 11 Aug 2021 16:51:03 +0200 -Subject: [PATCH 02/11] Backport PyModule_AddObjectRef as +Subject: [PATCH 01/10] Backport PyModule_AddObjectRef as _PyModule_AddObjectRef Having PyModule_AddObjectRef available should make backporting @@ -111,13 +71,13 @@ index 13482c6..fca1083 100644 PyModule_AddIntConstant(PyObject *m, const char *name, long value) { -- -2.34.1 +2.37.3 -From c67b383ffd3ccacedacbeb91c3bdeaf5f829ca09 Mon Sep 17 00:00:00 2001 +From 500314edea579965f5641d8ebdce8c8899fe2838 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 13 Aug 2021 13:16:43 +0200 -Subject: [PATCH 03/11] _hashopenssl: Uncomment and use initialization function +Subject: [PATCH 02/10] _hashopenssl: Uncomment and use initialization function list This simplifies backporting of future changes. @@ -129,10 +89,10 @@ We use this change instead of Python 3.10's: 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c -index a488945..62cf769 100644 +index 4db058c..56dfff9 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c -@@ -2216,7 +2216,6 @@ hashlib_init_hmactype(PyObject *module) +@@ -2227,7 +2227,6 @@ hashlib_init_hmactype(PyObject *module) return 0; } @@ -140,7 +100,7 @@ index a488945..62cf769 100644 static PyModuleDef_Slot hashlib_slots[] = { /* OpenSSL 1.0.2 and LibreSSL */ {Py_mod_exec, hashlib_openssl_legacy_init}, -@@ -2227,7 +2226,6 @@ static PyModuleDef_Slot hashlib_slots[] = { +@@ -2238,7 +2237,6 @@ static PyModuleDef_Slot hashlib_slots[] = { {Py_mod_exec, hashlib_md_meth_names}, {0, NULL} }; @@ -148,7 +108,7 @@ index a488945..62cf769 100644 static struct PyModuleDef _hashlibmodule = { PyModuleDef_HEAD_INIT, -@@ -2255,29 +2253,11 @@ PyInit__hashlib(void) +@@ -2266,29 +2264,11 @@ PyInit__hashlib(void) return NULL; } @@ -184,13 +144,13 @@ index a488945..62cf769 100644 return m; -- -2.34.1 +2.37.3 -From c49c1416d22fffc78204d66987f40e6d17a95c01 Mon Sep 17 00:00:00 2001 +From 76402d145bb24912f92d4013b8464e87b1493b45 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Sat, 27 Mar 2021 14:55:03 +0100 -Subject: [PATCH 04/11] bpo-40645: use C implementation of HMAC (GH-24920, +Subject: [PATCH 03/10] bpo-40645: use C implementation of HMAC (GH-24920, GH-25063, GH-26079) This backports the feature and 2 subsequent bugfixes @@ -621,7 +581,7 @@ index 0000000..a9ab1c0 +The :mod:`hmac` module now uses OpenSSL's HMAC implementation when digestmod +argument is a hash name or builtin hash function. diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c -index 62cf769..71ac832 100644 +index 56dfff9..ca9fea9 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -260,6 +260,8 @@ typedef struct { @@ -765,7 +725,7 @@ index 62cf769..71ac832 100644 return NULL; } -@@ -2106,6 +2145,8 @@ hashlib_traverse(PyObject *m, visitproc visit, void *arg) +@@ -2117,6 +2156,8 @@ hashlib_traverse(PyObject *m, visitproc visit, void *arg) #ifdef PY_OPENSSL_HAS_SHAKE Py_VISIT(state->EVPXOFtype); #endif @@ -774,7 +734,7 @@ index 62cf769..71ac832 100644 return 0; } -@@ -2118,10 +2159,14 @@ hashlib_clear(PyObject *m) +@@ -2129,10 +2170,14 @@ hashlib_clear(PyObject *m) #ifdef PY_OPENSSL_HAS_SHAKE Py_CLEAR(state->EVPXOFtype); #endif @@ -789,7 +749,7 @@ index 62cf769..71ac832 100644 return 0; } -@@ -2216,6 +2261,79 @@ hashlib_init_hmactype(PyObject *module) +@@ -2227,6 +2272,79 @@ hashlib_init_hmactype(PyObject *module) return 0; } @@ -869,7 +829,7 @@ index 62cf769..71ac832 100644 static PyModuleDef_Slot hashlib_slots[] = { /* OpenSSL 1.0.2 and LibreSSL */ {Py_mod_exec, hashlib_openssl_legacy_init}, -@@ -2224,6 +2342,8 @@ static PyModuleDef_Slot hashlib_slots[] = { +@@ -2235,6 +2353,8 @@ static PyModuleDef_Slot hashlib_slots[] = { {Py_mod_exec, hashlib_init_evpxoftype}, {Py_mod_exec, hashlib_init_hmactype}, {Py_mod_exec, hashlib_md_meth_names}, @@ -967,13 +927,13 @@ index 68aa765..4466ec4 100644 -/*[clinic end generated code: output=b6b280e46bf0b139 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=7ff9aad0bd53e7ce input=a9049054013a1b77]*/ -- -2.34.1 +2.37.3 -From f7ce31ebf3200952dadff556bfcbf2876139c823 Mon Sep 17 00:00:00 2001 +From 668a5b57d6454ff1a0e5c4db80002321e38cadfd Mon Sep 17 00:00:00 2001 From: Charalampos Stratakis Date: Thu, 12 Dec 2019 16:58:31 +0100 -Subject: [PATCH 05/11] Expose blake2b and blake2s hashes from OpenSSL +Subject: [PATCH 04/10] Expose blake2b and blake2s hashes from OpenSSL These aren't as powerful as Python's own implementation, but they can be used under FIPS. @@ -984,10 +944,10 @@ used under FIPS. 3 files changed, 148 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py -index 969e5e4..5b46016 100644 +index f845c7a..7aaeb76 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py -@@ -354,6 +354,12 @@ class HashLibTestCase(unittest.TestCase): +@@ -363,6 +363,12 @@ class HashLibTestCase(unittest.TestCase): # 2 is for hashlib.name(...) and hashlib.new(name, ...) self.assertGreaterEqual(len(constructors), 2) for hash_object_constructor in constructors: @@ -1001,7 +961,7 @@ index 969e5e4..5b46016 100644 computed = m.hexdigest() if not shake else m.hexdigest(length) self.assertEqual( diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c -index 71ac832..0b2c65e 100644 +index ca9fea9..9d98d20 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -1138,6 +1138,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj, @@ -1046,7 +1006,7 @@ index 71ac832..0b2c65e 100644 #ifdef PY_OPENSSL_HAS_SHA3 /*[clinic input] -@@ -2124,6 +2159,8 @@ static struct PyMethodDef EVP_functions[] = { +@@ -2135,6 +2170,8 @@ static struct PyMethodDef EVP_functions[] = { _HASHLIB_OPENSSL_SHA256_METHODDEF _HASHLIB_OPENSSL_SHA384_METHODDEF _HASHLIB_OPENSSL_SHA512_METHODDEF @@ -1177,13 +1137,13 @@ index 4466ec4..54c22b2 100644 -/*[clinic end generated code: output=7ff9aad0bd53e7ce input=a9049054013a1b77]*/ +/*[clinic end generated code: output=fab05055e982f112 input=a9049054013a1b77]*/ -- -2.34.1 +2.37.3 -From b8956168975170b8e7a797b6aa23e0d356f5ebec Mon Sep 17 00:00:00 2001 +From 1613c11b882e192456592a6adb63f73351f82829 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 1 Aug 2019 17:57:05 +0200 -Subject: [PATCH 06/11] Use a stronger hash in multiprocessing handshake +Subject: [PATCH 05/10] Use a stronger hash in multiprocessing handshake Adapted from patch by David Malcolm, https://bugs.python.org/issue17258 @@ -1225,13 +1185,13 @@ index 510e4b5..b68f2fb 100644 response = connection.recv_bytes(256) # reject large message if response != WELCOME: -- -2.34.1 +2.37.3 -From 20d86957b863e80d1f71b5681fccdb1fd16128b9 Mon Sep 17 00:00:00 2001 +From c0413586c6fb26bd4b7c4d5c40094ceeffb74612 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 25 Jul 2019 17:19:06 +0200 -Subject: [PATCH 07/11] Disable Python's hash implementations in FIPS mode, +Subject: [PATCH 06/10] Disable Python's hash implementations in FIPS mode, forcing OpenSSL --- @@ -1271,7 +1231,7 @@ index ffa3be0..3e3f4dd 100644 def __get_builtin_constructor(name): cache = __builtin_constructor_cache diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py -index 5b46016..72fdc67 100644 +index 7aaeb76..fa4a8d7 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -35,14 +35,15 @@ else: @@ -1295,7 +1255,7 @@ index 5b46016..72fdc67 100644 try: from _hashlib import HASH, HASHXOF, openssl_md_meth_names, get_fips_mode -@@ -116,6 +117,12 @@ class HashLibTestCase(unittest.TestCase): +@@ -118,6 +119,12 @@ class HashLibTestCase(unittest.TestCase): except ModuleNotFoundError as error: if self._warn_on_extension_import and module_name in builtin_hashes: warnings.warn('Did a C extension fail to compile? %s' % error) @@ -1411,10 +1371,10 @@ index 56ae7a5..45fb403 100644 + if (_Py_hashlib_fips_error(exc, name)) return NULL; \ +} while (0) diff --git a/setup.py b/setup.py -index c6023e1..371674c 100644 +index 0bec170..479f4b5 100644 --- a/setup.py +++ b/setup.py -@@ -2313,7 +2313,7 @@ class PyBuildExt(build_ext): +@@ -2315,7 +2315,7 @@ class PyBuildExt(build_ext): sources=sources, depends=depends)) @@ -1423,7 +1383,7 @@ index c6023e1..371674c 100644 # Detect SSL support for the socket module (via _ssl) config_vars = sysconfig.get_config_vars() -@@ -2333,16 +2333,14 @@ class PyBuildExt(build_ext): +@@ -2335,16 +2335,14 @@ class PyBuildExt(build_ext): openssl_libs = split_var('OPENSSL_LIBS', '-l') if not openssl_libs: # libssl and libcrypto not found @@ -1442,7 +1402,7 @@ index c6023e1..371674c 100644 # OpenSSL 1.0.2 uses Kerberos for KRB5 ciphers krb5_h = find_file( -@@ -2352,12 +2350,20 @@ class PyBuildExt(build_ext): +@@ -2354,12 +2352,20 @@ class PyBuildExt(build_ext): if krb5_h: ssl_incs.extend(krb5_h) @@ -1466,7 +1426,7 @@ index c6023e1..371674c 100644 depends=[ 'socketmodule.h', '_ssl/debughelpers.c', -@@ -2370,9 +2376,7 @@ class PyBuildExt(build_ext): +@@ -2372,9 +2378,7 @@ class PyBuildExt(build_ext): self.add(Extension('_hashlib', ['_hashopenssl.c'], depends=['hashlib.h'], @@ -1477,7 +1437,7 @@ index c6023e1..371674c 100644 def detect_hash_builtins(self): # By default we always compile these even when OpenSSL is available -@@ -2429,6 +2433,7 @@ class PyBuildExt(build_ext): +@@ -2431,6 +2435,7 @@ class PyBuildExt(build_ext): '_blake2/blake2b_impl.c', '_blake2/blake2s_impl.c' ], @@ -1486,13 +1446,13 @@ index c6023e1..371674c 100644 )) -- -2.34.1 +2.37.3 -From 76d17b46469d642f2acda31bb5e9e636d69fe945 Mon Sep 17 00:00:00 2001 +From 205bd746c16c7f8ac09251316c62bf78d6c31611 Mon Sep 17 00:00:00 2001 From: Charalampos Stratakis Date: Fri, 29 Jan 2021 14:16:21 +0100 -Subject: [PATCH 08/11] Use python's fall back crypto implementations only if +Subject: [PATCH 07/10] Use python's fall back crypto implementations only if we are not in FIPS mode --- @@ -1605,10 +1565,10 @@ index 3e3f4dd..b842f5f 100644 for __func_name in __always_supported: diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py -index 72fdc67..ac9c057 100644 +index fa4a8d7..ec6c883 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py -@@ -167,7 +167,13 @@ class HashLibTestCase(unittest.TestCase): +@@ -171,7 +171,13 @@ class HashLibTestCase(unittest.TestCase): constructors.add(constructor) def add_builtin_constructor(name): @@ -1623,7 +1583,7 @@ index 72fdc67..ac9c057 100644 self.constructors_to_test[name].add(constructor) _md5 = self._conditional_import_module('_md5') -@@ -257,6 +263,20 @@ class HashLibTestCase(unittest.TestCase): +@@ -266,6 +272,20 @@ class HashLibTestCase(unittest.TestCase): def test_new_upper_to_lower(self): self.assertEqual(hashlib.new("SHA256").name, "sha256") @@ -1644,7 +1604,7 @@ index 72fdc67..ac9c057 100644 def test_get_builtin_constructor(self): get_builtin_constructor = getattr(hashlib, '__get_builtin_constructor') -@@ -1052,6 +1072,7 @@ class KDFTests(unittest.TestCase): +@@ -1061,6 +1081,7 @@ class KDFTests(unittest.TestCase): iterations=1, dklen=None) self.assertEqual(out, self.pbkdf2_results['sha1'][0][0]) @@ -1653,13 +1613,13 @@ index 72fdc67..ac9c057 100644 def test_pbkdf2_hmac_py(self): self._test_pbkdf2_hmac(builtin_hashlib.pbkdf2_hmac, builtin_hashes) -- -2.34.1 +2.37.3 -From 95c861dda1659f5bc47d56bed8d096f4debbe281 Mon Sep 17 00:00:00 2001 +From 016e7dbfd92bd24b5f7cb613786fb99456ca6069 Mon Sep 17 00:00:00 2001 From: Charalampos Stratakis Date: Wed, 31 Jul 2019 15:43:43 +0200 -Subject: [PATCH 09/11] Test equivalence of hashes for the various digests with +Subject: [PATCH 08/10] Test equivalence of hashes for the various digests with usedforsecurity=True/False --- @@ -1699,7 +1659,7 @@ index 0000000..1f99dd7 +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py -index ac9c057..0aa0129 100644 +index ec6c883..0fd036f 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -20,6 +20,7 @@ import warnings @@ -1722,7 +1682,7 @@ index ac9c057..0aa0129 100644 try: import _blake2 except ImportError: -@@ -96,6 +102,11 @@ def read_vectors(hash_name): +@@ -98,6 +104,11 @@ def read_vectors(hash_name): parts[0] = bytes.fromhex(parts[0]) yield parts @@ -1734,8 +1694,8 @@ index ac9c057..0aa0129 100644 class HashLibTestCase(unittest.TestCase): supported_hash_names = ( 'md5', 'MD5', 'sha1', 'SHA1', -@@ -138,15 +149,21 @@ class HashLibTestCase(unittest.TestCase): - for algorithm in algorithms: +@@ -142,15 +153,21 @@ class HashLibTestCase(unittest.TestCase): + continue self.constructors_to_test[algorithm] = set() + def _add_constructor(algorithm, constructor): @@ -1758,7 +1718,7 @@ index ac9c057..0aa0129 100644 _hashlib = self._conditional_import_module('_hashlib') self._hashlib = _hashlib -@@ -158,13 +175,7 @@ class HashLibTestCase(unittest.TestCase): +@@ -162,13 +179,7 @@ class HashLibTestCase(unittest.TestCase): for algorithm, constructors in self.constructors_to_test.items(): constructor = getattr(_hashlib, 'openssl_'+algorithm, None) if constructor: @@ -1773,7 +1733,7 @@ index ac9c057..0aa0129 100644 def add_builtin_constructor(name): try: -@@ -337,6 +348,8 @@ class HashLibTestCase(unittest.TestCase): +@@ -346,6 +357,8 @@ class HashLibTestCase(unittest.TestCase): self.assertIn(h.name, self.supported_hash_names) else: self.assertNotIn(h.name, self.supported_hash_names) @@ -1782,7 +1742,7 @@ index ac9c057..0aa0129 100644 self.assertEqual( h.name, hashlib.new(h.name, usedforsecurity=False).name -@@ -383,8 +396,10 @@ class HashLibTestCase(unittest.TestCase): +@@ -392,8 +405,10 @@ class HashLibTestCase(unittest.TestCase): for hash_object_constructor in constructors: # OpenSSL's blake2s & blake2d don't support `key` @@ -1795,7 +1755,7 @@ index ac9c057..0aa0129 100644 return m = hash_object_constructor(data, **kwargs) -@@ -965,6 +980,15 @@ class HashLibTestCase(unittest.TestCase): +@@ -974,6 +989,15 @@ class HashLibTestCase(unittest.TestCase): ): HASHXOF() @@ -1812,13 +1772,13 @@ index ac9c057..0aa0129 100644 class KDFTests(unittest.TestCase): -- -2.34.1 +2.37.3 -From 62cf5b80d205ff6d6e719286feccca6bd6fdd862 Mon Sep 17 00:00:00 2001 +From 7c7a3260746d06d5f319944dc40d51f7642d92dc Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 26 Aug 2019 19:39:48 +0200 -Subject: [PATCH 10/11] Guard against Python HMAC in FIPS mode +Subject: [PATCH 09/10] Guard against Python HMAC in FIPS mode --- Lib/hmac.py | 13 +++++++++---- @@ -1929,13 +1889,13 @@ index adf52ad..41e6a14 100644 def test_realcopy_old(self): # Testing if the copy method created a real copy. -- -2.34.1 +2.37.3 -From ff7f518d32b7f1c47f35b841da78f5869470e381 Mon Sep 17 00:00:00 2001 +From 0db6e1bad3663006fe9352819bbbb53bfc5637be Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 25 Aug 2021 16:44:43 +0200 -Subject: [PATCH 11/11] Disable hash-based PYCs in FIPS mode +Subject: [PATCH 10/10] Disable hash-based PYCs in FIPS mode If FIPS mode is on, we can't use siphash-based HMAC (_Py_KeyedHash), so: @@ -1975,15 +1935,13 @@ index bba3642..02db901 100644 return PycInvalidationMode.CHECKED_HASH else: diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py -index 4ced130..11818ac 100644 +index 6dc0813..b9d5f9a 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py -@@ -3249,3 +3249,17 @@ def clear_ignored_deprecations(*tokens: object) -> None: - if warnings.filters != new_filters: - warnings.filters[:] = new_filters +@@ -3296,6 +3296,20 @@ def clear_ignored_deprecations(*tokens: object) -> None: warnings._filters_mutated() -+ -+ + + +def fails_in_fips_mode(expected_error): + import _hashlib + if _hashlib.get_fips_mode(): @@ -1996,6 +1954,11 @@ index 4ced130..11818ac 100644 + def _decorator(func): + return func + return _decorator ++ ++ + @contextlib.contextmanager + 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 7cb1370..61df232 100644 --- a/Lib/test/test_cmd_line_script.py @@ -2017,10 +1980,10 @@ index 7cb1370..61df232 100644 with support.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 6e1f4b2..ec6b165 100644 +index ab647d6..7d50f07 100644 --- a/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py -@@ -773,14 +773,23 @@ class CommandLineTestsBase: +@@ -758,14 +758,23 @@ class CommandLineTestsBase: out = self.assertRunOK('badfilename') self.assertRegex(out, b"Can't list 'badfilename'") @@ -2121,7 +2084,7 @@ diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py index b2d3dcf..7e4b0c5 100644 --- a/Lib/test/test_py_compile.py +++ b/Lib/test/test_py_compile.py -@@ -139,13 +139,16 @@ class PyCompileTestsBase: +@@ -141,13 +141,16 @@ class PyCompileTestsBase: importlib.util.cache_from_source(bad_coding))) def test_source_date_epoch(self): @@ -2139,7 +2102,7 @@ index b2d3dcf..7e4b0c5 100644 expected_flags = 0b11 else: expected_flags = 0b00 -@@ -176,7 +179,8 @@ class PyCompileTestsBase: +@@ -178,7 +181,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)) @@ -2149,7 +2112,7 @@ index b2d3dcf..7e4b0c5 100644 py_compile.compile( self.source_path, invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH, -@@ -185,6 +189,9 @@ class PyCompileTestsBase: +@@ -187,6 +191,9 @@ class PyCompileTestsBase: flags = importlib._bootstrap_external._classify_pyc( fp.read(), 'test', {}) self.assertEqual(flags, 0b11) @@ -2160,7 +2123,7 @@ index b2d3dcf..7e4b0c5 100644 self.source_path, invalidation_mode=py_compile.PycInvalidationMode.UNCHECKED_HASH, diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py -index 2e24388..11e7978 100644 +index b7347a3..09ea990 100644 --- a/Lib/test/test_zipimport.py +++ b/Lib/test/test_zipimport.py @@ -186,6 +186,7 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase): @@ -2211,5 +2174,5 @@ index 8358d70..1b7fb85 100644 uint64_t x; char data[sizeof(uint64_t)]; -- -2.34.1 +2.37.3 diff --git a/SOURCES/00378-support-expat-2-4-5.patch b/SOURCES/00378-support-expat-2-4-5.patch deleted file mode 100644 index 83b20fb..0000000 --- a/SOURCES/00378-support-expat-2-4-5.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 3950e203a4c625b7bc53e67e96d5d5239758f4fa Mon Sep 17 00:00:00 2001 -From: "Miss Islington (bot)" - <31488909+miss-islington@users.noreply.github.com> -Date: Mon, 21 Feb 2022 08:16:23 -0800 -Subject: [PATCH] bpo-46811: Make test suite support Expat >=2.4.5 (GH-31453) - (GH-31469) - -Curly brackets were never allowed in namespace URIs -according to RFC 3986, and so-called namespace-validating -XML parsers have the right to reject them a invalid URIs. - -libexpat >=2.4.5 has become strcter in that regard due to -related security issues; with ET.XML instantiating a -namespace-aware parser under the hood, this test has no -future in CPython. - -References: -- https://datatracker.ietf.org/doc/html/rfc3968 -- https://www.w3.org/TR/xml-names/ - -Also, test_minidom.py: Support Expat >=2.4.5 -(cherry picked from commit 2cae93832f46b245847bdc252456ddf7742ef45e) - -Co-authored-by: Sebastian Pipping - -Co-authored-by: Sebastian Pipping ---- - Lib/test/test_minidom.py | 12 +++++++++--- - Lib/test/test_xml_etree.py | 6 ------ - .../Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst | 1 + - 3 files changed, 10 insertions(+), 9 deletions(-) - create mode 100644 Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst - -diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py -index 1663b1f..5f52ed1 100644 ---- a/Lib/test/test_minidom.py -+++ b/Lib/test/test_minidom.py -@@ -6,10 +6,12 @@ import io - from test import support - import unittest - -+import pyexpat - import xml.dom.minidom - - from xml.dom.minidom import parse, Node, Document, parseString - from xml.dom.minidom import getDOMImplementation -+from xml.parsers.expat import ExpatError - - - tstfile = support.findfile("test.xml", subdir="xmltestdata") -@@ -1147,8 +1149,10 @@ class MinidomTest(unittest.TestCase): - - # Verify that character decoding errors raise exceptions instead - # of crashing -- self.assertRaises(UnicodeDecodeError, parseString, -- b'Comment \xe7a va ? Tr\xe8s bien ?') -+ self.assertRaises(ExpatError, parseString, -+ b'') -+ self.assertRaises(ExpatError, parseString, -+ b'Comment \xe7a va ? Tr\xe8s bien ?') - - doc.unlink() - -@@ -1609,7 +1613,9 @@ class MinidomTest(unittest.TestCase): - self.confirm(doc2.namespaceURI == xml.dom.EMPTY_NAMESPACE) - - def testExceptionOnSpacesInXMLNSValue(self): -- with self.assertRaisesRegex(ValueError, 'Unsupported syntax'): -+ context = self.assertRaisesRegex(ExpatError, 'syntax error') -+ -+ with context: - parseString('') - - def testDocRemoveChild(self): -diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py -index 23c4cd5..142ce2c 100644 ---- a/Lib/test/test_xml_etree.py -+++ b/Lib/test/test_xml_etree.py -@@ -2159,12 +2159,6 @@ class BugsTest(unittest.TestCase): - b"\n" - b'tãg') - -- def test_issue3151(self): -- e = ET.XML('') -- self.assertEqual(e.tag, '{${stuff}}localname') -- t = ET.ElementTree(e) -- self.assertEqual(ET.tostring(e), b'') -- - def test_issue6565(self): - elem = ET.XML("") - self.assertEqual(summarize_list(elem), ['tag']) -diff --git a/Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst b/Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst -new file mode 100644 -index 0000000..6969bd1 ---- /dev/null -+++ b/Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst -@@ -0,0 +1 @@ -+Make test suite support Expat >=2.4.5 --- -2.37.3 - diff --git a/SOURCES/00382-cve-2015-20107.patch b/SOURCES/00382-cve-2015-20107.patch new file mode 100644 index 0000000..619f636 --- /dev/null +++ b/SOURCES/00382-cve-2015-20107.patch @@ -0,0 +1,150 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Fri, 3 Jun 2022 11:43:35 +0200 +Subject: [PATCH] 00382: CVE-2015-20107 + +Make mailcap refuse to match unsafe filenames/types/params (GH-91993) + +Upstream: https://github.com/python/cpython/issues/68966 + +Tracker bug: https://bugzilla.redhat.com/show_bug.cgi?id=2075390 +--- + Doc/library/mailcap.rst | 12 +++++++++ + Lib/mailcap.py | 26 +++++++++++++++++-- + Lib/test/test_mailcap.py | 8 ++++-- + ...2-04-27-18-25-30.gh-issue-68966.gjS8zs.rst | 4 +++ + 4 files changed, 46 insertions(+), 4 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst + +diff --git a/Doc/library/mailcap.rst b/Doc/library/mailcap.rst +index a22b5b9c9e..7aa3380fec 100644 +--- a/Doc/library/mailcap.rst ++++ b/Doc/library/mailcap.rst +@@ -60,6 +60,18 @@ standard. However, mailcap files are supported on most Unix systems. + use) to determine whether or not the mailcap line applies. :func:`findmatch` + will automatically check such conditions and skip the entry if the check fails. + ++ .. versionchanged:: 3.11 ++ ++ To prevent security issues with shell metacharacters (symbols that have ++ special effects in a shell command line), ``findmatch`` will refuse ++ to inject ASCII characters other than alphanumerics and ``@+=:,./-_`` ++ into the returned command line. ++ ++ If a disallowed character appears in *filename*, ``findmatch`` will always ++ return ``(None, None)`` as if no entry was found. ++ If such a character appears elsewhere (a value in *plist* or in *MIMEtype*), ++ ``findmatch`` will ignore all mailcap entries which use that value. ++ A :mod:`warning ` will be raised in either case. + + .. function:: getcaps() + +diff --git a/Lib/mailcap.py b/Lib/mailcap.py +index ae416a8e9f..444c6408b5 100644 +--- a/Lib/mailcap.py ++++ b/Lib/mailcap.py +@@ -2,6 +2,7 @@ + + import os + import warnings ++import re + + __all__ = ["getcaps","findmatch"] + +@@ -13,6 +14,11 @@ def lineno_sort_key(entry): + else: + return 1, 0 + ++_find_unsafe = re.compile(r'[^\xa1-\U0010FFFF\w@+=:,./-]').search ++ ++class UnsafeMailcapInput(Warning): ++ """Warning raised when refusing unsafe input""" ++ + + # Part 1: top-level interface. + +@@ -165,15 +171,22 @@ def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]): + entry to use. + + """ ++ if _find_unsafe(filename): ++ msg = "Refusing to use mailcap with filename %r. Use a safe temporary filename." % (filename,) ++ warnings.warn(msg, UnsafeMailcapInput) ++ return None, None + entries = lookup(caps, MIMEtype, key) + # XXX This code should somehow check for the needsterminal flag. + for e in entries: + if 'test' in e: + test = subst(e['test'], filename, plist) ++ if test is None: ++ continue + if test and os.system(test) != 0: + continue + command = subst(e[key], MIMEtype, filename, plist) +- return command, e ++ if command is not None: ++ return command, e + return None, None + + def lookup(caps, MIMEtype, key=None): +@@ -206,6 +219,10 @@ def subst(field, MIMEtype, filename, plist=[]): + elif c == 's': + res = res + filename + elif c == 't': ++ if _find_unsafe(MIMEtype): ++ msg = "Refusing to substitute MIME type %r into a shell command." % (MIMEtype,) ++ warnings.warn(msg, UnsafeMailcapInput) ++ return None + res = res + MIMEtype + elif c == '{': + start = i +@@ -213,7 +230,12 @@ def subst(field, MIMEtype, filename, plist=[]): + i = i+1 + name = field[start:i] + i = i+1 +- res = res + findparam(name, plist) ++ param = findparam(name, plist) ++ if _find_unsafe(param): ++ msg = "Refusing to substitute parameter %r (%s) into a shell command" % (param, name) ++ warnings.warn(msg, UnsafeMailcapInput) ++ return None ++ res = res + param + # XXX To do: + # %n == number of parts if type is multipart/* + # %F == list of alternating type and filename for parts +diff --git a/Lib/test/test_mailcap.py b/Lib/test/test_mailcap.py +index c08423c670..920283d9a2 100644 +--- a/Lib/test/test_mailcap.py ++++ b/Lib/test/test_mailcap.py +@@ -121,7 +121,8 @@ class HelperFunctionTest(unittest.TestCase): + (["", "audio/*", "foo.txt"], ""), + (["echo foo", "audio/*", "foo.txt"], "echo foo"), + (["echo %s", "audio/*", "foo.txt"], "echo foo.txt"), +- (["echo %t", "audio/*", "foo.txt"], "echo audio/*"), ++ (["echo %t", "audio/*", "foo.txt"], None), ++ (["echo %t", "audio/wav", "foo.txt"], "echo audio/wav"), + (["echo \\%t", "audio/*", "foo.txt"], "echo %t"), + (["echo foo", "audio/*", "foo.txt", plist], "echo foo"), + (["echo %{total}", "audio/*", "foo.txt", plist], "echo 3") +@@ -205,7 +206,10 @@ class FindmatchTest(unittest.TestCase): + ('"An audio fragment"', audio_basic_entry)), + ([c, "audio/*"], + {"filename": fname}, +- ("/usr/local/bin/showaudio audio/*", audio_entry)), ++ (None, None)), ++ ([c, "audio/wav"], ++ {"filename": fname}, ++ ("/usr/local/bin/showaudio audio/wav", audio_entry)), + ([c, "message/external-body"], + {"plist": plist}, + ("showexternal /dev/null default john python.org /tmp foo bar", message_entry)) +diff --git a/Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst b/Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst +new file mode 100644 +index 0000000000..da81a1f699 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst +@@ -0,0 +1,4 @@ ++The deprecated mailcap module now refuses to inject unsafe text (filenames, ++MIME types, parameters) into shell commands. Instead of using such text, it ++will warn and act as if a match was not found (or for test commands, as if ++the test failed). diff --git a/SOURCES/00387-cve-2020-10735-prevent-dos-by-very-large-int.patch b/SOURCES/00387-cve-2020-10735-prevent-dos-by-very-large-int.patch deleted file mode 100644 index 5c4933b..0000000 --- a/SOURCES/00387-cve-2020-10735-prevent-dos-by-very-large-int.patch +++ /dev/null @@ -1,1499 +0,0 @@ -From 2b6758f55e3b0a4141a54f3c35a0d0cd377ce3cf Mon Sep 17 00:00:00 2001 -From: "Gregory P. Smith" -Date: Mon, 5 Sep 2022 02:21:03 -0700 -Subject: [PATCH] gh-95778: CVE-2020-10735: Prevent DoS by very large int() - (#96502) - -* Correctly pre-check for int-to-str conversion (#96537) - -Converting a large enough `int` to a decimal string raises `ValueError` as expected. However, the raise comes _after_ the quadratic-time base-conversion algorithm has run to completion. For effective DOS prevention, we need some kind of check before entering the quadratic-time loop. Oops! =) - -The quick fix: essentially we catch _most_ values that exceed the threshold up front. Those that slip through will still be on the small side (read: sufficiently fast), and will get caught by the existing check so that the limit remains exact. - -The justification for the current check. The C code check is: -```c -max_str_digits / (3 * PyLong_SHIFT) <= (size_a - 11) / 10 -``` - -In GitHub markdown math-speak, writing $M$ for `max_str_digits`, $L$ for `PyLong_SHIFT` and $s$ for `size_a`, that check is: -$$\left\lfloor\frac{M}{3L}\right\rfloor \le \left\lfloor\frac{s - 11}{10}\right\rfloor$$ - -From this it follows that -$$\frac{M}{3L} < \frac{s-1}{10}$$ -hence that -$$\frac{L(s-1)}{M} > \frac{10}{3} > \log_2(10).$$ -So -$$2^{L(s-1)} > 10^M.$$ -But our input integer $a$ satisfies $|a| \ge 2^{L(s-1)}$, so $|a|$ is larger than $10^M$. This shows that we don't accidentally capture anything _below_ the intended limit in the check. - - -* Issue: gh-95778 - - -Co-authored-by: Gregory P. Smith [Google LLC] -Co-authored-by: Christian Heimes -Co-authored-by: Mark Dickinson ---- - Doc/data/python3.9.abi | 5 +- - Doc/library/functions.rst | 8 + - Doc/library/json.rst | 11 + - Doc/library/stdtypes.rst | 159 ++++++++++++++ - Doc/library/sys.rst | 59 ++++-- - Doc/library/test.rst | 10 + - Doc/using/cmdline.rst | 13 ++ - Doc/whatsnew/3.9.rst | 14 ++ - Include/internal/pycore_initconfig.h | 2 + - Include/internal/pycore_interp.h | 2 + - Include/internal/pycore_long.h | 49 +++++ - Lib/test/support/__init__.py | 11 + - Lib/test/test_ast.py | 8 + - Lib/test/test_cmd_line.py | 33 +++ - Lib/test/test_compile.py | 13 ++ - Lib/test/test_decimal.py | 18 ++ - Lib/test/test_int.py | 196 ++++++++++++++++++ - Lib/test/test_json/test_decode.py | 9 + - Lib/test/test_sys.py | 11 +- - Lib/test/test_xmlrpc.py | 10 + - Makefile.pre.in | 1 + - ...08-07-16-53-38.gh-issue-95778.ch010gps.rst | 14 ++ - Objects/longobject.c | 65 +++++- - Parser/pegen/pegen.c | 18 ++ - Python/clinic/sysmodule.c.h | 60 +++++- - Python/initconfig.c | 60 ++++++ - Python/sysmodule.c | 46 +++- - 27 files changed, 886 insertions(+), 19 deletions(-) - create mode 100644 Include/internal/pycore_long.h - create mode 100644 Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst - -diff --git a/Doc/data/python3.9.abi b/Doc/data/python3.9.abi -index e203743..cca9779 100644 ---- a/Doc/data/python3.9.abi -+++ b/Doc/data/python3.9.abi -@@ -5653,7 +5653,7 @@ - - - -- -+ - - - -@@ -5774,6 +5774,9 @@ - - - -+ -+ -+ - - - -diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst -index 8df557e..820b313 100644 ---- a/Doc/library/functions.rst -+++ b/Doc/library/functions.rst -@@ -844,6 +844,14 @@ are always available. They are listed here in alphabetical order. - .. versionchanged:: 3.8 - Falls back to :meth:`__index__` if :meth:`__int__` is not defined. - -+ .. versionchanged:: 3.9.14 -+ :class:`int` string inputs and string representations can be limited to -+ help avoid denial of service attacks. A :exc:`ValueError` is raised when -+ the limit is exceeded while converting a string *x* to an :class:`int` or -+ when converting an :class:`int` into a string would exceed the limit. -+ See the :ref:`integer string conversion length limitation -+ ` documentation. -+ - - .. function:: isinstance(object, classinfo) - -diff --git a/Doc/library/json.rst b/Doc/library/json.rst -index 1810e04..6b715b5 100644 ---- a/Doc/library/json.rst -+++ b/Doc/library/json.rst -@@ -18,6 +18,11 @@ is a lightweight data interchange format inspired by - `JavaScript `_ object literal syntax - (although it is not a strict subset of JavaScript [#rfc-errata]_ ). - -+.. warning:: -+ Be cautious when parsing JSON data from untrusted sources. A malicious -+ JSON string may cause the decoder to consume considerable CPU and memory -+ resources. Limiting the size of data to be parsed is recommended. -+ - :mod:`json` exposes an API familiar to users of the standard library - :mod:`marshal` and :mod:`pickle` modules. - -@@ -255,6 +260,12 @@ Basic Usage - be used to use another datatype or parser for JSON integers - (e.g. :class:`float`). - -+ .. versionchanged:: 3.9.14 -+ The default *parse_int* of :func:`int` now limits the maximum length of -+ the integer string via the interpreter's :ref:`integer string -+ conversion length limitation ` to help avoid denial -+ of service attacks. -+ - *parse_constant*, if specified, will be called with one of the following - strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. - This can be used to raise an exception if invalid JSON numbers -diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst -index bfa0e74..f88e81f 100644 ---- a/Doc/library/stdtypes.rst -+++ b/Doc/library/stdtypes.rst -@@ -5206,6 +5206,165 @@ types, where they are relevant. Some of these are not reported by the - [] - - -+.. _int_max_str_digits: -+ -+Integer string conversion length limitation -+=========================================== -+ -+CPython has a global limit for converting between :class:`int` and :class:`str` -+to mitigate denial of service attacks. This limit *only* applies to decimal or -+other non-power-of-two number bases. Hexadecimal, octal, and binary conversions -+are unlimited. The limit can be configured. -+ -+The :class:`int` type in CPython is an abitrary length number stored in binary -+form (commonly known as a "bignum"). There exists no algorithm that can convert -+a string to a binary integer or a binary integer to a string in linear time, -+*unless* the base is a power of 2. Even the best known algorithms for base 10 -+have sub-quadratic complexity. Converting a large value such as ``int('1' * -+500_000)`` can take over a second on a fast CPU. -+ -+Limiting conversion size offers a practical way to avoid `CVE-2020-10735 -+`_. -+ -+The limit is applied to the number of digit characters in the input or output -+string when a non-linear conversion algorithm would be involved. Underscores -+and the sign are not counted towards the limit. -+ -+When an operation would exceed the limit, a :exc:`ValueError` is raised: -+ -+.. doctest:: -+ -+ >>> import sys -+ >>> sys.set_int_max_str_digits(4300) # Illustrative, this is the default. -+ >>> _ = int('2' * 5432) -+ Traceback (most recent call last): -+ ... -+ ValueError: Exceeds the limit (4300) for integer string conversion: value has 5432 digits. -+ >>> i = int('2' * 4300) -+ >>> len(str(i)) -+ 4300 -+ >>> i_squared = i*i -+ >>> len(str(i_squared)) -+ Traceback (most recent call last): -+ ... -+ ValueError: Exceeds the limit (4300) for integer string conversion: value has 8599 digits. -+ >>> len(hex(i_squared)) -+ 7144 -+ >>> assert int(hex(i_squared), base=16) == i*i # Hexadecimal is unlimited. -+ -+The default limit is 4300 digits as provided in -+:data:`sys.int_info.default_max_str_digits `. -+The lowest limit that can be configured is 640 digits as provided in -+:data:`sys.int_info.str_digits_check_threshold `. -+ -+Verification: -+ -+.. doctest:: -+ -+ >>> import sys -+ >>> assert sys.int_info.default_max_str_digits == 4300, sys.int_info -+ >>> assert sys.int_info.str_digits_check_threshold == 640, sys.int_info -+ >>> msg = int('578966293710682886880994035146873798396722250538762761564' -+ ... '9252925514383915483333812743580549779436104706260696366600' -+ ... '571186405732').to_bytes(53, 'big') -+ ... -+ -+.. versionadded:: 3.9.14 -+ -+Affected APIs -+------------- -+ -+The limitation only applies to potentially slow conversions between :class:`int` -+and :class:`str` or :class:`bytes`: -+ -+* ``int(string)`` with default base 10. -+* ``int(string, base)`` for all bases that are not a power of 2. -+* ``str(integer)``. -+* ``repr(integer)`` -+* any other string conversion to base 10, for example ``f"{integer}"``, -+ ``"{}".format(integer)``, or ``b"%d" % integer``. -+ -+The limitations do not apply to functions with a linear algorithm: -+ -+* ``int(string, base)`` with base 2, 4, 8, 16, or 32. -+* :func:`int.from_bytes` and :func:`int.to_bytes`. -+* :func:`hex`, :func:`oct`, :func:`bin`. -+* :ref:`formatspec` for hex, octal, and binary numbers. -+* :class:`str` to :class:`float`. -+* :class:`str` to :class:`decimal.Decimal`. -+ -+Configuring the limit -+--------------------- -+ -+Before Python starts up you can use an environment variable or an interpreter -+command line flag to configure the limit: -+ -+* :envvar:`PYTHONINTMAXSTRDIGITS`, e.g. -+ ``PYTHONINTMAXSTRDIGITS=640 python3`` to set the limit to 640 or -+ ``PYTHONINTMAXSTRDIGITS=0 python3`` to disable the limitation. -+* :option:`-X int_max_str_digits <-X>`, e.g. -+ ``python3 -X int_max_str_digits=640`` -+* :data:`sys.flags.int_max_str_digits` contains the value of -+ :envvar:`PYTHONINTMAXSTRDIGITS` or :option:`-X int_max_str_digits <-X>`. -+ If both the env var and the ``-X`` option are set, the ``-X`` option takes -+ precedence. A value of *-1* indicates that both were unset, thus a value of -+ :data:`sys.int_info.default_max_str_digits` was used during initilization. -+ -+From code, you can inspect the current limit and set a new one using these -+:mod:`sys` APIs: -+ -+* :func:`sys.get_int_max_str_digits` and :func:`sys.set_int_max_str_digits` are -+ a getter and setter for the interpreter-wide limit. Subinterpreters have -+ their own limit. -+ -+Information about the default and minimum can be found in :attr:`sys.int_info`: -+ -+* :data:`sys.int_info.default_max_str_digits ` is the compiled-in -+ default limit. -+* :data:`sys.int_info.str_digits_check_threshold ` is the lowest -+ accepted value for the limit (other than 0 which disables it). -+ -+.. versionadded:: 3.9.14 -+ -+.. caution:: -+ -+ Setting a low limit *can* lead to problems. While rare, code exists that -+ contains integer constants in decimal in their source that exceed the -+ minimum threshold. A consequence of setting the limit is that Python source -+ code containing decimal integer literals longer than the limit will -+ encounter an error during parsing, usually at startup time or import time or -+ even at installation time - anytime an up to date ``.pyc`` does not already -+ exist for the code. A workaround for source that contains such large -+ constants is to convert them to ``0x`` hexadecimal form as it has no limit. -+ -+ Test your application thoroughly if you use a low limit. Ensure your tests -+ run with the limit set early via the environment or flag so that it applies -+ during startup and even during any installation step that may invoke Python -+ to precompile ``.py`` sources to ``.pyc`` files. -+ -+Recommended configuration -+------------------------- -+ -+The default :data:`sys.int_info.default_max_str_digits` is expected to be -+reasonable for most applications. If your application requires a different -+limit, set it from your main entry point using Python version agnostic code as -+these APIs were added in security patch releases in versions before 3.11. -+ -+Example:: -+ -+ >>> import sys -+ >>> if hasattr(sys, "set_int_max_str_digits"): -+ ... upper_bound = 68000 -+ ... lower_bound = 4004 -+ ... current_limit = sys.get_int_max_str_digits() -+ ... if current_limit == 0 or current_limit > upper_bound: -+ ... sys.set_int_max_str_digits(upper_bound) -+ ... elif current_limit < lower_bound: -+ ... sys.set_int_max_str_digits(lower_bound) -+ -+If you need to disable it entirely, set it to ``0``. -+ -+ - .. rubric:: Footnotes - - .. [1] Additional information on these special methods may be found in the Python -diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst -index 9e18282..9b98bc5 100644 ---- a/Doc/library/sys.rst -+++ b/Doc/library/sys.rst -@@ -445,9 +445,9 @@ always available. - The :term:`named tuple` *flags* exposes the status of command line - flags. The attributes are read only. - -- ============================= ================================================================ -+ ============================= ============================================================================================================== - attribute flag -- ============================= ================================================================ -+ ============================= ============================================================================================================== - :const:`debug` :option:`-d` - :const:`inspect` :option:`-i` - :const:`interactive` :option:`-i` -@@ -463,7 +463,8 @@ always available. - :const:`hash_randomization` :option:`-R` - :const:`dev_mode` :option:`-X dev <-X>` (:ref:`Python Development Mode `) - :const:`utf8_mode` :option:`-X utf8 <-X>` -- ============================= ================================================================ -+ :const:`int_max_str_digits` :option:`-X int_max_str_digits <-X>` (:ref:`integer string conversion length limitation `) -+ ============================= ============================================================================================================== - - .. versionchanged:: 3.2 - Added ``quiet`` attribute for the new :option:`-q` flag. -@@ -482,6 +483,9 @@ always available. - Mode ` and the ``utf8_mode`` attribute for the new :option:`-X` - ``utf8`` flag. - -+ .. versionchanged:: 3.9.14 -+ Added the ``int_max_str_digits`` attribute. -+ - - .. data:: float_info - -@@ -660,6 +664,15 @@ always available. - - .. versionadded:: 3.6 - -+ -+.. function:: get_int_max_str_digits() -+ -+ Returns the current value for the :ref:`integer string conversion length -+ limitation `. See also :func:`set_int_max_str_digits`. -+ -+ .. versionadded:: 3.9.14 -+ -+ - .. function:: getrefcount(object) - - Return the reference count of the *object*. The count returned is generally one -@@ -933,19 +946,31 @@ always available. - - .. tabularcolumns:: |l|L| - -- +-------------------------+----------------------------------------------+ -- | Attribute | Explanation | -- +=========================+==============================================+ -- | :const:`bits_per_digit` | number of bits held in each digit. Python | -- | | integers are stored internally in base | -- | | ``2**int_info.bits_per_digit`` | -- +-------------------------+----------------------------------------------+ -- | :const:`sizeof_digit` | size in bytes of the C type used to | -- | | represent a digit | -- +-------------------------+----------------------------------------------+ -+ +----------------------------------------+-----------------------------------------------+ -+ | Attribute | Explanation | -+ +========================================+===============================================+ -+ | :const:`bits_per_digit` | number of bits held in each digit. Python | -+ | | integers are stored internally in base | -+ | | ``2**int_info.bits_per_digit`` | -+ +----------------------------------------+-----------------------------------------------+ -+ | :const:`sizeof_digit` | size in bytes of the C type used to | -+ | | represent a digit | -+ +----------------------------------------+-----------------------------------------------+ -+ | :const:`default_max_str_digits` | default value for | -+ | | :func:`sys.get_int_max_str_digits` when it | -+ | | is not otherwise explicitly configured. | -+ +----------------------------------------+-----------------------------------------------+ -+ | :const:`str_digits_check_threshold` | minimum non-zero value for | -+ | | :func:`sys.set_int_max_str_digits`, | -+ | | :envvar:`PYTHONINTMAXSTRDIGITS`, or | -+ | | :option:`-X int_max_str_digits <-X>`. | -+ +----------------------------------------+-----------------------------------------------+ - - .. versionadded:: 3.1 - -+ .. versionchanged:: 3.9.14 -+ Added ``default_max_str_digits`` and ``str_digits_check_threshold``. -+ - - .. data:: __interactivehook__ - -@@ -1223,6 +1248,14 @@ always available. - - .. availability:: Unix. - -+.. function:: set_int_max_str_digits(n) -+ -+ Set the :ref:`integer string conversion length limitation -+ ` used by this interpreter. See also -+ :func:`get_int_max_str_digits`. -+ -+ .. versionadded:: 3.9.14 -+ - .. function:: setprofile(profilefunc) - - .. index:: -diff --git a/Doc/library/test.rst b/Doc/library/test.rst -index 16f908c..563197f 100644 ---- a/Doc/library/test.rst -+++ b/Doc/library/test.rst -@@ -1302,6 +1302,16 @@ The :mod:`test.support` module defines the following functions: - .. versionadded:: 3.6 - - -+.. function:: adjust_int_max_str_digits(max_digits) -+ -+ This function returns a context manager that will change the global -+ :func:`sys.set_int_max_str_digits` setting for the duration of the -+ context to allow execution of test code that needs a different limit -+ on the number of digits when converting between an integer and string. -+ -+ .. versionadded:: 3.9.14 -+ -+ - The :mod:`test.support` module defines the following classes: - - .. class:: TransientResource(exc, **kwargs) -diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst -index 5739388..66d8d57 100644 ---- a/Doc/using/cmdline.rst -+++ b/Doc/using/cmdline.rst -@@ -436,6 +436,9 @@ Miscellaneous options - stored in a traceback of a trace. Use ``-X tracemalloc=NFRAME`` to start - tracing with a traceback limit of *NFRAME* frames. See the - :func:`tracemalloc.start` for more information. -+ * ``-X int_max_str_digits`` configures the :ref:`integer string conversion -+ length limitation `. See also -+ :envvar:`PYTHONINTMAXSTRDIGITS`. - * ``-X importtime`` to show how long each import takes. It shows module - name, cumulative time (including nested imports) and self time (excluding - nested imports). Note that its output may be broken in multi-threaded -@@ -480,6 +483,9 @@ Miscellaneous options - - The ``-X showalloccount`` option has been removed. - -+ .. versionadded:: 3.9.14 -+ The ``-X int_max_str_digits`` option. -+ - .. deprecated-removed:: 3.9 3.10 - The ``-X oldparser`` option. - -@@ -659,6 +665,13 @@ conflict. - - .. versionadded:: 3.2.3 - -+.. envvar:: PYTHONINTMAXSTRDIGITS -+ -+ If this variable is set to an integer, it is used to configure the -+ interpreter's global :ref:`integer string conversion length limitation -+ `. -+ -+ .. versionadded:: 3.9.14 - - .. envvar:: PYTHONIOENCODING - -diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst -index 0662adb..2270d3e 100644 ---- a/Doc/whatsnew/3.9.rst -+++ b/Doc/whatsnew/3.9.rst -@@ -1570,3 +1570,17 @@ URL by the parser in :mod:`urllib.parse` preventing such attacks. The removal - characters are controlled by a new module level variable - ``urllib.parse._UNSAFE_URL_BYTES_TO_REMOVE``. (See :issue:`43882`) - -+Notable security feature in 3.9.14 -+================================== -+ -+Converting between :class:`int` and :class:`str` in bases other than 2 -+(binary), 4, 8 (octal), 16 (hexadecimal), or 32 such as base 10 (decimal) -+now raises a :exc:`ValueError` if the number of digits in string form is -+above a limit to avoid potential denial of service attacks due to the -+algorithmic complexity. This is a mitigation for `CVE-2020-10735 -+`_. -+This limit can be configured or disabled by environment variable, command -+line flag, or :mod:`sys` APIs. See the :ref:`integer string conversion -+length limitation ` documentation. The default limit -+is 4300 digits in string form. -+ -diff --git a/Include/internal/pycore_initconfig.h b/Include/internal/pycore_initconfig.h -index 457a005..ad1b7e5 100644 ---- a/Include/internal/pycore_initconfig.h -+++ b/Include/internal/pycore_initconfig.h -@@ -156,6 +156,8 @@ extern PyStatus _PyConfig_SetPyArgv( - PyConfig *config, - const _PyArgv *args); - -+extern int _Py_global_config_int_max_str_digits; -+ - - /* --- Function used for testing ---------------------------------- */ - -diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h -index 551ad83..304d704 100644 ---- a/Include/internal/pycore_interp.h -+++ b/Include/internal/pycore_interp.h -@@ -154,6 +154,8 @@ struct _is { - */ - PyLongObject* small_ints[_PY_NSMALLNEGINTS + _PY_NSMALLPOSINTS]; - #endif -+ -+ int int_max_str_digits; - }; - - /* Used by _PyImport_Cleanup() */ -diff --git a/Include/internal/pycore_long.h b/Include/internal/pycore_long.h -new file mode 100644 -index 0000000..ae04332 ---- /dev/null -+++ b/Include/internal/pycore_long.h -@@ -0,0 +1,49 @@ -+#ifndef Py_INTERNAL_LONG_H -+#define Py_INTERNAL_LONG_H -+#ifdef __cplusplus -+extern "C" { -+#endif -+ -+#ifndef Py_BUILD_CORE -+# error "this header requires Py_BUILD_CORE define" -+#endif -+ -+/* -+ * Default int base conversion size limitation: Denial of Service prevention. -+ * -+ * Chosen such that this isn't wildly slow on modern hardware and so that -+ * everyone's existing deployed numpy test suite passes before -+ * https://github.com/numpy/numpy/issues/22098 is widely available. -+ * -+ * $ python -m timeit -s 's = "1"*4300' 'int(s)' -+ * 2000 loops, best of 5: 125 usec per loop -+ * $ python -m timeit -s 's = "1"*4300; v = int(s)' 'str(v)' -+ * 1000 loops, best of 5: 311 usec per loop -+ * (zen2 cloud VM) -+ * -+ * 4300 decimal digits fits a ~14284 bit number. -+ */ -+#define _PY_LONG_DEFAULT_MAX_STR_DIGITS 4300 -+/* -+ * Threshold for max digits check. For performance reasons int() and -+ * int.__str__() don't checks values that are smaller than this -+ * threshold. Acts as a guaranteed minimum size limit for bignums that -+ * applications can expect from CPython. -+ * -+ * % python -m timeit -s 's = "1"*640; v = int(s)' 'str(int(s))' -+ * 20000 loops, best of 5: 12 usec per loop -+ * -+ * "640 digits should be enough for anyone." - gps -+ * fits a ~2126 bit decimal number. -+ */ -+#define _PY_LONG_MAX_STR_DIGITS_THRESHOLD 640 -+ -+#if ((_PY_LONG_DEFAULT_MAX_STR_DIGITS != 0) && \ -+ (_PY_LONG_DEFAULT_MAX_STR_DIGITS < _PY_LONG_MAX_STR_DIGITS_THRESHOLD)) -+# error "_PY_LONG_DEFAULT_MAX_STR_DIGITS smaller than threshold." -+#endif -+ -+#ifdef __cplusplus -+} -+#endif -+#endif /* !Py_INTERNAL_LONG_H */ -diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py -index 11818ac..e5d6e2b 100644 ---- a/Lib/test/support/__init__.py -+++ b/Lib/test/support/__init__.py -@@ -3251,6 +3251,17 @@ def clear_ignored_deprecations(*tokens: object) -> None: - warnings._filters_mutated() - - -+@contextlib.contextmanager -+def adjust_int_max_str_digits(max_digits): -+ """Temporarily change the integer string conversion length limit.""" -+ current = sys.get_int_max_str_digits() -+ try: -+ sys.set_int_max_str_digits(max_digits) -+ yield -+ finally: -+ sys.set_int_max_str_digits(current) -+ -+ - def fails_in_fips_mode(expected_error): - import _hashlib - if _hashlib.get_fips_mode(): -diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py -index c3e3be6..a048d38 100644 ---- a/Lib/test/test_ast.py -+++ b/Lib/test/test_ast.py -@@ -978,6 +978,14 @@ Module( - self.assertRaises(ValueError, ast.literal_eval, '+True') - self.assertRaises(ValueError, ast.literal_eval, '2+3') - -+ def test_literal_eval_str_int_limit(self): -+ with support.adjust_int_max_str_digits(4000): -+ ast.literal_eval('3'*4000) # no error -+ with self.assertRaises(SyntaxError) as err_ctx: -+ ast.literal_eval('3'*4001) -+ self.assertIn('Exceeds the limit ', str(err_ctx.exception)) -+ self.assertIn(' Consider hexadecimal ', str(err_ctx.exception)) -+ - def test_literal_eval_complex(self): - # Issue #4907 - self.assertEqual(ast.literal_eval('6j'), 6j) -diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py -index 712d861..7ad9263 100644 ---- a/Lib/test/test_cmd_line.py -+++ b/Lib/test/test_cmd_line.py -@@ -804,6 +804,39 @@ class CmdLineTest(unittest.TestCase): - self.assertTrue(proc.stderr.startswith(err_msg), proc.stderr) - self.assertNotEqual(proc.returncode, 0) - -+ def test_int_max_str_digits(self): -+ code = "import sys; print(sys.flags.int_max_str_digits, sys.get_int_max_str_digits())" -+ -+ assert_python_failure('-X', 'int_max_str_digits', '-c', code) -+ assert_python_failure('-X', 'int_max_str_digits=foo', '-c', code) -+ assert_python_failure('-X', 'int_max_str_digits=100', '-c', code) -+ -+ assert_python_failure('-c', code, PYTHONINTMAXSTRDIGITS='foo') -+ assert_python_failure('-c', code, PYTHONINTMAXSTRDIGITS='100') -+ -+ def res2int(res): -+ out = res.out.strip().decode("utf-8") -+ return tuple(int(i) for i in out.split()) -+ -+ res = assert_python_ok('-c', code) -+ self.assertEqual(res2int(res), (-1, sys.get_int_max_str_digits())) -+ res = assert_python_ok('-X', 'int_max_str_digits=0', '-c', code) -+ self.assertEqual(res2int(res), (0, 0)) -+ res = assert_python_ok('-X', 'int_max_str_digits=4000', '-c', code) -+ self.assertEqual(res2int(res), (4000, 4000)) -+ res = assert_python_ok('-X', 'int_max_str_digits=100000', '-c', code) -+ self.assertEqual(res2int(res), (100000, 100000)) -+ -+ res = assert_python_ok('-c', code, PYTHONINTMAXSTRDIGITS='0') -+ self.assertEqual(res2int(res), (0, 0)) -+ res = assert_python_ok('-c', code, PYTHONINTMAXSTRDIGITS='4000') -+ self.assertEqual(res2int(res), (4000, 4000)) -+ res = assert_python_ok( -+ '-X', 'int_max_str_digits=6000', '-c', code, -+ PYTHONINTMAXSTRDIGITS='4000' -+ ) -+ self.assertEqual(res2int(res), (6000, 6000)) -+ - - @unittest.skipIf(interpreter_requires_environment(), - 'Cannot run -I tests when PYTHON env vars are required.') -diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py -index 55716fd..ec776b9 100644 ---- a/Lib/test/test_compile.py -+++ b/Lib/test/test_compile.py -@@ -189,6 +189,19 @@ if 1: - self.assertEqual(eval("0o777"), 511) - self.assertEqual(eval("-0o0000010"), -8) - -+ def test_int_literals_too_long(self): -+ n = 3000 -+ source = f"a = 1\nb = 2\nc = {'3'*n}\nd = 4" -+ with support.adjust_int_max_str_digits(n): -+ compile(source, "", "exec") # no errors. -+ with support.adjust_int_max_str_digits(n-1): -+ with self.assertRaises(SyntaxError) as err_ctx: -+ compile(source, "", "exec") -+ exc = err_ctx.exception -+ self.assertEqual(exc.lineno, 3) -+ self.assertIn('Exceeds the limit ', str(exc)) -+ self.assertIn(' Consider hexadecimal ', str(exc)) -+ - def test_unary_minus(self): - # Verify treatment of unary minus on negative numbers SF bug #660455 - if sys.maxsize == 2147483647: -diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py -index 3f30a93..c992815 100644 ---- a/Lib/test/test_decimal.py -+++ b/Lib/test/test_decimal.py -@@ -2462,6 +2462,15 @@ class CUsabilityTest(UsabilityTest): - class PyUsabilityTest(UsabilityTest): - decimal = P - -+ def setUp(self): -+ super().setUp() -+ self._previous_int_limit = sys.get_int_max_str_digits() -+ sys.set_int_max_str_digits(7000) -+ -+ def tearDown(self): -+ sys.set_int_max_str_digits(self._previous_int_limit) -+ super().tearDown() -+ - class PythonAPItests(unittest.TestCase): - - def test_abc(self): -@@ -4519,6 +4528,15 @@ class CCoverage(Coverage): - class PyCoverage(Coverage): - decimal = P - -+ def setUp(self): -+ super().setUp() -+ self._previous_int_limit = sys.get_int_max_str_digits() -+ sys.set_int_max_str_digits(7000) -+ -+ def tearDown(self): -+ sys.set_int_max_str_digits(self._previous_int_limit) -+ super().tearDown() -+ - class PyFunctionality(unittest.TestCase): - """Extra functionality in decimal.py""" - -diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py -index 6fdf52e..cbbddf5 100644 ---- a/Lib/test/test_int.py -+++ b/Lib/test/test_int.py -@@ -1,4 +1,5 @@ - import sys -+import time - - import unittest - from test import support -@@ -571,5 +572,200 @@ class IntTestCases(unittest.TestCase): - self.assertEqual(int('1_2_3_4_5_6_7', 32), 1144132807) - - -+class IntStrDigitLimitsTests(unittest.TestCase): -+ -+ int_class = int # Override this in subclasses to reuse the suite. -+ -+ def setUp(self): -+ super().setUp() -+ self._previous_limit = sys.get_int_max_str_digits() -+ sys.set_int_max_str_digits(2048) -+ -+ def tearDown(self): -+ sys.set_int_max_str_digits(self._previous_limit) -+ super().tearDown() -+ -+ def test_disabled_limit(self): -+ self.assertGreater(sys.get_int_max_str_digits(), 0) -+ self.assertLess(sys.get_int_max_str_digits(), 20_000) -+ with support.adjust_int_max_str_digits(0): -+ self.assertEqual(sys.get_int_max_str_digits(), 0) -+ i = self.int_class('1' * 20_000) -+ str(i) -+ self.assertGreater(sys.get_int_max_str_digits(), 0) -+ -+ def test_max_str_digits_edge_cases(self): -+ """Ignore the +/- sign and space padding.""" -+ int_class = self.int_class -+ maxdigits = sys.get_int_max_str_digits() -+ -+ int_class('1' * maxdigits) -+ int_class(' ' + '1' * maxdigits) -+ int_class('1' * maxdigits + ' ') -+ int_class('+' + '1' * maxdigits) -+ int_class('-' + '1' * maxdigits) -+ self.assertEqual(len(str(10 ** (maxdigits - 1))), maxdigits) -+ -+ def check(self, i, base=None): -+ with self.assertRaises(ValueError): -+ if base is None: -+ self.int_class(i) -+ else: -+ self.int_class(i, base) -+ -+ def test_max_str_digits(self): -+ maxdigits = sys.get_int_max_str_digits() -+ -+ self.check('1' * (maxdigits + 1)) -+ self.check(' ' + '1' * (maxdigits + 1)) -+ self.check('1' * (maxdigits + 1) + ' ') -+ self.check('+' + '1' * (maxdigits + 1)) -+ self.check('-' + '1' * (maxdigits + 1)) -+ self.check('1' * (maxdigits + 1)) -+ -+ i = 10 ** maxdigits -+ with self.assertRaises(ValueError): -+ str(i) -+ -+ def test_denial_of_service_prevented_int_to_str(self): -+ """Regression test: ensure we fail before performing O(N**2) work.""" -+ maxdigits = sys.get_int_max_str_digits() -+ assert maxdigits < 50_000, maxdigits # A test prerequisite. -+ get_time = time.process_time -+ if get_time() <= 0: # some platforms like WASM lack process_time() -+ get_time = time.monotonic -+ -+ huge_int = int(f'0x{"c"*65_000}', base=16) # 78268 decimal digits. -+ digits = 78_268 -+ with support.adjust_int_max_str_digits(digits): -+ start = get_time() -+ huge_decimal = str(huge_int) -+ seconds_to_convert = get_time() - start -+ self.assertEqual(len(huge_decimal), digits) -+ # Ensuring that we chose a slow enough conversion to measure. -+ # It takes 0.1 seconds on a Zen based cloud VM in an opt build. -+ if seconds_to_convert < 0.005: -+ raise unittest.SkipTest('"slow" conversion took only ' -+ f'{seconds_to_convert} seconds.') -+ -+ # We test with the limit almost at the size needed to check performance. -+ # The performant limit check is slightly fuzzy, give it a some room. -+ with support.adjust_int_max_str_digits(int(.995 * digits)): -+ with self.assertRaises(ValueError) as err: -+ start = get_time() -+ str(huge_int) -+ seconds_to_fail_huge = get_time() - start -+ self.assertIn('conversion', str(err.exception)) -+ self.assertLess(seconds_to_fail_huge, seconds_to_convert/8) -+ -+ # Now we test that a conversion that would take 30x as long also fails -+ # in a similarly fast fashion. -+ extra_huge_int = int(f'0x{"c"*500_000}', base=16) # 602060 digits. -+ with self.assertRaises(ValueError) as err: -+ start = get_time() -+ # If not limited, 8 seconds said Zen based cloud VM. -+ str(extra_huge_int) -+ seconds_to_fail_extra_huge = get_time() - start -+ self.assertIn('conversion', str(err.exception)) -+ self.assertLess(seconds_to_fail_extra_huge, seconds_to_convert/8) -+ -+ def test_denial_of_service_prevented_str_to_int(self): -+ """Regression test: ensure we fail before performing O(N**2) work.""" -+ maxdigits = sys.get_int_max_str_digits() -+ assert maxdigits < 100_000, maxdigits # A test prerequisite. -+ get_time = time.process_time -+ if get_time() <= 0: # some platforms like WASM lack process_time() -+ get_time = time.monotonic -+ -+ digits = 133700 -+ huge = '8'*digits -+ with support.adjust_int_max_str_digits(digits): -+ start = get_time() -+ int(huge) -+ seconds_to_convert = get_time() - start -+ # Ensuring that we chose a slow enough conversion to measure. -+ # It takes 0.1 seconds on a Zen based cloud VM in an opt build. -+ if seconds_to_convert < 0.005: -+ raise unittest.SkipTest('"slow" conversion took only ' -+ f'{seconds_to_convert} seconds.') -+ -+ with support.adjust_int_max_str_digits(digits - 1): -+ with self.assertRaises(ValueError) as err: -+ start = get_time() -+ int(huge) -+ seconds_to_fail_huge = get_time() - start -+ self.assertIn('conversion', str(err.exception)) -+ self.assertLess(seconds_to_fail_huge, seconds_to_convert/8) -+ -+ # Now we test that a conversion that would take 30x as long also fails -+ # in a similarly fast fashion. -+ extra_huge = '7'*1_200_000 -+ with self.assertRaises(ValueError) as err: -+ start = get_time() -+ # If not limited, 8 seconds in the Zen based cloud VM. -+ int(extra_huge) -+ seconds_to_fail_extra_huge = get_time() - start -+ self.assertIn('conversion', str(err.exception)) -+ self.assertLess(seconds_to_fail_extra_huge, seconds_to_convert/8) -+ -+ def test_power_of_two_bases_unlimited(self): -+ """The limit does not apply to power of 2 bases.""" -+ maxdigits = sys.get_int_max_str_digits() -+ -+ for base in (2, 4, 8, 16, 32): -+ with self.subTest(base=base): -+ self.int_class('1' * (maxdigits + 1), base) -+ assert maxdigits < 100_000 -+ self.int_class('1' * 100_000, base) -+ -+ def test_underscores_ignored(self): -+ maxdigits = sys.get_int_max_str_digits() -+ -+ triples = maxdigits // 3 -+ s = '111' * triples -+ s_ = '1_11' * triples -+ self.int_class(s) # succeeds -+ self.int_class(s_) # succeeds -+ self.check(f'{s}111') -+ self.check(f'{s_}_111') -+ -+ def test_sign_not_counted(self): -+ int_class = self.int_class -+ max_digits = sys.get_int_max_str_digits() -+ s = '5' * max_digits -+ i = int_class(s) -+ pos_i = int_class(f'+{s}') -+ assert i == pos_i -+ neg_i = int_class(f'-{s}') -+ assert -pos_i == neg_i -+ str(pos_i) -+ str(neg_i) -+ -+ def _other_base_helper(self, base): -+ int_class = self.int_class -+ max_digits = sys.get_int_max_str_digits() -+ s = '2' * max_digits -+ i = int_class(s, base) -+ if base > 10: -+ with self.assertRaises(ValueError): -+ str(i) -+ elif base < 10: -+ str(i) -+ with self.assertRaises(ValueError) as err: -+ int_class(f'{s}1', base) -+ -+ def test_int_from_other_bases(self): -+ base = 3 -+ with self.subTest(base=base): -+ self._other_base_helper(base) -+ base = 36 -+ with self.subTest(base=base): -+ self._other_base_helper(base) -+ -+ -+class IntSubclassStrDigitLimitsTests(IntStrDigitLimitsTests): -+ int_class = IntSubclass -+ -+ - if __name__ == "__main__": - unittest.main() -diff --git a/Lib/test/test_json/test_decode.py b/Lib/test/test_json/test_decode.py -index fdb9e62..124045b 100644 ---- a/Lib/test/test_json/test_decode.py -+++ b/Lib/test/test_json/test_decode.py -@@ -2,6 +2,7 @@ import decimal - from io import StringIO - from collections import OrderedDict - from test.test_json import PyTest, CTest -+from test import support - - - class TestDecode: -@@ -95,5 +96,13 @@ class TestDecode: - d = self.json.JSONDecoder() - self.assertRaises(ValueError, d.raw_decode, 'a'*42, -50000) - -+ def test_limit_int(self): -+ maxdigits = 5000 -+ with support.adjust_int_max_str_digits(maxdigits): -+ self.loads('1' * maxdigits) -+ with self.assertRaises(ValueError): -+ self.loads('1' * (maxdigits + 1)) -+ -+ - class TestPyDecode(TestDecode, PyTest): pass - class TestCDecode(TestDecode, CTest): pass -diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py -index 2f1e5e9..b11f1ff 100644 ---- a/Lib/test/test_sys.py -+++ b/Lib/test/test_sys.py -@@ -409,11 +409,17 @@ class SysModuleTest(unittest.TestCase): - self.assertIsInstance(sys.executable, str) - self.assertEqual(len(sys.float_info), 11) - self.assertEqual(sys.float_info.radix, 2) -- self.assertEqual(len(sys.int_info), 2) -+ self.assertEqual(len(sys.int_info), 4) - self.assertTrue(sys.int_info.bits_per_digit % 5 == 0) - self.assertTrue(sys.int_info.sizeof_digit >= 1) -+ self.assertGreaterEqual(sys.int_info.default_max_str_digits, 500) -+ self.assertGreaterEqual(sys.int_info.str_digits_check_threshold, 100) -+ self.assertGreater(sys.int_info.default_max_str_digits, -+ sys.int_info.str_digits_check_threshold) - self.assertEqual(type(sys.int_info.bits_per_digit), int) - self.assertEqual(type(sys.int_info.sizeof_digit), int) -+ self.assertIsInstance(sys.int_info.default_max_str_digits, int) -+ self.assertIsInstance(sys.int_info.str_digits_check_threshold, int) - self.assertIsInstance(sys.hexversion, int) - - self.assertEqual(len(sys.hash_info), 9) -@@ -517,7 +523,8 @@ class SysModuleTest(unittest.TestCase): - "inspect", "interactive", "optimize", - "dont_write_bytecode", "no_user_site", "no_site", - "ignore_environment", "verbose", "bytes_warning", "quiet", -- "hash_randomization", "isolated", "dev_mode", "utf8_mode") -+ "hash_randomization", "isolated", "dev_mode", "utf8_mode", -+ "int_max_str_digits") - for attr in attrs: - self.assertTrue(hasattr(sys.flags, attr), attr) - attr_type = bool if attr == "dev_mode" else int -diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py -index f714b77..d12da2f 100644 ---- a/Lib/test/test_xmlrpc.py -+++ b/Lib/test/test_xmlrpc.py -@@ -285,6 +285,16 @@ class XMLRPCTestCase(unittest.TestCase): - check('9876543210.0123456789', - decimal.Decimal('9876543210.0123456789')) - -+ def test_limit_int(self): -+ check = self.check_loads -+ maxdigits = 5000 -+ with support.adjust_int_max_str_digits(maxdigits): -+ s = '1' * (maxdigits + 1) -+ with self.assertRaises(ValueError): -+ check(f'{s}', None) -+ with self.assertRaises(ValueError): -+ check(f'{s}', None) -+ - def test_get_host_info(self): - # see bug #3613, this raised a TypeError - transp = xmlrpc.client.Transport() -diff --git a/Makefile.pre.in b/Makefile.pre.in -index c1cf158..b64837c 100644 ---- a/Makefile.pre.in -+++ b/Makefile.pre.in -@@ -1153,6 +1153,7 @@ PYTHON_HEADERS= \ - $(srcdir)/Include/internal/pycore_import.h \ - $(srcdir)/Include/internal/pycore_initconfig.h \ - $(srcdir)/Include/internal/pycore_interp.h \ -+ $(srcdir)/Include/internal/pycore_long.h \ - $(srcdir)/Include/internal/pycore_object.h \ - $(srcdir)/Include/internal/pycore_pathconfig.h \ - $(srcdir)/Include/internal/pycore_pyerrors.h \ -diff --git a/Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst b/Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst -new file mode 100644 -index 0000000..8eb8a34 ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst -@@ -0,0 +1,14 @@ -+Converting between :class:`int` and :class:`str` in bases other than 2 -+(binary), 4, 8 (octal), 16 (hexadecimal), or 32 such as base 10 (decimal) now -+raises a :exc:`ValueError` if the number of digits in string form is above a -+limit to avoid potential denial of service attacks due to the algorithmic -+complexity. This is a mitigation for `CVE-2020-10735 -+`_. -+ -+This new limit can be configured or disabled by environment variable, command -+line flag, or :mod:`sys` APIs. See the :ref:`integer string conversion length -+limitation ` documentation. The default limit is 4300 -+digits in string form. -+ -+Patch by Gregory P. Smith [Google] and Christian Heimes [Red Hat] with feedback -+from Victor Stinner, Thomas Wouters, Steve Dower, Ned Deily, and Mark Dickinson. -diff --git a/Objects/longobject.c b/Objects/longobject.c -index cf13b2c..ec18ec3 100644 ---- a/Objects/longobject.c -+++ b/Objects/longobject.c -@@ -3,7 +3,9 @@ - /* XXX The functional organization of this file is terrible */ - - #include "Python.h" -+#include "pycore_initconfig.h" // _Py_global_config_int_max_str_digits - #include "pycore_interp.h" // _PY_NSMALLPOSINTS -+#include "pycore_long.h" - #include "pycore_pystate.h" // _Py_IsMainInterpreter() - #include "longintrepr.h" - -@@ -36,6 +38,9 @@ PyObject *_PyLong_One = NULL; - #define IS_SMALL_INT(ival) (-NSMALLNEGINTS <= (ival) && (ival) < NSMALLPOSINTS) - #define IS_SMALL_UINT(ival) ((ival) < NSMALLPOSINTS) - -+#define _MAX_STR_DIGITS_ERROR_FMT_TO_INT "Exceeds the limit (%d) for integer string conversion: value has %zd digits" -+#define _MAX_STR_DIGITS_ERROR_FMT_TO_STR "Exceeds the limit (%d) for integer string conversion" -+ - static PyObject * - get_small_int(sdigit ival) - { -@@ -1718,6 +1723,23 @@ long_to_decimal_string_internal(PyObject *aa, - size_a = Py_ABS(Py_SIZE(a)); - negative = Py_SIZE(a) < 0; - -+ /* quick and dirty pre-check for overflowing the decimal digit limit, -+ based on the inequality 10/3 >= log2(10) -+ -+ explanation in https://github.com/python/cpython/pull/96537 -+ */ -+ if (size_a >= 10 * _PY_LONG_MAX_STR_DIGITS_THRESHOLD -+ / (3 * PyLong_SHIFT) + 2) { -+ PyInterpreterState *interp = _PyInterpreterState_GET(); -+ int max_str_digits = interp->int_max_str_digits; -+ if ((max_str_digits > 0) && -+ (max_str_digits / (3 * PyLong_SHIFT) <= (size_a - 11) / 10)) { -+ PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_STR, -+ max_str_digits); -+ return -1; -+ } -+ } -+ - /* quick and dirty upper bound for the number of digits - required to express a in base _PyLong_DECIMAL_BASE: - -@@ -1777,6 +1799,17 @@ long_to_decimal_string_internal(PyObject *aa, - tenpow *= 10; - strlen++; - } -+ if (strlen > _PY_LONG_MAX_STR_DIGITS_THRESHOLD) { -+ PyInterpreterState *interp = _PyInterpreterState_GET(); -+ int max_str_digits = interp->int_max_str_digits; -+ Py_ssize_t strlen_nosign = strlen - negative; -+ if ((max_str_digits > 0) && (strlen_nosign > max_str_digits)) { -+ Py_DECREF(scratch); -+ PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_STR, -+ max_str_digits); -+ return -1; -+ } -+ } - if (writer) { - if (_PyUnicodeWriter_Prepare(writer, strlen, '9') == -1) { - Py_DECREF(scratch); -@@ -2290,6 +2323,7 @@ PyLong_FromString(const char *str, char **pend, int base) - - start = str; - if ((base & (base - 1)) == 0) { -+ /* binary bases are not limited by int_max_str_digits */ - int res = long_from_binary_base(&str, base, &z); - if (res < 0) { - /* Syntax error. */ -@@ -2441,6 +2475,17 @@ digit beyond the first. - goto onError; - } - -+ /* Limit the size to avoid excessive computation attacks. */ -+ if (digits > _PY_LONG_MAX_STR_DIGITS_THRESHOLD) { -+ PyInterpreterState *interp = _PyInterpreterState_GET(); -+ int max_str_digits = interp->int_max_str_digits; -+ if ((max_str_digits > 0) && (digits > max_str_digits)) { -+ PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_INT, -+ max_str_digits, digits); -+ return NULL; -+ } -+ } -+ - /* Create an int object that can contain the largest possible - * integer with this base and length. Note that there's no - * need to initialize z->ob_digit -- no slot is read up before -@@ -5071,6 +5116,7 @@ long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase) - } - return PyLong_FromLong(0L); - } -+ /* default base and limit, forward to standard implementation */ - if (obase == NULL) - return PyNumber_Long(x); - -@@ -5723,6 +5769,8 @@ internal representation of integers. The attributes are read only."); - static PyStructSequence_Field int_info_fields[] = { - {"bits_per_digit", "size of a digit in bits"}, - {"sizeof_digit", "size in bytes of the C type used to represent a digit"}, -+ {"default_max_str_digits", "maximum string conversion digits limitation"}, -+ {"str_digits_check_threshold", "minimum positive value for int_max_str_digits"}, - {NULL, NULL} - }; - -@@ -5730,7 +5778,7 @@ static PyStructSequence_Desc int_info_desc = { - "sys.int_info", /* name */ - int_info__doc__, /* doc */ - int_info_fields, /* fields */ -- 2 /* number of fields */ -+ 4 /* number of fields */ - }; - - PyObject * -@@ -5745,6 +5793,17 @@ PyLong_GetInfo(void) - PyLong_FromLong(PyLong_SHIFT)); - PyStructSequence_SET_ITEM(int_info, field++, - PyLong_FromLong(sizeof(digit))); -+ /* -+ * The following two fields were added after investigating uses of -+ * sys.int_info in the wild: Exceedingly rarely used. The ONLY use found was -+ * numba using sys.int_info.bits_per_digit as attribute access rather than -+ * sequence unpacking. Cython and sympy also refer to sys.int_info but only -+ * as info for debugging. No concern about adding these in a backport. -+ */ -+ PyStructSequence_SET_ITEM(int_info, field++, -+ PyLong_FromLong(_PY_LONG_DEFAULT_MAX_STR_DIGITS)); -+ PyStructSequence_SET_ITEM(int_info, field++, -+ PyLong_FromLong(_PY_LONG_MAX_STR_DIGITS_THRESHOLD)); - if (PyErr_Occurred()) { - Py_CLEAR(int_info); - return NULL; -@@ -5790,6 +5849,10 @@ _PyLong_Init(PyThreadState *tstate) - } - } - } -+ tstate->interp->int_max_str_digits = _Py_global_config_int_max_str_digits; -+ if (tstate->interp->int_max_str_digits == -1) { -+ tstate->interp->int_max_str_digits = _PY_LONG_DEFAULT_MAX_STR_DIGITS; -+ } - - return 1; - } -diff --git a/Parser/pegen/pegen.c b/Parser/pegen/pegen.c -index cdfbc12..15b06ce 100644 ---- a/Parser/pegen/pegen.c -+++ b/Parser/pegen/pegen.c -@@ -967,6 +967,24 @@ _PyPegen_number_token(Parser *p) - - if (c == NULL) { - p->error_indicator = 1; -+ PyObject *exc_type, *exc_value, *exc_tb; -+ PyErr_Fetch(&exc_type, &exc_value, &exc_tb); -+ // The only way a ValueError should happen in _this_ code is via -+ // PyLong_FromString hitting a length limit. -+ if (exc_type == PyExc_ValueError && exc_value != NULL) { -+ // The Fetch acted as PyErr_Clear(), we're replacing the exception. -+ Py_XDECREF(exc_tb); -+ Py_DECREF(exc_type); -+ RAISE_ERROR_KNOWN_LOCATION( -+ p, PyExc_SyntaxError, -+ t->lineno, 0 /* col_offset */, -+ "%S - Consider hexadecimal for huge integer literals " -+ "to avoid decimal conversion limits.", -+ exc_value); -+ Py_DECREF(exc_value); -+ } else { -+ PyErr_Restore(exc_type, exc_value, exc_tb); -+ } - return NULL; - } - -diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h -index 4615eba..4144408 100644 ---- a/Python/clinic/sysmodule.c.h -+++ b/Python/clinic/sysmodule.c.h -@@ -667,6 +667,64 @@ exit: - - #endif /* defined(USE_MALLOPT) */ - -+PyDoc_STRVAR(sys_get_int_max_str_digits__doc__, -+"get_int_max_str_digits($module, /)\n" -+"--\n" -+"\n" -+"Set the maximum string digits limit for non-binary int<->str conversions."); -+ -+#define SYS_GET_INT_MAX_STR_DIGITS_METHODDEF \ -+ {"get_int_max_str_digits", (PyCFunction)sys_get_int_max_str_digits, METH_NOARGS, sys_get_int_max_str_digits__doc__}, -+ -+static PyObject * -+sys_get_int_max_str_digits_impl(PyObject *module); -+ -+static PyObject * -+sys_get_int_max_str_digits(PyObject *module, PyObject *Py_UNUSED(ignored)) -+{ -+ return sys_get_int_max_str_digits_impl(module); -+} -+ -+PyDoc_STRVAR(sys_set_int_max_str_digits__doc__, -+"set_int_max_str_digits($module, /, maxdigits)\n" -+"--\n" -+"\n" -+"Set the maximum string digits limit for non-binary int<->str conversions."); -+ -+#define SYS_SET_INT_MAX_STR_DIGITS_METHODDEF \ -+ {"set_int_max_str_digits", (PyCFunction)(void(*)(void))sys_set_int_max_str_digits, METH_FASTCALL|METH_KEYWORDS, sys_set_int_max_str_digits__doc__}, -+ -+static PyObject * -+sys_set_int_max_str_digits_impl(PyObject *module, int maxdigits); -+ -+static PyObject * -+sys_set_int_max_str_digits(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -+{ -+ PyObject *return_value = NULL; -+ static const char * const _keywords[] = {"maxdigits", NULL}; -+ static _PyArg_Parser _parser = {NULL, _keywords, "set_int_max_str_digits", 0}; -+ PyObject *argsbuf[1]; -+ int maxdigits; -+ -+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); -+ if (!args) { -+ goto exit; -+ } -+ if (PyFloat_Check(args[0])) { -+ PyErr_SetString(PyExc_TypeError, -+ "integer argument expected, got float" ); -+ goto exit; -+ } -+ maxdigits = _PyLong_AsInt(args[0]); -+ if (maxdigits == -1 && PyErr_Occurred()) { -+ goto exit; -+ } -+ return_value = sys_set_int_max_str_digits_impl(module, maxdigits); -+ -+exit: -+ return return_value; -+} -+ - PyDoc_STRVAR(sys_getrefcount__doc__, - "getrefcount($module, object, /)\n" - "--\n" -@@ -970,4 +1028,4 @@ sys_getandroidapilevel(PyObject *module, PyObject *Py_UNUSED(ignored)) - #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF - #define SYS_GETANDROIDAPILEVEL_METHODDEF - #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ --/*[clinic end generated code: output=39eb34a01fb9a919 input=a9049054013a1b77]*/ -+/*[clinic end generated code: output=401254a595859ac6 input=a9049054013a1b77]*/ -diff --git a/Python/initconfig.c b/Python/initconfig.c -index 116ee33..a2c435f 100644 ---- a/Python/initconfig.c -+++ b/Python/initconfig.c -@@ -3,6 +3,7 @@ - #include "pycore_getopt.h" // _PyOS_GetOpt() - #include "pycore_initconfig.h" // _PyStatus_OK() - #include "pycore_interp.h" // _PyInterpreterState.runtime -+#include "pycore_long.h" // _PY_LONG_MAX_STR_DIGITS_THRESHOLD - #include "pycore_pathconfig.h" // _Py_path_config - #include "pycore_pyerrors.h" // _PyErr_Fetch() - #include "pycore_pylifecycle.h" // _Py_PreInitializeFromConfig() -@@ -99,6 +100,9 @@ static const char usage_3[] = "\ - otherwise activate automatically)\n\ - -X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted at the\n\ - given directory instead of to the code tree\n\ -+ -X int_max_str_digits=number: limit the size of int<->str conversions.\n\ -+ This helps avoid denial of service attacks when parsing untrusted data.\n\ -+ The default is sys.int_info.default_max_str_digits. 0 disables.\n\ - \n\ - --check-hash-based-pycs always|default|never:\n\ - control how Python invalidates hash-based .pyc files\n\ -@@ -125,6 +129,10 @@ static const char usage_6[] = - " to seed the hashes of str and bytes objects. It can also be set to an\n" - " integer in the range [0,4294967295] to get hash values with a\n" - " predictable seed.\n" -+"PYTHONINTMAXSTRDIGITS: limits the maximum digit characters in an int value\n" -+" when converting from a string and when converting an int back to a str.\n" -+" A value of 0 disables the limit. Conversions to or from bases 2, 4, 8,\n" -+" 16, and 32 are never limited.\n" - "PYTHONMALLOC: set the Python memory allocators and/or install debug hooks\n" - " on Python memory allocators. Use PYTHONMALLOC=debug to install debug\n" - " hooks.\n" -@@ -646,6 +654,10 @@ _PyConfig_InitCompatConfig(PyConfig *config) - config->_use_peg_parser = 1; - } - -+/* Excluded from public struct PyConfig for backporting reasons. */ -+/* default to unconfigured, _PyLong_Init() does the rest */ -+int _Py_global_config_int_max_str_digits = -1; -+ - - static void - config_init_defaults(PyConfig *config) -@@ -1410,6 +1422,48 @@ config_init_tracemalloc(PyConfig *config) - return _PyStatus_OK(); - } - -+static PyStatus -+config_init_int_max_str_digits(PyConfig *config) -+{ -+ int maxdigits; -+ int valid = 0; -+ -+ const char *env = config_get_env(config, "PYTHONINTMAXSTRDIGITS"); -+ if (env) { -+ if (!_Py_str_to_int(env, &maxdigits)) { -+ valid = ((maxdigits == 0) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)); -+ } -+ if (!valid) { -+#define STRINGIFY(VAL) _STRINGIFY(VAL) -+#define _STRINGIFY(VAL) #VAL -+ return _PyStatus_ERR( -+ "PYTHONINTMAXSTRDIGITS: invalid limit; must be >= " -+ STRINGIFY(_PY_LONG_MAX_STR_DIGITS_THRESHOLD) -+ " or 0 for unlimited."); -+ } -+ _Py_global_config_int_max_str_digits = maxdigits; -+ } -+ -+ const wchar_t *xoption = config_get_xoption(config, L"int_max_str_digits"); -+ if (xoption) { -+ const wchar_t *sep = wcschr(xoption, L'='); -+ if (sep) { -+ if (!config_wstr_to_int(sep + 1, &maxdigits)) { -+ valid = ((maxdigits == 0) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)); -+ } -+ } -+ if (!valid) { -+ return _PyStatus_ERR( -+ "-X int_max_str_digits: invalid limit; must be >= " -+ STRINGIFY(_PY_LONG_MAX_STR_DIGITS_THRESHOLD) -+ " or 0 for unlimited."); -+#undef _STRINGIFY -+#undef STRINGIFY -+ } -+ _Py_global_config_int_max_str_digits = maxdigits; -+ } -+ return _PyStatus_OK(); -+} - - static PyStatus - config_init_pycache_prefix(PyConfig *config) -@@ -1466,6 +1520,12 @@ config_read_complex_options(PyConfig *config) - return status; - } - } -+ if (_Py_global_config_int_max_str_digits < 0) { -+ status = config_init_int_max_str_digits(config); -+ if (_PyStatus_EXCEPTION(status)) { -+ return status; -+ } -+ } - - if (config->pycache_prefix == NULL) { - status = config_init_pycache_prefix(config); -diff --git a/Python/sysmodule.c b/Python/sysmodule.c -index a52b299..8efa850 100644 ---- a/Python/sysmodule.c -+++ b/Python/sysmodule.c -@@ -19,6 +19,7 @@ Data members: - #include "frameobject.h" // PyFrame_GetBack() - #include "pycore_ceval.h" - #include "pycore_initconfig.h" -+#include "pycore_long.h" // _PY_LONG_MAX_STR_DIGITS_THRESHOLD - #include "pycore_object.h" - #include "pycore_pathconfig.h" - #include "pycore_pyerrors.h" -@@ -1636,6 +1637,45 @@ sys_mdebug_impl(PyObject *module, int flag) - } - #endif /* USE_MALLOPT */ - -+ -+/*[clinic input] -+sys.get_int_max_str_digits -+ -+Set the maximum string digits limit for non-binary int<->str conversions. -+[clinic start generated code]*/ -+ -+static PyObject * -+sys_get_int_max_str_digits_impl(PyObject *module) -+/*[clinic end generated code: output=0042f5e8ae0e8631 input=8dab13e2023e60d5]*/ -+{ -+ PyInterpreterState *interp = _PyInterpreterState_GET(); -+ return PyLong_FromSsize_t(interp->int_max_str_digits); -+} -+ -+/*[clinic input] -+sys.set_int_max_str_digits -+ -+ maxdigits: int -+ -+Set the maximum string digits limit for non-binary int<->str conversions. -+[clinic start generated code]*/ -+ -+static PyObject * -+sys_set_int_max_str_digits_impl(PyObject *module, int maxdigits) -+/*[clinic end generated code: output=734d4c2511f2a56d input=d7e3f325db6910c5]*/ -+{ -+ PyThreadState *tstate = _PyThreadState_GET(); -+ if ((!maxdigits) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)) { -+ tstate->interp->int_max_str_digits = maxdigits; -+ Py_RETURN_NONE; -+ } else { -+ PyErr_Format( -+ PyExc_ValueError, "maxdigits must be 0 or larger than %d", -+ _PY_LONG_MAX_STR_DIGITS_THRESHOLD); -+ return NULL; -+ } -+} -+ - size_t - _PySys_GetSizeOf(PyObject *o) - { -@@ -1980,6 +2020,8 @@ static PyMethodDef sys_methods[] = { - SYS_GET_ASYNCGEN_HOOKS_METHODDEF - SYS_GETANDROIDAPILEVEL_METHODDEF - SYS_UNRAISABLEHOOK_METHODDEF -+ SYS_GET_INT_MAX_STR_DIGITS_METHODDEF -+ SYS_SET_INT_MAX_STR_DIGITS_METHODDEF - {NULL, NULL} /* sentinel */ - }; - -@@ -2440,6 +2482,7 @@ static PyStructSequence_Field flags_fields[] = { - {"isolated", "-I"}, - {"dev_mode", "-X dev"}, - {"utf8_mode", "-X utf8"}, -+ {"int_max_str_digits", "-X int_max_str_digits"}, - {0} - }; - -@@ -2447,7 +2490,7 @@ static PyStructSequence_Desc flags_desc = { - "sys.flags", /* name */ - flags__doc__, /* doc */ - flags_fields, /* fields */ -- 15 -+ 16 - }; - - static PyObject* -@@ -2483,6 +2526,7 @@ make_flags(PyThreadState *tstate) - SetFlag(config->isolated); - PyStructSequence_SET_ITEM(seq, pos++, PyBool_FromLong(config->dev_mode)); - SetFlag(preconfig->utf8_mode); -+ SetFlag(_Py_global_config_int_max_str_digits); - #undef SetFlag - - if (_PyErr_Occurred(tstate)) { --- -2.37.3 - diff --git a/SOURCES/Python-3.9.10.tar.xz.asc b/SOURCES/Python-3.9.10.tar.xz.asc deleted file mode 100644 index ca807b4..0000000 --- a/SOURCES/Python-3.9.10.tar.xz.asc +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PGP SIGNATURE----- - -iQIzBAABCgAdFiEE4/8oOcBIslwITevpsmmV4xAlBWgFAmHgts4ACgkQsmmV4xAl -BWiDfQ/6A6ALwx3xrU5ks0Fec91x56ixg+S91cMGDwHM78/KT+gOSva0FxLlpVDO -OD0yLoBOwVjSaiFAZC9wH2tf6xWLujVeBscrVX3Di1DZznZGgusDhP+gWpugULrF -WCr1ZLxnerhOMmBU9hSOx1ObBc91CIsagqafeMuAWoEwsQpkG0hDHuRN8QeJn79s -mFt4/X6/2NQEw/ucxftwoLIfqDbeP468wthOBgr9TrYfseSojnnYCxOiitC8k2GS -ieLq1RQu6ZjSYtHyL+mvXwTy2tEyc6v/3s+3lJCcM6uK+vyAvwP4QwIXwpjwHMDW -0YdyB812pWUleAt7P+BvVbumjGCs3hFHydMm6iA1eiONQk5Z0QQv0qrh+rqorXkJ -aUgjiXjbNkx93m+hftUXXEcKWm6hi8CikcajBPbTZt6PlOCL2ZbN5NclTDpG3JMK -8WHFpeFF1uaKcsDF5L9QU+ilXR0wxrh99DePQiLqsMvzbocB3cd5x8Ws1MNX/5tU -6p88qUf1Zdg3EA7VQD7m90cTOHcskH8o+Bw+Jd+uDo+H8Pg9Yz9yHik6rHe8OwiA -ol7eiEO07WePwqX4PqHozRtCVNSOZtrSSoWK2LNQdK9Z+EhVhQvDDk3hFQiE7ymM -ZXHYHATjmGFN7WSThMR/8IFgTSqpuird+RXEkVZKorYPsA0P55I= -=KRy4 ------END PGP SIGNATURE----- diff --git a/SOURCES/Python-3.9.14.tar.xz.asc b/SOURCES/Python-3.9.14.tar.xz.asc new file mode 100644 index 0000000..810952d --- /dev/null +++ b/SOURCES/Python-3.9.14.tar.xz.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP SIGNATURE----- + +iQIzBAABCgAdFiEE4/8oOcBIslwITevpsmmV4xAlBWgFAmMXib8ACgkQsmmV4xAl +BWiwtA/+LhNMVhCwNFNtDaxDLv2Pt43oX07ka9kuRau6WU0bicf4zBboQW2Ut9en +epdnw06klvOrb2wlxU4jeWdzuq5bNlhW3rVyT2npbRfYeXqsi/i+sY9eV5SFRPAM +eZrnCOkuWJxTy/XRQsPFbhE0bG+npoR80RpDtZ9EfjKyL2PSyOsvudKHjepcdHMi +tCzcmHwJbakD18g268RiDZsR8q7lSQW0c3OhOPwXfFV2Xh+jS8eEEcdp2QSBq/S9 ++HIHEAYJWvAEtg9q3KMKDWOi5rd0Q6Cw4zcO8GI0HTCkNsnJmtLUJ4qTTZWOHC5t +M8Z0hzuXI9wK23GpxWuThuaMyQCW5HX8tBVuxaHNyWuJ4T6ID0eJJP4ijLNMXv4U +Q0MRbLYtIIagB8BsZtBnEIIsQ1k7THdJ5KAsWIjVaPe80yQWLOrmuXulJi+E4F/C +bBKcsFXC605xg5C3AQk58QXpyLDoPtLtRVVPtVi1aqpHCLRCikHA8kqwc7JQIf7v +p4VsfcNsYga/EF6FArdmVz8fOpQgItvhuHgMRWBZ35p/t9Ckbl7fJQB0PYfVKYWC +F+uNahFYvJ+gH96U6MddEzZlB0AHx3gfysBrgWXvgUAqvAZ/Vq6FiOf0jhFWUtb1 +8b7sp8GSp7QzIP9U3J75sonXEAvNOA31vuKSJ2sKdxsIP9/KgZE= +=kQup +-----END PGP SIGNATURE----- diff --git a/SPECS/python3.9.spec b/SPECS/python3.9.spec index e3c7e4b..7288645 100644 --- a/SPECS/python3.9.spec +++ b/SPECS/python3.9.spec @@ -13,11 +13,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}.10 +%global general_version %{pybasever}.14 #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 3%{?dist} +Release: 1%{?dist} License: Python @@ -325,7 +325,7 @@ Patch189: 00189-use-rpm-wheels.patch # The versions are written in Lib/ensurepip/__init__.py, this patch removes them. # When the bundled setuptools/pip wheel is updated, the patch no longer applies cleanly. # In such cases, the patch needs to be amended and the versions updated here: -%global pip_version 21.2.4 +%global pip_version 22.0.4 %global setuptools_version 58.1.0 # 00251 # 1b1047c14ff98eae6d355b4aac4df3e388813f62 @@ -399,51 +399,15 @@ Patch329: 00329-fips.patch # a nightmare because it's basically a binary file. Patch353: 00353-architecture-names-upstream-downstream.patch -# 00378 # -# Support expat 2.4.5 +# 00382 # 9e275dcdf3934b827994ecc3247d583d5bab7985 +# CVE-2015-20107 # -# Curly brackets were never allowed in namespace URIs -# according to RFC 3986, and so-called namespace-validating -# XML parsers have the right to reject them a invalid URIs. +# Make mailcap refuse to match unsafe filenames/types/params (GH-91993) # -# libexpat >=2.4.5 has become strcter in that regard due to -# related security issues; with ET.XML instantiating a -# namespace-aware parser under the hood, this test has no -# future in CPython. +# Upstream: https://github.com/python/cpython/issues/68966 # -# References: -# - https://datatracker.ietf.org/doc/html/rfc3968 -# - https://www.w3.org/TR/xml-names/ -# -# Also, test_minidom.py: Support Expat >=2.4.5 -# -# The patch has diverged from upstream as the python test -# suite was relying on checking the expat version, whereas -# in RHEL fixes get backported instead of rebasing packages. -# -# Upstream: https://bugs.python.org/issue46811 -Patch378: 00378-support-expat-2-4-5.patch - -# 00387 # 87d28f3f0f0c9165c67b2a156134c614c6f6dcf5 -# CVE-2020-10735: Prevent DoS by very large int() -# -# gh-95778: CVE-2020-10735: Prevent DoS by very large int() (GH-96504) -# -# Converting between `int` and `str` in bases other than 2 -# (binary), 4, 8 (octal), 16 (hexadecimal), or 32 such as base 10 (decimal) now -# raises a `ValueError` if the number of digits in string form is above a -# limit to avoid potential denial of service attacks due to the algorithmic -# complexity. This is a mitigation for CVE-2020-10735 -# (https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735). -# -# This new limit can be configured or disabled by environment variable, command -# line flag, or :mod:`sys` APIs. See the `Integer String Conversion Length -# Limitation` documentation. The default limit is 4300 -# digits in string form. -# -# Patch by Gregory P. Smith [Google] and Christian Heimes [Red Hat] with feedback -# from Victor Stinner, Thomas Wouters, Steve Dower, Ned Deily, and Mark Dickinson. -Patch387: 00387-cve-2020-10735-prevent-dos-by-very-large-int.patch +# Tracker bug: https://bugzilla.redhat.com/show_bug.cgi?id=2075390 +Patch382: 00382-cve-2015-20107.patch # (New patches go here ^^^) # @@ -1846,10 +1810,22 @@ CheckPython optimized # ====================================================== %changelog -* Fri Sep 23 2022 Charalampos Stratakis - 3.9.10-3 -- Security fix for CVE-2020-10735 -- Fix the test suite support for Expat >= 2.4.5 -Resolves: rhbz#1834423 +* Wed Sep 21 2022 Charalampos Stratakis - 3.9.14-1 +- Update to 3.9.14 +- Security fixes for CVE-2020-10735 and CVE-2021-28861 +Resolves: rhbz#2120642, rhbz#1834423, rhbz#2128249 + +* Mon Jul 25 2022 Lumír Balhar - 3.9.13-3 +- Fix test_get_ciphers in test_ssl.py for FIPS mode +Resolves: rhbz#2058233 + +* Thu Jun 09 2022 Charalampos Stratakis - 3.9.13-2 +- Security fix for CVE-2015-20107 +Resolves: rhbz#2075390 + +* Wed Jun 01 2022 Charalampos Stratakis - 3.9.13-1 +- Update to 3.9.13 +Resolves: rhbz#2054702, rhbz#2059951 * Wed Feb 09 2022 Charalampos Stratakis - 3.9.10-2 - Fix undefined behavior in Modules/_hashopenssl.c