Compare commits
	
		
			No commits in common. "c9" and "c8-beta" have entirely different histories.
		
	
	
		
	
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +1 @@ | ||||
| SOURCES/Python-3.12.9.tar.xz | ||||
| SOURCES/Python-3.12.1.tar.xz | ||||
|  | ||||
| @ -1 +1 @@ | ||||
| 465d8a664e63dc5aa1f0d90cd1d0000a970ee2fb SOURCES/Python-3.12.9.tar.xz | ||||
| 5b11c58ea58cd6b8e1943c7e9b5f6e0997ca3632 SOURCES/Python-3.12.1.tar.xz | ||||
|  | ||||
| @ -30,10 +30,10 @@ Co-authored-by: Lumír Balhar <frenzy.madness@gmail.com> | ||||
|  3 files changed, 71 insertions(+), 4 deletions(-) | ||||
| 
 | ||||
| diff --git a/Lib/site.py b/Lib/site.py
 | ||||
| index aed254ad50..568dbdb945 100644
 | ||||
| index 672fa7b000..0a9c5be53e 100644
 | ||||
| --- a/Lib/site.py
 | ||||
| +++ b/Lib/site.py
 | ||||
| @@ -398,8 +398,15 @@ def getsitepackages(prefixes=None):
 | ||||
| @@ -377,8 +377,15 @@ def getsitepackages(prefixes=None):
 | ||||
|      return sitepackages | ||||
|   | ||||
|  def addsitepackages(known_paths, prefixes=None): | ||||
| @ -51,7 +51,7 @@ index aed254ad50..568dbdb945 100644 | ||||
|          if os.path.isdir(sitedir): | ||||
|              addsitedir(sitedir, known_paths) | ||||
| diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py
 | ||||
| index 517b13acaf..928d1a0541 100644
 | ||||
| index 122d441bd1..2d354a11da 100644
 | ||||
| --- a/Lib/sysconfig.py
 | ||||
| +++ b/Lib/sysconfig.py
 | ||||
| @@ -104,6 +104,11 @@
 | ||||
| @ -86,7 +86,7 @@ index 517b13acaf..928d1a0541 100644 | ||||
|  _SCHEME_KEYS = ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include', | ||||
|                  'scripts', 'data') | ||||
|   | ||||
| @@ -261,11 +279,40 @@ def _extend_dict(target_dict, other_dict):
 | ||||
| @@ -263,11 +281,40 @@ def _extend_dict(target_dict, other_dict):
 | ||||
|          target_dict[key] = value | ||||
|   | ||||
|   | ||||
| @ -119,7 +119,7 @@ index 517b13acaf..928d1a0541 100644 | ||||
| +    # we only change the defaults here, so explicit --prefix will take precedence
 | ||||
| +    # https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
 | ||||
| +    if (scheme == 'posix_prefix' and
 | ||||
| +        sys.prefix == '/usr' and
 | ||||
| +        _PREFIX == '/usr' and
 | ||||
| +        'RPM_BUILD_ROOT' not in os.environ):
 | ||||
| +            _extend_dict(vars, _config_vars_local())
 | ||||
| +    else:
 | ||||
| @ -129,10 +129,10 @@ index 517b13acaf..928d1a0541 100644 | ||||
|          # On Windows we want to substitute 'lib' for schemes rather | ||||
|          # than the native value (without modifying vars, in case it | ||||
| diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py
 | ||||
| index 3468d0ce02..ff31010427 100644
 | ||||
| index b6dbf3d52c..4f06a7673c 100644
 | ||||
| --- a/Lib/test/test_sysconfig.py
 | ||||
| +++ b/Lib/test/test_sysconfig.py
 | ||||
| @@ -119,8 +119,19 @@ def test_get_path(self):
 | ||||
| @@ -110,8 +110,19 @@ def test_get_path(self):
 | ||||
|          for scheme in _INSTALL_SCHEMES: | ||||
|              for name in _INSTALL_SCHEMES[scheme]: | ||||
|                  expected = _INSTALL_SCHEMES[scheme][name].format(**config_vars) | ||||
| @ -153,7 +153,7 @@ index 3468d0ce02..ff31010427 100644 | ||||
|                      os.path.normpath(expected), | ||||
|                  ) | ||||
|   | ||||
| @@ -353,7 +364,7 @@ def test_get_config_h_filename(self):
 | ||||
| @@ -335,7 +346,7 @@ def test_get_config_h_filename(self):
 | ||||
|          self.assertTrue(os.path.isfile(config_h), config_h) | ||||
|   | ||||
|      def test_get_scheme_names(self): | ||||
| @ -162,7 +162,7 @@ index 3468d0ce02..ff31010427 100644 | ||||
|          if HAS_USER_BASE: | ||||
|              wanted.extend(['nt_user', 'osx_framework_user', 'posix_user']) | ||||
|          self.assertEqual(get_scheme_names(), tuple(sorted(wanted))) | ||||
| @@ -365,6 +376,8 @@ def test_symlink(self): # Issue 7880
 | ||||
| @@ -347,6 +358,8 @@ def test_symlink(self): # Issue 7880
 | ||||
|              cmd = "-c", "import sysconfig; print(sysconfig.get_platform())" | ||||
|              self.assertEqual(py.call_real(*cmd), py.call_link(*cmd)) | ||||
|   | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| From 11deb3112bd90bc2dce2fcd4a1f5975c08b91360 Mon Sep 17 00:00:00 2001 | ||||
| From 5cedde59cde3f05af798a7cb5bc722cb0deb4835 Mon Sep 17 00:00:00 2001 | ||||
| From: Charalampos Stratakis <cstratak@redhat.com> | ||||
| Date: Thu, 12 Dec 2019 16:58:31 +0100 | ||||
| Subject: [PATCH 1/5] Expose blake2b and blake2s hashes from OpenSSL | ||||
| Subject: [PATCH 1/6] Expose blake2b and blake2s hashes from OpenSSL | ||||
| 
 | ||||
| These aren't as powerful as Python's own implementation, but they can be | ||||
| used under FIPS. | ||||
| @ -29,10 +29,10 @@ index 73d758a..5921360 100644 | ||||
|              computed = m.hexdigest() if not shake else m.hexdigest(length) | ||||
|              self.assertEqual( | ||||
| diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
 | ||||
| index 2998820..b96001e 100644
 | ||||
| index af6d1b2..980712f 100644
 | ||||
| --- a/Modules/_hashopenssl.c
 | ||||
| +++ b/Modules/_hashopenssl.c
 | ||||
| @@ -1128,6 +1128,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj,
 | ||||
| @@ -1079,6 +1079,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj,
 | ||||
|  } | ||||
|   | ||||
|   | ||||
| @ -74,7 +74,7 @@ index 2998820..b96001e 100644 | ||||
|  #ifdef PY_OPENSSL_HAS_SHA3 | ||||
|   | ||||
|  /*[clinic input] | ||||
| @@ -2116,6 +2151,8 @@ static struct PyMethodDef EVP_functions[] = {
 | ||||
| @@ -2067,6 +2102,8 @@ static struct PyMethodDef EVP_functions[] = {
 | ||||
|      _HASHLIB_OPENSSL_SHA256_METHODDEF | ||||
|      _HASHLIB_OPENSSL_SHA384_METHODDEF | ||||
|      _HASHLIB_OPENSSL_SHA512_METHODDEF | ||||
| @ -84,7 +84,7 @@ index 2998820..b96001e 100644 | ||||
|      _HASHLIB_OPENSSL_SHA3_256_METHODDEF | ||||
|      _HASHLIB_OPENSSL_SHA3_384_METHODDEF | ||||
| diff --git a/Modules/clinic/_hashopenssl.c.h b/Modules/clinic/_hashopenssl.c.h
 | ||||
| index 84e2346..7fe03a3 100644
 | ||||
| index fb61a44..1e42b87 100644
 | ||||
| --- a/Modules/clinic/_hashopenssl.c.h
 | ||||
| +++ b/Modules/clinic/_hashopenssl.c.h
 | ||||
| @@ -743,6 +743,156 @@ exit:
 | ||||
| @ -248,16 +248,16 @@ index 84e2346..7fe03a3 100644 | ||||
|  #ifndef _HASHLIB_SCRYPT_METHODDEF | ||||
|      #define _HASHLIB_SCRYPT_METHODDEF | ||||
|  #endif /* !defined(_HASHLIB_SCRYPT_METHODDEF) */ | ||||
| -/*[clinic end generated code: output=4734184f6555dc95 input=a9049054013a1b77]*/
 | ||||
| +/*[clinic end generated code: output=f0bfddb963a21208 input=a9049054013a1b77]*/
 | ||||
| -/*[clinic end generated code: output=b339e255db698147 input=a9049054013a1b77]*/
 | ||||
| +/*[clinic end generated code: output=1d988d457a8beebe input=a9049054013a1b77]*/
 | ||||
| -- 
 | ||||
| 2.47.1 | ||||
| 2.43.0 | ||||
| 
 | ||||
| 
 | ||||
| From ea9d5c84e25b5c04c2823e1edee4354dd6b2b7a5 Mon Sep 17 00:00:00 2001 | ||||
| From 2a12baa9e201f54560ec99ad5ee1fa5b0006aa39 Mon Sep 17 00:00:00 2001 | ||||
| From: Petr Viktorin <pviktori@redhat.com> | ||||
| Date: Thu, 25 Jul 2019 17:19:06 +0200 | ||||
| Subject: [PATCH 2/5] Disable Python's hash implementations in FIPS mode, | ||||
| Subject: [PATCH 2/6] Disable Python's hash implementations in FIPS mode, | ||||
|  forcing OpenSSL | ||||
| 
 | ||||
| ---
 | ||||
| @ -445,10 +445,10 @@ index a8bad9d..1b1d937 100644 | ||||
| +    if (_Py_hashlib_fips_error(exc, name)) return NULL; \
 | ||||
| +} while (0)
 | ||||
| diff --git a/configure.ac b/configure.ac
 | ||||
| index 9270b5f..a9eb2c9 100644
 | ||||
| index 1876f77..1875d1e 100644
 | ||||
| --- a/configure.ac
 | ||||
| +++ b/configure.ac
 | ||||
