Compare commits

...

No commits in common. "c9s" and "c8-beta" have entirely different histories.
c9s ... c8-beta

24 changed files with 1528 additions and 498 deletions

4
.gitignore vendored
View File

@ -1,3 +1 @@
/*.tar.* SOURCES/Python-3.12.1.tar.xz
/*.src.rpm
/results_python3*

1
.python3.12.metadata Normal file
View File

@ -0,0 +1 @@
5b11c58ea58cd6b8e1943c7e9b5f6e0997ca3632 SOURCES/Python-3.12.1.tar.xz

View File

@ -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

View File

@ -30,10 +30,10 @@ Co-authored-by: Lumír Balhar <frenzy.madness@gmail.com>
3 files changed, 71 insertions(+), 4 deletions(-) 3 files changed, 71 insertions(+), 4 deletions(-)
diff --git a/Lib/site.py b/Lib/site.py diff --git a/Lib/site.py b/Lib/site.py
index 924cfbecec..e2871ecc89 100644 index 672fa7b000..0a9c5be53e 100644
--- a/Lib/site.py --- a/Lib/site.py
+++ b/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 return sitepackages
def addsitepackages(known_paths, prefixes=None): def addsitepackages(known_paths, prefixes=None):
@ -129,7 +129,7 @@ index 122d441bd1..2d354a11da 100644
# On Windows we want to substitute 'lib' for schemes rather # On Windows we want to substitute 'lib' for schemes rather
# than the native value (without modifying vars, in case it # than the native value (without modifying vars, in case it
diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py
index 1137c2032b..8fc2b84f52 100644 index b6dbf3d52c..4f06a7673c 100644
--- a/Lib/test/test_sysconfig.py --- a/Lib/test/test_sysconfig.py
+++ b/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py
@@ -110,8 +110,19 @@ def test_get_path(self): @@ -110,8 +110,19 @@ def test_get_path(self):
@ -153,7 +153,7 @@ index 1137c2032b..8fc2b84f52 100644
os.path.normpath(expected), os.path.normpath(expected),
) )
@@ -344,7 +355,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) self.assertTrue(os.path.isfile(config_h), config_h)
def test_get_scheme_names(self): def test_get_scheme_names(self):
@ -162,7 +162,7 @@ index 1137c2032b..8fc2b84f52 100644
if HAS_USER_BASE: if HAS_USER_BASE:
wanted.extend(['nt_user', 'osx_framework_user', 'posix_user']) wanted.extend(['nt_user', 'osx_framework_user', 'posix_user'])
self.assertEqual(get_scheme_names(), tuple(sorted(wanted))) self.assertEqual(get_scheme_names(), tuple(sorted(wanted)))
@@ -356,6 +367,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())" cmd = "-c", "import sysconfig; print(sysconfig.get_platform())"
self.assertEqual(py.call_real(*cmd), py.call_link(*cmd)) self.assertEqual(py.call_real(*cmd), py.call_link(*cmd))

View File

@ -1,7 +1,7 @@
From 43ce74d971fad62db6ccd723fe6b01da9c7ff407 Mon Sep 17 00:00:00 2001 From 5cedde59cde3f05af798a7cb5bc722cb0deb4835 Mon Sep 17 00:00:00 2001
From: Charalampos Stratakis <cstratak@redhat.com> From: Charalampos Stratakis <cstratak@redhat.com>
Date: Thu, 12 Dec 2019 16:58:31 +0100 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 These aren't as powerful as Python's own implementation, but they can be
used under FIPS. used under FIPS.
@ -251,13 +251,13 @@ index fb61a44..1e42b87 100644
-/*[clinic end generated code: output=b339e255db698147 input=a9049054013a1b77]*/ -/*[clinic end generated code: output=b339e255db698147 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=1d988d457a8beebe input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1d988d457a8beebe input=a9049054013a1b77]*/
-- --
2.45.0 2.43.0
From 6872b634078a2c69644235781ebffb07f8edcb83 Mon Sep 17 00:00:00 2001 From 2a12baa9e201f54560ec99ad5ee1fa5b0006aa39 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori@redhat.com> From: Petr Viktorin <pviktori@redhat.com>
Date: Thu, 25 Jul 2019 17:19:06 +0200 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 forcing OpenSSL
--- ---
@ -445,10 +445,10 @@ index a8bad9d..1b1d937 100644
+ if (_Py_hashlib_fips_error(exc, name)) return NULL; \ + if (_Py_hashlib_fips_error(exc, name)) return NULL; \
+} while (0) +} while (0)
diff --git a/configure.ac b/configure.ac diff --git a/configure.ac b/configure.ac
index 65ad1c2..b5f9ab5 100644 index 1876f77..1875d1e 100644
--- a/configure.ac --- a/configure.ac
+++ b/configure.ac +++ b/configure.ac
@@ -7463,7 +7463,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([_sha3], [test "$with_builtin_sha3" = yes])
PY_STDLIB_MOD([_blake2], PY_STDLIB_MOD([_blake2],
[test "$with_builtin_blake2" = yes], [], [test "$with_builtin_blake2" = yes], [],
@ -459,13 +459,13 @@ index 65ad1c2..b5f9ab5 100644
PY_STDLIB_MOD([_crypt], PY_STDLIB_MOD([_crypt],
[], [test "$ac_cv_crypt_crypt" = yes], [], [test "$ac_cv_crypt_crypt" = yes],
-- --
2.45.0 2.43.0
From f904abdd7a607282c2cdfd18288045cedfa28414 Mon Sep 17 00:00:00 2001 From bca05b7fdb8dcab21ef80db1d59dd5daa835d84b Mon Sep 17 00:00:00 2001
From: Charalampos Stratakis <cstratak@redhat.com> From: Charalampos Stratakis <cstratak@redhat.com>
Date: Fri, 29 Jan 2021 14:16:21 +0100 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 are not in FIPS mode
--- ---
@ -552,13 +552,13 @@ index dd61a9a..6031b02 100644
get_builtin_constructor = getattr(hashlib, get_builtin_constructor = getattr(hashlib,
'__get_builtin_constructor') '__get_builtin_constructor')
-- --
2.45.0 2.43.0
From 9bf0a53b7831409613c44fd7feecb56476f5e5e7 Mon Sep 17 00:00:00 2001 From c9a79f0aafd28677e3e0b8a1f6410105a71ff071 Mon Sep 17 00:00:00 2001
From: Charalampos Stratakis <cstratak@redhat.com> From: Charalampos Stratakis <cstratak@redhat.com>
Date: Wed, 31 Jul 2019 15:43:43 +0200 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 usedforsecurity=True/False
--- ---
@ -712,13 +712,13 @@ index 6031b02..5bd5297 100644
class KDFTests(unittest.TestCase): class KDFTests(unittest.TestCase):
-- --
2.45.0 2.43.0
From 8a76571515a64a57b4ea0586ae8376cf2ef0ac60 Mon Sep 17 00:00:00 2001 From e972a838729ea84a0f2e0ca8e88ae1bfc129e7d8 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori@redhat.com> From: Petr Viktorin <pviktori@redhat.com>
Date: Mon, 26 Aug 2019 19:39:48 +0200 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 | 13 +++++++++---- Lib/hmac.py | 13 +++++++++----
@ -726,7 +726,7 @@ Subject: [PATCH 5/5] Guard against Python HMAC in FIPS mode
2 files changed, 19 insertions(+), 4 deletions(-) 2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/Lib/hmac.py b/Lib/hmac.py diff --git a/Lib/hmac.py b/Lib/hmac.py
index 8b4eb2f..e8e4864 100644 index 8b4f920..20ef96c 100644
--- a/Lib/hmac.py --- a/Lib/hmac.py
+++ b/Lib/hmac.py +++ b/Lib/hmac.py
@@ -16,8 +16,9 @@ else: @@ -16,8 +16,9 @@ else:
@ -750,7 +750,7 @@ index 8b4eb2f..e8e4864 100644
raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__)
if not digestmod: if not digestmod:
raise TypeError("Missing required argument 'digestmod'.") raise TypeError("Missing required parameter 'digestmod'.")
- if _hashopenssl and isinstance(digestmod, (str, _functype)): - if _hashopenssl and isinstance(digestmod, (str, _functype)):
+ if _hashopenssl.get_fips_mode() or (_hashopenssl and isinstance(digestmod, (str, _functype))): + if _hashopenssl.get_fips_mode() or (_hashopenssl and isinstance(digestmod, (str, _functype))):
@ -773,7 +773,7 @@ index 8b4eb2f..e8e4864 100644
digest_cons = digestmod digest_cons = digestmod
elif isinstance(digestmod, str): elif isinstance(digestmod, str):
diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py 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 --- a/Lib/test/test_hmac.py
+++ b/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py
@@ -5,6 +5,7 @@ import hashlib @@ -5,6 +5,7 @@ import hashlib
@ -812,7 +812,7 @@ index 1502fba..7997073 100644
@unittest.skipUnless(sha256_module is not None, 'need _sha256') @unittest.skipUnless(sha256_module is not None, 'need _sha256')
def test_with_sha256_module(self): def test_with_sha256_module(self):
h = hmac.HMAC(b"key", b"hash this!", digestmod=sha256_module.sha256) 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): class CopyTestCase(unittest.TestCase):
@ -820,7 +820,7 @@ index 1502fba..7997073 100644
@hashlib_helper.requires_hashdigest('sha256') @hashlib_helper.requires_hashdigest('sha256')
def test_attributes_old(self): def test_attributes_old(self):
# Testing if attributes are of same type. # 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), self.assertEqual(type(h1._outer), type(h2._outer),
"Types of outer don't match.") "Types of outer don't match.")
@ -829,5 +829,270 @@ index 1502fba..7997073 100644
def test_realcopy_old(self): def test_realcopy_old(self):
# Testing if the copy method created a real copy. # Testing if the copy method created a real copy.
-- --
2.45.0 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

