import python3.11-3.11.7-1.el9

c9-beta imports/c9-beta/python3.11-3.11.7-1.el9
MSVSphere Packaging Team 8 months ago
parent 7c01f56f5b
commit 0910409026

2
.gitignore vendored

@ -1 +1 @@
SOURCES/Python-3.11.4.tar.xz SOURCES/Python-3.11.7.tar.xz

@ -1 +1 @@
413b3715d919a7b473281529ab91eeea5c82e632 SOURCES/Python-3.11.4.tar.xz f2534d591121f3845388fbdd6a121b96dfe305a6 SOURCES/Python-3.11.7.tar.xz

@ -1,4 +1,4 @@
From c96f1bea2ffc5c0ca849d5406236c07ea229a64f Mon Sep 17 00:00:00 2001 From ecc5137120f471c22ff6dcb1bd128561c31e023c Mon Sep 17 00:00:00 2001
From: Charalampos Stratakis <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/7] Expose blake2b and blake2s hashes from OpenSSL Subject: [PATCH 1/7] Expose blake2b and blake2s hashes from OpenSSL
@ -29,10 +29,10 @@ index 67becdd..6607ef7 100644
computed = m.hexdigest() if not shake else m.hexdigest(length) computed = m.hexdigest() if not shake else m.hexdigest(length)
self.assertEqual( self.assertEqual(
diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
index 3c40f09..e819d02 100644 index 57d64bd..d0c3b9e 100644
--- a/Modules/_hashopenssl.c --- a/Modules/_hashopenssl.c
+++ b/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c
@@ -1077,6 +1077,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj, @@ -1078,6 +1078,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj,
} }
@ -74,7 +74,7 @@ index 3c40f09..e819d02 100644
#ifdef PY_OPENSSL_HAS_SHA3 #ifdef PY_OPENSSL_HAS_SHA3
/*[clinic input] /*[clinic input]
@@ -2065,6 +2100,8 @@ static struct PyMethodDef EVP_functions[] = { @@ -2066,6 +2101,8 @@ static struct PyMethodDef EVP_functions[] = {
_HASHLIB_OPENSSL_SHA256_METHODDEF _HASHLIB_OPENSSL_SHA256_METHODDEF
_HASHLIB_OPENSSL_SHA384_METHODDEF _HASHLIB_OPENSSL_SHA384_METHODDEF
_HASHLIB_OPENSSL_SHA512_METHODDEF _HASHLIB_OPENSSL_SHA512_METHODDEF
@ -205,10 +205,10 @@ index 5d84f4a..011026a 100644
-/*[clinic end generated code: output=69f2374071bff707 input=a9049054013a1b77]*/ -/*[clinic end generated code: output=69f2374071bff707 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=c6a9af5563972eda input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c6a9af5563972eda input=a9049054013a1b77]*/
-- --
2.39.1 2.43.0
From 9a7e164840aa35602e1c6dddadd461fafc666a63 Mon Sep 17 00:00:00 2001 From 0198d467525e79cb4be4418708719af3eaee7a40 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori@redhat.com> From: Petr Viktorin <pviktori@redhat.com>
Date: Thu, 1 Aug 2019 17:57:05 +0200 Date: Thu, 1 Aug 2019 17:57:05 +0200
Subject: [PATCH 2/7] Use a stronger hash in multiprocessing handshake Subject: [PATCH 2/7] Use a stronger hash in multiprocessing handshake
@ -220,10 +220,10 @@ https://bugs.python.org/issue17258
1 file changed, 6 insertions(+), 2 deletions(-) 1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py
index b08144f..0497557 100644 index 8b81f99..69c0b7e 100644
--- a/Lib/multiprocessing/connection.py --- a/Lib/multiprocessing/connection.py
+++ b/Lib/multiprocessing/connection.py +++ b/Lib/multiprocessing/connection.py
@@ -42,6 +42,10 @@ BUFSIZE = 8192 @@ -43,6 +43,10 @@ BUFSIZE = 8192
# A very generous timeout when it comes to local connections... # A very generous timeout when it comes to local connections...
CONNECTION_TIMEOUT = 20. CONNECTION_TIMEOUT = 20.
@ -234,7 +234,7 @@ index b08144f..0497557 100644
_mmap_counter = itertools.count() _mmap_counter = itertools.count()
default_family = 'AF_INET' default_family = 'AF_INET'
@@ -735,7 +739,7 @@ def deliver_challenge(connection, authkey): @@ -752,7 +756,7 @@ def deliver_challenge(connection, authkey):
"Authkey must be bytes, not {0!s}".format(type(authkey))) "Authkey must be bytes, not {0!s}".format(type(authkey)))
message = os.urandom(MESSAGE_LENGTH) message = os.urandom(MESSAGE_LENGTH)
connection.send_bytes(CHALLENGE + message) connection.send_bytes(CHALLENGE + message)
@ -243,7 +243,7 @@ index b08144f..0497557 100644
response = connection.recv_bytes(256) # reject large message response = connection.recv_bytes(256) # reject large message
if response == digest: if response == digest:
connection.send_bytes(WELCOME) connection.send_bytes(WELCOME)
@@ -751,7 +755,7 @@ def answer_challenge(connection, authkey): @@ -768,7 +772,7 @@ def answer_challenge(connection, authkey):
message = connection.recv_bytes(256) # reject large message message = connection.recv_bytes(256) # reject large message
assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message
message = message[len(CHALLENGE):] message = message[len(CHALLENGE):]
@ -253,10 +253,10 @@ index b08144f..0497557 100644
response = connection.recv_bytes(256) # reject large message response = connection.recv_bytes(256) # reject large message
if response != WELCOME: if response != WELCOME:
-- --
2.39.1 2.43.0
From 10b91783a2f22153738c5658a98daf7475ad9a8c Mon Sep 17 00:00:00 2001 From a7822e2e1f21529e9730885bd8c9c6ab7c704d5b Mon Sep 17 00:00:00 2001
From: Petr Viktorin <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 3/7] Disable Python's hash implementations in FIPS mode, Subject: [PATCH 3/7] Disable Python's hash implementations in FIPS mode,
@ -359,7 +359,7 @@ index c2cac98..55b1677 100644
if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE)
diff --git a/Modules/_blake2/blake2module.c b/Modules/_blake2/blake2module.c diff --git a/Modules/_blake2/blake2module.c b/Modules/_blake2/blake2module.c
index 44d783b..d247e44 100644 index 93478f5..e3a024d 100644
--- a/Modules/_blake2/blake2module.c --- a/Modules/_blake2/blake2module.c
+++ b/Modules/_blake2/blake2module.c +++ b/Modules/_blake2/blake2module.c
@@ -13,6 +13,7 @@ @@ -13,6 +13,7 @@
@ -370,7 +370,7 @@ index 44d783b..d247e44 100644
#include "blake2module.h" #include "blake2module.h"
extern PyType_Spec blake2b_type_spec; extern PyType_Spec blake2b_type_spec;
@@ -77,6 +78,7 @@ _blake2_free(void *module) @@ -83,6 +84,7 @@ _blake2_free(void *module)
static int static int
blake2_exec(PyObject *m) blake2_exec(PyObject *m)
{ {
@ -378,7 +378,7 @@ index 44d783b..d247e44 100644
Blake2State* st = blake2_get_state(m); Blake2State* st = blake2_get_state(m);
st->blake2b_type = (PyTypeObject *)PyType_FromModuleAndSpec( st->blake2b_type = (PyTypeObject *)PyType_FromModuleAndSpec(
@@ -145,5 +147,6 @@ static struct PyModuleDef blake2_module = { @@ -154,5 +156,6 @@ static struct PyModuleDef blake2_module = {
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__blake2(void) PyInit__blake2(void)
{ {
@ -446,10 +446,10 @@ index 56ae7a5..45fb403 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 c62a565..861f7a0 100644 index 52d5c1f..56aff78 100644
--- a/configure.ac --- a/configure.ac
+++ b/configure.ac +++ b/configure.ac
@@ -7044,7 +7044,8 @@ PY_STDLIB_MOD([_sha512], [test "$with_builtin_sha512" = yes]) @@ -7069,7 +7069,8 @@ PY_STDLIB_MOD([_sha512], [test "$with_builtin_sha512" = yes])
PY_STDLIB_MOD([_sha3], [test "$with_builtin_sha3" = yes]) PY_STDLIB_MOD([_sha3], [test "$with_builtin_sha3" = yes])
PY_STDLIB_MOD([_blake2], PY_STDLIB_MOD([_blake2],
[test "$with_builtin_blake2" = yes], [], [test "$with_builtin_blake2" = yes], [],
@ -460,10 +460,10 @@ index c62a565..861f7a0 100644
PY_STDLIB_MOD([_crypt], PY_STDLIB_MOD([_crypt],
[], [test "$ac_cv_crypt_crypt" = yes], [], [test "$ac_cv_crypt_crypt" = yes],
-- --
2.39.1 2.43.0
From e26066b1c05c9768e38cb6f45d6a01058de55b3f Mon Sep 17 00:00:00 2001 From e9ce6d33544559172dbebbe0c0dfba2757c62331 Mon Sep 17 00:00:00 2001
From: Charalampos Stratakis <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 4/7] Use python's fall back crypto implementations only if we Subject: [PATCH 4/7] Use python's fall back crypto implementations only if we
@ -623,10 +623,10 @@ index 01d12f5..a7cdb07 100644
def test_pbkdf2_hmac_py(self): def test_pbkdf2_hmac_py(self):
with warnings_helper.check_warnings(): with warnings_helper.check_warnings():
-- --
2.39.1 2.43.0
From 9ccbd22b8538fee379717c8b2916dc1ff8b96f07 Mon Sep 17 00:00:00 2001 From 641c617775b6973ed84711a2602ba190fe064474 Mon Sep 17 00:00:00 2001
From: Charalampos Stratakis <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 5/7] Test equivalence of hashes for the various digests with Subject: [PATCH 5/7] Test equivalence of hashes for the various digests with
@ -783,10 +783,10 @@ index a7cdb07..c071f28 100644
class KDFTests(unittest.TestCase): class KDFTests(unittest.TestCase):
-- --
2.39.1 2.43.0
From c3b8d6ecc76c87e8b05fd2cb212d5dece50ce0b1 Mon Sep 17 00:00:00 2001 From a706c8342f0f9307d44c43c203702e1476fe73b4 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <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 6/7] Guard against Python HMAC in FIPS mode Subject: [PATCH 6/7] Guard against Python HMAC in FIPS mode
@ -844,7 +844,7 @@ index 8b4f920..20ef96c 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 7cf9973..a9e4e39 100644 index a39a2c4..0742a1c 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
@ -875,7 +875,7 @@ index 7cf9973..a9e4e39 100644
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.simplefilter('error', RuntimeWarning) warnings.simplefilter('error', RuntimeWarning)
with self.assertRaises(RuntimeWarning): with self.assertRaises(RuntimeWarning):
@@ -443,6 +450,7 @@ class ConstructorTestCase(unittest.TestCase): @@ -453,6 +460,7 @@ class ConstructorTestCase(unittest.TestCase):
with self.assertRaisesRegex(TypeError, "immutable type"): with self.assertRaisesRegex(TypeError, "immutable type"):
C_HMAC.value = None C_HMAC.value = None
@ -883,7 +883,7 @@ index 7cf9973..a9e4e39 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)
@@ -471,6 +479,7 @@ class SanityTestCase(unittest.TestCase): @@ -481,6 +489,7 @@ class SanityTestCase(unittest.TestCase):
class CopyTestCase(unittest.TestCase): class CopyTestCase(unittest.TestCase):
@ -891,7 +891,7 @@ index 7cf9973..a9e4e39 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.
@@ -482,6 +491,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.")
@ -900,10 +900,10 @@ index 7cf9973..a9e4e39 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.39.1 2.43.0
From 2b06ee89344e8735cdc8435aadbdf83fe289e934 Mon Sep 17 00:00:00 2001 From 03f1dedfe5d29af20fb3686d76b045384d41d8dd Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com> From: Petr Viktorin <encukou@gmail.com>
Date: Wed, 25 Aug 2021 16:44:43 +0200 Date: Wed, 25 Aug 2021 16:44:43 +0200
Subject: [PATCH 7/7] Disable hash-based PYCs in FIPS mode Subject: [PATCH 7/7] Disable hash-based PYCs in FIPS mode
@ -946,11 +946,11 @@ index db52725..5fca65e 100644
return PycInvalidationMode.CHECKED_HASH return PycInvalidationMode.CHECKED_HASH
else: else:
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index c33f90d..7d40540 100644 index dc7a6e6..646b328 100644
--- a/Lib/test/support/__init__.py --- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py
@@ -2225,6 +2225,20 @@ def requires_venv_with_pip(): @@ -2203,6 +2203,20 @@ def sleeping_retry(timeout, err_msg=None, /,
return unittest.skipUnless(ctypes, 'venv: pip requires ctypes') delay = min(delay * 2, max_delay)
+def fails_in_fips_mode(expected_error): +def fails_in_fips_mode(expected_error):
@ -971,7 +971,7 @@ index c33f90d..7d40540 100644
def adjust_int_max_str_digits(max_digits): def adjust_int_max_str_digits(max_digits):
"""Temporarily change the integer string conversion length limit.""" """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 diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
index 4dadbc0..7dc7e51 100644 index 7fcd563..476b557 100644
--- a/Lib/test/test_cmd_line_script.py --- a/Lib/test/test_cmd_line_script.py
+++ b/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py
@@ -286,6 +286,7 @@ class CmdLineTest(unittest.TestCase): @@ -286,6 +286,7 @@ class CmdLineTest(unittest.TestCase):
@ -991,10 +991,10 @@ index 4dadbc0..7dc7e51 100644
with os_helper.temp_dir() as script_dir: with os_helper.temp_dir() as script_dir:
script_name = _make_test_script(script_dir, '__main__') script_name = _make_test_script(script_dir, '__main__')
diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
index 05154c8..c678d4a 100644 index 9cd92ad..4ec29a1 100644
--- a/Lib/test/test_compileall.py --- a/Lib/test/test_compileall.py
+++ b/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py
@@ -800,14 +800,23 @@ class CommandLineTestsBase: @@ -806,14 +806,23 @@ class CommandLineTestsBase:
out = self.assertRunOK('badfilename') out = self.assertRunOK('badfilename')
self.assertRegex(out, b"Can't list 'badfilename'") self.assertRegex(out, b"Can't list 'badfilename'")
@ -1020,10 +1020,10 @@ index 05154c8..c678d4a 100644
with open(pyc, 'rb') as fp: with open(pyc, 'rb') as fp:
data = fp.read() data = fp.read()
diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py
index 4bb0390..ff62483 100644 index 4062afd..6bc276d 100644
--- a/Lib/test/test_imp.py --- a/Lib/test/test_imp.py
+++ b/Lib/test/test_imp.py +++ b/Lib/test/test_imp.py
@@ -350,6 +350,7 @@ class ImportTests(unittest.TestCase): @@ -352,6 +352,7 @@ class ImportTests(unittest.TestCase):
import _frozen_importlib import _frozen_importlib
self.assertEqual(_frozen_importlib.__spec__.origin, "frozen") self.assertEqual(_frozen_importlib.__spec__.origin, "frozen")
@ -1031,7 +1031,7 @@ index 4bb0390..ff62483 100644
def test_source_hash(self): def test_source_hash(self):
self.assertEqual(_imp.source_hash(42, b'hi'), b'\xfb\xd9G\x05\xaf$\x9b~') self.assertEqual(_imp.source_hash(42, b'hi'), b'\xfb\xd9G\x05\xaf$\x9b~')
self.assertEqual(_imp.source_hash(43, b'hi'), b'\xd0/\x87C\xccC\xff\xe2') self.assertEqual(_imp.source_hash(43, b'hi'), b'\xd0/\x87C\xccC\xff\xe2')
@@ -369,6 +370,7 @@ class ImportTests(unittest.TestCase): @@ -371,6 +372,7 @@ class ImportTests(unittest.TestCase):
res = script_helper.assert_python_ok(*args) res = script_helper.assert_python_ok(*args)
self.assertEqual(res.out.strip().decode('utf-8'), expected) self.assertEqual(res.out.strip().decode('utf-8'), expected)
@ -1092,10 +1092,10 @@ index 378dcbe..7b223a1 100644
with util.create_modules('_temp') as mapping: with util.create_modules('_temp') as mapping:
bc_path = self.manipulate_bytecode( bc_path = self.manipulate_bytecode(
diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py
index e53f5d9..7266212 100644 index 9b420d2..dd6460a 100644
--- a/Lib/test/test_py_compile.py --- a/Lib/test/test_py_compile.py
+++ b/Lib/test/test_py_compile.py +++ b/Lib/test/test_py_compile.py
@@ -141,13 +141,16 @@ class PyCompileTestsBase: @@ -143,13 +143,16 @@ class PyCompileTestsBase:
importlib.util.cache_from_source(bad_coding))) importlib.util.cache_from_source(bad_coding)))
def test_source_date_epoch(self): def test_source_date_epoch(self):
@ -1113,7 +1113,7 @@ index e53f5d9..7266212 100644
expected_flags = 0b11 expected_flags = 0b11
else: else:
expected_flags = 0b00 expected_flags = 0b00
@@ -178,7 +181,8 @@ class PyCompileTestsBase: @@ -180,7 +183,8 @@ class PyCompileTestsBase:
# Specifying optimized bytecode should lead to a path reflecting that. # Specifying optimized bytecode should lead to a path reflecting that.
self.assertIn('opt-2', py_compile.compile(self.source_path, optimize=2)) self.assertIn('opt-2', py_compile.compile(self.source_path, optimize=2))
@ -1123,7 +1123,7 @@ index e53f5d9..7266212 100644
py_compile.compile( py_compile.compile(
self.source_path, self.source_path,
invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH, invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH,
@@ -187,6 +191,9 @@ class PyCompileTestsBase: @@ -189,6 +193,9 @@ class PyCompileTestsBase:
flags = importlib._bootstrap_external._classify_pyc( flags = importlib._bootstrap_external._classify_pyc(
fp.read(), 'test', {}) fp.read(), 'test', {})
self.assertEqual(flags, 0b11) self.assertEqual(flags, 0b11)
@ -1154,10 +1154,10 @@ index 59a5200..81fadb3 100644
def test_checked_hash_based_change_pyc(self): def test_checked_hash_based_change_pyc(self):
source = b"state = 'old'" source = b"state = 'old'"
diff --git a/Python/import.c b/Python/import.c diff --git a/Python/import.c b/Python/import.c
index 07a8b90..e97b47b 100644 index 39144d3..b439059 100644
--- a/Python/import.c --- a/Python/import.c
+++ b/Python/import.c +++ b/Python/import.c
@@ -2437,6 +2437,26 @@ static PyObject * @@ -2449,6 +2449,26 @@ static PyObject *
_imp_source_hash_impl(PyObject *module, long key, Py_buffer *source) _imp_source_hash_impl(PyObject *module, long key, Py_buffer *source)
/*[clinic end generated code: output=edb292448cf399ea input=9aaad1e590089789]*/ /*[clinic end generated code: output=edb292448cf399ea input=9aaad1e590089789]*/
{ {
@ -1185,5 +1185,5 @@ index 07a8b90..e97b47b 100644
uint64_t x; uint64_t x;
char data[sizeof(uint64_t)]; char data[sizeof(uint64_t)];
-- --
2.39.1 2.43.0

@ -1,113 +1,3 @@
From f36519078bde3cce4328c03fffccb846121fb5bc Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com>
Date: Wed, 9 Aug 2023 20:23:03 +0200
Subject: [PATCH] Fix symlink handling for tarfile.data_filter
---
Doc/library/tarfile.rst | 5 +++++
Lib/tarfile.py | 9 ++++++++-
Lib/test/test_tarfile.py | 26 ++++++++++++++++++++++++--
3 files changed, 37 insertions(+), 3 deletions(-)
diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst
index 00f3070324e..e0511bfeb64 100644
--- a/Doc/library/tarfile.rst
+++ b/Doc/library/tarfile.rst
@@ -740,6 +740,11 @@ A ``TarInfo`` object has the following public data attributes:
Name of the target file name, which is only present in :class:`TarInfo` objects
of type :const:`LNKTYPE` and :const:`SYMTYPE`.
+ For symbolic links (``SYMTYPE``), the linkname is relative to the directory
+ that contains the link.
+ For hard links (``LNKTYPE``), the linkname is relative to the root of
+ the archive.
+
.. attribute:: TarInfo.uid
:type: int
diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index df4e41f7a0d..d62323715b4 100755
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -802,7 +802,14 @@ def _get_filtered_attrs(member, dest_path, for_data=True):
if member.islnk() or member.issym():
if os.path.isabs(member.linkname):
raise AbsoluteLinkError(member)
- target_path = os.path.realpath(os.path.join(dest_path, member.linkname))
+ if member.issym():
+ target_path = os.path.join(dest_path,
+ os.path.dirname(name),
+ member.linkname)
+ else:
+ target_path = os.path.join(dest_path,
+ member.linkname)
+ target_path = os.path.realpath(target_path)
if os.path.commonpath([target_path, dest_path]) != dest_path:
raise LinkOutsideDestinationError(member, target_path)
return new_attrs
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
index 2eda7fc4cea..79fc35c2895 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -3337,10 +3337,12 @@ def __exit__(self, *exc):
self.bio = None
def add(self, name, *, type=None, symlink_to=None, hardlink_to=None,
- mode=None, **kwargs):
+ mode=None, size=None, **kwargs):
"""Add a member to the test archive. Call within `with`."""
name = str(name)
tarinfo = tarfile.TarInfo(name).replace(**kwargs)
+ if size is not None:
+ tarinfo.size = size
if mode:
tarinfo.mode = _filemode_to_int(mode)
if symlink_to is not None:
@@ -3416,7 +3418,8 @@ def check_context(self, tar, filter):
raise self.raised_exception
self.assertEqual(self.expected_paths, set())
- def expect_file(self, name, type=None, symlink_to=None, mode=None):
+ def expect_file(self, name, type=None, symlink_to=None, mode=None,
+ size=None):
"""Check a single file. See check_context."""
if self.raised_exception:
raise self.raised_exception
@@ -3445,6 +3448,8 @@ def expect_file(self, name, type=None, symlink_to=None, mode=None):
self.assertTrue(path.is_fifo())
else:
raise NotImplementedError(type)
+ if size is not None:
+ self.assertEqual(path.stat().st_size, size)
for parent in path.parents:
self.expected_paths.discard(parent)
@@ -3649,6 +3654,22 @@ def test_sly_relative2(self):
+ """['"].*moo['"], which is outside the """
+ "destination")
+ def test_deep_symlink(self):
+ with ArchiveMaker() as arc:
+ arc.add('targetdir/target', size=3)
+ arc.add('linkdir/hardlink', hardlink_to='targetdir/target')
+ arc.add('linkdir/symlink', symlink_to='../targetdir/target')
+
+ for filter in 'tar', 'data', 'fully_trusted':
+ with self.check_context(arc.open(), filter):
+ self.expect_file('targetdir/target', size=3)
+ self.expect_file('linkdir/hardlink', size=3)
+ if os_helper.can_symlink():
+ self.expect_file('linkdir/symlink', size=3,
+ symlink_to='../targetdir/target')
+ else:
+ self.expect_file('linkdir/symlink', size=3)
+
def test_modes(self):
# Test how file modes are extracted
# (Note that the modes are ignored on platforms without working chmod)
--
2.41.0
From 8b70605b594b3831331a9340ba764ff751871612 Mon Sep 17 00:00:00 2001 From 8b70605b594b3831331a9340ba764ff751871612 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com> From: Petr Viktorin <encukou@gmail.com>
Date: Mon, 6 Mar 2023 17:24:24 +0100 Date: Mon, 6 Mar 2023 17:24:24 +0100

@ -0,0 +1,755 @@
From d8b0fafb202bf884135a3f7f0ce0b086217a2da2 Mon Sep 17 00:00:00 2001
From: Victor Stinner <vstinner@python.org>
Date: Fri, 15 Dec 2023 16:10:40 +0100
Subject: [PATCH 1/2] 00415: [CVE-2023-27043] gh-102988: Reject malformed
addresses in email.parseaddr() (#111116)
Detect email address parsing errors and return empty tuple to
indicate the parsing error (old API). Add an optional 'strict'
parameter to getaddresses() and parseaddr() functions. Patch by
Thomas Dwyer.
Co-Authored-By: Thomas Dwyer <github@tomd.tel>
---
Doc/library/email.utils.rst | 19 +-
Lib/email/utils.py | 151 ++++++++++++-
Lib/test/test_email/test_email.py | 204 +++++++++++++++++-
...-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8 +
4 files changed, 361 insertions(+), 21 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst
index 0e266b6..6723dc4 100644
--- a/Doc/library/email.utils.rst
+++ b/Doc/library/email.utils.rst
@@ -60,13 +60,18 @@ of the new API.
begins with angle brackets, they are stripped off.
-.. function:: parseaddr(address)
+.. function:: parseaddr(address, *, strict=True)
Parse address -- which should be the value of some address-containing field such
as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and
*email address* parts. Returns a tuple of that information, unless the parse
fails, in which case a 2-tuple of ``('', '')`` is returned.
+ If *strict* is true, use a strict parser which rejects malformed inputs.
+
+ .. versionchanged:: 3.13
+ Add *strict* optional parameter and reject malformed inputs by default.
+
.. function:: formataddr(pair, charset='utf-8')
@@ -84,12 +89,15 @@ of the new API.
Added the *charset* option.
-.. function:: getaddresses(fieldvalues)
+.. function:: getaddresses(fieldvalues, *, strict=True)
This method returns a list of 2-tuples of the form returned by ``parseaddr()``.
*fieldvalues* is a sequence of header field values as might be returned by
- :meth:`Message.get_all <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
@@ -99,6 +107,9 @@ of the new API.
resent_ccs = msg.get_all('resent-cc', [])
all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs)
+ .. versionchanged:: 3.13
+ Add *strict* optional parameter and reject malformed inputs by default.
+
.. function:: parsedate(date)
diff --git a/Lib/email/utils.py b/Lib/email/utils.py
index cfdfeb3..9522341 100644
--- a/Lib/email/utils.py
+++ b/Lib/email/utils.py
@@ -48,6 +48,7 @@ TICK = "'"
specialsre = re.compile(r'[][\\()<>@,:;".]')
escapesre = re.compile(r'[\\"]')
+
def _has_surrogates(s):
"""Return True if s contains surrogate-escaped binary data."""
# This check is based on the fact that unless there are surrogates, utf8
@@ -106,12 +107,127 @@ def formataddr(pair, charset='utf-8'):
return address
+def _iter_escaped_chars(addr):
+ pos = 0
+ escape = False
+ for pos, ch in enumerate(addr):
+ if escape:
+ yield (pos, '\\' + ch)
+ escape = False
+ elif ch == '\\':
+ escape = True
+ else:
+ yield (pos, ch)
+ if escape:
+ yield (pos, '\\')
+
+
+def _strip_quoted_realnames(addr):
+ """Strip real names between quotes."""
+ if '"' not in addr:
+ # Fast path
+ return addr
+
+ start = 0
+ open_pos = None
+ result = []
+ for pos, ch in _iter_escaped_chars(addr):
+ if ch == '"':
+ if open_pos is None:
+ open_pos = pos
+ else:
+ if start != open_pos:
+ result.append(addr[start:open_pos])
+ start = pos + 1
+ open_pos = None
+
+ if start < len(addr):
+ result.append(addr[start:])
+
+ return ''.join(result)
-def getaddresses(fieldvalues):
- """Return a list of (REALNAME, EMAIL) for each fieldvalue."""
- all = COMMASPACE.join(str(v) for v in fieldvalues)
- a = _AddressList(all)
- return a.addresslist
+
+supports_strict_parsing = True
+
+def getaddresses(fieldvalues, *, strict=True):
+ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
+
+ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
+ its place.
+
+ If strict is true, use a strict parser which rejects malformed inputs.
+ """
+
+ # If strict is true, if the resulting list of parsed addresses is greater
+ # than the number of fieldvalues in the input list, a parsing error has
+ # occurred and consequently a list containing a single empty 2-tuple [('',
+ # '')] is returned in its place. This is done to avoid invalid output.
+ #
+ # Malformed input: getaddresses(['alice@example.com <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 677f209..20b6779 100644
--- a/Lib/test/test_email/test_email.py
+++ b/Lib/test/test_email/test_email.py
@@ -17,6 +17,7 @@ from unittest.mock import patch
import email
import email.policy
+import email.utils
from email.charset import Charset
from email.generator import Generator, DecodedGenerator, BytesGenerator
@@ -3321,15 +3322,154 @@ Foo
[('Al Person', 'aperson@dom.ain'),
('Bud Person', 'bperson@dom.ain')])
+ def test_getaddresses_comma_in_name(self):
+ """GH-106669 regression test."""
+ self.assertEqual(
+ utils.getaddresses(
+ [
+ '"Bud, Person" <bperson@dom.ain>',
+ 'aperson@dom.ain (Al Person)',
+ '"Mariusz Felisiak" <to@example.com>',
+ ]
+ ),
+ [
+ ('Bud, Person', 'bperson@dom.ain'),
+ ('Al Person', 'aperson@dom.ain'),
+ ('Mariusz Felisiak', 'to@example.com'),
+ ],
+ )
+
+ def test_parsing_errors(self):
+ """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056"""
+ alice = 'alice@example.org'
+ bob = 'bob@example.com'
+ empty = ('', '')
+
+ # Test utils.getaddresses() and utils.parseaddr() on malformed email
+ # addresses: default behavior (strict=True) rejects malformed address,
+ # and strict=False which tolerates malformed address.
+ for invalid_separator, expected_non_strict in (
+ ('(', [(f'<{bob}>', alice)]),
+ (')', [('', alice), empty, ('', bob)]),
+ ('<', [('', alice), empty, ('', bob), empty]),
+ ('>', [('', alice), empty, ('', bob)]),
+ ('[', [('', f'{alice}[<{bob}>]')]),
+ (']', [('', alice), empty, ('', bob)]),
+ ('@', [empty, empty, ('', bob)]),
+ (';', [('', alice), empty, ('', bob)]),
+ (':', [('', alice), ('', bob)]),
+ ('.', [('', alice + '.'), ('', bob)]),
+ ('"', [('', alice), ('', f'<{bob}>')]),
+ ):
+ address = f'{alice}{invalid_separator}<{bob}>'
+ with self.subTest(address=address):
+ self.assertEqual(utils.getaddresses([address]),
+ [empty])
+ self.assertEqual(utils.getaddresses([address], strict=False),
+ expected_non_strict)
+
+ self.assertEqual(utils.parseaddr([address]),
+ empty)
+ self.assertEqual(utils.parseaddr([address], strict=False),
+ ('', address))
+
+ # Comma (',') is treated differently depending on strict parameter.
+ # Comma without quotes.
+ address = f'{alice},<{bob}>'
+ self.assertEqual(utils.getaddresses([address]),
+ [('', alice), ('', bob)])
+ self.assertEqual(utils.getaddresses([address], strict=False),
+ [('', alice), ('', bob)])
+ self.assertEqual(utils.parseaddr([address]),
+ empty)
+ self.assertEqual(utils.parseaddr([address], strict=False),
+ ('', address))
+
+ # Real name between quotes containing comma.
+ address = '"Alice, alice@example.org" <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"""
@@ -3520,6 +3660,54 @@ multipart/report
m = cls(*constructor, policy=email.policy.default)
self.assertIs(m.policy, email.policy.default)
+ def test_iter_escaped_chars(self):
+ self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')),
+ [(0, 'a'),
+ (2, '\\\\'),
+ (3, 'b'),
+ (5, '\\"'),
+ (6, 'c'),
+ (8, '\\\\'),
+ (9, '"'),
+ (10, 'd')])
+ self.assertEqual(list(utils._iter_escaped_chars('a\\')),
+ [(0, 'a'), (1, '\\')])
+
+ def test_strip_quoted_realnames(self):
+ def check(addr, expected):
+ self.assertEqual(utils._strip_quoted_realnames(addr), expected)
+
+ check('"Jane Doe" <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 0000000..3d0e9e4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
@@ -0,0 +1,8 @@
+:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now
+return ``('', '')`` 2-tuples in more situations where invalid email
+addresses are encountered instead of potentially inaccurate values. Add
+optional *strict* parameter to these two functions: use ``strict=False`` to
+get the old behavior, accept malformed inputs.
+``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to check
+if the *strict* paramater is available. Patch by Thomas Dwyer and Victor
+Stinner to improve the CVE-2023-27043 fix.
--
2.43.0
From 6c34f5b95da90bd494e29776c0e807af44689fae Mon Sep 17 00:00:00 2001
From: Lumir Balhar <lbalhar@redhat.com>
Date: Wed, 10 Jan 2024 08:53:53 +0100
Subject: [PATCH 2/2] Make it possible to disable strict parsing in email
module
---
Doc/library/email.utils.rst | 26 +++++++++++
Lib/email/utils.py | 54 +++++++++++++++++++++-
Lib/test/test_email/test_email.py | 74 ++++++++++++++++++++++++++++++-
3 files changed, 150 insertions(+), 4 deletions(-)
diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst
index 6723dc4..c89602d 100644
--- a/Doc/library/email.utils.rst
+++ b/Doc/library/email.utils.rst
@@ -69,6 +69,19 @@ of the new API.
If *strict* is true, use a strict parser which rejects malformed inputs.
+ The default setting for *strict* is set to ``True``, but you can override
+ it by setting the environment variable ``PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING``
+ to non-empty string.
+
+ Additionally, you can permanently set the default value for *strict* to
+ ``False`` by creating the configuration file ``/etc/python/email.cfg``
+ with the following content:
+
+ .. code-block:: ini
+
+ [email_addr_parsing]
+ PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true
+
.. versionchanged:: 3.13
Add *strict* optional parameter and reject malformed inputs by default.
@@ -97,6 +110,19 @@ of the new API.
If *strict* is true, use a strict parser which rejects malformed inputs.
+ The default setting for *strict* is set to ``True``, but you can override
+ it by setting the environment variable ``PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING``
+ to non-empty string.
+
+ Additionally, you can permanently set the default value for *strict* to
+ ``False`` by creating the configuration file ``/etc/python/email.cfg``
+ with the following content:
+
+ .. code-block:: ini
+
+ [email_addr_parsing]
+ PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true
+
Here's a simple example that gets all the recipients of a message::
from email.utils import getaddresses
diff --git a/Lib/email/utils.py b/Lib/email/utils.py
index 9522341..2e30e09 100644
--- a/Lib/email/utils.py
+++ b/Lib/email/utils.py
@@ -48,6 +48,46 @@ TICK = "'"
specialsre = re.compile(r'[][\\()<>@,:;".]')
escapesre = re.compile(r'[\\"]')
+_EMAIL_CONFIG_FILE = "/etc/python/email.cfg"
+_cached_strict_addr_parsing = None
+
+
+def _use_strict_email_parsing():
+ """"Cache implementation for _cached_strict_addr_parsing"""
+ global _cached_strict_addr_parsing
+ if _cached_strict_addr_parsing is None:
+ _cached_strict_addr_parsing = _use_strict_email_parsing_impl()
+ return _cached_strict_addr_parsing
+
+
+def _use_strict_email_parsing_impl():
+ """Returns True if strict email parsing is not disabled by
+ config file or env variable.
+ """
+ disabled = bool(os.environ.get("PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING"))
+ if disabled:
+ return False
+
+ try:
+ file = open(_EMAIL_CONFIG_FILE)
+ except FileNotFoundError:
+ pass
+ else:
+ with file:
+ import configparser
+ config = configparser.ConfigParser(
+ interpolation=None,
+ comment_prefixes=('#', ),
+
+ )
+ config.read_file(file)
+ disabled = config.getboolean('email_addr_parsing', "PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING", fallback=None)
+
+ if disabled:
+ return False
+
+ return True
+
def _has_surrogates(s):
"""Return True if s contains surrogate-escaped binary data."""
@@ -149,7 +189,7 @@ def _strip_quoted_realnames(addr):
supports_strict_parsing = True
-def getaddresses(fieldvalues, *, strict=True):
+def getaddresses(fieldvalues, *, strict=None):
"""Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
@@ -158,6 +198,11 @@ def getaddresses(fieldvalues, *, strict=True):
If strict is true, use a strict parser which rejects malformed inputs.
"""
+ # If default is used, it's True unless disabled
+ # by env variable or config file.
+ if strict == None:
+ strict = _use_strict_email_parsing()
+
# If strict is true, if the resulting list of parsed addresses is greater
# than the number of fieldvalues in the input list, a parsing error has
# occurred and consequently a list containing a single empty 2-tuple [('',
@@ -321,7 +366,7 @@ def parsedate_to_datetime(data):
tzinfo=datetime.timezone(datetime.timedelta(seconds=tz)))
-def parseaddr(addr, *, strict=True):
+def parseaddr(addr, *, strict=None):
"""
Parse addr into its constituent realname and email address parts.
@@ -330,6 +375,11 @@ def parseaddr(addr, *, strict=True):
If strict is True, use a strict parser which rejects malformed inputs.
"""
+ # If default is used, it's True unless disabled
+ # by env variable or config file.
+ if strict == None:
+ strict = _use_strict_email_parsing()
+
if not strict:
addrs = _AddressList(addr).addresslist
if not addrs:
diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
index 20b6779..d7d99f0 100644
--- a/Lib/test/test_email/test_email.py
+++ b/Lib/test/test_email/test_email.py
@@ -8,6 +8,9 @@ import base64
import unittest
import textwrap
import warnings
+import contextlib
+import tempfile
+import os
from io import StringIO, BytesIO
from itertools import chain
@@ -41,8 +44,8 @@ from email import quoprimime
from email import utils
from test import support
-from test.support import threading_helper
-from test.support.os_helper import unlink
+from test.support import threading_helper, swap_attr
+from test.support.os_helper import unlink, EnvironmentVarGuard
from test.test_email import openfile, TestEmailBase
# These imports are documented to work, but we are testing them using a
@@ -3427,6 +3430,73 @@ Foo
# Test email.utils.supports_strict_parsing attribute
self.assertEqual(email.utils.supports_strict_parsing, True)
+ def test_parsing_errors_strict_set_via_env_var(self):
+ address = 'alice@example.org )Alice('
+ empty = ('', '')
+
+ # Reset cached default value to make the function
+ # reload the config file provided below.
+ utils._cached_strict_addr_parsing = None
+
+ # Strict disabled via env variable, old behavior expected
+ with EnvironmentVarGuard() as environ:
+ environ["PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING"] = "1"
+
+ self.assertEqual(utils.getaddresses([address]),
+ [('', 'alice@example.org'), ('', ''), ('', 'Alice')])
+ self.assertEqual(utils.parseaddr([address]), ('', address))
+
+ # Clear cache again
+ utils._cached_strict_addr_parsing = None
+
+ # Default strict=True, empty result expected
+ self.assertEqual(utils.getaddresses([address]), [empty])
+ self.assertEqual(utils.parseaddr([address]), empty)
+
+ # Clear cache again
+ utils._cached_strict_addr_parsing = None
+
+ # Empty string in env variable = strict parsing enabled (default)
+ with EnvironmentVarGuard() as environ:
+ environ["PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING"] = ""
+
+ # Default strict=True, empty result expected
+ self.assertEqual(utils.getaddresses([address]), [empty])
+ self.assertEqual(utils.parseaddr([address]), empty)
+
+ @contextlib.contextmanager
+ def _email_strict_parsing_conf(self):
+ """Context for the given email strict parsing configured in config file"""
+ with tempfile.TemporaryDirectory() as tmpdirname:
+ filename = os.path.join(tmpdirname, 'conf.cfg')
+ with swap_attr(utils, "_EMAIL_CONFIG_FILE", filename):
+ with open(filename, 'w') as file:
+ file.write('[email_addr_parsing]\n')
+ file.write('PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true')
+ utils._EMAIL_CONFIG_FILE = filename
+ yield
+
+ def test_parsing_errors_strict_disabled_via_config_file(self):
+ address = 'alice@example.org )Alice('
+ empty = ('', '')
+
+ # Reset cached default value to make the function
+ # reload the config file provided below.
+ utils._cached_strict_addr_parsing = None
+
+ # Strict disabled via config file, old results expected
+ with self._email_strict_parsing_conf():
+ self.assertEqual(utils.getaddresses([address]),
+ [('', 'alice@example.org'), ('', ''), ('', 'Alice')])
+ self.assertEqual(utils.parseaddr([address]), ('', address))
+
+ # Clear cache again
+ utils._cached_strict_addr_parsing = None
+
+ # Default strict=True, empty result expected
+ self.assertEqual(utils.getaddresses([address]), [empty])
+ self.assertEqual(utils.parseaddr([address]), empty)
+
def test_getaddresses_nasty(self):
for addresses, expected in (
(['"Sürname, Firstname" <to@example.com>'],
--
2.43.0

@ -1,16 +0,0 @@
-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmR/sHIACgkQ/+h0BBaL
2EfQDQ//eFWvcQ5ijhVd3r5lp7NTNUPK6xKR2iqzpNWlN2Z4QkGJ2+IworBaZoGA
tzmbT0j0LB9ZQ+ba3xnqXGXD8Ky+fHLg8GV5yshPlH/bD7tPuHtfDRxNcWplEVSS
MbMuLjAYavTIHhYEz/Rpx4jvZTI5lwplVqj9WxNI/8tNrL5M2bsCtv+IB6brohiw
rUOUlT/KDkZbrGfB1Fe033Ep8hay5MkKjhgr7O1dU7zMuDRG+HRsCYGs7a5x6KhH
3QNTEp+GEIAKEsip5nR7vl5KqL02lHa5sf36SV2wjRTwO+IhgV7lvtJEwOD12oE5
c+TCQMFbmBXg2vVmNBN/Lwftw1SwT/+orFX6V4U93jq6QNUo4GvPqum6YzuayGYc
/JM4MNziqmfdNW2YjEHPPfzti3f40eTapys97YufOrmYjM2NY0Fs+kAErvyxiWqi
guVQtaZIYeLl/9KWqQ0F/Apy1N+fVDuWBkZlizwHrUsGips4Rp7Bh/iCrDdOj+1D
gRCio7+KvdtzHavZPZnU5dcpUiXZgsDzOTI138IyYaEtVUS59ELkA2qxI1yCb5mk
eLVG1L7r/J2tIaTcguQppp5Z+62UDTArlUbnRxda0buzA2r1aFiQCTMwp+kTRegw
T9Ht/CT/D4vpMdmSQTun9MkKifcK+2uGfSsS7Lz4fSWjQLqg36k=
=zSfJ
-----END PGP SIGNATURE-----

@ -0,0 +1,16 @@
-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmVuFigACgkQ/+h0BBaL
2EeHPg/+LU5xs2ZDrQogDcH+A1v8RyursiggypdM5hXTrsFsTCIk4iekcI9xkhG1
ltNX4UuCe5PUEbTgtaWP0ncXARrUnPCoQaQ1sHVDTYoHegancsk+sXZc1JM7qr0p
Y4Ig6mKjuHFMXCInQSI2GaH4t5r4Z1jGk/PGrecIHOPJgqfA/6Z3TBF5N+y3jEvS
2QazMB298q4RDhh9m3REe8LwFPHDlfw9eRohv0MB8xygg9KtxhLZrN7gLBQZvKGD
ihNw6EgJj5OZ0dvwKCCXnlZuwknuJW7vAOPHhYeenPdVdYCGoRSyN7JdD07L+5AG
O14l2rqZrz5Eu28by+kAUrcPYAfAXekw1PmtT3HSd9U/nqnUiTkkJcjyGG/e3cjJ
sUDKMNCSBq0G7j5DB3bB6VHkZjVuz+T+iR5QdfJ4kI2pYSuE/rUj1rhkUXApYsHl
7Wff0QbOW6QT1wCtQcMpJSzkTDVJVYxiqrko/ihlOhphDHYLdOIGOrxWAUwc06x/
BhJD6tM1kEVZvifoJp1OsNwDzZ/Ku6CUs05E1vWxdeNVeANyKAgCZ5hOVmhnv866
11zfgo/znRsMzMIyJuy0bhO0C6omVLzzfhipAbZM2jDorn37xxV0v/I0pceNtLrp
YR7Tjs7+Ihe6/oItjW53j9T7ANdgQ1RVDg98lKlPFNL+hxfctwY=
=0Pkd
-----END PGP SIGNATURE-----

@ -16,9 +16,9 @@ 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/*',
'*/test/bad_coding.py', '*/test/*/bad_coding.py',
'*/test/bad_coding2.py', '*/test/*/bad_coding2.py',
'*/test/badsyntax_*.py', '*/test/*/badsyntax_*.py',
'*/lib2to3/tests/data/bom.py', '*/lib2to3/tests/data/bom.py',
'*/lib2to3/tests/data/crlf.py', '*/lib2to3/tests/data/crlf.py',
'*/lib2to3/tests/data/different_encoding.py', '*/lib2to3/tests/data/different_encoding.py',

@ -16,11 +16,11 @@ 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}.4 %global general_version %{pybasever}.7
#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: 3%{?dist} Release: 1%{?dist}
License: Python License: Python
@ -63,7 +63,7 @@ License: Python
# 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 23.1.2 %global pip_version 23.2.1
%global setuptools_version 65.5.0 %global setuptools_version 65.5.0
# Expensive optimizations (mainly, profile-guided optimizations) # Expensive optimizations (mainly, profile-guided optimizations)
@ -342,6 +342,20 @@ Patch371: 00371-revert-bpo-1596321-fix-threading-_shutdown-for-the-main-thread-g
# - https://access.redhat.com/articles/7004769 # - https://access.redhat.com/articles/7004769
Patch397: 00397-tarfile-filter.patch Patch397: 00397-tarfile-filter.patch
# 00415 #
# [CVE-2023-27043] gh-102988: Reject malformed addresses in email.parseaddr() (#111116)
#
# Detect email address parsing errors and return empty tuple to
# indicate the parsing error (old API). Add an optional 'strict'
# parameter to getaddresses() and parseaddr() functions. Patch by
# Thomas Dwyer.
#
# Upstream PR: https://github.com/python/cpython/pull/111116
#
# Second patch implmenets the possibility to restore the old behavior via
# config file or environment variable.
Patch415: 00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch
# (New patches go here ^^^) # (New patches go here ^^^)
# #
# When adding new patches to "python" and "python3" in Fedora, EL, etc., # When adding new patches to "python" and "python3" in Fedora, EL, etc.,
@ -937,6 +951,10 @@ for tool in pygettext msgfmt; do
ln -s ${tool}%{pybasever}.py %{buildroot}%{_bindir}/${tool}3.py ln -s ${tool}%{pybasever}.py %{buildroot}%{_bindir}/${tool}3.py
done done
# Install missing test data
# Fixed upstream: https://github.com/python/cpython/pull/112784
cp -rp Lib/test/regrtestdata/ %{buildroot}%{pylibdir}/test/
# Switch all shebangs to refer to the specific Python version. # 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.
@ -1095,10 +1113,14 @@ CheckPython() {
# test_freeze_simple_script is skipped, because it fails when bundled wheels # test_freeze_simple_script is skipped, because it fails when bundled wheels
# are removed in Fedora. # are removed in Fedora.
# upstream report: https://bugs.python.org/issue45783 # upstream report: https://bugs.python.org/issue45783
# test_check_probes is failing since it was introduced in 3.11.5,
# the test is skipped until it is fixed in upstream.
# see: https://github.com/python/cpython/issues/104280#issuecomment-1669249980
LD_LIBRARY_PATH=$ConfDir $ConfDir/python -m test.regrtest \ LD_LIBRARY_PATH=$ConfDir $ConfDir/python -m test.regrtest \
-wW --slowest -j0 --timeout=1800 \ -wW --slowest -j0 --timeout=1800 \
-i test_freeze_simple_script \ -i test_freeze_simple_script \
-i test_check_probes \
%if %{with bootstrap} %if %{with bootstrap}
-x test_distutils \ -x test_distutils \
%endif %endif
@ -1609,6 +1631,19 @@ CheckPython optimized
# ====================================================== # ======================================================
%changelog %changelog
* Mon Jan 22 2024 Charalampos Stratakis <cstratak@redhat.com> - 3.11.7-1
- Rebase to 3.11.7
Resolves: RHEL-20233
* Tue Jan 09 2024 Lumír Balhar <lbalhar@redhat.com> - 3.11.5-2
- Security fix for CVE-2023-27043
Resolves: RHEL-21325
* Thu Sep 07 2023 Charalampos Stratakis <cstratak@redhat.com> - 3.11.5-1
- Rebase to 3.11.5
- Security fixes for CVE-2023-40217 and CVE-2023-41105
Resolves: RHEL-3045, RHEL-3269
* Wed Aug 09 2023 Petr Viktorin <pviktori@redhat.com> - 3.11.4-3 * Wed Aug 09 2023 Petr Viktorin <pviktori@redhat.com> - 3.11.4-3
- Fix symlink handling in the fix for CVE-2023-24329 - Fix symlink handling in the fix for CVE-2023-24329
Resolves: rhbz#263261 Resolves: rhbz#263261

Loading…
Cancel
Save