| @@ -7482,7 +7482,8 @@ PY_STDLIB_MOD([_sha2],
 | ||||
| @@ -7439,7 +7439,8 @@ PY_STDLIB_MOD([_sha2],
 | ||||
|  PY_STDLIB_MOD([_sha3], [test "$with_builtin_sha3" = yes]) | ||||
|  PY_STDLIB_MOD([_blake2], | ||||
|    [test "$with_builtin_blake2" = yes], [], | ||||
| @ -459,13 +459,13 @@ index 9270b5f..a9eb2c9 100644 | ||||
|  PY_STDLIB_MOD([_crypt], | ||||
|    [], [test "$ac_cv_crypt_crypt" = yes], | ||||
| -- 
 | ||||
| 2.47.1 | ||||
| 2.43.0 | ||||
| 
 | ||||
| 
 | ||||
| From 29a7b7ac9e18a501ed78bde7a449b90c57d44e24 Mon Sep 17 00:00:00 2001 | ||||
| From bca05b7fdb8dcab21ef80db1d59dd5daa835d84b Mon Sep 17 00:00:00 2001 | ||||
| From: Charalampos Stratakis <cstratak@redhat.com> | ||||
| Date: Fri, 29 Jan 2021 14:16:21 +0100 | ||||
| Subject: [PATCH 3/5] Use python's fall back crypto implementations only if we | ||||
| Subject: [PATCH 3/6] Use python's fall back crypto implementations only if we | ||||
|  are not in FIPS mode | ||||
| 
 | ||||
| ---
 | ||||
| @ -552,13 +552,13 @@ index dd61a9a..6031b02 100644 | ||||
|          get_builtin_constructor = getattr(hashlib, | ||||
|                                            '__get_builtin_constructor') | ||||
| -- 
 | ||||
| 2.47.1 | ||||
| 2.43.0 | ||||
| 
 | ||||
| 
 | ||||
| From 59accf544492400c9fd32a8e682fb6f2206e932e Mon Sep 17 00:00:00 2001 | ||||
| From c9a79f0aafd28677e3e0b8a1f6410105a71ff071 Mon Sep 17 00:00:00 2001 | ||||
| From: Charalampos Stratakis <cstratak@redhat.com> | ||||
| Date: Wed, 31 Jul 2019 15:43:43 +0200 | ||||
| Subject: [PATCH 4/5] Test equivalence of hashes for the various digests with | ||||
| Subject: [PATCH 4/6] Test equivalence of hashes for the various digests with | ||||
|  usedforsecurity=True/False | ||||
| 
 | ||||
| ---
 | ||||
| @ -712,21 +712,21 @@ index 6031b02..5bd5297 100644 | ||||
|  class KDFTests(unittest.TestCase): | ||||
|   | ||||
| -- 
 | ||||
| 2.47.1 | ||||
| 2.43.0 | ||||
| 
 | ||||
| 
 | ||||
| From 21efadd8b488956482bdc6ccd91c37dcef705129 Mon Sep 17 00:00:00 2001 | ||||
| From e972a838729ea84a0f2e0ca8e88ae1bfc129e7d8 Mon Sep 17 00:00:00 2001 | ||||
| From: Petr Viktorin <pviktori@redhat.com> | ||||
| Date: Mon, 26 Aug 2019 19:39:48 +0200 | ||||
| Subject: [PATCH 5/5] Guard against Python HMAC in FIPS mode | ||||
| Subject: [PATCH 5/6] Guard against Python HMAC in FIPS mode | ||||
| 
 | ||||
| ---
 | ||||
|  Lib/hmac.py           | 12 +++++++++--- | ||||
|  Lib/hmac.py           | 13 +++++++++---- | ||||
|  Lib/test/test_hmac.py | 10 ++++++++++ | ||||
|  2 files changed, 19 insertions(+), 3 deletions(-) | ||||
|  2 files changed, 19 insertions(+), 4 deletions(-) | ||||
| 
 | ||||
| diff --git a/Lib/hmac.py b/Lib/hmac.py
 | ||||
| index 8b4eb2f..8930bda 100644
 | ||||
| index 8b4f920..20ef96c 100644
 | ||||
| --- a/Lib/hmac.py
 | ||||
| +++ b/Lib/hmac.py
 | ||||
| @@ -16,8 +16,9 @@ else:
 | ||||
| @ -741,9 +741,16 @@ index 8b4eb2f..8930bda 100644 | ||||
|   | ||||
|  # The size of the digests returned by HMAC depends on the underlying | ||||
|  # hashing module used.  Use digest_size from the instance of HMAC instead. | ||||
| @@ -55,10 +56,12 @@ class HMAC:
 | ||||
| @@ -48,17 +49,18 @@ class HMAC:
 | ||||
|                     msg argument.  Passing it as a keyword argument is | ||||
|                     recommended, though not required for legacy API reasons. | ||||
|          """ | ||||
| -
 | ||||
|          if not isinstance(key, (bytes, bytearray)): | ||||
|              raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) | ||||
|   | ||||
|          if not digestmod: | ||||
|              raise TypeError("Missing required argument 'digestmod'.") | ||||
|              raise TypeError("Missing required parameter 'digestmod'.") | ||||
|   | ||||
| -        if _hashopenssl and isinstance(digestmod, (str, _functype)):
 | ||||
| +        if _hashopenssl.get_fips_mode() or (_hashopenssl and isinstance(digestmod, (str, _functype))):
 | ||||
| @ -755,7 +762,7 @@ index 8b4eb2f..8930bda 100644 | ||||
|                  self._init_old(key, msg, digestmod) | ||||
|          else: | ||||
|              self._init_old(key, msg, digestmod) | ||||
| @@ -69,6 +72,9 @@ class HMAC:
 | ||||
| @@ -69,6 +71,9 @@ class HMAC:
 | ||||
|          self.block_size = self._hmac.block_size | ||||
|   | ||||
|      def _init_old(self, key, msg, digestmod): | ||||
| @ -766,7 +773,7 @@ index 8b4eb2f..8930bda 100644 | ||||
|              digest_cons = digestmod | ||||
|          elif isinstance(digestmod, str): | ||||
| diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
 | ||||
| index 1502fba..7997073 100644
 | ||||
| index a39a2c4..b7b24ab 100644
 | ||||
| --- a/Lib/test/test_hmac.py
 | ||||
| +++ b/Lib/test/test_hmac.py
 | ||||
| @@ -5,6 +5,7 @@ import hashlib
 | ||||
| @ -805,7 +812,7 @@ index 1502fba..7997073 100644 | ||||
|      @unittest.skipUnless(sha256_module is not None, 'need _sha256') | ||||
|      def test_with_sha256_module(self): | ||||
|          h = hmac.HMAC(b"key", b"hash this!", digestmod=sha256_module.sha256) | ||||
| @@ -489,6 +497,7 @@ class UpdateTestCase(unittest.TestCase):
 | ||||
| @@ -481,6 +489,7 @@ class SanityTestCase(unittest.TestCase):
 | ||||
|   | ||||
|  class CopyTestCase(unittest.TestCase): | ||||
|   | ||||
| @ -813,7 +820,7 @@ index 1502fba..7997073 100644 | ||||
|      @hashlib_helper.requires_hashdigest('sha256') | ||||
|      def test_attributes_old(self): | ||||
|          # Testing if attributes are of same type. | ||||
| @@ -500,6 +509,7 @@ class CopyTestCase(unittest.TestCase):
 | ||||
| @@ -492,6 +501,7 @@ class CopyTestCase(unittest.TestCase):
 | ||||
|          self.assertEqual(type(h1._outer), type(h2._outer), | ||||
|              "Types of outer don't match.") | ||||
|   | ||||
| @ -822,5 +829,270 @@ index 1502fba..7997073 100644 | ||||
|      def test_realcopy_old(self): | ||||
|          # Testing if the copy method created a real copy. | ||||
| -- 
 | ||||
| 2.47.1 | ||||
| 2.43.0 | ||||
| 
 | ||||
| 
 | ||||
| From b12202196a78b877dcd32cfea273051b60038a41 Mon Sep 17 00:00:00 2001 | ||||
| From: Petr Viktorin <encukou@gmail.com> | ||||
| Date: Wed, 25 Aug 2021 16:44:43 +0200 | ||||
| Subject: [PATCH 6/6] Disable hash-based PYCs in FIPS mode | ||||
| 
 | ||||
| If FIPS mode is on, we can't use siphash-based HMAC | ||||
| (_Py_KeyedHash), so: | ||||
| 
 | ||||
| - Unchecked hash PYCs can be imported, but not created
 | ||||
| - Checked hash PYCs can not be imported nor created
 | ||||
| - The default mode is timestamp-based PYCs, even if
 | ||||
|   SOURCE_DATE_EPOCH is set. | ||||
| 
 | ||||
| If FIPS mode is off, there are no changes in behavior. | ||||
| 
 | ||||
| Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1835169 | ||||
| ---
 | ||||
|  Lib/py_compile.py                             |  4 +++- | ||||
|  Lib/test/support/__init__.py                  | 14 +++++++++++++ | ||||
|  Lib/test/test_cmd_line_script.py              |  2 ++ | ||||
|  Lib/test/test_compileall.py                   | 11 +++++++++- | ||||
|  .../test_importlib/source/test_file_loader.py |  6 ++++++ | ||||
|  Lib/test/test_py_compile.py                   | 11 ++++++++-- | ||||
|  Lib/test/test_zipimport.py                    |  2 ++ | ||||
|  Python/import.c                               | 20 +++++++++++++++++++ | ||||
|  8 files changed, 66 insertions(+), 4 deletions(-) | ||||
| 
 | ||||
| diff --git a/Lib/py_compile.py b/Lib/py_compile.py
 | ||||
| index 388614e..fd9a139 100644
 | ||||
| --- a/Lib/py_compile.py
 | ||||
| +++ b/Lib/py_compile.py
 | ||||
| @@ -70,7 +70,9 @@ class PycInvalidationMode(enum.Enum):
 | ||||
|   | ||||
|   | ||||
|  def _get_default_invalidation_mode(): | ||||
| -    if os.environ.get('SOURCE_DATE_EPOCH'):
 | ||||
| +    import _hashlib
 | ||||
| +    if (os.environ.get('SOURCE_DATE_EPOCH') and not
 | ||||
| +          _hashlib.get_fips_mode()):
 | ||||
|          return PycInvalidationMode.CHECKED_HASH | ||||
|      else: | ||||
|          return PycInvalidationMode.TIMESTAMP | ||||
| diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
 | ||||
| index fd9265c..fcd1ea7 100644
 | ||||
| --- a/Lib/test/support/__init__.py
 | ||||
| +++ b/Lib/test/support/__init__.py
 | ||||
| @@ -2346,6 +2346,20 @@ def sleeping_retry(timeout, err_msg=None, /,
 | ||||
|          delay = min(delay * 2, max_delay) | ||||
|   | ||||
|   | ||||
| +def fails_in_fips_mode(expected_error):
 | ||||
| +    import _hashlib
 | ||||
| +    if _hashlib.get_fips_mode():
 | ||||
| +        def _decorator(func):
 | ||||
| +            def _wrapper(self, *args, **kwargs):
 | ||||
| +                with self.assertRaises(expected_error):
 | ||||
| +                    func(self, *args, **kwargs)
 | ||||
| +            return _wrapper
 | ||||
| +    else:
 | ||||
| +        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 1b58882..d6caff1 100644
 | ||||
| --- a/Lib/test/test_cmd_line_script.py
 | ||||
| +++ b/Lib/test/test_cmd_line_script.py
 | ||||
| @@ -286,6 +286,7 @@ class CmdLineTest(unittest.TestCase):
 | ||||
|              self._check_script(zip_name, run_name, zip_name, zip_name, '', | ||||
|                                 zipimport.zipimporter) | ||||
|   | ||||
| +    @support.fails_in_fips_mode(ImportError)
 | ||||
|      def test_zipfile_compiled_checked_hash(self): | ||||
|          with os_helper.temp_dir() as script_dir: | ||||
|              script_name = _make_test_script(script_dir, '__main__') | ||||
| @@ -296,6 +297,7 @@ class CmdLineTest(unittest.TestCase):
 | ||||
|              self._check_script(zip_name, run_name, zip_name, zip_name, '', | ||||
|                                 zipimport.zipimporter) | ||||
|   | ||||
| +    @support.fails_in_fips_mode(ImportError)
 | ||||
|      def test_zipfile_compiled_unchecked_hash(self): | ||||
|          with os_helper.temp_dir() as script_dir: | ||||
|              script_name = _make_test_script(script_dir, '__main__') | ||||
| diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
 | ||||
| index 9cd92ad..4ec29a1 100644
 | ||||
| --- a/Lib/test/test_compileall.py
 | ||||
| +++ b/Lib/test/test_compileall.py
 | ||||
| @@ -806,14 +806,23 @@ class CommandLineTestsBase:
 | ||||
|          out = self.assertRunOK('badfilename') | ||||
|          self.assertRegex(out, b"Can't list 'badfilename'") | ||||
|   | ||||
| -    def test_pyc_invalidation_mode(self):
 | ||||
| +    @support.fails_in_fips_mode(AssertionError)
 | ||||
| +    def test_pyc_invalidation_mode_checked(self):
 | ||||
|          script_helper.make_script(self.pkgdir, 'f1', '') | ||||
|          pyc = importlib.util.cache_from_source( | ||||
|              os.path.join(self.pkgdir, 'f1.py')) | ||||
| +
 | ||||
|          self.assertRunOK('--invalidation-mode=checked-hash', self.pkgdir) | ||||
|          with open(pyc, 'rb') as fp: | ||||
|              data = fp.read() | ||||
|          self.assertEqual(int.from_bytes(data[4:8], 'little'), 0b11) | ||||
| +
 | ||||
| +    @support.fails_in_fips_mode(AssertionError)
 | ||||
| +    def test_pyc_invalidation_mode_unchecked(self):
 | ||||
| +        script_helper.make_script(self.pkgdir, 'f1', '')
 | ||||
| +        pyc = importlib.util.cache_from_source(
 | ||||
| +            os.path.join(self.pkgdir, 'f1.py'))
 | ||||
| +
 | ||||
|          self.assertRunOK('--invalidation-mode=unchecked-hash', self.pkgdir) | ||||
|          with open(pyc, 'rb') as fp: | ||||
|              data = fp.read() | ||||
| diff --git a/Lib/test/test_importlib/source/test_file_loader.py b/Lib/test/test_importlib/source/test_file_loader.py
 | ||||
| index f35adec..62087c6 100644
 | ||||
| --- a/Lib/test/test_importlib/source/test_file_loader.py
 | ||||
| +++ b/Lib/test/test_importlib/source/test_file_loader.py
 | ||||
| @@ -16,6 +16,7 @@ import types
 | ||||
|  import unittest | ||||
|  import warnings | ||||
|   | ||||
| +from test import support
 | ||||
|  from test.support.import_helper import make_legacy_pyc, unload | ||||
|   | ||||
|  from test.test_py_compile import without_source_date_epoch | ||||
| @@ -237,6 +238,7 @@ class SimpleTest(abc.LoaderTests):
 | ||||
|                  loader.load_module('bad name') | ||||
|   | ||||
|      @util.writes_bytecode_files | ||||
| +    @support.fails_in_fips_mode(ImportError)
 | ||||
|      def test_checked_hash_based_pyc(self): | ||||
|          with util.create_modules('_temp') as mapping: | ||||
|              source = mapping['_temp'] | ||||
| @@ -268,6 +270,7 @@ class SimpleTest(abc.LoaderTests):
 | ||||
|              ) | ||||
|   | ||||
|      @util.writes_bytecode_files | ||||
| +    @support.fails_in_fips_mode(ImportError)
 | ||||
|      def test_overridden_checked_hash_based_pyc(self): | ||||
|          with util.create_modules('_temp') as mapping, \ | ||||
|               unittest.mock.patch('_imp.check_hash_based_pycs', 'never'): | ||||
| @@ -293,6 +296,7 @@ class SimpleTest(abc.LoaderTests):
 | ||||
|              self.assertEqual(mod.state, 'old') | ||||
|   | ||||
|      @util.writes_bytecode_files | ||||
| +    @support.fails_in_fips_mode(ImportError)
 | ||||
|      def test_unchecked_hash_based_pyc(self): | ||||
|          with util.create_modules('_temp') as mapping: | ||||
|              source = mapping['_temp'] | ||||
| @@ -323,6 +327,7 @@ class SimpleTest(abc.LoaderTests):
 | ||||
|              ) | ||||
|   | ||||
|      @util.writes_bytecode_files | ||||
| +    @support.fails_in_fips_mode(ImportError)
 | ||||
|      def test_overridden_unchecked_hash_based_pyc(self): | ||||
|          with util.create_modules('_temp') as mapping, \ | ||||
|               unittest.mock.patch('_imp.check_hash_based_pycs', 'always'): | ||||
| @@ -432,6 +437,7 @@ class BadBytecodeTest:
 | ||||
|                                                 del_source=del_source) | ||||
|              test('_temp', mapping, bc_path) | ||||
|   | ||||
| +    @support.fails_in_fips_mode(ImportError)
 | ||||
|      def _test_partial_hash(self, test, *, del_source=False): | ||||
|          with util.create_modules('_temp') as mapping: | ||||
|              bc_path = self.manipulate_bytecode( | ||||
| diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py
 | ||||
| index c4e6551..81fd962 100644
 | ||||
| --- a/Lib/test/test_py_compile.py
 | ||||
| +++ b/Lib/test/test_py_compile.py
 | ||||
| @@ -141,13 +141,16 @@ class PyCompileTestsBase:
 | ||||
|              importlib.util.cache_from_source(bad_coding))) | ||||
|   | ||||
|      def test_source_date_epoch(self): | ||||
| +        import _hashlib
 | ||||
|          py_compile.compile(self.source_path, self.pyc_path) | ||||
|          self.assertTrue(os.path.exists(self.pyc_path)) | ||||
|          self.assertFalse(os.path.exists(self.cache_path)) | ||||
|          with open(self.pyc_path, 'rb') as fp: | ||||
|              flags = importlib._bootstrap_external._classify_pyc( | ||||
|                  fp.read(), 'test', {}) | ||||
| -        if os.environ.get('SOURCE_DATE_EPOCH'):
 | ||||
| +        if _hashlib.get_fips_mode():
 | ||||
| +            expected_flags = 0b00
 | ||||
| +        elif os.environ.get('SOURCE_DATE_EPOCH'):
 | ||||
|              expected_flags = 0b11 | ||||
|          else: | ||||
|              expected_flags = 0b00 | ||||
| @@ -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)) | ||||
|   | ||||
| -    def test_invalidation_mode(self):
 | ||||
| +    @support.fails_in_fips_mode(ImportError)
 | ||||
| +    def test_invalidation_mode_checked(self):
 | ||||
|          py_compile.compile( | ||||
|              self.source_path, | ||||
|              invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH, | ||||
| @@ -187,6 +191,9 @@ class PyCompileTestsBase:
 | ||||
|              flags = importlib._bootstrap_external._classify_pyc( | ||||
|                  fp.read(), 'test', {}) | ||||
|          self.assertEqual(flags, 0b11) | ||||
| +
 | ||||
| +    @support.fails_in_fips_mode(ImportError)
 | ||||
| +    def test_invalidation_mode_unchecked(self):
 | ||||
|          py_compile.compile( | ||||
|              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 14c1971..bcd1466 100644
 | ||||
| --- a/Lib/test/test_zipimport.py
 | ||||
| +++ b/Lib/test/test_zipimport.py
 | ||||
| @@ -190,6 +190,7 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
 | ||||
|                   TESTMOD + pyc_ext: (NOW, test_pyc)} | ||||
|          self.doTest(pyc_ext, files, TESTMOD) | ||||
|   | ||||
| +    @support.fails_in_fips_mode(ImportError)
 | ||||
|      def testUncheckedHashBasedPyc(self): | ||||
|          source = b"state = 'old'" | ||||
|          source_hash = importlib.util.source_hash(source) | ||||
| @@ -204,6 +205,7 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
 | ||||
|              self.assertEqual(mod.state, 'old') | ||||
|          self.doTest(None, files, TESTMOD, call=check) | ||||
|   | ||||
| +    @support.fails_in_fips_mode(ImportError)
 | ||||
|      @unittest.mock.patch('_imp.check_hash_based_pycs', 'always') | ||||
|      def test_checked_hash_based_change_pyc(self): | ||||
|          source = b"state = 'old'" | ||||
| diff --git a/Python/import.c b/Python/import.c
 | ||||
| index 54232a1..236786b 100644
 | ||||
| --- a/Python/import.c
 | ||||
| +++ b/Python/import.c
 | ||||
| @@ -3829,6 +3829,26 @@ static PyObject *
 | ||||
|  _imp_source_hash_impl(PyObject *module, long key, Py_buffer *source) | ||||
|  /*[clinic end generated code: output=edb292448cf399ea input=9aaad1e590089789]*/ | ||||
|  { | ||||
| +    PyObject *_hashlib = PyImport_ImportModule("_hashlib");
 | ||||
| +    if (_hashlib == NULL) {
 | ||||
| +        return NULL;
 | ||||
| +    }
 | ||||
| +    PyObject *fips_mode_obj = PyObject_CallMethod(_hashlib, "get_fips_mode", NULL);
 | ||||
| +    Py_DECREF(_hashlib);
 | ||||
| +    if (fips_mode_obj == NULL) {
 | ||||
| +        return NULL;
 | ||||
| +    }
 | ||||
| +    int fips_mode = PyObject_IsTrue(fips_mode_obj);
 | ||||
| +    Py_DECREF(fips_mode_obj);
 | ||||
| +    if (fips_mode < 0) {
 | ||||
| +        return NULL;
 | ||||
| +    }
 | ||||
| +    if (fips_mode) {
 | ||||
| +        PyErr_SetString(
 | ||||
| +            PyExc_ImportError,
 | ||||
| +            "hash-based PYC validation (siphash24) not available in FIPS mode");
 | ||||
| +        return NULL;
 | ||||
| +    };
 | ||||
|      union { | ||||
|          uint64_t x; | ||||
|          char data[sizeof(uint64_t)]; | ||||
| -- 
 | ||||
| 2.43.0 | ||||
| 
 | ||||
|  | ||||
| @ -16,10 +16,10 @@ https://github.com/GrahamDumpleton/mod_wsgi/issues/730 | ||||
|  2 files changed, 8 insertions(+), 50 deletions(-) | ||||
| 
 | ||||
| diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
 | ||||
| index 75a56f7830..c2509fced1 100644
 | ||||
| index 756d5e329f..5d09775efc 100644
 | ||||
| --- a/Lib/test/test_threading.py
 | ||||
| +++ b/Lib/test/test_threading.py
 | ||||
| @@ -1100,39 +1100,6 @@ def noop(): pass
 | ||||
| @@ -1007,39 +1007,6 @@ def noop(): pass
 | ||||
|              threading.Thread(target=noop).start() | ||||
|              # Thread.join() is not called | ||||
|   | ||||
| @ -56,14 +56,14 @@ index 75a56f7830..c2509fced1 100644 | ||||
| -        self.assertEqual(out, b'')
 | ||||
| -        self.assertEqual(err, b'')
 | ||||
| -
 | ||||
|      def test_start_new_thread_at_finalization(self): | ||||
|      def test_start_new_thread_at_exit(self): | ||||
|          code = """if 1: | ||||
|              import _thread | ||||
|              import atexit | ||||
| diff --git a/Lib/threading.py b/Lib/threading.py
 | ||||
| index 0bba85d08a..b256e3273f 100644
 | ||||
| index 8dcaf8ca6a..ed0b0f4632 100644
 | ||||
| --- a/Lib/threading.py
 | ||||
| +++ b/Lib/threading.py
 | ||||
| @@ -1587,29 +1587,20 @@ def _shutdown():
 | ||||
| @@ -1586,29 +1586,20 @@ def _shutdown():
 | ||||
|   | ||||
|      global _SHUTTING_DOWN | ||||
|      _SHUTTING_DOWN = True | ||||
|  | ||||
| @ -1,24 +1,19 @@ | ||||
| From ddd8064257a1916726b784d43f18e889ea1634f7 Mon Sep 17 00:00:00 2001 | ||||
| From 73d2995223c725638d53b9cb8e1d26b82daf0874 Mon Sep 17 00:00:00 2001 | ||||
| From: Petr Viktorin <encukou@gmail.com> | ||||
| Date: Tue, 2 Jul 2024 11:40:37 +0200 | ||||
| Date: Mon, 6 Mar 2023 17:24:24 +0100 | ||||
| Subject: [PATCH] CVE-2007-4559, PEP-706: Add filters for tarfile extraction | ||||
|  (downstream) | ||||
| MIME-Version: 1.0 | ||||
| Content-Type: text/plain; charset=UTF-8 | ||||
| Content-Transfer-Encoding: 8bit | ||||
| 
 | ||||
| Add and test RHEL-specific ways of configuring the default behavior: environment | ||||
| variable and config file. | ||||
| 
 | ||||
| Co-Authored-By: Tomáš Hrnčiar <thrnciar@redhat.com> | ||||
| ---
 | ||||
|  Lib/tarfile.py           |  47 +++++++++++-- | ||||
|  Lib/tarfile.py           |  47 +++++++++++++-- | ||||
|  Lib/test/test_shutil.py  |   2 +- | ||||
|  Lib/test/test_tarfile.py | 147 +++++++++++++++++++++++++++++++++++++-- | ||||
|  3 files changed, 185 insertions(+), 11 deletions(-) | ||||
|  Lib/test/test_tarfile.py | 123 ++++++++++++++++++++++++++++++++++++++- | ||||
|  3 files changed, 163 insertions(+), 9 deletions(-) | ||||
| 
 | ||||
| diff --git a/Lib/tarfile.py b/Lib/tarfile.py
 | ||||
| index e1487e3..89b6843 100755
 | ||||
| index 02f5e3b..f7109f3 100755
 | ||||
| --- a/Lib/tarfile.py
 | ||||
| +++ b/Lib/tarfile.py
 | ||||
| @@ -71,6 +71,13 @@ __all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError", "ReadError",
 | ||||
| @ -35,7 +30,7 @@ index e1487e3..89b6843 100755 | ||||
|   | ||||
|  #--------------------------------------------------------- | ||||
|  # tar constants | ||||
| @@ -2218,11 +2225,41 @@ class TarFile(object):
 | ||||
| @@ -2217,11 +2224,41 @@ class TarFile(object):
 | ||||
|          if filter is None: | ||||
|              filter = self.extraction_filter | ||||
|              if filter is None: | ||||
| @ -43,7 +38,7 @@ index e1487e3..89b6843 100755 | ||||
| -                    'Python 3.14 will, by default, filter extracted tar '
 | ||||
| -                    + 'archives and reject files or modify their metadata. '
 | ||||
| -                    + 'Use the filter argument to control this behavior.',
 | ||||
| -                    DeprecationWarning, stacklevel=3)
 | ||||
| -                    DeprecationWarning)
 | ||||
| +                name = os.environ.get('PYTHON_TARFILE_EXTRACTION_FILTER')
 | ||||
| +                if name is None:
 | ||||
| +                    try:
 | ||||
| @ -77,16 +72,16 @@ index e1487e3..89b6843 100755 | ||||
| +                        + 'and some mode bits are cleared. '
 | ||||
| +                        + 'See https://access.redhat.com/articles/7004769 '
 | ||||
| +                        + 'for more details.',
 | ||||
| +                        RuntimeWarning, stacklevel=3)
 | ||||
| +                        RuntimeWarning)
 | ||||