View File

@ -16,10 +16,10 @@ https://github.com/GrahamDumpleton/mod_wsgi/issues/730
2 files changed, 8 insertions(+), 50 deletions(-) 2 files changed, 8 insertions(+), 50 deletions(-)
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index 2e4b860b97..3066b23ee1 100644 index 756d5e329f..5d09775efc 100644
--- a/Lib/test/test_threading.py --- a/Lib/test/test_threading.py
+++ b/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() threading.Thread(target=noop).start()
# Thread.join() is not called # Thread.join() is not called
@ -56,14 +56,14 @@ index 2e4b860b97..3066b23ee1 100644
- self.assertEqual(out, b'') - self.assertEqual(out, b'')
- self.assertEqual(err, b'') - self.assertEqual(err, b'')
- -
def test_start_new_thread_at_finalization(self): def test_start_new_thread_at_exit(self):
code = """if 1: code = """if 1:
import _thread import atexit
diff --git a/Lib/threading.py b/Lib/threading.py diff --git a/Lib/threading.py b/Lib/threading.py
index 0bba85d08a..b256e3273f 100644 index 8dcaf8ca6a..ed0b0f4632 100644
--- a/Lib/threading.py --- a/Lib/threading.py
+++ b/Lib/threading.py +++ b/Lib/threading.py
@@ -1587,29 +1587,20 @@ def _shutdown(): @@ -1586,29 +1586,20 @@ def _shutdown():
global _SHUTTING_DOWN global _SHUTTING_DOWN
_SHUTTING_DOWN = True _SHUTTING_DOWN = True