| +                    return tar_filter
 | ||||
|                  return fully_trusted_filter | ||||
|              if isinstance(filter, str): | ||||
|                  raise TypeError( | ||||
| diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
 | ||||
| index 7bc5d12..88b4bdb 100644
 | ||||
| index 5fd8fb4..501da8f 100644
 | ||||
| --- a/Lib/test/test_shutil.py
 | ||||
| +++ b/Lib/test/test_shutil.py
 | ||||
| @@ -2096,7 +2096,7 @@ class TestArchives(BaseTest, unittest.TestCase):
 | ||||
| @@ -1950,7 +1950,7 @@ class TestArchives(BaseTest, unittest.TestCase):
 | ||||
|          self.check_unpack_archive(format, filter='fully_trusted') | ||||
|          self.check_unpack_archive(format, filter='data') | ||||
|          with warnings_helper.check_warnings( | ||||
| @ -96,48 +91,10 @@ index 7bc5d12..88b4bdb 100644 | ||||
|   | ||||
|      def test_unpack_archive_tar(self): | ||||
| diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
 | ||||
| index 3fbd25e..9aa727e 100644
 | ||||
| index c5fc76d..397e334 100644
 | ||||
| --- a/Lib/test/test_tarfile.py
 | ||||
| +++ b/Lib/test/test_tarfile.py
 | ||||
| @@ -727,7 +727,17 @@ class MiscReadTestBase(CommonReadTest):
 | ||||
|              tarfile.open(tarname, encoding="iso8859-1") as tar | ||||
|          ): | ||||
|              directories = [t for t in tar if t.isdir()] | ||||
| -            with self.assertWarnsRegex(DeprecationWarning, "Use the filter argument") as cm:
 | ||||
| +            with self.assertWarnsRegex(
 | ||||
| +                RuntimeWarning,
 | ||||
| +                re.escape(
 | ||||
| +                    'The default behavior of tarfile extraction has been '
 | ||||
| +                    'changed to disallow common exploits '
 | ||||
| +                    '(including CVE-2007-4559). '
 | ||||
| +                    'By default, absolute/parent paths are disallowed '
 | ||||
| +                    'and some mode bits are cleared. '
 | ||||
| +                    'See https://access.redhat.com/articles/7004769 '
 | ||||
| +                    'for more details.'
 | ||||
| +                )) as cm:
 | ||||
|                  tar.extractall(DIR, directories) | ||||
|              # check that the stacklevel of the deprecation warning is correct: | ||||
|              self.assertEqual(cm.filename, __file__) | ||||
| @@ -740,7 +750,17 @@ class MiscReadTestBase(CommonReadTest):
 | ||||
|              tarfile.open(tarname, encoding="iso8859-1") as tar | ||||
|          ): | ||||
|              tarinfo = tar.getmember(dirtype) | ||||
| -            with self.assertWarnsRegex(DeprecationWarning, "Use the filter argument") as cm:
 | ||||
| +            with self.assertWarnsRegex(
 | ||||
| +                RuntimeWarning,
 | ||||
| +                re.escape(
 | ||||
| +                    'The default behavior of tarfile extraction has been '
 | ||||
| +                    'changed to disallow common exploits '
 | ||||
| +                    '(including CVE-2007-4559). '
 | ||||
| +                    'By default, absolute/parent paths are disallowed '
 | ||||
| +                    'and some mode bits are cleared. '
 | ||||
| +                    'See https://access.redhat.com/articles/7004769 '
 | ||||
| +                    'for more details.'
 | ||||
| +                )) as cm:
 | ||||
|                  tar.extract(tarinfo, path=DIR) | ||||
|              # check that the stacklevel of the deprecation warning is correct: | ||||
|              self.assertEqual(cm.filename, __file__) | ||||
| @@ -3144,8 +3164,8 @@ class NoneInfoExtractTests(ReadTest):
 | ||||
| @@ -3097,8 +3097,8 @@ class NoneInfoExtractTests(ReadTest):
 | ||||
|          tar.errorlevel = 0 | ||||
|          with ExitStack() as cm: | ||||
|              if cls.extraction_filter is None: | ||||
| @ -148,7 +105,7 @@ index 3fbd25e..9aa727e 100644 | ||||
|              tar.extractall(cls.control_dir, filter=cls.extraction_filter) | ||||
|          tar.close() | ||||
|          cls.control_paths = set( | ||||
| @@ -3966,7 +3986,7 @@ class TestExtractionFilters(unittest.TestCase):
 | ||||
| @@ -3919,7 +3919,7 @@ class TestExtractionFilters(unittest.TestCase):
 | ||||
|          with ArchiveMaker() as arc: | ||||
|              arc.add('foo') | ||||
|          with warnings_helper.check_warnings( | ||||
| @ -157,7 +114,7 @@ index 3fbd25e..9aa727e 100644 | ||||
|              with self.check_context(arc.open(), None): | ||||
|                  self.expect_file('foo') | ||||
|   | ||||
| @@ -4136,6 +4156,123 @@ class TestExtractionFilters(unittest.TestCase):
 | ||||
| @@ -4089,6 +4089,123 @@ class TestExtractionFilters(unittest.TestCase):
 | ||||
|              self.expect_exception(TypeError)  # errorlevel is not int | ||||
|   | ||||
|   | ||||
| @ -278,9 +235,9 @@ index 3fbd25e..9aa727e 100644 | ||||
| +                self.check_trusted_default(tar, tempdir)
 | ||||
| +
 | ||||
| +
 | ||||
|  class OverwriteTests(archiver_tests.OverwriteTests, unittest.TestCase): | ||||
|      testdir = os.path.join(TEMPDIR, "testoverwrite") | ||||
|   | ||||
|  def setUpModule(): | ||||
|      os_helper.unlink(TEMPDIR) | ||||
|      os.makedirs(TEMPDIR) | ||||
| -- 
 | ||||
| 2.44.0 | ||||
| 2.43.0 | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,483 @@ | ||||
| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 | ||||
| From: Victor Stinner <vstinner@python.org> | ||||
| Date: Fri, 15 Dec 2023 16:10:40 +0100 | ||||
| Subject: [PATCH] 00415: [CVE-2023-27043] gh-102988: Reject malformed addresses | ||||
|  in email.parseaddr() (#111116) | ||||
| 
 | ||||
| Detect email address parsing errors and return empty tuple to | ||||
| indicate the parsing error (old API). Add an optional 'strict' | ||||
| parameter to getaddresses() and parseaddr() functions. Patch by | ||||
| Thomas Dwyer. | ||||
| 
 | ||||
| Co-Authored-By: Thomas Dwyer <github@tomd.tel> | ||||
| ---
 | ||||
|  Doc/library/email.utils.rst                   |  19 +- | ||||
|  Lib/email/utils.py                            | 151 +++++++++++++- | ||||
|  Lib/test/test_email/test_email.py             | 187 +++++++++++++++++- | ||||
|  ...-10-20-15-28-08.gh-issue-102988.dStNO7.rst |   8 + | ||||
|  4 files changed, 344 insertions(+), 21 deletions(-) | ||||
|  create mode 100644 Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst | ||||
| 
 | ||||
| diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst
 | ||||
| index 345b64001c..d693a9bc39 100644
 | ||||
| --- a/Doc/library/email.utils.rst
 | ||||
| +++ b/Doc/library/email.utils.rst
 | ||||
| @@ -58,13 +58,18 @@ of the new API.
 | ||||
|     begins with angle brackets, they are stripped off. | ||||
|   | ||||
|   | ||||
| -.. function:: parseaddr(address)
 | ||||
| +.. function:: parseaddr(address, *, strict=True)
 | ||||
|   | ||||
|     Parse address -- which should be the value of some address-containing field such | ||||
|     as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and | ||||
|     *email address* parts.  Returns a tuple of that information, unless the parse | ||||
|     fails, in which case a 2-tuple of ``('', '')`` is returned. | ||||
|   | ||||
| +   If *strict* is true, use a strict parser which rejects malformed inputs.
 | ||||
| +
 | ||||
| +   .. versionchanged:: 3.13
 | ||||
| +      Add *strict* optional parameter and reject malformed inputs by default.
 | ||||
| +
 | ||||
|   | ||||
|  .. function:: formataddr(pair, charset='utf-8') | ||||
|   | ||||
| @@ -82,12 +87,15 @@ of the new API.
 | ||||
|        Added the *charset* option. | ||||
|   | ||||
|   | ||||
| -.. function:: getaddresses(fieldvalues)
 | ||||
| +.. function:: getaddresses(fieldvalues, *, strict=True)
 | ||||
|   | ||||
|     This method returns a list of 2-tuples of the form returned by ``parseaddr()``. | ||||
|     *fieldvalues* is a sequence of header field values as might be returned by | ||||
| -   :meth:`Message.get_all <email.message.Message.get_all>`.  Here's a simple
 | ||||
| -   example that gets all the recipients of a message::
 | ||||
| +   :meth:`Message.get_all <email.message.Message.get_all>`.
 | ||||
| +
 | ||||
| +   If *strict* is true, use a strict parser which rejects malformed inputs.
 | ||||
| +
 | ||||
| +   Here's a simple example that gets all the recipients of a message::
 | ||||
|   | ||||
|        from email.utils import getaddresses | ||||
|   | ||||
| @@ -97,6 +105,9 @@ of the new API.
 | ||||
|        resent_ccs = msg.get_all('resent-cc', []) | ||||
|        all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) | ||||
|   | ||||
| +   .. versionchanged:: 3.13
 | ||||
| +      Add *strict* optional parameter and reject malformed inputs by default.
 | ||||
| +
 | ||||
|   | ||||
|  .. function:: parsedate(date) | ||||
|   | ||||
| diff --git a/Lib/email/utils.py b/Lib/email/utils.py
 | ||||
| index 81da5394ea..43c3627fca 100644
 | ||||
| --- a/Lib/email/utils.py
 | ||||
| +++ b/Lib/email/utils.py
 | ||||
| @@ -48,6 +48,7 @@
 | ||||
|  specialsre = re.compile(r'[][\\()<>@,:;".]') | ||||
|  escapesre = re.compile(r'[\\"]') | ||||
|   | ||||
| +
 | ||||
|  def _has_surrogates(s): | ||||
|      """Return True if s contains surrogate-escaped binary data.""" | ||||
|      # This check is based on the fact that unless there are surrogates, utf8 | ||||
| @@ -106,12 +107,127 @@ def formataddr(pair, charset='utf-8'):
 | ||||
|      return address | ||||
|   | ||||
|   | ||||
| +def _iter_escaped_chars(addr):
 | ||||
| +    pos = 0
 | ||||
| +    escape = False
 | ||||
| +    for pos, ch in enumerate(addr):
 | ||||
| +        if escape:
 | ||||
| +            yield (pos, '\\' + ch)
 | ||||
| +            escape = False
 | ||||
| +        elif ch == '\\':
 | ||||
| +            escape = True
 | ||||
| +        else:
 | ||||
| +            yield (pos, ch)
 | ||||
| +    if escape:
 | ||||
| +        yield (pos, '\\')
 | ||||
|   | ||||
| -def getaddresses(fieldvalues):
 | ||||
| -    """Return a list of (REALNAME, EMAIL) for each fieldvalue."""
 | ||||
| -    all = COMMASPACE.join(str(v) for v in fieldvalues)
 | ||||
| -    a = _AddressList(all)
 | ||||
| -    return a.addresslist
 | ||||
| +
 | ||||
| +def _strip_quoted_realnames(addr):
 | ||||
| +    """Strip real names between quotes."""
 | ||||
| +    if '"' not in addr:
 | ||||
| +        # Fast path
 | ||||
| +        return addr
 | ||||
| +
 | ||||
| +    start = 0
 | ||||
| +    open_pos = None
 | ||||
| +    result = []
 | ||||
| +    for pos, ch in _iter_escaped_chars(addr):
 | ||||
| +        if ch == '"':
 | ||||
| +            if open_pos is None:
 | ||||
| +                open_pos = pos
 | ||||
| +            else:
 | ||||
| +                if start != open_pos:
 | ||||
| +                    result.append(addr[start:open_pos])
 | ||||
| +                start = pos + 1
 | ||||
| +                open_pos = None
 | ||||
| +
 | ||||
| +    if start < len(addr):
 | ||||
| +        result.append(addr[start:])
 | ||||
| +
 | ||||
| +    return ''.join(result)
 | ||||
| +
 | ||||
| +
 | ||||
| +supports_strict_parsing = True
 | ||||
| +
 | ||||
| +def getaddresses(fieldvalues, *, strict=True):
 | ||||
| +    """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
 | ||||
| +
 | ||||
| +    When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
 | ||||
| +    its place.
 | ||||
| +
 | ||||
| +    If strict is true, use a strict parser which rejects malformed inputs.
 | ||||
| +    """
 | ||||
| +
 | ||||
| +    # If strict is true, if the resulting list of parsed addresses is greater
 | ||||
| +    # than the number of fieldvalues in the input list, a parsing error has
 | ||||
| +    # occurred and consequently a list containing a single empty 2-tuple [('',
 | ||||
| +    # '')] is returned in its place. This is done to avoid invalid output.
 | ||||
| +    #
 | ||||
| +    # Malformed input: getaddresses(['alice@example.com <bob@example.com>'])
 | ||||
| +    # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')]
 | ||||
| +    # Safe output: [('', '')]
 | ||||
| +
 | ||||
| +    if not strict:
 | ||||
| +        all = COMMASPACE.join(str(v) for v in fieldvalues)
 | ||||
| +        a = _AddressList(all)
 | ||||
| +        return a.addresslist
 | ||||
| +
 | ||||
| +    fieldvalues = [str(v) for v in fieldvalues]
 | ||||
| +    fieldvalues = _pre_parse_validation(fieldvalues)
 | ||||
| +    addr = COMMASPACE.join(fieldvalues)
 | ||||
| +    a = _AddressList(addr)
 | ||||
| +    result = _post_parse_validation(a.addresslist)
 | ||||
| +
 | ||||
| +    # Treat output as invalid if the number of addresses is not equal to the
 | ||||
| +    # expected number of addresses.
 | ||||
| +    n = 0
 | ||||
| +    for v in fieldvalues:
 | ||||
| +        # When a comma is used in the Real Name part it is not a deliminator.
 | ||||
| +        # So strip those out before counting the commas.
 | ||||
| +        v = _strip_quoted_realnames(v)
 | ||||
| +        # Expected number of addresses: 1 + number of commas
 | ||||
| +        n += 1 + v.count(',')
 | ||||
| +    if len(result) != n:
 | ||||
| +        return [('', '')]
 | ||||
| +
 | ||||
| +    return result
 | ||||
| +
 | ||||
| +
 | ||||
| +def _check_parenthesis(addr):
 | ||||
| +    # Ignore parenthesis in quoted real names.
 | ||||
| +    addr = _strip_quoted_realnames(addr)
 | ||||
| +
 | ||||
| +    opens = 0
 | ||||
| +    for pos, ch in _iter_escaped_chars(addr):
 | ||||
| +        if ch == '(':
 | ||||
| +            opens += 1
 | ||||
| +        elif ch == ')':
 | ||||
| +            opens -= 1
 | ||||
| +            if opens < 0:
 | ||||
| +                return False
 | ||||
| +    return (opens == 0)
 | ||||
| +
 | ||||
| +
 | ||||
| +def _pre_parse_validation(email_header_fields):
 | ||||
| +    accepted_values = []
 | ||||
| +    for v in email_header_fields:
 | ||||
| +        if not _check_parenthesis(v):
 | ||||
| +            v = "('', '')"
 | ||||
| +        accepted_values.append(v)
 | ||||
| +
 | ||||
| +    return accepted_values
 | ||||
| +
 | ||||
| +
 | ||||
| +def _post_parse_validation(parsed_email_header_tuples):
 | ||||
| +    accepted_values = []
 | ||||
| +    # The parser would have parsed a correctly formatted domain-literal
 | ||||
| +    # The existence of an [ after parsing indicates a parsing failure
 | ||||
| +    for v in parsed_email_header_tuples:
 | ||||
| +        if '[' in v[1]:
 | ||||
| +            v = ('', '')
 | ||||
| +        accepted_values.append(v)
 | ||||
| +
 | ||||
| +    return accepted_values
 | ||||
|   | ||||
|   | ||||
|  def _format_timetuple_and_zone(timetuple, zone): | ||||
| @@ -205,16 +321,33 @@ def parsedate_to_datetime(data):
 | ||||
|              tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) | ||||
|   | ||||
|   | ||||
| -def parseaddr(addr):
 | ||||
| +def parseaddr(addr, *, strict=True):
 | ||||
|      """ | ||||
|      Parse addr into its constituent realname and email address parts. | ||||
|   | ||||
|      Return a tuple of realname and email address, unless the parse fails, in | ||||
|      which case return a 2-tuple of ('', ''). | ||||
| +
 | ||||
| +    If strict is True, use a strict parser which rejects malformed inputs.
 | ||||
|      """ | ||||
| -    addrs = _AddressList(addr).addresslist
 | ||||
| -    if not addrs:
 | ||||
| -        return '', ''
 | ||||
| +    if not strict:
 | ||||
| +        addrs = _AddressList(addr).addresslist
 | ||||
| +        if not addrs:
 | ||||
| +            return ('', '')
 | ||||
| +        return addrs[0]
 | ||||
| +
 | ||||
| +    if isinstance(addr, list):
 | ||||
| +        addr = addr[0]
 | ||||
| +
 | ||||
| +    if not isinstance(addr, str):
 | ||||
| +        return ('', '')
 | ||||
| +
 | ||||
| +    addr = _pre_parse_validation([addr])[0]
 | ||||
| +    addrs = _post_parse_validation(_AddressList(addr).addresslist)
 | ||||
| +
 | ||||
| +    if not addrs or len(addrs) > 1:
 | ||||
| +        return ('', '')
 | ||||
| +
 | ||||
|      return addrs[0] | ||||
|   | ||||
|   | ||||
| diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
 | ||||
| index 2a237095b9..4672b790d8 100644
 | ||||
| --- a/Lib/test/test_email/test_email.py
 | ||||
| +++ b/Lib/test/test_email/test_email.py
 | ||||
| @@ -16,6 +16,7 @@
 | ||||
|   | ||||
|  import email | ||||
|  import email.policy | ||||
| +import email.utils
 | ||||
|   | ||||
|  from email.charset import Charset | ||||
|  from email.generator import Generator, DecodedGenerator, BytesGenerator | ||||
| @@ -3337,15 +3338,137 @@ def test_getaddresses_comma_in_name(self):
 | ||||
|              ], | ||||
|          ) | ||||
|   | ||||
| +    def test_parsing_errors(self):
 | ||||
| +        """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056"""
 | ||||
| +        alice = 'alice@example.org'
 | ||||
| +        bob = 'bob@example.com'
 | ||||
| +        empty = ('', '')
 | ||||
| +
 | ||||
| +        # Test utils.getaddresses() and utils.parseaddr() on malformed email
 | ||||
| +        # addresses: default behavior (strict=True) rejects malformed address,
 | ||||
| +        # and strict=False which tolerates malformed address.
 | ||||
| +        for invalid_separator, expected_non_strict in (
 | ||||
| +            ('(', [(f'<{bob}>', alice)]),
 | ||||
| +            (')', [('', alice), empty, ('', bob)]),
 | ||||
| +            ('<', [('', alice), empty, ('', bob), empty]),
 | ||||
| +            ('>', [('', alice), empty, ('', bob)]),
 | ||||
| +            ('[', [('', f'{alice}[<{bob}>]')]),
 | ||||
| +            (']', [('', alice), empty, ('', bob)]),
 | ||||
| +            ('@', [empty, empty, ('', bob)]),
 | ||||
| +            (';', [('', alice), empty, ('', bob)]),
 | ||||
| +            (':', [('', alice), ('', bob)]),
 | ||||
| +            ('.', [('', alice + '.'), ('', bob)]),
 | ||||
| +            ('"', [('', alice), ('', f'<{bob}>')]),
 | ||||
| +        ):
 | ||||
| +            address = f'{alice}{invalid_separator}<{bob}>'
 | ||||
| +            with self.subTest(address=address):
 | ||||
| +                self.assertEqual(utils.getaddresses([address]),
 | ||||
| +                                 [empty])
 | ||||
| +                self.assertEqual(utils.getaddresses([address], strict=False),
 | ||||
| +                                 expected_non_strict)
 | ||||
| +
 | ||||
| +                self.assertEqual(utils.parseaddr([address]),
 | ||||
| +                                 empty)
 | ||||
| +                self.assertEqual(utils.parseaddr([address], strict=False),
 | ||||
| +                                 ('', address))
 | ||||
| +
 | ||||
| +        # Comma (',') is treated differently depending on strict parameter.
 | ||||
| +        # Comma without quotes.
 | ||||
| +        address = f'{alice},<{bob}>'
 | ||||
| +        self.assertEqual(utils.getaddresses([address]),
 | ||||
| +                         [('', alice), ('', bob)])
 | ||||
| +        self.assertEqual(utils.getaddresses([address], strict=False),
 | ||||
| +                         [('', alice), ('', bob)])
 | ||||
| +        self.assertEqual(utils.parseaddr([address]),
 | ||||
| +                         empty)
 | ||||
| +        self.assertEqual(utils.parseaddr([address], strict=False),
 | ||||
| +                         ('', address))
 | ||||
| +
 | ||||
| +        # Real name between quotes containing comma.
 | ||||
| +        address = '"Alice, alice@example.org" <bob@example.com>'
 | ||||
| +        expected_strict = ('Alice, alice@example.org', 'bob@example.com')
 | ||||
| +        self.assertEqual(utils.getaddresses([address]), [expected_strict])
 | ||||
| +        self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
 | ||||
| +        self.assertEqual(utils.parseaddr([address]), expected_strict)
 | ||||
| +        self.assertEqual(utils.parseaddr([address], strict=False),
 | ||||
| +                         ('', address))
 | ||||
| +
 | ||||
| +        # Valid parenthesis in comments.
 | ||||
| +        address = 'alice@example.org (Alice)'
 | ||||
| +        expected_strict = ('Alice', 'alice@example.org')
 | ||||
| +        self.assertEqual(utils.getaddresses([address]), [expected_strict])
 | ||||
| +        self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
 | ||||
| +        self.assertEqual(utils.parseaddr([address]), expected_strict)
 | ||||
| +        self.assertEqual(utils.parseaddr([address], strict=False),
 | ||||
| +                         ('', address))
 | ||||
| +
 | ||||
| +        # Invalid parenthesis in comments.
 | ||||
| +        address = 'alice@example.org )Alice('
 | ||||
| +        self.assertEqual(utils.getaddresses([address]), [empty])
 | ||||
| +        self.assertEqual(utils.getaddresses([address], strict=False),
 | ||||
| +                         [('', 'alice@example.org'), ('', ''), ('', 'Alice')])
 | ||||
| +        self.assertEqual(utils.parseaddr([address]), empty)
 | ||||
| +        self.assertEqual(utils.parseaddr([address], strict=False),
 | ||||
| +                         ('', address))
 | ||||
| +
 | ||||
| +        # Two addresses with quotes separated by comma.
 | ||||
| +        address = '"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>'
 | ||||
| +        self.assertEqual(utils.getaddresses([address]),
 | ||||
| +                         [('Jane Doe', 'jane@example.net'),
 | ||||
| +                          ('John Doe', 'john@example.net')])
 | ||||
| +        self.assertEqual(utils.getaddresses([address], strict=False),
 | ||||
| +                         [('Jane Doe', 'jane@example.net'),
 | ||||
| +                          ('John Doe', 'john@example.net')])
 | ||||
| +        self.assertEqual(utils.parseaddr([address]), empty)
 | ||||
| +        self.assertEqual(utils.parseaddr([address], strict=False),
 | ||||
| +                         ('', address))
 | ||||
| +
 | ||||
| +        # Test email.utils.supports_strict_parsing attribute
 | ||||
| +        self.assertEqual(email.utils.supports_strict_parsing, True)
 | ||||
| +
 | ||||
|      def test_getaddresses_nasty(self): | ||||
| -        eq = self.assertEqual
 | ||||
| -        eq(utils.getaddresses(['foo: ;']), [('', '')])
 | ||||
| -        eq(utils.getaddresses(
 | ||||
| -           ['[]*-- =~$']),
 | ||||
| -           [('', ''), ('', ''), ('', '*--')])
 | ||||
| -        eq(utils.getaddresses(
 | ||||
| -           ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
 | ||||
| -           [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
 | ||||
| +        for addresses, expected in (
 | ||||
| +            (['"Sürname, Firstname" <to@example.com>'],
 | ||||
| +             [('Sürname, Firstname', 'to@example.com')]),
 | ||||
| +
 | ||||
| +            (['foo: ;'],
 | ||||
| +             [('', '')]),
 | ||||
| +
 | ||||
| +            (['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>'],
 | ||||
| +             [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]),
 | ||||
| +
 | ||||
| +            ([r'Pete(A nice \) chap) <pete(his account)@silly.test(his host)>'],
 | ||||
| +             [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]),
 | ||||
| +
 | ||||
| +            (['(Empty list)(start)Undisclosed recipients  :(nobody(I know))'],
 | ||||
| +             [('', '')]),
 | ||||
| +
 | ||||
| +            (['Mary <@machine.tld:mary@example.net>, , jdoe@test   . example'],
 | ||||
| +             [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]),
 | ||||
| +
 | ||||
| +            (['John Doe <jdoe@machine(comment).  example>'],
 | ||||
| +             [('John Doe (comment)', 'jdoe@machine.example')]),
 | ||||
| +
 | ||||
| +            (['"Mary Smith: Personal Account" <smith@home.example>'],
 | ||||
| +             [('Mary Smith: Personal Account', 'smith@home.example')]),
 | ||||
| +
 | ||||
| +            (['Undisclosed recipients:;'],
 | ||||
| +             [('', '')]),
 | ||||
| +
 | ||||
| +            ([r'<boss@nil.test>, "Giant; \"Big\" Box" <bob@example.net>'],
 | ||||
| +             [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]),
 | ||||
| +        ):
 | ||||
| +            with self.subTest(addresses=addresses):
 | ||||
| +                self.assertEqual(utils.getaddresses(addresses),
 | ||||
| +                                 expected)
 | ||||
| +                self.assertEqual(utils.getaddresses(addresses, strict=False),
 | ||||
| +                                 expected)
 | ||||
| +
 | ||||
| +        addresses = ['[]*-- =~$']
 | ||||
| +        self.assertEqual(utils.getaddresses(addresses),
 | ||||
| +                         [('', '')])
 | ||||
| +        self.assertEqual(utils.getaddresses(addresses, strict=False),
 | ||||
| +                         [('', ''), ('', ''), ('', '*--')])
 | ||||
|   | ||||
|      def test_getaddresses_embedded_comment(self): | ||||
|          """Test proper handling of a nested comment""" | ||||
| @@ -3536,6 +3659,54 @@ def test_mime_classes_policy_argument(self):
 | ||||
|                  m = cls(*constructor, policy=email.policy.default) | ||||
|                  self.assertIs(m.policy, email.policy.default) | ||||
|   | ||||
| +    def test_iter_escaped_chars(self):
 | ||||
| +        self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')),
 | ||||
| +                         [(0, 'a'),
 | ||||
| +                          (2, '\\\\'),
 | ||||
| +                          (3, 'b'),
 | ||||
| +                          (5, '\\"'),
 | ||||
| +                          (6, 'c'),
 | ||||
| +                          (8, '\\\\'),
 | ||||
| +                          (9, '"'),
 | ||||
| +                          (10, 'd')])
 | ||||
| +        self.assertEqual(list(utils._iter_escaped_chars('a\\')),
 | ||||
| +                         [(0, 'a'), (1, '\\')])
 | ||||
| +
 | ||||
| +    def test_strip_quoted_realnames(self):
 | ||||
| +        def check(addr, expected):
 | ||||
| +            self.assertEqual(utils._strip_quoted_realnames(addr), expected)
 | ||||
| +
 | ||||
| +        check('"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>',
 | ||||
| +              ' <jane@example.net>,  <john@example.net>')
 | ||||
| +        check(r'"Jane \"Doe\"." <jane@example.net>',
 | ||||
| +              ' <jane@example.net>')
 | ||||
| +
 | ||||
| +        # special cases
 | ||||
| +        check(r'before"name"after', 'beforeafter')
 | ||||
| +        check(r'before"name"', 'before')
 | ||||
| +        check(r'b"name"', 'b')  # single char
 | ||||
| +        check(r'"name"after', 'after')
 | ||||
| +        check(r'"name"a', 'a')  # single char
 | ||||
| +        check(r'"name"', '')
 | ||||
| +
 | ||||
| +        # no change
 | ||||
| +        for addr in (
 | ||||
| +            'Jane Doe <jane@example.net>, John Doe <john@example.net>',
 | ||||
| +            'lone " quote',
 | ||||
| +        ):
 | ||||
| +            self.assertEqual(utils._strip_quoted_realnames(addr), addr)
 | ||||
| +
 | ||||
| +
 | ||||
| +    def test_check_parenthesis(self):
 | ||||
| +        addr = 'alice@example.net'
 | ||||
| +        self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)'))
 | ||||
| +        self.assertFalse(utils._check_parenthesis(f'{addr} )Alice('))
 | ||||
| +        self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))'))
 | ||||
| +        self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)'))
 | ||||
| +
 | ||||
| +        # Ignore real name between quotes
 | ||||
| +        self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}'))
 | ||||
| +
 | ||||
|   | ||||
|  # Test the iterator/generators | ||||
|  class TestIterators(TestEmailBase): | ||||
| diff --git a/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
 | ||||
| new file mode 100644 | ||||
| index 0000000000..3d0e9e4078
 | ||||
| --- /dev/null
 | ||||
| +++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
 | ||||
| @@ -0,0 +1,8 @@
 | ||||
| +:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now
 | ||||
| +return ``('', '')`` 2-tuples in more situations where invalid email
 | ||||
| +addresses are encountered instead of potentially inaccurate values. Add
 | ||||
| +optional *strict* parameter to these two functions: use ``strict=False`` to
 | ||||
| +get the old behavior, accept malformed inputs.
 | ||||
| +``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to check
 | ||||
| +if the *strict* paramater is available. Patch by Thomas Dwyer and Victor
 | ||||
| +Stinner to improve the CVE-2023-27043 fix.
 | ||||
| @ -1,63 +0,0 @@ | ||||
| From 60d40d7095983e0bc23a103b2050adc519dc7fe3 Mon Sep 17 00:00:00 2001 | ||||
| From: Lumir Balhar <lbalhar@redhat.com> | ||||
| Date: Fri, 3 May 2024 14:17:48 +0200 | ||||
| Subject: [PATCH] Expect failures in tests not working properly with expat with | ||||
|  a fixed CVE in RHEL | ||||
| 
 | ||||
| ---
 | ||||
|  Lib/test/test_pyexpat.py   | 1 + | ||||
|  Lib/test/test_sax.py       | 1 + | ||||
|  Lib/test/test_xml_etree.py | 3 +++ | ||||
|  3 files changed, 5 insertions(+) | ||||
| 
 | ||||
| diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py
 | ||||
| index 43cbd27..27b1502 100644
 | ||||
| --- a/Lib/test/test_pyexpat.py
 | ||||
| +++ b/Lib/test/test_pyexpat.py
 | ||||
| @@ -793,6 +793,7 @@ class ReparseDeferralTest(unittest.TestCase):
 | ||||
|   | ||||
|          self.assertEqual(started, ['doc']) | ||||
|   | ||||
| +    @unittest.expectedFailure
 | ||||
|      def test_reparse_deferral_disabled(self): | ||||
|          started = [] | ||||
|   | ||||
| diff --git a/Lib/test/test_sax.py b/Lib/test/test_sax.py
 | ||||
| index 9b3014a..646c92d 100644
 | ||||
| --- a/Lib/test/test_sax.py
 | ||||
| +++ b/Lib/test/test_sax.py
 | ||||
| @@ -1240,6 +1240,7 @@ class ExpatReaderTest(XmlTestBase):
 | ||||
|   | ||||
|          self.assertEqual(result.getvalue(), start + b"<doc></doc>") | ||||
|   | ||||
| +    @unittest.expectedFailure
 | ||||
|      def test_flush_reparse_deferral_disabled(self): | ||||
|          result = BytesIO() | ||||
|          xmlgen = XMLGenerator(result) | ||||
| diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
 | ||||
| index 9c382d1..62f2871 100644
 | ||||
| --- a/Lib/test/test_xml_etree.py
 | ||||
| +++ b/Lib/test/test_xml_etree.py
 | ||||
| @@ -1424,9 +1424,11 @@ class XMLPullParserTest(unittest.TestCase):
 | ||||
|          self.assert_event_tags(parser, [('end', 'root')]) | ||||
|          self.assertIsNone(parser.close()) | ||||
|   | ||||
| +    @unittest.expectedFailure
 | ||||
|      def test_simple_xml_chunk_1(self): | ||||
|          self.test_simple_xml(chunk_size=1, flush=True) | ||||
|   | ||||
| +    @unittest.expectedFailure
 | ||||
|      def test_simple_xml_chunk_5(self): | ||||
|          self.test_simple_xml(chunk_size=5, flush=True) | ||||
|   | ||||
| @@ -1651,6 +1653,7 @@ class XMLPullParserTest(unittest.TestCase):
 | ||||
|   | ||||
|          self.assert_event_tags(parser, [('end', 'doc')]) | ||||
|   | ||||
| +    @unittest.expectedFailure
 | ||||
|      def test_flush_reparse_deferral_disabled(self): | ||||
|          parser = ET.XMLPullParser(events=('start', 'end')) | ||||
|   | ||||
| -- 
 | ||||
| 2.44.0 | ||||
| 
 | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,215 +0,0 @@ | ||||