View File

@ -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> 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 Subject: [PATCH] CVE-2007-4559, PEP-706: Add filters for tarfile extraction
(downstream) (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 Add and test RHEL-specific ways of configuring the default behavior: environment
variable and config file. 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_shutil.py | 2 +-
Lib/test/test_tarfile.py | 147 +++++++++++++++++++++++++++++++++++++-- Lib/test/test_tarfile.py | 123 ++++++++++++++++++++++++++++++++++++++-
3 files changed, 185 insertions(+), 11 deletions(-) 3 files changed, 163 insertions(+), 9 deletions(-)
diff --git a/Lib/tarfile.py b/Lib/tarfile.py diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index e1487e3..89b6843 100755 index 02f5e3b..f7109f3 100755
--- a/Lib/tarfile.py --- a/Lib/tarfile.py
+++ b/Lib/tarfile.py +++ b/Lib/tarfile.py
@@ -71,6 +71,13 @@ __all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError", "ReadError", @@ -71,6 +71,13 @@ __all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError", "ReadError",
@ -35,7 +30,7 @@ index e1487e3..89b6843 100755
#--------------------------------------------------------- #---------------------------------------------------------
# tar constants # tar constants
@@ -2218,11 +2225,41 @@ class TarFile(object): @@ -2217,11 +2224,41 @@ class TarFile(object):
if filter is None: if filter is None:
filter = self.extraction_filter filter = self.extraction_filter
if filter is None: if filter is None:
@ -43,7 +38,7 @@ index e1487e3..89b6843 100755
- 'Python 3.14 will, by default, filter extracted tar ' - 'Python 3.14 will, by default, filter extracted tar '
- + 'archives and reject files or modify their metadata. ' - + 'archives and reject files or modify their metadata. '
- + 'Use the filter argument to control this behavior.', - + 'Use the filter argument to control this behavior.',
- DeprecationWarning, stacklevel=3) - DeprecationWarning)
+ name = os.environ.get('PYTHON_TARFILE_EXTRACTION_FILTER') + name = os.environ.get('PYTHON_TARFILE_EXTRACTION_FILTER')
+ if name is None: + if name is None:
+ try: + try:
@ -77,16 +72,16 @@ index e1487e3..89b6843 100755
+ + 'and some mode bits are cleared. ' + + 'and some mode bits are cleared. '
+ + 'See https://access.redhat.com/articles/7004769 ' + + 'See https://access.redhat.com/articles/7004769 '
+ + 'for more details.', + + 'for more details.',
+ RuntimeWarning, stacklevel=3) + RuntimeWarning)
+ return tar_filter + return tar_filter
return fully_trusted_filter return fully_trusted_filter
if isinstance(filter, str): if isinstance(filter, str):
raise TypeError( raise TypeError(
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py 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 --- a/Lib/test/test_shutil.py
+++ b/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='fully_trusted')
self.check_unpack_archive(format, filter='data') self.check_unpack_archive(format, filter='data')
with warnings_helper.check_warnings( with warnings_helper.check_warnings(
@ -96,48 +91,10 @@ index 7bc5d12..88b4bdb 100644
def test_unpack_archive_tar(self): def test_unpack_archive_tar(self):
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py 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 --- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py
@@ -727,7 +727,17 @@ class MiscReadTestBase(CommonReadTest): @@ -3097,8 +3097,8 @@ class NoneInfoExtractTests(ReadTest):
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):
tar.errorlevel = 0 tar.errorlevel = 0
with ExitStack() as cm: with ExitStack() as cm:
if cls.extraction_filter is None: if cls.extraction_filter is None:
@ -148,7 +105,7 @@ index 3fbd25e..9aa727e 100644
tar.extractall(cls.control_dir, filter=cls.extraction_filter) tar.extractall(cls.control_dir, filter=cls.extraction_filter)
tar.close() tar.close()
cls.control_paths = set( cls.control_paths = set(
@@ -3966,7 +3986,7 @@ class TestExtractionFilters(unittest.TestCase): @@ -3919,7 +3919,7 @@ class TestExtractionFilters(unittest.TestCase):
with ArchiveMaker() as arc: with ArchiveMaker() as arc:
arc.add('foo') arc.add('foo')
with warnings_helper.check_warnings( with warnings_helper.check_warnings(
@ -157,7 +114,7 @@ index 3fbd25e..9aa727e 100644
with self.check_context(arc.open(), None): with self.check_context(arc.open(), None):
self.expect_file('foo') 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 self.expect_exception(TypeError) # errorlevel is not int
@ -278,9 +235,9 @@ index 3fbd25e..9aa727e 100644
+ self.check_trusted_default(tar, tempdir) + self.check_trusted_default(tar, tempdir)
+ +
+ +
class OverwriteTests(archiver_tests.OverwriteTests, unittest.TestCase): def setUpModule():
testdir = os.path.join(TEMPDIR, "testoverwrite") os_helper.unlink(TEMPDIR)
os.makedirs(TEMPDIR)
-- --
2.44.0 2.43.0

View File

@ -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.

View 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-----

View File

@ -16,6 +16,7 @@ LEVELS = (None, 1, 2)
# list of globs of test and other files that we expect not to have bytecode # list of globs of test and other files that we expect not to have bytecode
not_compiled = [ not_compiled = [
'/usr/bin/*', '/usr/bin/*',
'/usr/lib/rpm/redhat/*',
'*/test/badsyntax_*.py', '*/test/badsyntax_*.py',
'*/tokenizedata/bad_coding.py', '*/tokenizedata/bad_coding.py',
'*/tokenizedata/bad_coding2.py', '*/tokenizedata/bad_coding2.py',

View 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
View 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
View 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()

View File

@ -16,12 +16,12 @@ URL: https://www.python.org/
# WARNING When rebasing to a new Python version, # WARNING When rebasing to a new Python version,
# remember to update the python3-docs package as well # remember to update the python3-docs package as well
%global general_version %{pybasever}.6 %global general_version %{pybasever}.1
#global prerel ... #global prerel ...
%global upstream_version %{general_version}%{?prerel} %global upstream_version %{general_version}%{?prerel}
Version: %{general_version}%{?prerel:~%{prerel}} Version: %{general_version}%{?prerel:~%{prerel}}
Release: 1%{?dist} Release: 4%{?dist}
License: Python-2.0.1 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 # Whether to use RPM build wheels from the python-{pip,setuptools,wheel}-wheel packages
# Uses upstream bundled prebuilt wheels otherwise # Uses upstream bundled prebuilt wheels otherwise
%bcond_without rpmwheels %bcond_without rpmwheels
# If the rpmwheels condition is disabled, we use the bundled wheel packages # If the rpmwheels condition is disabled, we use the bundled wheel packages
# from Python with the versions below. # from Python with the versions below.
# This needs to be manually updated when we update Python. # This needs to be manually updated when we update Python.
%global pip_version 24.2 %global pip_version 23.2.1
%global setuptools_version 67.6.1 %global setuptools_version 67.6.1
%global wheel_version 0.40.0 %global wheel_version 0.40.0
# All of those also include a list of indirect bundled libs: # All of those also include a list of indirect bundled libs:
# pip # pip
# $ %%{_rpmconfigdir}/pythonbundles.py <(unzip -p Lib/ensurepip/_bundled/pip-*.whl pip/_vendor/vendor.txt) # $ %%{_rpmconfigdir}/pythonbundles.py <(unzip -p Lib/ensurepip/_bundled/pip-*.whl pip/_vendor/vendor.txt)
%global pip_bundled_provides %{expand: %global pip_bundled_provides %{expand:
Provides: bundled(python3dist(cachecontrol)) = 0.14 Provides: bundled(python3dist(cachecontrol)) = 0.12.11
Provides: bundled(python3dist(certifi)) = 2024.7.4 Provides: bundled(python3dist(certifi)) = 2023.5.7
Provides: bundled(python3dist(distlib)) = 0.3.8 Provides: bundled(python3dist(chardet)) = 5.1
Provides: bundled(python3dist(distro)) = 1.9 Provides: bundled(python3dist(colorama)) = 0.4.6
Provides: bundled(python3dist(idna)) = 3.7 Provides: bundled(python3dist(distlib)) = 0.3.6
Provides: bundled(python3dist(msgpack)) = 1.0.8 Provides: bundled(python3dist(distro)) = 1.8
Provides: bundled(python3dist(packaging)) = 24.1 Provides: bundled(python3dist(idna)) = 3.4
Provides: bundled(python3dist(platformdirs)) = 4.2.2 Provides: bundled(python3dist(msgpack)) = 1.0.5
Provides: bundled(python3dist(pygments)) = 2.18 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(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(resolvelib)) = 1.0.1
Provides: bundled(python3dist(rich)) = 13.7.1 Provides: bundled(python3dist(rich)) = 13.4.2
Provides: bundled(python3dist(setuptools)) = 70.3 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(tomli)) = 2.0.1
Provides: bundled(python3dist(truststore)) = 0.9.1 Provides: bundled(python3dist(typing-extensions)) = 4.7.1
Provides: bundled(python3dist(typing-extensions)) = 4.12.2 Provides: bundled(python3dist(urllib3)) = 1.26.16
Provides: bundled(python3dist(urllib3)) = 1.26.18 Provides: bundled(python3dist(webencodings)) = 0.5.1
} }
# setuptools # setuptools
# vendor.txt files not in .whl # vendor.txt files not in .whl
@ -111,7 +115,7 @@ Provides: bundled(python3dist(typing-extensions)) = 4.4
Provides: bundled(python3dist(zipp)) = 3.7 Provides: bundled(python3dist(zipp)) = 3.7
} }
# wheel # 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: %global wheel_bundled_provides %{expand:
Provides: bundled(python3dist(packaging)) = 23 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_optimized libpython%{LDVERSION_optimized}.so.%{py_SOVERSION}
%global py_INSTSONAME_debug libpython%{LDVERSION_debug}.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 # Disable automatic bytecompilation. The python3 binary is not yet be
# available in /usr/bin when Python is built. Also, the bytecompilation fails # available in /usr/bin when Python is built. Also, the bytecompilation fails
# on files that test invalid syntax. # 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 # The release manager for Python 3.12 is Thomas Wouters
Source2: https://github.com/Yhg1s.gpg 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 # A simple script to check timestamps of bytecode files
# Run in check section with Python that is currently being built # Run in check section with Python that is currently being built
# Originally written by bkabrda # Originally written by bkabrda
@ -378,12 +380,14 @@ Patch371: 00371-revert-bpo-1596321-fix-threading-_shutdown-for-the-main-thread-g
# - https://access.redhat.com/articles/7004769 # - https://access.redhat.com/articles/7004769
Patch397: 00397-tarfile-filter.patch Patch397: 00397-tarfile-filter.patch
# 00422 # a353cebef737c41420dc7ae2469dd657371b8881 # 00415 # 83e0fc3ec7bc38055c536f482578a10f6efcc08c
# Fix tests for XMLPullParser with Expat 2.6.0 # [CVE-2023-27043] gh-102988: Reject malformed addresses in email.parseaddr() (#111116)
# #
# Feeding the parser by too small chunks defers parsing to prevent # Detect email address parsing errors and return empty tuple to
# CVE-2023-52425. Future versions of Expat may be more reactive. # indicate the parsing error (old API). Add an optional 'strict'
Patch422: 00422-fix-tests-for-xmlpullparser-with-expat-2-6-0.patch # 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 ^^^) # (New patches go here ^^^)
# #
@ -403,6 +407,14 @@ Patch422: 00422-fix-tests-for-xmlpullparser-with-expat-2-6-0.patch
# Descriptions, and metadata for subpackages # 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} %if %{with main_python}
# Description for the python3X SRPM only: # Description for the python3X SRPM only:
@ -477,6 +489,7 @@ Documentation for Python is provided in the %{pkgname}-docs package.
Packages containing additional libraries for Python are generally named with Packages containing additional libraries for Python are generally named with
the "%{pkgname}-" prefix. the "%{pkgname}-" prefix.
For the unversioned "python" executable, see manual page "unversioned-python".
%if %{with main_python} %if %{with main_python}
# https://fedoraproject.org/wiki/Changes/Move_usr_bin_python_into_separate_package # https://fedoraproject.org/wiki/Changes/Move_usr_bin_python_into_separate_package
@ -557,9 +570,12 @@ Requires: (python3-rpm-macros if rpm-build)
# On Fedora, we keep this to avoid one additional round of %%generate_buildrequires. # On Fedora, we keep this to avoid one additional round of %%generate_buildrequires.
%{!?rhel:Requires: (pyproject-rpm-macros if rpm-build)} %{!?rhel:Requires: (pyproject-rpm-macros if rpm-build)}
# We provide the python3.12-rpm-macros here to make it possible to # Require alternatives version that implements the --keep-foreign flag and fixes rhbz#2203820
# BuildRequire them in the same manner as RHEL8. Requires(postun): alternatives >= 1.19.2-1
Provides: %{pkgname}-rpm-macros = %{version}-%{release}
# 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 %unversioned_obsoletes_of_python3_X_if_main devel
@ -608,6 +624,13 @@ Provides: idle = %{version}-%{release}
Provides: %{pkgname}-tools = %{version}-%{release} Provides: %{pkgname}-tools = %{version}-%{release}
Provides: %{pkgname}-tools%{?_isa} = %{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 %description -n %{pkgname}-idle
IDLE is Pythons Integrated Development and Learning Environment. IDLE is Pythons Integrated Development and Learning Environment.
@ -679,6 +702,13 @@ Requires: %{pkgname}-idle%{?_isa} = %{version}-%{release}
%unversioned_obsoletes_of_python3_X_if_main debug %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 %description -n %{pkgname}-debug
python3-debug provides a version of the Python runtime with numerous debugging python3-debug provides a version of the Python runtime with numerous debugging
features enabled, aimed at advanced Python users such as developers of Python features enabled, aimed at advanced Python users such as developers of Python
@ -697,6 +727,24 @@ The debug runtime additionally supports debug builds of C-API extensions
%endif # with debug_build %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: # The prep phase of the build:
# ====================================================== # ======================================================
@ -705,18 +753,10 @@ The debug runtime additionally supports debug builds of C-API extensions
%gpgverify -k2 -s1 -d0 %gpgverify -k2 -s1 -d0
%autosetup -S git_am -n Python-%{upstream_version} %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} %if %{with rpmwheels}
rm Lib/ensurepip/_bundled/pip-%{pip_version}-py3-none-any.whl 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/setuptools-%{setuptools_version}-py3-none-any.whl
rm Lib/test/wheeldata/wheel-%{wheel_version}-py3-none-any.whl rm Lib/test/wheel-%{wheel_version}-py3-none-any.whl
%endif %endif
# Remove all exe files to ensure we are not shipping prebuilt binaries # Remove all exe files to ensure we are not shipping prebuilt binaries
@ -795,7 +835,6 @@ BuildPython() {
ConfName=$1 ConfName=$1
ExtraConfigArgs=$2 ExtraConfigArgs=$2
MoreCFlags=$3 MoreCFlags=$3
MoreCFlagsNodist=$4
# Each build is done in its own directory # Each build is done in its own directory
ConfDir=build/$ConfName ConfDir=build/$ConfName
@ -835,7 +874,7 @@ BuildPython() {
$ExtraConfigArgs \ $ExtraConfigArgs \
%{nil} %{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} %if %{without bootstrap}
# Regenerate generated files (needs python3) # Regenerate generated files (needs python3)
@ -858,14 +897,12 @@ BuildPython() {
# See also: https://bugzilla.redhat.com/show_bug.cgi?id=1818857 # See also: https://bugzilla.redhat.com/show_bug.cgi?id=1818857
BuildPython debug \ BuildPython debug \
"--without-ensurepip --with-pydebug" \ "--without-ensurepip --with-pydebug" \
"%{optflags_debug}" \ "-O0 -Wno-cpp"
""
%endif # with debug_build %endif # with debug_build
BuildPython optimized \ BuildPython optimized \
"--without-ensurepip %{optimizations_flag}" \ "--without-ensurepip %{optimizations_flag}" \
"" \ ""
"%{optflags_optimized}"
# ====================================================== # ======================================================
# Installing the built code: # Installing the built code:
@ -970,7 +1007,7 @@ EOF
%if %{with debug_build} %if %{with debug_build}
InstallPython debug \ InstallPython debug \
%{py_INSTSONAME_debug} \ %{py_INSTSONAME_debug} \
"%{optflags_debug}" \ -O0 \
%{LDVERSION_debug} %{LDVERSION_debug}
%endif # with debug_build %endif # with debug_build
@ -1022,8 +1059,11 @@ done
# Switch all shebangs to refer to the specific Python version. # Switch all shebangs to refer to the specific Python version.
# This currently only covers files matching ^[a-zA-Z0-9_]+\.py$, # This currently only covers files matching ^[a-zA-Z0-9_]+\.py$,
# so handle files named using other naming scheme separately. # 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 \ LD_LIBRARY_PATH=./build/optimized ./build/optimized/python \
%{_rpmconfigdir}/redhat/pathfix.py \ %{SOURCE5} \
-i "%{_bindir}/python%{pybasever}" -pn \ -i "%{_bindir}/python%{pybasever}" -pn \
%{buildroot} \ %{buildroot} \
%{buildroot}%{_bindir}/*%{pybasever}.py \ %{buildroot}%{_bindir}/*%{pybasever}.py \
@ -1125,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} rm ${directory}/{__pycache__/${module}.cpython-%{pyshortver}.opt-?.pyc,${module}.py}
done 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 # Checks for packaging issues
# ====================================================== # ======================================================
@ -1209,10 +1272,119 @@ CheckPython optimized
%endif # with tests %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} %files -n %{pkgname}
%doc README.rst %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} %if %{with main_python}
%{_bindir}/pydoc* %{_bindir}/pydoc*
%{_bindir}/python3 %{_bindir}/python3
@ -1345,6 +1517,10 @@ CheckPython optimized
%{dynload_dir}/termios.%{SOABI_optimized}.so %{dynload_dir}/termios.%{SOABI_optimized}.so
%{dynload_dir}/unicodedata.%{SOABI_optimized}.so %{dynload_dir}/unicodedata.%{SOABI_optimized}.so
%{dynload_dir}/_uuid.%{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}/zlib.%{SOABI_optimized}.so
%{dynload_dir}/_zoneinfo.%{SOABI_optimized}.so %{dynload_dir}/_zoneinfo.%{SOABI_optimized}.so
@ -1445,6 +1621,12 @@ CheckPython optimized
%{pylibdir}/zoneinfo %{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" %if "%{_lib}" == "lib64"
%attr(0755,root,root) %dir %{_prefix}/lib/python%{pybasever} %attr(0755,root,root) %dir %{_prefix}/lib/python%{pybasever}
%attr(0755,root,root) %dir %{_prefix}/lib/python%{pybasever}/site-packages %attr(0755,root,root) %dir %{_prefix}/lib/python%{pybasever}/site-packages
@ -1494,6 +1676,9 @@ CheckPython optimized
%{_bindir}/python%{pybasever}-config %{_bindir}/python%{pybasever}-config
%{_bindir}/python%{LDVERSION_optimized}-config %{_bindir}/python%{LDVERSION_optimized}-config
%{_bindir}/python%{LDVERSION_optimized}-*-config %{_bindir}/python%{LDVERSION_optimized}-*-config
# Alternatives
%ghost %{_bindir}/python3-config
%{_libdir}/libpython%{LDVERSION_optimized}.so %{_libdir}/libpython%{LDVERSION_optimized}.so
%{_libdir}/pkgconfig/python-%{LDVERSION_optimized}.pc %{_libdir}/pkgconfig/python-%{LDVERSION_optimized}.pc
%{_libdir}/pkgconfig/python-%{LDVERSION_optimized}-embed.pc %{_libdir}/pkgconfig/python-%{LDVERSION_optimized}-embed.pc
@ -1506,6 +1691,8 @@ CheckPython optimized
%{_bindir}/idle* %{_bindir}/idle*
%else %else
%{_bindir}/idle%{pybasever} %{_bindir}/idle%{pybasever}
# Alternatives
%ghost %{_bindir}/idle3
%endif %endif
%{pylibdir}/idlelib %{pylibdir}/idlelib
@ -1539,17 +1726,7 @@ CheckPython optimized
%{dynload_dir}/_testmultiphase.%{SOABI_optimized}.so %{dynload_dir}/_testmultiphase.%{SOABI_optimized}.so
%{dynload_dir}/_testsinglephase.%{SOABI_optimized}.so %{dynload_dir}/_testsinglephase.%{SOABI_optimized}.so
%{dynload_dir}/_xxinterpchannels.%{SOABI_optimized}.so %{dynload_dir}/_xxinterpchannels.%{SOABI_optimized}.so
%{dynload_dir}/_xxsubinterpreters.%{SOABI_optimized}.so
%{dynload_dir}/_xxtestfuzz.%{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: # We don't bother splitting the debug build out into further subpackages:
# if you need it, you're probably a developer. # if you need it, you're probably a developer.
@ -1631,6 +1808,10 @@ CheckPython optimized
%{dynload_dir}/termios.%{SOABI_debug}.so %{dynload_dir}/termios.%{SOABI_debug}.so
%{dynload_dir}/unicodedata.%{SOABI_debug}.so %{dynload_dir}/unicodedata.%{SOABI_debug}.so
%{dynload_dir}/_uuid.%{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}/zlib.%{SOABI_debug}.so
%{dynload_dir}/_zoneinfo.%{SOABI_debug}.so %{dynload_dir}/_zoneinfo.%{SOABI_debug}.so
@ -1667,11 +1848,7 @@ CheckPython optimized
%{dynload_dir}/_testmultiphase.%{SOABI_debug}.so %{dynload_dir}/_testmultiphase.%{SOABI_debug}.so
%{dynload_dir}/_testsinglephase.%{SOABI_debug}.so %{dynload_dir}/_testsinglephase.%{SOABI_debug}.so
%{dynload_dir}/_xxinterpchannels.%{SOABI_debug}.so %{dynload_dir}/_xxinterpchannels.%{SOABI_debug}.so
%{dynload_dir}/_xxsubinterpreters.%{SOABI_debug}.so
%{dynload_dir}/_xxtestfuzz.%{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}/_sysconfigdata_%{ABIFLAGS_debug}_linux_%{platform_triplet}.py
%{pylibdir}/__pycache__/_sysconfigdata_%{ABIFLAGS_debug}_linux_%{platform_triplet}%{bytecode_suffixes} %{pylibdir}/__pycache__/_sysconfigdata_%{ABIFLAGS_debug}_linux_%{platform_triplet}%{bytecode_suffixes}
@ -1699,53 +1876,6 @@ CheckPython optimized
# ====================================================== # ======================================================
%changelog %changelog
* 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 * Mon Feb 19 2024 Charalampos Stratakis <cstratak@redhat.com> - 3.12.1-4
- Add Red Hat configuration for CVE-2007-4559 - Add Red Hat configuration for CVE-2007-4559
@ -1802,3 +1932,4 @@ Resolves: RHEL-33690
Ville Skyttä <ville.skytta@iki.fi> Ville Skyttä <ville.skytta@iki.fi>
Yaakov Selkowitz <yselkowi@redhat.com> Yaakov Selkowitz <yselkowi@redhat.com>
Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>

View File

@ -1,8 +0,0 @@
--- !Policy
product_versions:
- rhel-9
decision_context: osci_compose_gate
rules:
- !PassingTestCaseRule {test_case_name: osci.brew-build.tier0.functional}

View File

@ -1,37 +0,0 @@
# exclude test XML data (not always valid) from XML validity check:
xml:
ignore:
- /usr/lib*/python*/test/xmltestdata/*
- /usr/lib*/python*/test/xmltestdata/*/*
# exclude _socket from ipv4 only functions check, it has both ipv4 and ipv6 only
badfuncs:
allowed:
/usr/lib*/python*/lib-dynload/_socket.*:
- inet_aton
- inet_ntoa
# exclude the debug build from annocheck entirely
annocheck:
ignore:
- /usr/bin/python*d
- /usr/lib*/libpython*d.so.1.0
- /usr/lib*/python*/lib-dynload/*.cpython-*d-*-*-*.so
# don't report changed content of compiled files
# that is expected with every toolchain update and not reproducible yet
changedfiles:
# note that this is a posix regex, so no \d
exclude_path: (\.so(\.[0-9]+(\.[0-9]+)?)?$|^/usr/bin/python[0-9]+\.[0-9]+d?m?$)
# files change size all the time, we don't need to VERIFY it
# however, the INFO is useful, so we don't disable the check entirely
filesize:
# artificially large number, TODO a better way
size_threshold: 100000
# completely disabled inspections:
inspections:
# we know about our patches, no need to report anything
patches: off

View File

@ -1,106 +0,0 @@
Filters = [
# KNOWN BUGS:
# https://bugzilla.redhat.com/show_bug.cgi?id=1489816
'crypto-policy-non-compliance-openssl',
# TESTS:
'(zero-length|pem-certificate|uncompressed-zip) /usr/lib(64)?/python3\.\d+/test',
# OTHER DELIBERATES:
# chroot function
'missing-call-to-chdir-with-chroot',
# gethostbyname function calls gethostbyname
'(E|W): binary-or-shlib-calls-gethostbyname /usr/lib(64)?/python3\.\d+/lib-dynload/_socket\.',
# intentionally unversioned and selfobsoleted
'unversioned-explicit-obsoletes python',
'unversioned Obsoletes: Obsoletes: python3\.\d+$',
'self-obsoletion python3\.\d+(-\S+)? obsoletes python3\.\d+(-\S+)?',
# intentionally hardcoded
'hardcoded-library-path in %{_prefix}/lib/(debug/%{_libdir}|python%{pybasever})',
# we have non binary stuff, python files
'only-non-binary-in-usr-lib',
# some devel files that are deliberately needed
'devel-file-in-non-devel-package /usr/include/python3\.\d+m?/pyconfig-(32|64)\.h',
'devel-file-in-non-devel-package /usr/lib(64)?/python3\.\d+/distutils/tests/xxmodule\.c',
# ...or are used as test data
'devel-file-in-non-devel-package /usr/lib(64)?/python3\.\d+/test',
# some bytecode is shipped without sources on purpose, as a space optimization
# if this regex needs to be relaxed in the future, make sure it **does not** match pyc files in __pycache__
'python-bytecode-without-source /usr/lib(64)?/python3\.\d+/(encodings|pydoc_data)/[^/]+.pyc',
# DUPLICATE FILES
# test data are often duplicated
'(E|W): files-duplicate /usr/lib(64)?/python3\.\d+/(test|__phello__)/',
# duplicated inits or mains are also common
'(E|W): files-duplicate .+__init__\.py.+__init__\.py',
'(E|W): files-duplicate .+__main__\.py.+__main__\.py',
# files in the debugsource package
'(E|W): files-duplicate /usr/src/debug',
# general waste report
'(E|W): files-duplicated-waste',
# SORRY, NOT SORRY:
# manual pages
'no-manual-page-for-binary (idle|pydoc|pyvenv|2to3|python3?-debug|pathfix|msgfmt|pygettext)',
'no-manual-page-for-binary python3?.*-config$',
'no-manual-page-for-binary python3\.\d+dm?$',
# missing documentation from subpackages
'^python3(\.\d+)?-(debug|tkinter|test|idle)\.[^:]+: (E|W): no-documentation',
# platform python is obsoleted, but not provided
'obsolete-not-provided platform-python',
# we have extra tokens at the end of %endif/%else directives, we consider them useful
'extra tokens at the end of %(endif|else) directive',
# RPMLINT IMPERFECTIONS
# https://github.com/rpm-software-management/rpmlint/issues/780
'/usr/lib/debug',
# we provide python(abi) manually to be sure. createrepo will merge this with the automatic
'python3(\.\d+)?\.[^:-]+: (E|W): useless-provides python\(abi\)',
# debugsource and debuginfo have no docs
'^python3(\.\d+)?-debug(source|info)\.[^:]+: (E|W): no-documentation',
# this is OK for F28+
'library-without-ldconfig-post',
# debug package contains devel and non-devel files
'python3(\.\d+)?-debug\.[^:]+: (E|W): (non-)?devel-file-in-(non-)?devel-package',
# this goes to other subpackage, hence not actually dangling
'dangling-relative-symlink /usr/bin/python python3',
'dangling-relative-symlink /usr/share/man/man1/python\.1\.gz python3\.1\.gz',
'dangling-relative-symlink /usr/lib(64)?/pkgconfig/python-3\.\d+dm?(-embed)?\.pc python-3\.\d+(-embed)?\.pc',
# the python-unversioned-command package contains dangling symlinks by design
'^python-unversioned-command\.[^:]+: (E|W): dangling-relative-symlink (/usr/bin/python \./python3|/usr/share/man/man1/python\.1\S* ./python3\.1\S*)$',
# we need this macro to evaluate, even if the line starts with #
'macro-in-comment %\{_pyconfig(32|64)_h\}',
# Python modules don't need to be linked against libc
# Since 3.8 they are no longer linked against libpython3.8.so.1.0
'(E|W): library-not-linked-against-libc /usr/lib(64)?/python3\.\d+/lib-dynload/',
'(E|W): shared-lib(rary)?-without-dependency-information /usr/lib(64)?/python3\.\d+/lib-dynload/',
# specfile-errors are listed twice, once with reason and once without
# we filter out the empty ones
'\bpython3(\.\d+)?\.(src|spec): (E|W): specfile-error\s+$',
# SPELLING ERRORS
'spelling-error .* en_US (bytecode|pyc|filename|tkinter|namespaces|pytest) ',
]

View File

@ -1,2 +0,0 @@
SHA512 (Python-3.12.6.tar.xz) = e658b0d59b5cfdc591d626e8282b9945759f27ee6fbc8bcb8670737db32ffc11fb832dfed9b0e80188fb5f7f3f39fe6dd6191ab7736376453c9e248321e9b063
SHA512 (Python-3.12.6.tar.xz.asc) = 91a15bb7e8dd26616a2cdabe69c3ee81668cc67cb55a88b2be20433d24c9f8ae41c8f93f67aff2fa5858cd5b94600409cd472bd437a2fd33153483734ecd863f

View File

@ -1 +0,0 @@
1

View File

@ -1,4 +0,0 @@
---
standard-inventory-qcow2:
qemu:
m: 3G # Amount of VM memory

View File

@ -1,64 +0,0 @@
---
- hosts: localhost
tags:
- classic
tasks:
- dnf:
name: "*"
state: latest
- hosts: localhost
roles:
- role: standard-test-basic
tags:
- classic
repositories:
- repo: "https://src.fedoraproject.org/tests/python.git"
dest: "python"
pybasever: "3.12"
tests:
- rpm_qa:
run: rpm -qa
- smoke:
dir: python/smoke
run: "VERSION={{ pybasever }} ./venv.sh"
- smoke_virtualenv:
dir: python/smoke
run: "VERSION={{ pybasever }} METHOD=virtualenv ./venv.sh"
- debugsmoke:
dir: python/smoke
run: "PYTHON=python{{ pybasever }}d TOX=false VERSION={{ pybasever }} ./venv.sh"
- selftest:
dir: python/selftest
run: "VERSION={{ pybasever }} X='-i test_check_probes' ./parallel.sh"
- debugtest:
dir: python/selftest
run: "VERSION={{ pybasever }} PYTHON=python{{ pybasever }}d X='-i test_check_probes' ./parallel.sh"
- optimizedflags_self:
dir: python/flags
run: "python{{ pybasever }} ./assertflags.py -O3 PY_BUILTIN_MODULE_CFLAGS PY_CFLAGS_NODIST PY_CORE_CFLAGS PY_STDMODULE_CFLAGS"
- optimizedflags_3rd:
dir: python/flags
run: "python{{ pybasever }} ./assertflags.py -O2 CFLAGS PY_CFLAGS"
- debugflags:
dir: python/flags
run: "python{{ pybasever }}d ./assertflags.py -O0"
- marshalparser:
dir: python/marshalparser
run: "VERSION={{ pybasever }} SAMPLE=10 test_marshalparser_compatibility.sh"
required_packages:
- 'https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm'
- 'https://dl.fedoraproject.org/pub/epel/epel-next-release-latest-9.noarch.rpm'
- gcc # for extension building in venv and selftest
- gcc-c++ # for test_cppext
- gdb # for test_gdb
- "python{{ pybasever }}" # the test subject
- "python{{ pybasever }}-debug" # for leak testing
- "python{{ pybasever }}-devel" # for extension building in venv and selftest
- "python{{ pybasever }}-tkinter" # for selftest
- "python{{ pybasever }}-test" # for selftest
- tox # for venv tests
- virtualenv # for virtualenv tests
- glibc-all-langpacks # for locale tests
- marshalparser # for testing compatibility (magic numbers) with marshalparser
- rpm # for debugging