| From c9d9f78feb1467e73fd29356c040bde1c104f29f Mon Sep 17 00:00:00 2001 | ||||
| From: "Miss Islington (bot)" | ||||
|  <31488909+miss-islington@users.noreply.github.com> | ||||
| Date: Mon, 4 Aug 2025 13:45:06 +0200 | ||||
| Subject: [PATCH] [3.12] gh-130577: tarfile now validates archives to ensure | ||||
|  member offsets are non-negative (GH-137027) (#137171) | ||||
| 
 | ||||
| (cherry picked from commit 7040aa54f14676938970e10c5f74ea93cd56aa38) | ||||
| 
 | ||||
| Co-authored-by: Alexander Urieles <aeurielesn@users.noreply.github.com> | ||||
| Co-authored-by: Gregory P. Smith <greg@krypto.org> | ||||
| ---
 | ||||
|  Lib/tarfile.py                                |   3 + | ||||
|  Lib/test/test_tarfile.py                      | 156 ++++++++++++++++++ | ||||
|  ...-07-23-00-35-29.gh-issue-130577.c7EITy.rst |   3 + | ||||
|  3 files changed, 162 insertions(+) | ||||
|  create mode 100644 Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst | ||||
| 
 | ||||
| diff --git a/Lib/tarfile.py b/Lib/tarfile.py
 | ||||
| index 9999a99d54d8b9..59d3f6e5cce165 100755
 | ||||
| --- a/Lib/tarfile.py
 | ||||
| +++ b/Lib/tarfile.py
 | ||||
| @@ -1615,6 +1615,9 @@ def _block(self, count):
 | ||||
|          """Round up a byte count by BLOCKSIZE and return it, | ||||
|             e.g. _block(834) => 1024. | ||||
|          """ | ||||
| +        # Only non-negative offsets are allowed
 | ||||
| +        if count < 0:
 | ||||
| +            raise InvalidHeaderError("invalid offset")
 | ||||
|          blocks, remainder = divmod(count, BLOCKSIZE) | ||||
|          if remainder: | ||||
|              blocks += 1 | ||||
| diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
 | ||||
| index a184ba75a8851b..759fa03ead70b0 100644
 | ||||
| --- a/Lib/test/test_tarfile.py
 | ||||
| +++ b/Lib/test/test_tarfile.py
 | ||||
| @@ -50,6 +50,7 @@ def sha256sum(data):
 | ||||
|  xzname = os.path.join(TEMPDIR, "testtar.tar.xz") | ||||
|  tmpname = os.path.join(TEMPDIR, "tmp.tar") | ||||
|  dotlessname = os.path.join(TEMPDIR, "testtar") | ||||
| +SPACE = b" "
 | ||||
|   | ||||
|  sha256_regtype = ( | ||||
|      "e09e4bc8b3c9d9177e77256353b36c159f5f040531bbd4b024a8f9b9196c71ce" | ||||
| @@ -4488,6 +4489,161 @@ def extractall(self, ar):
 | ||||
|          ar.extractall(self.testdir, filter='fully_trusted') | ||||
|   | ||||
|   | ||||
| +class OffsetValidationTests(unittest.TestCase):
 | ||||
| +    tarname = tmpname
 | ||||
| +    invalid_posix_header = (
 | ||||
| +        # name: 100 bytes
 | ||||
| +        tarfile.NUL * tarfile.LENGTH_NAME
 | ||||
| +        # mode, space, null terminator: 8 bytes
 | ||||
| +        + b"000755" + SPACE + tarfile.NUL
 | ||||
| +        # uid, space, null terminator: 8 bytes
 | ||||
| +        + b"000001" + SPACE + tarfile.NUL
 | ||||
| +        # gid, space, null terminator: 8 bytes
 | ||||
| +        + b"000001" + SPACE + tarfile.NUL
 | ||||
| +        # size, space: 12 bytes
 | ||||
| +        + b"\xff" * 11 + SPACE
 | ||||
| +        # mtime, space: 12 bytes
 | ||||
| +        + tarfile.NUL * 11 + SPACE
 | ||||
| +        # chksum: 8 bytes
 | ||||
| +        + b"0011407" + tarfile.NUL
 | ||||
| +        # type: 1 byte
 | ||||
| +        + tarfile.REGTYPE
 | ||||
| +        # linkname: 100 bytes
 | ||||
| +        + tarfile.NUL * tarfile.LENGTH_LINK
 | ||||
| +        # magic: 6 bytes, version: 2 bytes
 | ||||
| +        + tarfile.POSIX_MAGIC
 | ||||
| +        # uname: 32 bytes
 | ||||
| +        + tarfile.NUL * 32
 | ||||
| +        # gname: 32 bytes
 | ||||
| +        + tarfile.NUL * 32
 | ||||
| +        # devmajor, space, null terminator: 8 bytes
 | ||||
| +        + tarfile.NUL * 6 + SPACE + tarfile.NUL
 | ||||
| +        # devminor, space, null terminator: 8 bytes
 | ||||
| +        + tarfile.NUL * 6 + SPACE + tarfile.NUL
 | ||||
| +        # prefix: 155 bytes
 | ||||
| +        + tarfile.NUL * tarfile.LENGTH_PREFIX
 | ||||
| +        # padding: 12 bytes
 | ||||
| +        + tarfile.NUL * 12
 | ||||
| +    )
 | ||||
| +    invalid_gnu_header = (
 | ||||
| +        # name: 100 bytes
 | ||||
| +        tarfile.NUL * tarfile.LENGTH_NAME
 | ||||
| +        # mode, null terminator: 8 bytes
 | ||||
| +        + b"0000755" + tarfile.NUL
 | ||||
| +        # uid, null terminator: 8 bytes
 | ||||
| +        + b"0000001" + tarfile.NUL
 | ||||
| +        # gid, space, null terminator: 8 bytes
 | ||||
| +        + b"0000001" + tarfile.NUL
 | ||||
| +        # size, space: 12 bytes
 | ||||
| +        + b"\xff" * 11 + SPACE
 | ||||
| +        # mtime, space: 12 bytes
 | ||||
| +        + tarfile.NUL * 11 + SPACE
 | ||||
| +        # chksum: 8 bytes
 | ||||
| +        + b"0011327" + tarfile.NUL
 | ||||
| +        # type: 1 byte
 | ||||
| +        + tarfile.REGTYPE
 | ||||
| +        # linkname: 100 bytes
 | ||||
| +        + tarfile.NUL * tarfile.LENGTH_LINK
 | ||||
| +        # magic: 8 bytes
 | ||||
| +        + tarfile.GNU_MAGIC
 | ||||
| +        # uname: 32 bytes
 | ||||
| +        + tarfile.NUL * 32
 | ||||
| +        # gname: 32 bytes
 | ||||
| +        + tarfile.NUL * 32
 | ||||
| +        # devmajor, null terminator: 8 bytes
 | ||||
| +        + tarfile.NUL * 8
 | ||||
| +        # devminor, null terminator: 8 bytes
 | ||||
| +        + tarfile.NUL * 8
 | ||||
| +        # padding: 167 bytes
 | ||||
| +        + tarfile.NUL * 167
 | ||||
| +    )
 | ||||
| +    invalid_v7_header = (
 | ||||
| +        # name: 100 bytes
 | ||||
| +        tarfile.NUL * tarfile.LENGTH_NAME
 | ||||
| +        # mode, space, null terminator: 8 bytes
 | ||||
| +        + b"000755" + SPACE + tarfile.NUL
 | ||||
| +        # uid, space, null terminator: 8 bytes
 | ||||
| +        + b"000001" + SPACE + tarfile.NUL
 | ||||
| +        # gid, space, null terminator: 8 bytes
 | ||||
| +        + b"000001" + SPACE + tarfile.NUL
 | ||||
| +        # size, space: 12 bytes
 | ||||
| +        + b"\xff" * 11 + SPACE
 | ||||
| +        # mtime, space: 12 bytes
 | ||||
| +        + tarfile.NUL * 11 + SPACE
 | ||||
| +        # chksum: 8 bytes
 | ||||
| +        + b"0010070" + tarfile.NUL
 | ||||
| +        # type: 1 byte
 | ||||
| +        + tarfile.REGTYPE
 | ||||
| +        # linkname: 100 bytes
 | ||||
| +        + tarfile.NUL * tarfile.LENGTH_LINK
 | ||||
| +        # padding: 255 bytes
 | ||||
| +        + tarfile.NUL * 255
 | ||||
| +    )
 | ||||
| +    valid_gnu_header = tarfile.TarInfo("filename").tobuf(tarfile.GNU_FORMAT)
 | ||||
| +    data_block = b"\xff" * tarfile.BLOCKSIZE
 | ||||
| +
 | ||||
| +    def _write_buffer(self, buffer):
 | ||||
| +        with open(self.tarname, "wb") as f:
 | ||||
| +            f.write(buffer)
 | ||||
| +
 | ||||
| +    def _get_members(self, ignore_zeros=None):
 | ||||
| +        with open(self.tarname, "rb") as f:
 | ||||
| +            with tarfile.open(
 | ||||
| +                mode="r", fileobj=f, ignore_zeros=ignore_zeros
 | ||||
| +            ) as tar:
 | ||||
| +                return tar.getmembers()
 | ||||
| +
 | ||||
| +    def _assert_raises_read_error_exception(self):
 | ||||
| +        with self.assertRaisesRegex(
 | ||||
| +            tarfile.ReadError, "file could not be opened successfully"
 | ||||
| +        ):
 | ||||
| +            self._get_members()
 | ||||
| +
 | ||||
| +    def test_invalid_offset_header_validations(self):
 | ||||
| +        for tar_format, invalid_header in (
 | ||||
| +            ("posix", self.invalid_posix_header),
 | ||||
| +            ("gnu", self.invalid_gnu_header),
 | ||||
| +            ("v7", self.invalid_v7_header),
 | ||||
| +        ):
 | ||||
| +            with self.subTest(format=tar_format):
 | ||||
| +                self._write_buffer(invalid_header)
 | ||||
| +                self._assert_raises_read_error_exception()
 | ||||
| +
 | ||||
| +    def test_early_stop_at_invalid_offset_header(self):
 | ||||
| +        buffer = self.valid_gnu_header + self.invalid_gnu_header + self.valid_gnu_header
 | ||||
| +        self._write_buffer(buffer)
 | ||||
| +        members = self._get_members()
 | ||||
| +        self.assertEqual(len(members), 1)
 | ||||
| +        self.assertEqual(members[0].name, "filename")
 | ||||
| +        self.assertEqual(members[0].offset, 0)
 | ||||
| +
 | ||||
| +    def test_ignore_invalid_archive(self):
 | ||||
| +        # 3 invalid headers with their respective data
 | ||||
| +        buffer = (self.invalid_gnu_header + self.data_block) * 3
 | ||||
| +        self._write_buffer(buffer)
 | ||||
| +        members = self._get_members(ignore_zeros=True)
 | ||||
| +        self.assertEqual(len(members), 0)
 | ||||
| +
 | ||||
| +    def test_ignore_invalid_offset_headers(self):
 | ||||
| +        for first_block, second_block, expected_offset in (
 | ||||
| +            (
 | ||||
| +                (self.valid_gnu_header),
 | ||||
| +                (self.invalid_gnu_header + self.data_block),
 | ||||
| +                0,
 | ||||
| +            ),
 | ||||
| +            (
 | ||||
| +                (self.invalid_gnu_header + self.data_block),
 | ||||
| +                (self.valid_gnu_header),
 | ||||
| +                1024,
 | ||||
| +            ),
 | ||||
| +        ):
 | ||||
| +            self._write_buffer(first_block + second_block)
 | ||||
| +            members = self._get_members(ignore_zeros=True)
 | ||||
| +            self.assertEqual(len(members), 1)
 | ||||
| +            self.assertEqual(members[0].name, "filename")
 | ||||
| +            self.assertEqual(members[0].offset, expected_offset)
 | ||||
| +
 | ||||
| +
 | ||||
|  def setUpModule(): | ||||
|      os_helper.unlink(TEMPDIR) | ||||
|      os.makedirs(TEMPDIR) | ||||
| diff --git a/Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst b/Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst
 | ||||
| new file mode 100644 | ||||
| index 00000000000000..342cabbc865dc4
 | ||||
| --- /dev/null
 | ||||
| +++ b/Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst
 | ||||
| @@ -0,0 +1,3 @@
 | ||||
| +:mod:`tarfile` now validates archives to ensure member offsets are
 | ||||
| +non-negative.  (Contributed by Alexander Enrique Urieles Nieto in
 | ||||
| +:gh:`130577`.)
 | ||||
							
								
								
									
										18
									
								
								SOURCES/Python-3.12.1.tar.xz.asc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								SOURCES/Python-3.12.1.tar.xz.asc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| -----BEGIN PGP SIGNATURE----- | ||||
| 
 | ||||
| iQKTBAABCgB9FiEEcWlgX2LHUTVtBUomqCHmgOX6YwUFAmVyMspfFIAAAAAALgAo | ||||
| aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDcx | ||||
| Njk2MDVGNjJDNzUxMzU2RDA1NEEyNkE4MjFFNjgwRTVGQTYzMDUACgkQqCHmgOX6 | ||||
| YwWv5w/+JlGtfy+x+6mtauH1uOkt7n9PMQou1LcthDs5s41wuwjO7RbwnmJD6aDk | ||||
| DqwLHheoq6Kjbl6PF1kG2T8ZbHkMudhnc5yH4eQG52IGNQ6evilxoC6AyhVg8ANi | ||||
| +u6Juh9r2Hjz/LDWFB4hzwcOBKy0jYw98+A0uMvpPd2bmdFMBLQE0GTZCdrRsGYs | ||||
| q0oysUX7uCJBfINp7XwiVGAK/6ma0nrr0A1ho6LCau+VGkDnJZdKZgIMyyxp6qL1 | ||||
| 7tMjb3LUpV3FWp57L2za59TaayApNf5BlanC+de6oKEhEJ8oEFyWxOx2GmXHZwch | ||||
| ucj7Z1dxuI7fjNVkEvZ+JuheLGtB9mAmUZslXgUJf5wo49bCo9E4/ZlIFQk7VJR3 | ||||
| Bm9VlQb5mMydB8QJbMy/BpgNjgKmEvBTnir37prJpUV/TL1YZT0eZ5JxCnlUIL/F | ||||
| 6cOzAE3zHPnvHcyHhKV3q5CoONdBtB3RWgS66m4eMneuWoNKaoEbO5IDxtKvCd1J | ||||
| AKLmzCB0/KCWVUIYBTfJ8ytBVQA0Z2w8CZ7SC8asX4DocDCvxim1sQg5s8c4mzh+ | ||||
| 1JVbyqqEmf9m74Mqby0vICC6UVvgaPyiOxTphtRXLIYHUscLVn5+586RMYnM9nP4 | ||||
| nEK+H/fq6Rcp1XEtIPzCG4IPUAYnuDLjbGQegltpKV/SAYn+DGg= | ||||
| =dCpy | ||||
| -----END PGP SIGNATURE----- | ||||
| @ -1,18 +0,0 @@ | ||||
| -----BEGIN PGP SIGNATURE----- | ||||
| 
 | ||||
| iQKTBAABCgB9FiEEcWlgX2LHUTVtBUomqCHmgOX6YwUFAmeiX7JfFIAAAAAALgAo | ||||
| aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDcx | ||||
| Njk2MDVGNjJDNzUxMzU2RDA1NEEyNkE4MjFFNjgwRTVGQTYzMDUACgkQqCHmgOX6 | ||||
| YwXTqw//VlGJA5CRDfljMwN9BmG2hdXB1B7Lj0PssuAo4A/lH99gb4DRVDS9LNjr | ||||
| 99WdH/fQQovx6rTbtyJnN8Vh7SSduBi/vOc5n5VOXZB0buqR0l+0wu4m43Slu6xP | ||||
| fXO349Hr6585lemU8x54TrP756rSVUhy3T+krUuNDL9W1Wrp2yDCpt4tUoEhNXGw | ||||
| DoYS8MrK/ygLNV/7p2DeMWOHNdbjKNH6rfzl60IAwAp7oANcyoj6Pho960bbeUDo | ||||
| tb47Pw0WWZv3EuITP6bPa8+Z6dj096cFL3AQJ3ap16OduwiaOsGhqTfe4+kbp6ut | ||||
| Gp/1HeIHzPbEV0E5K78RWHuzBYgU1oPGiMjlp7WkA7bP2OSTF7nM4EBkiiihk2qx | ||||
| 3d5VF9wpVRJ4AuR/aWcWcMnvD2ziSWfzZM3Z3VLnTaWYpuRkQp8TTiFr1vHqxMYm | ||||
| p/8AozzBJMfOS6u/Q0WNAdk6x3VB0DXnTAETXQVIrex4DXqX/3WSMWK5/x/OyCh9 | ||||
| ytdreIQYbv1KvlNQJkgpPb7jlUSXp8t9fHCXt4hszhJgtjwIj/+CuSeAgX0bhopV | ||||
| XsqOBseDNhATg38mhwBVaeFKGRpxsKdpxcdqSEGKuhXtEI/hJmkpZGw49gy3xWxB | ||||
| KlgRgKjCPw+BGAIVV9qvdtJzam8a09SKVcslqgF619q0byQoBmo= | ||||
| =1TbP | ||||
| -----END PGP SIGNATURE----- | ||||
| @ -16,6 +16,7 @@ LEVELS = (None, 1, 2) | ||||
| # list of globs of test and other files that we expect not to have bytecode | ||||
| not_compiled = [ | ||||
|     '/usr/bin/*', | ||||
|     '/usr/lib/rpm/redhat/*', | ||||
|     '*/test/badsyntax_*.py', | ||||
|     '*/tokenizedata/bad_coding.py', | ||||
|     '*/tokenizedata/bad_coding2.py', | ||||
|  | ||||
							
								
								
									
										171
									
								
								SOURCES/import_all_modules_py3_12.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								SOURCES/import_all_modules_py3_12.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,171 @@ | ||||
| '''Script to perform import of each module given to %%py_check_import | ||||
| ''' | ||||
| import argparse | ||||
| import importlib | ||||
| import fnmatch | ||||
| import os | ||||
| import re | ||||
| import site | ||||
| import sys | ||||
| 
 | ||||
| from contextlib import contextmanager | ||||
| from pathlib import Path | ||||
| 
 | ||||
| 
 | ||||
| def read_modules_files(file_paths): | ||||
|     '''Read module names from the files (modules must be newline separated). | ||||
| 
 | ||||
|     Return the module names list or, if no files were provided, an empty list. | ||||
|     ''' | ||||
| 
 | ||||
|     if not file_paths: | ||||
|         return [] | ||||
| 
 | ||||
|     modules = [] | ||||
|     for file in file_paths: | ||||
|         file_contents = file.read_text() | ||||
|         modules.extend(file_contents.split()) | ||||
|     return modules | ||||
| 
 | ||||
| 
 | ||||
| def read_modules_from_cli(argv): | ||||
|     '''Read module names from command-line arguments (space or comma separated). | ||||
| 
 | ||||
|     Return the module names list. | ||||
|     ''' | ||||
| 
 | ||||
|     if not argv: | ||||
|         return [] | ||||
| 
 | ||||
|     # %%py3_check_import allows to separate module list with comma or whitespace, | ||||
|     # we need to unify the output to a list of particular elements | ||||
|     modules_as_str = ' '.join(argv) | ||||
|     modules = re.split(r'[\s,]+', modules_as_str) | ||||
|     # Because of shell expansion in some less typical cases it may happen | ||||
|     # that a trailing space will occur at the end of the list. | ||||
|     # Remove the empty items from the list before passing it further | ||||
|     modules = [m for m in modules if m] | ||||
|     return modules | ||||
| 
 | ||||
| 
 | ||||
| def filter_top_level_modules_only(modules): | ||||
|     '''Filter out entries with nested modules (containing dot) ie. 'foo.bar'. | ||||
| 
 | ||||
|     Return the list of top-level modules. | ||||
|     ''' | ||||
| 
 | ||||
|     return [module for module in modules if '.' not in module] | ||||
| 
 | ||||
| 
 | ||||
| def any_match(text, globs): | ||||
|     '''Return True if any of given globs fnmatchcase's the given text.''' | ||||
| 
 | ||||
|     return any(fnmatch.fnmatchcase(text, g) for g in globs) | ||||
| 
 | ||||
| 
 | ||||
| def exclude_unwanted_module_globs(globs, modules): | ||||
|     '''Filter out entries which match the either of the globs given as argv. | ||||
| 
 | ||||
|     Return the list of filtered modules. | ||||
|     ''' | ||||
| 
 | ||||
|     return [m for m in modules if not any_match(m, globs)] | ||||
| 
 | ||||
| 
 | ||||
| def read_modules_from_all_args(args): | ||||
|     '''Return a joined list of modules from all given command-line arguments. | ||||
|     ''' | ||||
| 
 | ||||
|     modules = read_modules_files(args.filename) | ||||
|     modules.extend(read_modules_from_cli(args.modules)) | ||||
|     if args.exclude: | ||||
|         modules = exclude_unwanted_module_globs(args.exclude, modules) | ||||
| 
 | ||||
|     if args.top_level: | ||||
|         modules = filter_top_level_modules_only(modules) | ||||
| 
 | ||||
|     # Error when someone accidentally managed to filter out everything | ||||
|     if len(modules) == 0: | ||||
|         raise ValueError('No modules to check were left') | ||||
| 
 | ||||
|     return modules | ||||
| 
 | ||||
| 
 | ||||
| def import_modules(modules): | ||||
|     '''Procedure to perform import check for each module name from the given list of modules. | ||||
|     ''' | ||||
| 
 | ||||
|     for module in modules: | ||||
|         print('Check import:', module, file=sys.stderr) | ||||
|         importlib.import_module(module) | ||||
| 
 | ||||
| 
 | ||||
| def argparser(): | ||||
|     parser = argparse.ArgumentParser( | ||||
|         description='Generate list of all importable modules for import check.' | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         'modules', nargs='*', | ||||
|         help=('Add modules to check the import (space or comma separated).'), | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '-f', '--filename', action='append', type=Path, | ||||
|         help='Add importable module names list from file.', | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '-t', '--top-level', action='store_true', | ||||
|         help='Check only top-level modules.', | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '-e', '--exclude', action='append', | ||||
|         help='Provide modules globs to be excluded from the check.', | ||||
|     ) | ||||
|     return parser | ||||
| 
 | ||||
| 
 | ||||
| @contextmanager | ||||
| def remove_unwanteds_from_sys_path(): | ||||
|     '''Remove cwd and this script's parent from sys.path for the import test. | ||||
|     Bring the original contents back after import is done (or failed) | ||||
|     ''' | ||||
| 
 | ||||
|     cwd_absolute = Path.cwd().absolute() | ||||
|     this_file_parent = Path(__file__).parent.absolute() | ||||
|     old_sys_path = list(sys.path) | ||||
|     for path in old_sys_path: | ||||
|         if Path(path).absolute() in (cwd_absolute, this_file_parent): | ||||
|             sys.path.remove(path) | ||||
|     try: | ||||
|         yield | ||||
|     finally: | ||||
|         sys.path = old_sys_path | ||||
| 
 | ||||
| 
 | ||||
| def addsitedirs_from_environ(): | ||||
|     '''Load directories from the _PYTHONSITE environment variable (separated by :) | ||||
|     and load the ones already present in sys.path via site.addsitedir() | ||||
|     to handle .pth files in them. | ||||
| 
 | ||||
|     This is needed to properly import old-style namespace packages with nspkg.pth files. | ||||
|     See https://bugzilla.redhat.com/2018551 for a more detailed rationale.''' | ||||
|     for path in os.getenv('_PYTHONSITE', '').split(':'): | ||||
|         if path in sys.path: | ||||
|             site.addsitedir(path) | ||||
| 
 | ||||
| 
 | ||||
| def main(argv=None): | ||||
| 
 | ||||
|     cli_args = argparser().parse_args(argv) | ||||
| 
 | ||||
|     if not cli_args.modules and not cli_args.filename: | ||||
|         raise ValueError('No modules to check were provided') | ||||
| 
 | ||||
|     modules = read_modules_from_all_args(cli_args) | ||||
| 
 | ||||
|     with remove_unwanteds_from_sys_path(): | ||||
|         addsitedirs_from_environ() | ||||
|         import_modules(modules) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										91
									
								
								SOURCES/macros.python3.12
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								SOURCES/macros.python3.12
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | ||||
| %__python3 /usr/bin/python3.12 | ||||
| %python3_pkgversion 3.12 | ||||
| 
 | ||||
| # The following are macros from macros.python3 in Fedora that are newer/different than those in the python3-rpm-macros package in RHEL 8. | ||||
| # These macros overwrite/supercede some of the macros in the python3-rpm-macros package in RHEL. | ||||
| 
 | ||||
| # nb: $RPM_BUILD_ROOT is not set when the macros are expanded (at spec parse time) | ||||
| #     so we set it manually (to empty string), making our Python prefer the correct install scheme location | ||||
| # platbase/base is explicitly set to %%{_prefix} to support custom values, such as /app for flatpaks | ||||
| %python3_sitelib %(RPM_BUILD_ROOT= %{__python3} -Ic "import sysconfig; print(sysconfig.get_path('purelib', vars={'platbase': '%{_prefix}', 'base': '%{_prefix}'}))") | ||||
| %python3_sitearch %(RPM_BUILD_ROOT= %{__python3} -Ic "import sysconfig; print(sysconfig.get_path('platlib', vars={'platbase': '%{_prefix}', 'base': '%{_prefix}'}))") | ||||
| %python3_version %(RPM_BUILD_ROOT= %{__python3} -Ic "import sys; sys.stdout.write('{0.major}.{0.minor}'.format(sys.version_info))") | ||||
| %python3_version_nodots %(RPM_BUILD_ROOT= %{__python3} -Ic "import sys; sys.stdout.write('{0.major}{0.minor}'.format(sys.version_info))") | ||||
| %python3_platform %(RPM_BUILD_ROOT= %{__python3} -Ic "import sysconfig; print(sysconfig.get_platform())") | ||||
| %python3_platform_triplet %(RPM_BUILD_ROOT= %{__python3} -Ic "import sysconfig; print(sysconfig.get_config_var('MULTIARCH'))") | ||||
| %python3_ext_suffix %(RPM_BUILD_ROOT= %{__python3} -Ic "import sysconfig; print(sysconfig.get_config_var('EXT_SUFFIX'))") | ||||
| %python3_cache_tag %(RPM_BUILD_ROOT= %{__python3} -Ic "import sys; print(sys.implementation.cache_tag)") | ||||
| 
 | ||||
| %_py3_shebang_s s | ||||
| %_py3_shebang_P %(RPM_BUILD_ROOT= %{__python3} -Ic "import sys; print('P' if hasattr(sys.flags, 'safe_path') else '')") | ||||
| %py3_shbang_opts -%{?_py3_shebang_s}%{?_py3_shebang_P} | ||||
| 
 | ||||
| %py3_shebang_fix %{expand:\\\ | ||||
|   if [ -z "%{?py3_shebang_flags}" ]; then | ||||
|     shebang_flags="-k" | ||||
|   else | ||||
|     shebang_flags="-ka%{py3_shebang_flags}" | ||||
|   fi | ||||
|   %{__python3} -B %{_rpmconfigdir}/redhat/pathfix_py3_12.py -pni %{__python3} $shebang_flags} | ||||
| 
 | ||||
| %py3_install() %{expand:\\\ | ||||
|   CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}"\\\ | ||||
|   %{__python3} %{py_setup} %{?py_setup_args} install -O1 --skip-build --root %{buildroot} --prefix %{_prefix} %{?*} | ||||
|   rm -rfv %{buildroot}%{_bindir}/__pycache__ | ||||
| } | ||||
| 
 | ||||
| %py3_install_egg() %{expand:\\\ | ||||
|   mkdir -p %{buildroot}%{python3_sitelib} | ||||
|   %{__python3} -m easy_install -m --prefix %{buildroot}%{_prefix} -Z dist/*-py%{python3_version}.egg %{?*} | ||||
|   rm -rfv %{buildroot}%{_bindir}/__pycache__ | ||||
| } | ||||
| 
 | ||||
| %py3_install_wheel() %{expand:\\\ | ||||
|   %{__python3} -m pip install -I dist/%{1} --root %{buildroot} --prefix %{_prefix} --no-deps --no-index --no-warn-script-location | ||||
|   rm -rfv %{buildroot}%{_bindir}/__pycache__ | ||||
|   for distinfo in %{buildroot}%{python3_sitelib}/*.dist-info %{buildroot}%{python3_sitearch}/*.dist-info; do | ||||
|     if [ -f ${distinfo}/direct_url.json ]; then | ||||
|       rm -fv ${distinfo}/direct_url.json | ||||
|       sed -i '/direct_url.json/d' ${distinfo}/RECORD | ||||
|     fi | ||||
|   done | ||||
| } | ||||
| 
 | ||||
| # With $PATH and $PYTHONPATH set to the %%buildroot, | ||||
| # try to import the Python 3 module(s) given as command-line args or read from file (-f). | ||||
| # Respect the custom values of %%py3_shebang_flags or set nothing if it's undefined. | ||||
| # Filter and check import on only top-level modules using -t flag. | ||||
| # Exclude unwanted modules by passing their globs to -e option. | ||||
| # Useful as a smoke test in %%check when running tests is not feasible. | ||||
| # Use spaces or commas as separators if providing list directly. | ||||
| # Use newlines as separators if providing list in a file. | ||||
| %py3_check_import(e:tf:) %{expand:\\\ | ||||
|   PATH="%{buildroot}%{_bindir}:$PATH"\\\ | ||||
|   PYTHONPATH="${PYTHONPATH:-%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}}"\\\ | ||||
|   _PYTHONSITE="%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}"\\\ | ||||
|   PYTHONDONTWRITEBYTECODE=1\\\ | ||||
|   %{lua: | ||||
|   local command = "%{__python3} " | ||||
|   if rpm.expand("%{?py3_shebang_flags}") ~= "" then | ||||
|     command = command .. "-%{py3_shebang_flags}" | ||||
|   end | ||||
|   command = command .. " %{_rpmconfigdir}/redhat/import_all_modules_py3_12.py " | ||||
|   -- handle multiline arguments correctly, see https://bugzilla.redhat.com/2018809 | ||||
|   local args=rpm.expand('%{?**}'):gsub("[%s\\\\]*%s+", " ") | ||||
|   print(command .. args) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| # Environment variables used by %%pytest, %%tox or standalone, e.g.: | ||||
| #  %%{py3_test_envvars} %%{python3} -m unittest | ||||
| %py3_test_envvars %{expand:\\\ | ||||
|   CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}"\\\ | ||||
|   PATH="%{buildroot}%{_bindir}:$PATH"\\\ | ||||
|   PYTHONPATH="${PYTHONPATH:-%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}}"\\\ | ||||
|   PYTHONDONTWRITEBYTECODE=1\\\ | ||||
|   %{?__pytest_addopts:PYTEST_ADDOPTS="${PYTEST_ADDOPTS:-} %{__pytest_addopts}"}\\\ | ||||
|   PYTEST_XDIST_AUTO_NUM_WORKERS=%{_smp_build_ncpus}} | ||||
| 
 | ||||
| # This is intended for Python 3 only, hence also no Python version in the name. | ||||
| %__pytest /usr/bin/pytest-%{python3_version} | ||||
| %pytest %py3_test_envvars %__pytest | ||||
							
								
								
									
										199
									
								
								SOURCES/pathfix_py3_12.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								SOURCES/pathfix_py3_12.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,199 @@ | ||||
| #!/usr/bin/env python3 | ||||
| 
 | ||||
| import sys | ||||
| import os | ||||
| from stat import * | ||||
| import getopt | ||||
| 
 | ||||
| err = sys.stderr.write | ||||
| dbg = err | ||||
| rep = sys.stdout.write | ||||
| 
 | ||||
| new_interpreter = None | ||||
| preserve_timestamps = False | ||||
| create_backup = True | ||||
| keep_flags = False | ||||
| add_flags = b'' | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     global new_interpreter | ||||
|     global preserve_timestamps | ||||
|     global create_backup | ||||
|     global keep_flags | ||||
|     global add_flags | ||||
| 
 | ||||
|     usage = ('usage: %s -i /interpreter -p -n -k -a file-or-directory ...\n' % | ||||
|              sys.argv[0]) | ||||
|     try: | ||||
|         opts, args = getopt.getopt(sys.argv[1:], 'i:a:kpn') | ||||
|     except getopt.error as msg: | ||||
|         err(str(msg) + '\n') | ||||
|         err(usage) | ||||
|         sys.exit(2) | ||||
|     for o, a in opts: | ||||
|         if o == '-i': | ||||
|             new_interpreter = a.encode() | ||||
|         if o == '-p': | ||||
|             preserve_timestamps = True | ||||
|         if o == '-n': | ||||
|             create_backup = False | ||||
|         if o == '-k': | ||||
|             keep_flags = True | ||||
|         if o == '-a': | ||||
|             add_flags = a.encode() | ||||
|             if b' ' in add_flags: | ||||
|                 err("-a option doesn't support whitespaces") | ||||
|                 sys.exit(2) | ||||
|     if not new_interpreter or not new_interpreter.startswith(b'/') or \ | ||||
|            not args: | ||||
|         err('-i option or file-or-directory missing\n') | ||||
|         err(usage) | ||||
|         sys.exit(2) | ||||
|     bad = 0 | ||||
|     for arg in args: | ||||
|         if os.path.isdir(arg): | ||||
|             if recursedown(arg): bad = 1 | ||||
|         elif os.path.islink(arg): | ||||
|             err(arg + ': will not process symbolic links\n') | ||||
|             bad = 1 | ||||
|         else: | ||||
|             if fix(arg): bad = 1 | ||||
|     sys.exit(bad) | ||||
| 
 | ||||
| 
 | ||||
| def ispython(name): | ||||
|     return name.endswith('.py') | ||||
| 
 | ||||
| 
 | ||||
| def recursedown(dirname): | ||||
|     dbg('recursedown(%r)\n' % (dirname,)) | ||||
|     bad = 0 | ||||
|     try: | ||||
|         names = os.listdir(dirname) | ||||
|     except OSError as msg: | ||||
|         err('%s: cannot list directory: %r\n' % (dirname, msg)) | ||||
|         return 1 | ||||
|     names.sort() | ||||
|     subdirs = [] | ||||
|     for name in names: | ||||
|         if name in (os.curdir, os.pardir): continue | ||||
|         fullname = os.path.join(dirname, name) | ||||
|         if os.path.islink(fullname): pass | ||||
|         elif os.path.isdir(fullname): | ||||
|             subdirs.append(fullname) | ||||
|         elif ispython(name): | ||||
|             if fix(fullname): bad = 1 | ||||
|     for fullname in subdirs: | ||||
|         if recursedown(fullname): bad = 1 | ||||
|     return bad | ||||
| 
 | ||||
| 
 | ||||
| def fix(filename): | ||||
| ##  dbg('fix(%r)\n' % (filename,)) | ||||
|     try: | ||||
|         f = open(filename, 'rb') | ||||
|     except IOError as msg: | ||||
|         err('%s: cannot open: %r\n' % (filename, msg)) | ||||
|         return 1 | ||||
|     with f: | ||||
|         line = f.readline() | ||||
|         fixed = fixline(line) | ||||
|         if line == fixed: | ||||
|             rep(filename+': no change\n') | ||||
|             return | ||||
|         head, tail = os.path.split(filename) | ||||
|         tempname = os.path.join(head, '@' + tail) | ||||
|         try: | ||||
|             g = open(tempname, 'wb') | ||||
|         except IOError as msg: | ||||
|             err('%s: cannot create: %r\n' % (tempname, msg)) | ||||
|             return 1 | ||||
|         with g: | ||||
|             rep(filename + ': updating\n') | ||||
|             g.write(fixed) | ||||
|             BUFSIZE = 8*1024 | ||||
|             while 1: | ||||
|                 buf = f.read(BUFSIZE) | ||||
|                 if not buf: break | ||||
|                 g.write(buf) | ||||
| 
 | ||||
|     # Finishing touch -- move files | ||||
| 
 | ||||
|     mtime = None | ||||
|     atime = None | ||||
|     # First copy the file's mode to the temp file | ||||
|     try: | ||||
|         statbuf = os.stat(filename) | ||||
|         mtime = statbuf.st_mtime | ||||
|         atime = statbuf.st_atime | ||||
|         os.chmod(tempname, statbuf[ST_MODE] & 0o7777) | ||||
|     except OSError as msg: | ||||
|         err('%s: warning: chmod failed (%r)\n' % (tempname, msg)) | ||||
|     # Then make a backup of the original file as filename~ | ||||
|     if create_backup: | ||||
|         try: | ||||
|             os.rename(filename, filename + '~') | ||||
|         except OSError as msg: | ||||
|             err('%s: warning: backup failed (%r)\n' % (filename, msg)) | ||||
|     else: | ||||
|         try: | ||||
|             os.remove(filename) | ||||
|         except OSError as msg: | ||||
|             err('%s: warning: removing failed (%r)\n' % (filename, msg)) | ||||
|     # Now move the temp file to the original file | ||||
|     try: | ||||
|         os.rename(tempname, filename) | ||||
|     except OSError as msg: | ||||
|         err('%s: rename failed (%r)\n' % (filename, msg)) | ||||
|         return 1 | ||||
|     if preserve_timestamps: | ||||
|         if atime and mtime: | ||||
|             try: | ||||
|                 os.utime(filename, (atime, mtime)) | ||||
|             except OSError as msg: | ||||
|                 err('%s: reset of timestamp failed (%r)\n' % (filename, msg)) | ||||
|                 return 1 | ||||
|     # Return success | ||||
|     return 0 | ||||
| 
 | ||||
| 
 | ||||
| def parse_shebang(shebangline): | ||||
|     shebangline = shebangline.rstrip(b'\n') | ||||
|     start = shebangline.find(b' -') | ||||
|     if start == -1: | ||||
|         return b'' | ||||
|     return shebangline[start:] | ||||
| 
 | ||||
| 
 | ||||
| def populate_flags(shebangline): | ||||
|     old_flags = b'' | ||||
|     if keep_flags: | ||||
|         old_flags = parse_shebang(shebangline) | ||||
|         if old_flags: | ||||
|             old_flags = old_flags[2:] | ||||
|     if not (old_flags or add_flags): | ||||
|         return b'' | ||||
|     # On Linux, the entire string following the interpreter name | ||||
|     # is passed as a single argument to the interpreter. | ||||
|     # e.g. "#! /usr/bin/python3 -W Error -s" runs "/usr/bin/python3 "-W Error -s" | ||||
|     # so shebang should have single '-' where flags are given and | ||||
|     # flag might need argument for that reasons adding new flags is | ||||
|     # between '-' and original flags | ||||
|     # e.g. #! /usr/bin/python3 -sW Error | ||||
|     return b' -' + add_flags + old_flags | ||||
| 
 | ||||
| 
 | ||||
| def fixline(line): | ||||
|     if not line.startswith(b'#!'): | ||||
|         return line | ||||
| 
 | ||||
|     if b"python" not in line: | ||||
|         return line | ||||
| 
 | ||||
|     flags = populate_flags(line) | ||||
|     return b'#! ' + new_interpreter + flags + b'\n' | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
| @ -16,12 +16,12 @@ 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}.9 | ||||
| %global general_version %{pybasever}.1 | ||||
| #global prerel ... | ||||
| %global upstream_version %{general_version}%{?prerel} | ||||
| Version: %{general_version}%{?prerel:~%{prerel}} | ||||
| Release: 1%{?dist}.2 | ||||
| License: Python-2.0.1 | ||||
| Release: 4%{?dist} | ||||
| License: Python | ||||
| 
 | ||||
| 
 | ||||
| # ================================== | ||||
| @ -62,35 +62,39 @@ License: Python-2.0.1 | ||||
| # Whether to use RPM build wheels from the python-{pip,setuptools,wheel}-wheel packages | ||||
| # Uses upstream bundled prebuilt wheels otherwise | ||||
| %bcond_without rpmwheels | ||||
| 
 | ||||
| # If the rpmwheels condition is disabled, we use the bundled wheel packages | ||||
| # from Python with the versions below. | ||||
| # This needs to be manually updated when we update Python. | ||||
| %global pip_version 24.3.1 | ||||
| %global pip_version 23.2.1 | ||||
| %global setuptools_version 67.6.1 | ||||
| %global wheel_version 0.40.0 | ||||
| # All of those also include a list of indirect bundled libs: | ||||
| # pip | ||||
| #  $ %%{_rpmconfigdir}/pythonbundles.py <(unzip -p Lib/ensurepip/_bundled/pip-*.whl pip/_vendor/vendor.txt) | ||||
| %global pip_bundled_provides %{expand: | ||||
| Provides: bundled(python3dist(cachecontrol)) = 0.14 | ||||
| Provides: bundled(python3dist(certifi)) = 2024.8.30 | ||||
| Provides: bundled(python3dist(distlib)) = 0.3.9 | ||||
| Provides: bundled(python3dist(distro)) = 1.9 | ||||
| Provides: bundled(python3dist(idna)) = 3.7 | ||||
| Provides: bundled(python3dist(msgpack)) = 1.0.8 | ||||
| Provides: bundled(python3dist(packaging)) = 24.1 | ||||
| Provides: bundled(python3dist(platformdirs)) = 4.2.2 | ||||
| Provides: bundled(python3dist(pygments)) = 2.18 | ||||
| Provides: bundled(python3dist(cachecontrol)) = 0.12.11 | ||||
| Provides: bundled(python3dist(certifi)) = 2023.5.7 | ||||
| Provides: bundled(python3dist(chardet)) = 5.1 | ||||
| Provides: bundled(python3dist(colorama)) = 0.4.6 | ||||
| Provides: bundled(python3dist(distlib)) = 0.3.6 | ||||
| Provides: bundled(python3dist(distro)) = 1.8 | ||||
| Provides: bundled(python3dist(idna)) = 3.4 | ||||
| Provides: bundled(python3dist(msgpack)) = 1.0.5 | ||||
| Provides: bundled(python3dist(packaging)) = 21.3 | ||||
| Provides: bundled(python3dist(platformdirs)) = 3.8.1 | ||||
| Provides: bundled(python3dist(pygments)) = 2.15.1 | ||||
| Provides: bundled(python3dist(pyparsing)) = 3.1 | ||||
| Provides: bundled(python3dist(pyproject-hooks)) = 1 | ||||
| Provides: bundled(python3dist(requests)) = 2.32.3 | ||||
| Provides: bundled(python3dist(requests)) = 2.31 | ||||
| Provides: bundled(python3dist(resolvelib)) = 1.0.1 | ||||
| Provides: bundled(python3dist(rich)) = 13.7.1 | ||||
| Provides: bundled(python3dist(setuptools)) = 70.3 | ||||
| Provides: bundled(python3dist(rich)) = 13.4.2 | ||||
| Provides: bundled(python3dist(setuptools)) = 68 | ||||
| Provides: bundled(python3dist(six)) = 1.16 | ||||
| Provides: bundled(python3dist(tenacity)) = 8.2.2 | ||||
| Provides: bundled(python3dist(tomli)) = 2.0.1 | ||||
| Provides: bundled(python3dist(truststore)) = 0.10 | ||||
| Provides: bundled(python3dist(typing-extensions)) = 4.12.2 | ||||
| Provides: bundled(python3dist(urllib3)) = 1.26.20 | ||||
| Provides: bundled(python3dist(typing-extensions)) = 4.7.1 | ||||
| Provides: bundled(python3dist(urllib3)) = 1.26.16 | ||||
| Provides: bundled(python3dist(webencodings)) = 0.5.1 | ||||
| } | ||||
| # setuptools | ||||
| # vendor.txt files not in .whl | ||||
| @ -111,7 +115,7 @@ Provides: bundled(python3dist(typing-extensions)) = 4.4 | ||||
| Provides: bundled(python3dist(zipp)) = 3.7 | ||||
| } | ||||
| # wheel | ||||
| #  $ %%{_rpmconfigdir}/pythonbundles.py <(unzip -p Lib/test/wheeldata/wheel-*.whl wheel/vendored/vendor.txt) | ||||
| #  $ %%{_rpmconfigdir}/pythonbundles.py <(unzip -p Lib/test/wheel-*.whl wheel/vendored/vendor.txt) | ||||
| %global wheel_bundled_provides %{expand: | ||||
| Provides: bundled(python3dist(packaging)) = 23 | ||||
| } | ||||
| @ -195,13 +199,6 @@ Provides: bundled(python3dist(packaging)) = 23 | ||||
| %global py_INSTSONAME_optimized libpython%{LDVERSION_optimized}.so.%{py_SOVERSION} | ||||
| %global py_INSTSONAME_debug     libpython%{LDVERSION_debug}.so.%{py_SOVERSION} | ||||
| 
 | ||||
| # The -O flag for the compiler, optimized builds | ||||
| # https://fedoraproject.org/wiki/Changes/Python_built_with_gcc_O3 | ||||
| %global optflags_optimized -O3 | ||||
| # The -O flag for the compiler, debug builds | ||||
| # -Wno-cpp avoids some warnings with -O0 | ||||
| %global optflags_debug -O0 -Wno-cpp | ||||
| 
 | ||||
| # Disable automatic bytecompilation. The python3 binary is not yet be | ||||
| # available in /usr/bin when Python is built. Also, the bytecompilation fails | ||||
| # on files that test invalid syntax. | ||||
| @ -316,6 +313,11 @@ Source1: %{url}ftp/python/%{general_version}/Python-%{upstream_version}.tar.xz.a | ||||
| # The release manager for Python 3.12 is Thomas Wouters | ||||
| Source2: https://github.com/Yhg1s.gpg | ||||
| 
 | ||||
| # Sources for the python3.12-rpm-macros | ||||
| Source3: macros.python3.12 | ||||
| Source4: import_all_modules_py3_12.py | ||||
| Source5: pathfix_py3_12.py | ||||
| 
 | ||||
| # A simple script to check timestamps of bytecode files | ||||
| # Run in check section with Python that is currently being built | ||||
| # Originally written by bkabrda | ||||
| @ -329,7 +331,7 @@ Source11: idle3.appdata.xml | ||||
| 
 | ||||
| # (Patches taken from github.com/fedora-python/cpython) | ||||
| 
 | ||||
| # 00251 # 6a4ec74157aa01f1ada9f29f30a371cd9e5369e8 | ||||
| # 00251 # cae5a6abc5df08239c85b83e4e250b6f2702e4f5 | ||||
| # Change user install location | ||||
| # | ||||
| # Set values of base and platbase in sysconfig from /usr | ||||
| @ -378,27 +380,14 @@ Patch371: 00371-revert-bpo-1596321-fix-threading-_shutdown-for-the-main-thread-g | ||||
| # - https://access.redhat.com/articles/7004769 | ||||
| Patch397: 00397-tarfile-filter.patch | ||||
| 
 | ||||
| # 00422 # a353cebef737c41420dc7ae2469dd657371b8881 | ||||
| # Fix tests for XMLPullParser with Expat 2.6.0 | ||||
| # 00415 # 83e0fc3ec7bc38055c536f482578a10f6efcc08c | ||||
| # [CVE-2023-27043] gh-102988: Reject malformed addresses in email.parseaddr() (#111116) | ||||
| # | ||||
| # Feeding the parser by too small chunks defers parsing to prevent | ||||
| # CVE-2023-52425. Future versions of Expat may be more reactive. | ||||
| Patch422: 00422-fix-tests-for-xmlpullparser-with-expat-2-6-0.patch | ||||
| 
 | ||||
| # 00465 # | ||||
| # Security fixes for: | ||||
| # CVE-2025-4517, CVE-2025-4330, CVE-2025-4138, CVE-2024-12718 and CVE-2025-4435 in the tarfile module. | ||||
| # | ||||
| # Resolved upstream: https://github.com/python/cpython/pull/135066 | ||||
| Patch465: 00465-tarfile-cves.patch | ||||
| 
 | ||||
| # 00467 # | ||||
| # CVE-2025-8194 | ||||
| # | ||||
| # tarfile now validates archives to ensure member offsets are non-negative. | ||||
| # | ||||
| # Upstream PR: https://github.com/python/cpython/pull/137171 | ||||
| Patch467: 00467-CVE-2025-8194.patch | ||||
| # Detect email address parsing errors and return empty tuple to | ||||
| # indicate the parsing error (old API). Add an optional 'strict' | ||||
| # parameter to getaddresses() and parseaddr() functions. Patch by | ||||
| # Thomas Dwyer. | ||||
| Patch415: 00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch | ||||
| 
 | ||||
| # (New patches go here ^^^) | ||||
| # | ||||
| @ -418,6 +407,14 @@ Patch467: 00467-CVE-2025-8194.patch | ||||
| # Descriptions, and metadata for subpackages | ||||
| # ========================================== | ||||
| 
 | ||||
| # Require alternatives version that implements the --keep-foreign flag and fixes rhbz#2203820 | ||||
| Requires:         alternatives >= 1.19.2-1 | ||||
| Requires(post):   alternatives >= 1.19.2-1 | ||||
| Requires(postun): alternatives >= 1.19.2-1 | ||||
| 
 | ||||
| # When the user tries to `yum install python`, yum will list this package among | ||||
| # the possible alternatives | ||||
| Provides: alternative-for(python) | ||||
| 
 | ||||
| %if %{with main_python} | ||||
| # Description for the python3X SRPM only: | ||||
| @ -492,6 +489,7 @@ Documentation for Python is provided in the %{pkgname}-docs package. | ||||
| Packages containing additional libraries for Python are generally named with | ||||
| the "%{pkgname}-" prefix. | ||||
| 
 | ||||
| For the unversioned "python" executable, see manual page "unversioned-python". | ||||
| 
 | ||||
| %if %{with main_python} | ||||
| # https://fedoraproject.org/wiki/Changes/Move_usr_bin_python_into_separate_package | ||||
| @ -572,9 +570,12 @@ Requires: (python3-rpm-macros if rpm-build) | ||||
| # On Fedora, we keep this to avoid one additional round of %%generate_buildrequires. | ||||
| %{!?rhel:Requires: (pyproject-rpm-macros if rpm-build)} | ||||
| 
 | ||||
| # We provide the python3.12-rpm-macros here to make it possible to | ||||
| # BuildRequire them in the same manner as RHEL8. | ||||
| Provides: %{pkgname}-rpm-macros = %{version}-%{release} | ||||
| # Require alternatives version that implements the --keep-foreign flag and fixes rhbz#2203820 | ||||
| Requires(postun): alternatives >= 1.19.2-1 | ||||
| 
 | ||||
| # python3.12 installs the alternatives master symlink to which we attach a slave | ||||
| Requires(post): %{pkgname} | ||||
| Requires(postun): %{pkgname} | ||||
| 
 | ||||
| %unversioned_obsoletes_of_python3_X_if_main devel | ||||
| 
 | ||||
| @ -623,6 +624,13 @@ Provides: idle = %{version}-%{release} | ||||
| Provides: %{pkgname}-tools = %{version}-%{release} | ||||
| Provides: %{pkgname}-tools%{?_isa} = %{version}-%{release} | ||||
| 
 | ||||
| # Require alternatives version that implements the --keep-foreign flag and fixes rhbz#2203820 | ||||
| Requires(postun): alternatives >= 1.19.2-1 | ||||
| 
 | ||||
| # python3.12 installs the alternatives master symlink to which we attach a slave | ||||
| Requires(post): %{pkgname} | ||||
| Requires(postun): %{pkgname} | ||||
| 
 | ||||
| %description -n %{pkgname}-idle | ||||
| IDLE is Python’s Integrated Development and Learning Environment. | ||||
| 
 | ||||
| @ -694,6 +702,13 @@ Requires: %{pkgname}-idle%{?_isa} = %{version}-%{release} | ||||
| 
 | ||||
| %unversioned_obsoletes_of_python3_X_if_main debug | ||||
| 
 | ||||
| # Require alternatives version that implements the --keep-foreign flag and fixes rhbz#2203820 | ||||
| Requires(postun): alternatives >= 1.19.2-1 | ||||
| 
 | ||||
| # python3.12 installs the alternatives master symlink to which we attach a slave | ||||
| Requires(post): %{pkgname} | ||||
| Requires(postun): %{pkgname} | ||||
| 
 | ||||
| %description -n %{pkgname}-debug | ||||
| python3-debug provides a version of the Python runtime with numerous debugging | ||||
| features enabled, aimed at advanced Python users such as developers of Python | ||||
| @ -712,6 +727,24 @@ The debug runtime additionally supports debug builds of C-API extensions | ||||
| %endif # with debug_build | ||||
| 
 | ||||
| 
 | ||||
| # We package the python3.12-rpm-macros in RHEL8 as to properly set the | ||||
| # %%__python3 and %%python3_pkgversion macros as well as provide modern | ||||
| # versions the current base macros. | ||||
| %package -n %{pkgname}-rpm-macros | ||||
| Summary:    RPM macros for building RPMs with Python %{pybasever} | ||||
| License:    MIT | ||||
| Provides:   python-modular-rpm-macros == %{pybasever} | ||||
| Conflicts:  python-modular-rpm-macros > %{pybasever} | ||||
| Requires:   python3-rpm-macros | ||||
| BuildArch:  noarch | ||||
| 
 | ||||
| %description -n %{pkgname}-rpm-macros | ||||
| RPM macros for building RPMs with Python %{pybasever} from the python%{pyshortver} module. | ||||
| If you want to build an RPM against the python%{pyshortver} module, you need to add: | ||||
| 
 | ||||
|     BuildRequire: %{pkgname}-rpm-macros. | ||||
| 
 | ||||
| 
 | ||||
| # ====================================================== | ||||
| # The prep phase of the build: | ||||
| # ====================================================== | ||||
| @ -720,18 +753,10 @@ The debug runtime additionally supports debug builds of C-API extensions | ||||
| %gpgverify -k2 -s1 -d0 | ||||
| %autosetup -S git_am -n Python-%{upstream_version} | ||||
| 
 | ||||
| # Verify the second level of bundled provides is up to date | ||||
| # Arguably this should be done in %%check, but %%prep has a faster feedback loop | ||||
| # setuptools.whl does not contain the vendored.txt files | ||||
| if [ -f %{_rpmconfigdir}/pythonbundles.py ]; then | ||||
|   %{_rpmconfigdir}/pythonbundles.py <(unzip -p Lib/ensurepip/_bundled/pip-*.whl pip/_vendor/vendor.txt) --compare-with '%pip_bundled_provides' | ||||
|   %{_rpmconfigdir}/pythonbundles.py <(unzip -p Lib/test/wheeldata/wheel-*.whl wheel/vendored/vendor.txt) --compare-with '%wheel_bundled_provides' | ||||
| fi | ||||
| 
 | ||||
| %if %{with rpmwheels} | ||||
| rm Lib/ensurepip/_bundled/pip-%{pip_version}-py3-none-any.whl | ||||
| rm Lib/test/wheeldata/setuptools-%{setuptools_version}-py3-none-any.whl | ||||
| rm Lib/test/wheeldata/wheel-%{wheel_version}-py3-none-any.whl | ||||
| rm Lib/test/setuptools-%{setuptools_version}-py3-none-any.whl | ||||
| rm Lib/test/wheel-%{wheel_version}-py3-none-any.whl | ||||
| %endif | ||||
| 
 | ||||
| # Remove all exe files to ensure we are not shipping prebuilt binaries | ||||
| @ -810,7 +835,6 @@ BuildPython() { | ||||
|   ConfName=$1 | ||||
|   ExtraConfigArgs=$2 | ||||
|   MoreCFlags=$3 | ||||
|   MoreCFlagsNodist=$4 | ||||
| 
 | ||||
|   # Each build is done in its own directory | ||||
|   ConfDir=build/$ConfName | ||||
| @ -850,7 +874,7 @@ BuildPython() { | ||||
|   $ExtraConfigArgs \ | ||||
|   %{nil} | ||||
| 
 | ||||
| %global flags_override EXTRA_CFLAGS="$MoreCFlags" CFLAGS_NODIST="$CFLAGS_NODIST $MoreCFlags $MoreCFlagsNodist" | ||||
| %global flags_override EXTRA_CFLAGS="$MoreCFlags" CFLAGS_NODIST="$CFLAGS_NODIST $MoreCFlags" | ||||
| 
 | ||||
| %if %{without bootstrap} | ||||
|   # Regenerate generated files (needs python3) | ||||
| @ -873,14 +897,12 @@ BuildPython() { | ||||
| # See also: https://bugzilla.redhat.com/show_bug.cgi?id=1818857 | ||||
| BuildPython debug \ | ||||
|   "--without-ensurepip --with-pydebug" \ | ||||
|   "%{optflags_debug}" \ | ||||
|   "" | ||||
|   "-O0 -Wno-cpp" | ||||
| %endif # with debug_build | ||||
| 
 | ||||
| BuildPython optimized \ | ||||
|   "--without-ensurepip %{optimizations_flag}" \ | ||||
|   "" \ | ||||
|   "%{optflags_optimized}" | ||||
|   "" | ||||
| 
 | ||||
| # ====================================================== | ||||
| # Installing the built code: | ||||
| @ -985,7 +1007,7 @@ EOF | ||||
| %if %{with debug_build} | ||||
| InstallPython debug \ | ||||
|   %{py_INSTSONAME_debug} \ | ||||
|   "%{optflags_debug}" \ | ||||
|   -O0 \ | ||||
|   %{LDVERSION_debug} | ||||
| %endif # with debug_build | ||||
| 
 | ||||
| @ -1037,8 +1059,11 @@ done | ||||
| # Switch all shebangs to refer to the specific Python version. | ||||
| # This currently only covers files matching ^[a-zA-Z0-9_]+\.py$, | ||||
| # so handle files named using other naming scheme separately. | ||||
| # - RHEL 8 note: we use %%{SOURCE5} instead of pathfix.py, because in RHEL 8 we | ||||
| #   ship our own versioned pathfix_py3_12.py in this package, but during | ||||
| #   bootstrap it's not yet installed. | ||||
| LD_LIBRARY_PATH=./build/optimized ./build/optimized/python \ | ||||
|   %{_rpmconfigdir}/redhat/pathfix.py \ | ||||
|   %{SOURCE5} \ | ||||
|   -i "%{_bindir}/python%{pybasever}" -pn \ | ||||
|   %{buildroot} \ | ||||
|   %{buildroot}%{_bindir}/*%{pybasever}.py \ | ||||
| @ -1140,6 +1165,29 @@ for file in %{buildroot}%{pylibdir}/pydoc_data/topics.py $(grep --include='*.py' | ||||
|     rm ${directory}/{__pycache__/${module}.cpython-%{pyshortver}.opt-?.pyc,${module}.py} | ||||
| done | ||||
| 
 | ||||
| # Python RPM macros for python3.12-rpm-macros | ||||
| mkdir -p %{buildroot}%{rpmmacrodir}/ | ||||
| install -m 644 %{SOURCE3} \ | ||||
|     %{buildroot}/%{rpmmacrodir}/ | ||||
| 
 | ||||
| # Add scripts that are being used by python3.12-rpm-macros | ||||
| mkdir -p %{buildroot}%{_rpmconfigdir}/redhat | ||||
| install -m 644 %{SOURCE4} %{buildroot}%{_rpmconfigdir}/redhat/ | ||||
| install -m 644 %{SOURCE5} %{buildroot}%{_rpmconfigdir}/redhat/ | ||||
| 
 | ||||
| # All ghost files controlled by alternatives need to exist for the files | ||||
| # section check to succeed | ||||
| # - Don't list /usr/bin/python as a ghost file so `yum install /usr/bin/python` | ||||
| #   doesn't install this package | ||||
| touch %{buildroot}%{_bindir}/unversioned-python | ||||
| touch %{buildroot}%{_mandir}/man1/python.1.gz | ||||
| touch %{buildroot}%{_bindir}/python3 | ||||
| touch %{buildroot}%{_mandir}/man1/python3.1.gz | ||||
| touch %{buildroot}%{_bindir}/pydoc3 | ||||
| touch %{buildroot}%{_bindir}/pydoc-3 | ||||
| touch %{buildroot}%{_bindir}/idle3 | ||||
| touch %{buildroot}%{_bindir}/python3-config | ||||
| 
 | ||||
| # ====================================================== | ||||
| # Checks for packaging issues | ||||
| # ====================================================== | ||||
| @ -1224,10 +1272,119 @@ CheckPython optimized | ||||
| 
 | ||||
| %endif # with tests | ||||
| 
 | ||||
| # ====================================================== | ||||
| # Scriptlets for alternatives on rhel8 | ||||
| # ====================================================== | ||||
| %post | ||||
| # Alternative for /usr/bin/python -> /usr/bin/python3 + man page | ||||
| alternatives --install %{_bindir}/unversioned-python \ | ||||
|                         python \ | ||||
|                         %{_bindir}/python3 \ | ||||
|                         300 \ | ||||
|               --slave   %{_bindir}/python \ | ||||
|                         unversioned-python \ | ||||
|                         %{_bindir}/python3 \ | ||||
|               --slave   %{_mandir}/man1/python.1.gz \ | ||||
|                         unversioned-python-man \ | ||||
|                         %{_mandir}/man1/python3.1.gz | ||||
| 
 | ||||
| # Alternative for /usr/bin/python -> /usr/bin/python3.12 + man page | ||||
| alternatives --install %{_bindir}/unversioned-python \ | ||||
|                         python \ | ||||
|                         %{_bindir}/python3.12 \ | ||||
|                         211 \ | ||||
|               --slave   %{_bindir}/python \ | ||||
|                         unversioned-python \ | ||||
|                         %{_bindir}/python3.12 \ | ||||
|               --slave   %{_mandir}/man1/python.1.gz \ | ||||
|                         unversioned-python-man \ | ||||
|                         %{_mandir}/man1/python3.12.1.gz | ||||
| 
 | ||||
| # Alternative for /usr/bin/python3 -> /usr/bin/python3.12 + related files | ||||
| # Create only if it doesn't exist already | ||||
| EXISTS=`alternatives --display python3 | \ | ||||
|          grep -c "^/usr/bin/python3.12 - priority [0-9]*"` | ||||
| 
 | ||||
| if [ $EXISTS -eq 0 ]; then | ||||
|      alternatives --install %{_bindir}/python3 \ | ||||
|                             python3 \ | ||||
|                             %{_bindir}/python3.12 \ | ||||
|                             31200 \ | ||||
|                   --slave   %{_mandir}/man1/python3.1.gz \ | ||||
|                             python3-man \ | ||||
|                             %{_mandir}/man1/python3.12.1.gz \ | ||||
|                   --slave   %{_bindir}/pydoc3 \ | ||||
|                             pydoc3 \ | ||||
|                             %{_bindir}/pydoc3.12 \ | ||||
|                   --slave   %{_bindir}/pydoc-3 \ | ||||
|                             pydoc-3 \ | ||||
|                             %{_bindir}/pydoc3.12 | ||||
| fi | ||||
| 
 | ||||
| %postun | ||||
| # Do this only during uninstall process (not during update) | ||||
| if [ $1 -eq 0 ]; then | ||||
|      alternatives --keep-foreign --remove python \ | ||||
|                          %{_bindir}/python3.12 | ||||
| 
 | ||||
|      alternatives --keep-foreign --remove python3 \ | ||||
|                          %{_bindir}/python3.12 | ||||
| 
 | ||||
|      # Remove link python → python3 if no other python3.* exists | ||||
|      if ! alternatives --display python3 > /dev/null; then | ||||
|          alternatives --keep-foreign --remove python \ | ||||
|                              %{_bindir}/python3 | ||||
|      fi | ||||
| fi | ||||
| 
 | ||||
| 
 | ||||
| %post devel | ||||
| alternatives --add-slave python3 %{_bindir}/python3.12 \ | ||||
|      %{_bindir}/python3-config \ | ||||
|      python3-config \ | ||||
|      %{_bindir}/python3.12-config | ||||
| 
 | ||||
| %postun devel | ||||
| # Do this only during uninstall process (not during update) | ||||
| if [ $1 -eq 0 ]; then | ||||
|      alternatives --keep-foreign --remove-slave python3 %{_bindir}/python3.12 \ | ||||
|          python3-config | ||||
| fi | ||||
| 
 | ||||
| %post idle | ||||
| alternatives --add-slave python3 %{_bindir}/python3.12 \ | ||||
|      %{_bindir}/idle3 \ | ||||
|      idle3 \ | ||||
|      %{_bindir}/idle3.12 | ||||
| 
 | ||||
| %postun idle | ||||
| # Do this only during uninstall process (not during update) | ||||
| if [ $1 -eq 0 ]; then | ||||
|      alternatives --keep-foreign --remove-slave python3 %{_bindir}/python3.12 \ | ||||
|         idle3 | ||||
| fi | ||||
| 
 | ||||
| # ====================================================== | ||||
| # Files for each RPM (sub)package | ||||
| # ====================================================== | ||||
| 
 | ||||
| %files -n %{pkgname}-rpm-macros | ||||
| %{rpmmacrodir}/macros.python%{pybasever} | ||||
| %{_rpmconfigdir}/redhat/import_all_modules_py3_12.py | ||||
| %{_rpmconfigdir}/redhat/pathfix_py3_12.py | ||||
| 
 | ||||
| 
 | ||||
| %files -n %{pkgname} | ||||
| %doc README.rst | ||||
| 
 | ||||
| # Alternatives | ||||
| %ghost %{_bindir}/unversioned-python | ||||
| %ghost %{_mandir}/man1/python.1.gz | ||||
| %ghost %{_bindir}/python3 | ||||
| %ghost %{_mandir}/man1/python3.1.gz | ||||
| %ghost %{_bindir}/pydoc3 | ||||
| %ghost %{_bindir}/pydoc-3 | ||||
| 
 | ||||
| %if %{with main_python} | ||||
| %{_bindir}/pydoc* | ||||
| %{_bindir}/python3 | ||||
| @ -1360,6 +1517,10 @@ CheckPython optimized | ||||
| %{dynload_dir}/termios.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/unicodedata.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/_uuid.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/xxlimited.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/xxlimited_35.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/_xxsubinterpreters.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/xxsubtype.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/zlib.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/_zoneinfo.%{SOABI_optimized}.so | ||||
| 
 | ||||
| @ -1460,6 +1621,12 @@ CheckPython optimized | ||||
| 
 | ||||
| %{pylibdir}/zoneinfo | ||||
| 
 | ||||
| %dir %{pylibdir}/__phello__/ | ||||
| %dir %{pylibdir}/__phello__/__pycache__/ | ||||
| %{pylibdir}/__phello__/__init__.py | ||||
| %{pylibdir}/__phello__/spam.py | ||||
| %{pylibdir}/__phello__/__pycache__/*%{bytecode_suffixes} | ||||
| 
 | ||||
| %if "%{_lib}" == "lib64" | ||||
| %attr(0755,root,root) %dir %{_prefix}/lib/python%{pybasever} | ||||
| %attr(0755,root,root) %dir %{_prefix}/lib/python%{pybasever}/site-packages | ||||
| @ -1509,6 +1676,9 @@ CheckPython optimized | ||||
| %{_bindir}/python%{pybasever}-config | ||||
| %{_bindir}/python%{LDVERSION_optimized}-config | ||||
| %{_bindir}/python%{LDVERSION_optimized}-*-config | ||||
| # Alternatives | ||||
| %ghost %{_bindir}/python3-config | ||||
| 
 | ||||
| %{_libdir}/libpython%{LDVERSION_optimized}.so | ||||
| %{_libdir}/pkgconfig/python-%{LDVERSION_optimized}.pc | ||||
| %{_libdir}/pkgconfig/python-%{LDVERSION_optimized}-embed.pc | ||||
| @ -1521,6 +1691,8 @@ CheckPython optimized | ||||
| %{_bindir}/idle* | ||||
| %else | ||||
| %{_bindir}/idle%{pybasever} | ||||
| # Alternatives | ||||
| %ghost %{_bindir}/idle3 | ||||
| %endif | ||||
| 
 | ||||
| %{pylibdir}/idlelib | ||||
| @ -1554,17 +1726,7 @@ CheckPython optimized | ||||
| %{dynload_dir}/_testmultiphase.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/_testsinglephase.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/_xxinterpchannels.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/_xxsubinterpreters.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/_xxtestfuzz.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/xxlimited.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/xxlimited_35.%{SOABI_optimized}.so | ||||
| %{dynload_dir}/xxsubtype.%{SOABI_optimized}.so | ||||
| 
 | ||||
| %dir %{pylibdir}/__phello__/ | ||||
| %dir %{pylibdir}/__phello__/__pycache__/ | ||||
| %{pylibdir}/__phello__/__init__.py | ||||
| %{pylibdir}/__phello__/spam.py | ||||
| %{pylibdir}/__phello__/__pycache__/*%{bytecode_suffixes} | ||||
| 
 | ||||
| # We don't bother splitting the debug build out into further subpackages: | ||||
| # if you need it, you're probably a developer. | ||||
| @ -1646,6 +1808,10 @@ CheckPython optimized | ||||
| %{dynload_dir}/termios.%{SOABI_debug}.so | ||||
| %{dynload_dir}/unicodedata.%{SOABI_debug}.so | ||||
| %{dynload_dir}/_uuid.%{SOABI_debug}.so | ||||
| %{dynload_dir}/xxlimited.%{SOABI_debug}.so | ||||
| %{dynload_dir}/xxlimited_35.%{SOABI_debug}.so | ||||
| %{dynload_dir}/_xxsubinterpreters.%{SOABI_debug}.so | ||||
| %{dynload_dir}/xxsubtype.%{SOABI_debug}.so | ||||
| %{dynload_dir}/zlib.%{SOABI_debug}.so | ||||
| %{dynload_dir}/_zoneinfo.%{SOABI_debug}.so | ||||
| 
 | ||||
| @ -1682,11 +1848,7 @@ CheckPython optimized | ||||
| %{dynload_dir}/_testmultiphase.%{SOABI_debug}.so | ||||
| %{dynload_dir}/_testsinglephase.%{SOABI_debug}.so | ||||
| %{dynload_dir}/_xxinterpchannels.%{SOABI_debug}.so | ||||
| %{dynload_dir}/_xxsubinterpreters.%{SOABI_debug}.so | ||||
| %{dynload_dir}/_xxtestfuzz.%{SOABI_debug}.so | ||||
| %{dynload_dir}/xxlimited.%{SOABI_debug}.so | ||||
| %{dynload_dir}/xxlimited_35.%{SOABI_debug}.so | ||||
| %{dynload_dir}/xxsubtype.%{SOABI_debug}.so | ||||
| 
 | ||||
| %{pylibdir}/_sysconfigdata_%{ABIFLAGS_debug}_linux_%{platform_triplet}.py | ||||
| %{pylibdir}/__pycache__/_sysconfigdata_%{ABIFLAGS_debug}_linux_%{platform_triplet}%{bytecode_suffixes} | ||||
| @ -1714,71 +1876,6 @@ CheckPython optimized | ||||
| # ====================================================== | ||||
| 
 | ||||
| %changelog | ||||
| * Thu Aug 14 2025 Lumír Balhar <lbalhar@redhat.com> - 3.12.9-1.2 | ||||
| - Security fix for CVE-2025-8194 | ||||
| Resolves: RHEL-106370 | ||||
| 
 | ||||
| * Fri Jun 20 2025 Charalampos Stratakis <cstratak@redhat.com> - 3.12.9-1.1 | ||||
| - Security fixes for CVE-2025-4517, CVE-2025-4330, CVE-2025-4138, CVE-2024-12718, CVE-2025-4435 | ||||
| - Resolves: RHEL-98058, RHEL-98020, RHEL-97809, RHEL-98184, RHEL-98211 | ||||
| 
 | ||||
| * Tue Feb 04 2025 Charalampos Stratakis <cstratak@redhat.com> - 3.12.9-1 | ||||
| - Update to 3.12.9 | ||||
| - Security fix for CVE-2025-0938 | ||||
| Resolves: RHEL-77261 | ||||
| 
 | ||||
| * Tue Dec 03 2024 Charalampos Stratakis <cstratak@redhat.com> - 3.12.8-1 | ||||
| - Update to 3.12.8 | ||||
| - Security fix for CVE-2024-9287 and CVE-2024-12254 | ||||
| Resolves: RHEL-64886, RHEL-70320 | ||||
| 
 | ||||
| * Mon Sep 09 2024 Tomáš Hrnčiar <thrnciar@redhat.com> - 3.12.6-1 | ||||
| - Update to 3.12.6 | ||||
| Resolves: RHEL-57417 | ||||
| 
 | ||||
| * Fri Aug 23 2024 Charalampos Stratakis <cstratak@redhat.com> - 3.12.5-2 | ||||
| - Security fix for CVE-2024-8088 | ||||
| Resolves: RHEL-55963 | ||||
| 
 | ||||
| * Wed Aug 07 2024 Tomáš Hrnčiar <thrnciar@redhat.com> - 3.12.5-1 | ||||
| - Update to 3.12.5 | ||||
| - Security fix for CVE-2024-6923 | ||||
| Resolves: RHEL-53041 | ||||
| 
 | ||||
| * Thu Jul 25 2024 Charalampos Stratakis <cstratak@redhat.com> - 3.12.4-3 | ||||
| - Properly propagate the optimization flags to C extensions | ||||
| 
 | ||||
| * Wed Jul 17 2024 Charalampos Stratakis <cstratak@redhat.com> - 3.12.4-2 | ||||
| - Build Python with -O3 | ||||
| - https://fedoraproject.org/wiki/Changes/Python_built_with_gcc_O3 | ||||
| 
 | ||||
| * Fri Jun 28 2024 Tomáš Hrnčiar <thrnciar@redhat.com> - 3.12.4-1 | ||||
| - Update to 3.12.4 | ||||
| Resolves: RHEL-44103 | ||||
| 
 | ||||
| * Tue Jun 11 2024 Charalampos Stratakis <cstratak@redhat.com> - 3.12.3-2 | ||||
| - Enable importing of hash-based .pyc files under FIPS mode | ||||
| Resolves: RHEL-40772 | ||||
| 
 | ||||
| * Fri May 03 2024 Lumír Balhar <lbalhar@redhat.com> - 3.12.3-1 | ||||
| - Update to 3.12.3 | ||||
| Related: RHEL-33690 | ||||
| 
 | ||||
| * Fri May 03 2024 Lumír Balhar <lbalhar@redhat.com> - 3.12.2-3 | ||||
| - Move all test modules to the python3-test package, namely: | ||||
|    - __phello__ | ||||
|    - _xxsubinterpreters | ||||
|    - xxlimited | ||||
|    - xxlimited_35 | ||||
|    - xxsubtype | ||||
| 
 | ||||
| * Fri May 03 2024 Lumír Balhar <lbalhar@redhat.com> - 3.12.2-2 | ||||
| - Fix tests for XMLPullParser with Expat with fixed CVE | ||||
| 
 | ||||
| * Fri May 03 2024 Lumír Balhar <lbalhar@redhat.com> - 3.12.2-1 | ||||
| - Update to 3.12.2 | ||||
| Resolves: RHEL-33690 | ||||
| 
 | ||||
| * Mon Feb 19 2024 Charalampos Stratakis <cstratak@redhat.com> - 3.12.1-4 | ||||
| - Add Red Hat configuration for CVE-2007-4559 | ||||
| 
 | ||||
| @ -1835,3 +1932,4 @@ Resolves: RHEL-33690 | ||||
|       Ville Skyttä <ville.skytta@iki.fi> | ||||
|       Yaakov Selkowitz <yselkowi@redhat.com> | ||||
|       Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user