import UBI python3.12-3.12.9-1.el9
This commit is contained in:
		
							parent
							
								
									f36695e019
								
							
						
					
					
						commit
						01b8d998b9
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +1 @@ | ||||
| SOURCES/Python-3.12.5.tar.xz | ||||
| SOURCES/Python-3.12.9.tar.xz | ||||
|  | ||||
| @ -1 +1 @@ | ||||
| d9b83c17a717e1cbd3ab6bd14cfe3e508e6d87b2 SOURCES/Python-3.12.5.tar.xz | ||||
| 465d8a664e63dc5aa1f0d90cd1d0000a970ee2fb SOURCES/Python-3.12.9.tar.xz | ||||
|  | ||||
| @ -30,7 +30,7 @@ Co-authored-by: Lumír Balhar <frenzy.madness@gmail.com> | ||||
|  3 files changed, 71 insertions(+), 4 deletions(-) | ||||
| 
 | ||||
| diff --git a/Lib/site.py b/Lib/site.py
 | ||||
| index 924cfbecec..e2871ecc89 100644
 | ||||
| index aed254ad50..568dbdb945 100644
 | ||||
| --- a/Lib/site.py
 | ||||
| +++ b/Lib/site.py
 | ||||
| @@ -398,8 +398,15 @@ def getsitepackages(prefixes=None):
 | ||||
| @ -51,7 +51,7 @@ index 924cfbecec..e2871ecc89 100644 | ||||
|          if os.path.isdir(sitedir): | ||||
|              addsitedir(sitedir, known_paths) | ||||
| diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py
 | ||||
| index 122d441bd1..2d354a11da 100644
 | ||||
| index 517b13acaf..928d1a0541 100644
 | ||||
| --- a/Lib/sysconfig.py
 | ||||
| +++ b/Lib/sysconfig.py
 | ||||
| @@ -104,6 +104,11 @@
 | ||||
| @ -86,7 +86,7 @@ index 122d441bd1..2d354a11da 100644 | ||||
|  _SCHEME_KEYS = ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include', | ||||
|                  'scripts', 'data') | ||||
|   | ||||
| @@ -263,11 +281,40 @@ def _extend_dict(target_dict, other_dict):
 | ||||
| @@ -261,11 +279,40 @@ def _extend_dict(target_dict, other_dict):
 | ||||
|          target_dict[key] = value | ||||
|   | ||||
|   | ||||
| @ -119,7 +119,7 @@ index 122d441bd1..2d354a11da 100644 | ||||
| +    # we only change the defaults here, so explicit --prefix will take precedence
 | ||||
| +    # https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
 | ||||
| +    if (scheme == 'posix_prefix' and
 | ||||
| +        _PREFIX == '/usr' and
 | ||||
| +        sys.prefix == '/usr' and
 | ||||
| +        'RPM_BUILD_ROOT' not in os.environ):
 | ||||
| +            _extend_dict(vars, _config_vars_local())
 | ||||
| +    else:
 | ||||
| @ -129,10 +129,10 @@ index 122d441bd1..2d354a11da 100644 | ||||
|          # On Windows we want to substitute 'lib' for schemes rather | ||||
|          # than the native value (without modifying vars, in case it | ||||
| diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py
 | ||||
| index 1137c2032b..8fc2b84f52 100644
 | ||||
| index 3468d0ce02..ff31010427 100644
 | ||||
| --- a/Lib/test/test_sysconfig.py
 | ||||
| +++ b/Lib/test/test_sysconfig.py
 | ||||
| @@ -110,8 +110,19 @@ def test_get_path(self):
 | ||||
| @@ -119,8 +119,19 @@ def test_get_path(self):
 | ||||
|          for scheme in _INSTALL_SCHEMES: | ||||
|              for name in _INSTALL_SCHEMES[scheme]: | ||||
|                  expected = _INSTALL_SCHEMES[scheme][name].format(**config_vars) | ||||
| @ -153,7 +153,7 @@ index 1137c2032b..8fc2b84f52 100644 | ||||
|                      os.path.normpath(expected), | ||||
|                  ) | ||||
|   | ||||
| @@ -344,7 +355,7 @@ def test_get_config_h_filename(self):
 | ||||
| @@ -353,7 +364,7 @@ def test_get_config_h_filename(self):
 | ||||
|          self.assertTrue(os.path.isfile(config_h), config_h) | ||||
|   | ||||
|      def test_get_scheme_names(self): | ||||
| @ -162,7 +162,7 @@ index 1137c2032b..8fc2b84f52 100644 | ||||
|          if HAS_USER_BASE: | ||||
|              wanted.extend(['nt_user', 'osx_framework_user', 'posix_user']) | ||||
|          self.assertEqual(get_scheme_names(), tuple(sorted(wanted))) | ||||
| @@ -356,6 +367,8 @@ def test_symlink(self): # Issue 7880
 | ||||
| @@ -365,6 +376,8 @@ def test_symlink(self): # Issue 7880
 | ||||
|              cmd = "-c", "import sysconfig; print(sysconfig.get_platform())" | ||||
|              self.assertEqual(py.call_real(*cmd), py.call_link(*cmd)) | ||||
|   | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| From 43ce74d971fad62db6ccd723fe6b01da9c7ff407 Mon Sep 17 00:00:00 2001 | ||||
| From 11deb3112bd90bc2dce2fcd4a1f5975c08b91360 Mon Sep 17 00:00:00 2001 | ||||
| From: Charalampos Stratakis <cstratak@redhat.com> | ||||
| Date: Thu, 12 Dec 2019 16:58:31 +0100 | ||||
| Subject: [PATCH 1/5] Expose blake2b and blake2s hashes from OpenSSL | ||||
| @ -29,10 +29,10 @@ index 73d758a..5921360 100644 | ||||
|              computed = m.hexdigest() if not shake else m.hexdigest(length) | ||||
|              self.assertEqual( | ||||
| diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
 | ||||
| index af6d1b2..980712f 100644
 | ||||
| index 2998820..b96001e 100644
 | ||||
| --- a/Modules/_hashopenssl.c
 | ||||
| +++ b/Modules/_hashopenssl.c
 | ||||
| @@ -1079,6 +1079,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj,
 | ||||
| @@ -1128,6 +1128,41 @@ _hashlib_openssl_sha512_impl(PyObject *module, PyObject *data_obj,
 | ||||
|  } | ||||
|   | ||||
|   | ||||
| @ -74,7 +74,7 @@ index af6d1b2..980712f 100644 | ||||
|  #ifdef PY_OPENSSL_HAS_SHA3 | ||||
|   | ||||
|  /*[clinic input] | ||||
| @@ -2067,6 +2102,8 @@ static struct PyMethodDef EVP_functions[] = {
 | ||||
| @@ -2116,6 +2151,8 @@ static struct PyMethodDef EVP_functions[] = {
 | ||||
|      _HASHLIB_OPENSSL_SHA256_METHODDEF | ||||
|      _HASHLIB_OPENSSL_SHA384_METHODDEF | ||||
|      _HASHLIB_OPENSSL_SHA512_METHODDEF | ||||
| @ -84,7 +84,7 @@ index af6d1b2..980712f 100644 | ||||
|      _HASHLIB_OPENSSL_SHA3_256_METHODDEF | ||||
|      _HASHLIB_OPENSSL_SHA3_384_METHODDEF | ||||
| diff --git a/Modules/clinic/_hashopenssl.c.h b/Modules/clinic/_hashopenssl.c.h
 | ||||
| index fb61a44..1e42b87 100644
 | ||||
| index 84e2346..7fe03a3 100644
 | ||||
| --- a/Modules/clinic/_hashopenssl.c.h
 | ||||
| +++ b/Modules/clinic/_hashopenssl.c.h
 | ||||
| @@ -743,6 +743,156 @@ exit:
 | ||||
| @ -248,13 +248,13 @@ index fb61a44..1e42b87 100644 | ||||
|  #ifndef _HASHLIB_SCRYPT_METHODDEF | ||||
|      #define _HASHLIB_SCRYPT_METHODDEF | ||||
|  #endif /* !defined(_HASHLIB_SCRYPT_METHODDEF) */ | ||||
| -/*[clinic end generated code: output=b339e255db698147 input=a9049054013a1b77]*/
 | ||||
| +/*[clinic end generated code: output=1d988d457a8beebe input=a9049054013a1b77]*/
 | ||||
| -/*[clinic end generated code: output=4734184f6555dc95 input=a9049054013a1b77]*/
 | ||||
| +/*[clinic end generated code: output=f0bfddb963a21208 input=a9049054013a1b77]*/
 | ||||
| -- 
 | ||||
| 2.45.0 | ||||
| 2.47.1 | ||||
| 
 | ||||
| 
 | ||||
| From 6872b634078a2c69644235781ebffb07f8edcb83 Mon Sep 17 00:00:00 2001 | ||||
| From ea9d5c84e25b5c04c2823e1edee4354dd6b2b7a5 Mon Sep 17 00:00:00 2001 | ||||
| From: Petr Viktorin <pviktori@redhat.com> | ||||
| Date: Thu, 25 Jul 2019 17:19:06 +0200 | ||||
| Subject: [PATCH 2/5] Disable Python's hash implementations in FIPS mode, | ||||
| @ -445,10 +445,10 @@ index a8bad9d..1b1d937 100644 | ||||
| +    if (_Py_hashlib_fips_error(exc, name)) return NULL; \
 | ||||
| +} while (0)
 | ||||
| diff --git a/configure.ac b/configure.ac
 | ||||
| index 65ad1c2..b5f9ab5 100644
 | ||||
| index 9270b5f..a9eb2c9 100644
 | ||||
| --- a/configure.ac
 | ||||
| +++ b/configure.ac
 | ||||
| @@ -7463,7 +7463,8 @@ PY_STDLIB_MOD([_sha2],
 | ||||
| @@ -7482,7 +7482,8 @@ PY_STDLIB_MOD([_sha2],
 | ||||
|  PY_STDLIB_MOD([_sha3], [test "$with_builtin_sha3" = yes]) | ||||
|  PY_STDLIB_MOD([_blake2], | ||||
|    [test "$with_builtin_blake2" = yes], [], | ||||
| @ -459,10 +459,10 @@ index 65ad1c2..b5f9ab5 100644 | ||||
|  PY_STDLIB_MOD([_crypt], | ||||
|    [], [test "$ac_cv_crypt_crypt" = yes], | ||||
| -- 
 | ||||
| 2.45.0 | ||||
| 2.47.1 | ||||
| 
 | ||||
| 
 | ||||
| From f904abdd7a607282c2cdfd18288045cedfa28414 Mon Sep 17 00:00:00 2001 | ||||
| From 29a7b7ac9e18a501ed78bde7a449b90c57d44e24 Mon Sep 17 00:00:00 2001 | ||||
| From: Charalampos Stratakis <cstratak@redhat.com> | ||||
| Date: Fri, 29 Jan 2021 14:16:21 +0100 | ||||
| Subject: [PATCH 3/5] Use python's fall back crypto implementations only if we | ||||
| @ -552,10 +552,10 @@ index dd61a9a..6031b02 100644 | ||||
|          get_builtin_constructor = getattr(hashlib, | ||||
|                                            '__get_builtin_constructor') | ||||
| -- 
 | ||||
| 2.45.0 | ||||
| 2.47.1 | ||||
| 
 | ||||
| 
 | ||||
| From 9bf0a53b7831409613c44fd7feecb56476f5e5e7 Mon Sep 17 00:00:00 2001 | ||||
| From 59accf544492400c9fd32a8e682fb6f2206e932e Mon Sep 17 00:00:00 2001 | ||||
| From: Charalampos Stratakis <cstratak@redhat.com> | ||||
| Date: Wed, 31 Jul 2019 15:43:43 +0200 | ||||
| Subject: [PATCH 4/5] Test equivalence of hashes for the various digests with | ||||
| @ -712,21 +712,21 @@ index 6031b02..5bd5297 100644 | ||||
|  class KDFTests(unittest.TestCase): | ||||
|   | ||||
| -- 
 | ||||
| 2.45.0 | ||||
| 2.47.1 | ||||
| 
 | ||||
| 
 | ||||
| From 8a76571515a64a57b4ea0586ae8376cf2ef0ac60 Mon Sep 17 00:00:00 2001 | ||||
| From 21efadd8b488956482bdc6ccd91c37dcef705129 Mon Sep 17 00:00:00 2001 | ||||
| From: Petr Viktorin <pviktori@redhat.com> | ||||
| Date: Mon, 26 Aug 2019 19:39:48 +0200 | ||||
| Subject: [PATCH 5/5] Guard against Python HMAC in FIPS mode | ||||
| 
 | ||||
| ---
 | ||||
|  Lib/hmac.py           | 13 +++++++++---- | ||||
|  Lib/hmac.py           | 12 +++++++++--- | ||||
|  Lib/test/test_hmac.py | 10 ++++++++++ | ||||
|  2 files changed, 19 insertions(+), 4 deletions(-) | ||||
|  2 files changed, 19 insertions(+), 3 deletions(-) | ||||
| 
 | ||||
| diff --git a/Lib/hmac.py b/Lib/hmac.py
 | ||||
| index 8b4eb2f..e8e4864 100644
 | ||||
| index 8b4eb2f..8930bda 100644
 | ||||
| --- a/Lib/hmac.py
 | ||||
| +++ b/Lib/hmac.py
 | ||||
| @@ -16,8 +16,9 @@ else:
 | ||||
| @ -741,14 +741,7 @@ index 8b4eb2f..e8e4864 100644 | ||||
|   | ||||
|  # The size of the digests returned by HMAC depends on the underlying | ||||
|  # hashing module used.  Use digest_size from the instance of HMAC instead. | ||||
| @@ -48,17 +49,18 @@ class HMAC:
 | ||||
|                     msg argument.  Passing it as a keyword argument is | ||||
|                     recommended, though not required for legacy API reasons. | ||||
|          """ | ||||
| -
 | ||||
|          if not isinstance(key, (bytes, bytearray)): | ||||
|              raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) | ||||
|   | ||||
| @@ -55,10 +56,12 @@ class HMAC:
 | ||||
|          if not digestmod: | ||||
|              raise TypeError("Missing required argument 'digestmod'.") | ||||
|   | ||||
| @ -762,7 +755,7 @@ index 8b4eb2f..e8e4864 100644 | ||||
|                  self._init_old(key, msg, digestmod) | ||||
|          else: | ||||
|              self._init_old(key, msg, digestmod) | ||||
| @@ -69,6 +71,9 @@ class HMAC:
 | ||||
| @@ -69,6 +72,9 @@ class HMAC:
 | ||||
|          self.block_size = self._hmac.block_size | ||||
|   | ||||
|      def _init_old(self, key, msg, digestmod): | ||||
| @ -829,5 +822,5 @@ index 1502fba..7997073 100644 | ||||
|      def test_realcopy_old(self): | ||||
|          # Testing if the copy method created a real copy. | ||||
| -- 
 | ||||
| 2.45.0 | ||||
| 2.47.1 | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,7 @@ https://github.com/GrahamDumpleton/mod_wsgi/issues/730 | ||||
|  2 files changed, 8 insertions(+), 50 deletions(-) | ||||
| 
 | ||||
| diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
 | ||||
| index 2e4b860b97..3066b23ee1 100644
 | ||||
| index 75a56f7830..c2509fced1 100644
 | ||||
| --- a/Lib/test/test_threading.py
 | ||||
| +++ b/Lib/test/test_threading.py
 | ||||
| @@ -1100,39 +1100,6 @@ def noop(): pass
 | ||||
|  | ||||
| @ -1,483 +0,0 @@ | ||||
| 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 6ba42491d6..6bd45200d8 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 1de547a011..e53abc8b84 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 may contain 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 fc8d87974e..ef8aa0d53c 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 | ||||
| @@ -3352,15 +3353,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""" | ||||
| @@ -3551,6 +3674,54 @@ def test_mime_classes_policy_argument(self):
 | ||||
|                  m = cls(*constructor, policy=email.policy.default) | ||||
|                  self.assertIs(m.policy, email.policy.default) | ||||
|   | ||||
| +    def test_iter_escaped_chars(self):
 | ||||
| +        self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')),
 | ||||
| +                         [(0, 'a'),
 | ||||
| +                          (2, '\\\\'),
 | ||||
| +                          (3, 'b'),
 | ||||
| +                          (5, '\\"'),
 | ||||
| +                          (6, 'c'),
 | ||||
| +                          (8, '\\\\'),
 | ||||
| +                          (9, '"'),
 | ||||
| +                          (10, 'd')])
 | ||||
| +        self.assertEqual(list(utils._iter_escaped_chars('a\\')),
 | ||||
| +                         [(0, 'a'), (1, '\\')])
 | ||||
| +
 | ||||
| +    def test_strip_quoted_realnames(self):
 | ||||
| +        def check(addr, expected):
 | ||||
| +            self.assertEqual(utils._strip_quoted_realnames(addr), expected)
 | ||||
| +
 | ||||
| +        check('"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>',
 | ||||
| +              ' <jane@example.net>,  <john@example.net>')
 | ||||
| +        check(r'"Jane \"Doe\"." <jane@example.net>',
 | ||||
| +              ' <jane@example.net>')
 | ||||
| +
 | ||||
| +        # special cases
 | ||||
| +        check(r'before"name"after', 'beforeafter')
 | ||||
| +        check(r'before"name"', 'before')
 | ||||
| +        check(r'b"name"', 'b')  # single char
 | ||||
| +        check(r'"name"after', 'after')
 | ||||
| +        check(r'"name"a', 'a')  # single char
 | ||||
| +        check(r'"name"', '')
 | ||||
| +
 | ||||
| +        # no change
 | ||||
| +        for addr in (
 | ||||
| +            'Jane Doe <jane@example.net>, John Doe <john@example.net>',
 | ||||
| +            'lone " quote',
 | ||||
| +        ):
 | ||||
| +            self.assertEqual(utils._strip_quoted_realnames(addr), addr)
 | ||||
| +
 | ||||
| +
 | ||||
| +    def test_check_parenthesis(self):
 | ||||
| +        addr = 'alice@example.net'
 | ||||
| +        self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)'))
 | ||||
| +        self.assertFalse(utils._check_parenthesis(f'{addr} )Alice('))
 | ||||
| +        self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))'))
 | ||||
| +        self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)'))
 | ||||
| +
 | ||||
| +        # Ignore real name between quotes
 | ||||
| +        self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}'))
 | ||||
| +
 | ||||
|   | ||||
|  # Test the iterator/generators | ||||
|  class TestIterators(TestEmailBase): | ||||
| diff --git a/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
 | ||||
| new file mode 100644 | ||||
| index 0000000000..3d0e9e4078
 | ||||
| --- /dev/null
 | ||||
| +++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
 | ||||
| @@ -0,0 +1,8 @@
 | ||||
| +:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now
 | ||||
| +return ``('', '')`` 2-tuples in more situations where invalid email
 | ||||
| +addresses are encountered instead of potentially inaccurate values. Add
 | ||||
| +optional *strict* parameter to these two functions: use ``strict=False`` to
 | ||||
| +get the old behavior, accept malformed inputs.
 | ||||
| +``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to check
 | ||||
| +if the *strict* paramater is available. Patch by Thomas Dwyer and Victor
 | ||||
| +Stinner to improve the CVE-2023-27043 fix.
 | ||||
| @ -1,121 +0,0 @@ | ||||
| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 | ||||
| From: "Miss Islington (bot)" | ||||
|  <31488909+miss-islington@users.noreply.github.com> | ||||
| Date: Mon, 12 Aug 2024 02:35:17 +0200 | ||||
| Subject: [PATCH] 00436: [CVE-2024-8088] gh-122905: Sanitize names in | ||||
|  zipfile.Path. | ||||
| 
 | ||||
| ---
 | ||||
|  Lib/test/test_zipfile/_path/test_path.py      | 17 +++++ | ||||
|  Lib/zipfile/_path/__init__.py                 | 64 ++++++++++++++++++- | ||||
|  ...-08-11-14-08-04.gh-issue-122905.7tDsxA.rst |  1 + | ||||
|  3 files changed, 81 insertions(+), 1 deletion(-) | ||||
|  create mode 100644 Misc/NEWS.d/next/Library/2024-08-11-14-08-04.gh-issue-122905.7tDsxA.rst | ||||
| 
 | ||||
| diff --git a/Lib/test/test_zipfile/_path/test_path.py b/Lib/test/test_zipfile/_path/test_path.py
 | ||||
| index 06d5aab69b..90885dbbe3 100644
 | ||||
| --- a/Lib/test/test_zipfile/_path/test_path.py
 | ||||
| +++ b/Lib/test/test_zipfile/_path/test_path.py
 | ||||
| @@ -577,3 +577,20 @@ def test_getinfo_missing(self, alpharep):
 | ||||
|          zipfile.Path(alpharep) | ||||
|          with self.assertRaises(KeyError): | ||||
|              alpharep.getinfo('does-not-exist') | ||||
| +
 | ||||
| +    def test_malformed_paths(self):
 | ||||
| +        """
 | ||||
| +        Path should handle malformed paths.
 | ||||
| +        """
 | ||||
| +        data = io.BytesIO()
 | ||||
| +        zf = zipfile.ZipFile(data, "w")
 | ||||
| +        zf.writestr("/one-slash.txt", b"content")
 | ||||
| +        zf.writestr("//two-slash.txt", b"content")
 | ||||
| +        zf.writestr("../parent.txt", b"content")
 | ||||
| +        zf.filename = ''
 | ||||
| +        root = zipfile.Path(zf)
 | ||||
| +        assert list(map(str, root.iterdir())) == [
 | ||||
| +            'one-slash.txt',
 | ||||
| +            'two-slash.txt',
 | ||||
| +            'parent.txt',
 | ||||
| +        ]
 | ||||
| diff --git a/Lib/zipfile/_path/__init__.py b/Lib/zipfile/_path/__init__.py
 | ||||
| index 78c413563b..42f9fded21 100644
 | ||||
| --- a/Lib/zipfile/_path/__init__.py
 | ||||
| +++ b/Lib/zipfile/_path/__init__.py
 | ||||
| @@ -83,7 +83,69 @@ def __setstate__(self, state):
 | ||||
|          super().__init__(*args, **kwargs) | ||||
|   | ||||
|   | ||||
| -class CompleteDirs(InitializedState, zipfile.ZipFile):
 | ||||
| +class SanitizedNames:
 | ||||
| +    """
 | ||||
| +    ZipFile mix-in to ensure names are sanitized.
 | ||||
| +    """
 | ||||
| +
 | ||||
| +    def namelist(self):
 | ||||
| +        return list(map(self._sanitize, super().namelist()))
 | ||||
| +
 | ||||
| +    @staticmethod
 | ||||
| +    def _sanitize(name):
 | ||||
| +        r"""
 | ||||
| +        Ensure a relative path with posix separators and no dot names.
 | ||||
| +
 | ||||
| +        Modeled after
 | ||||
| +        https://github.com/python/cpython/blob/bcc1be39cb1d04ad9fc0bd1b9193d3972835a57c/Lib/zipfile/__init__.py#L1799-L1813
 | ||||
| +        but provides consistent cross-platform behavior.
 | ||||
| +
 | ||||
| +        >>> san = SanitizedNames._sanitize
 | ||||
| +        >>> san('/foo/bar')
 | ||||
| +        'foo/bar'
 | ||||
| +        >>> san('//foo.txt')
 | ||||
| +        'foo.txt'
 | ||||
| +        >>> san('foo/.././bar.txt')
 | ||||
| +        'foo/bar.txt'
 | ||||
| +        >>> san('foo../.bar.txt')
 | ||||
| +        'foo../.bar.txt'
 | ||||
| +        >>> san('\\foo\\bar.txt')
 | ||||
| +        'foo/bar.txt'
 | ||||
| +        >>> san('D:\\foo.txt')
 | ||||
| +        'D/foo.txt'
 | ||||
| +        >>> san('\\\\server\\share\\file.txt')
 | ||||
| +        'server/share/file.txt'
 | ||||
| +        >>> san('\\\\?\\GLOBALROOT\\Volume3')
 | ||||
| +        '?/GLOBALROOT/Volume3'
 | ||||
| +        >>> san('\\\\.\\PhysicalDrive1\\root')
 | ||||
| +        'PhysicalDrive1/root'
 | ||||
| +
 | ||||
| +        Retain any trailing slash.
 | ||||
| +        >>> san('abc/')
 | ||||
| +        'abc/'
 | ||||
| +
 | ||||
| +        Raises a ValueError if the result is empty.
 | ||||
| +        >>> san('../..')
 | ||||
| +        Traceback (most recent call last):
 | ||||
| +        ...
 | ||||
| +        ValueError: Empty filename
 | ||||
| +        """
 | ||||
| +
 | ||||
| +        def allowed(part):
 | ||||
| +            return part and part not in {'..', '.'}
 | ||||
| +
 | ||||
| +        # Remove the drive letter.
 | ||||
| +        # Don't use ntpath.splitdrive, because that also strips UNC paths
 | ||||
| +        bare = re.sub('^([A-Z]):', r'\1', name, flags=re.IGNORECASE)
 | ||||
| +        clean = bare.replace('\\', '/')
 | ||||
| +        parts = clean.split('/')
 | ||||
| +        joined = '/'.join(filter(allowed, parts))
 | ||||
| +        if not joined:
 | ||||
| +            raise ValueError("Empty filename")
 | ||||
| +        return joined + '/' * name.endswith('/')
 | ||||
| +
 | ||||
| +
 | ||||
| +class CompleteDirs(InitializedState, SanitizedNames, zipfile.ZipFile):
 | ||||
|      """ | ||||
|      A ZipFile subclass that ensures that implied directories | ||||
|      are always included in the namelist. | ||||
| diff --git a/Misc/NEWS.d/next/Library/2024-08-11-14-08-04.gh-issue-122905.7tDsxA.rst b/Misc/NEWS.d/next/Library/2024-08-11-14-08-04.gh-issue-122905.7tDsxA.rst
 | ||||
| new file mode 100644 | ||||
| index 0000000000..1be44c906c
 | ||||
| --- /dev/null
 | ||||
| +++ b/Misc/NEWS.d/next/Library/2024-08-11-14-08-04.gh-issue-122905.7tDsxA.rst
 | ||||
| @@ -0,0 +1 @@
 | ||||
| +:class:`zipfile.Path` objects now sanitize names from the zipfile.
 | ||||
| @ -1,245 +0,0 @@ | ||||
| From 4eaf4891c12589e3c7bdad5f5b076e4c8392dd06 Mon Sep 17 00:00:00 2001 | ||||
| From: "Miss Islington (bot)" | ||||
|  <31488909+miss-islington@users.noreply.github.com> | ||||
| Date: Sun, 1 Sep 2024 00:35:24 +0200 | ||||
| Subject: [PATCH] [3.12] gh-121285: Remove backtracking when parsing tarfile | ||||
|  headers (GH-121286) (GH-123543) | ||||
| 
 | ||||
| gh-121285: Remove backtracking when parsing tarfile headers (GH-121286) | ||||
| 
 | ||||
| * Remove backtracking when parsing tarfile headers | ||||
| * Rewrite PAX header parsing to be stricter | ||||
| * Optimize parsing of GNU extended sparse headers v0.0 | ||||
| 
 | ||||
| (cherry picked from commit 34ddb64d088dd7ccc321f6103d23153256caa5d4) | ||||
| 
 | ||||
| Co-authored-by: Seth Michael Larson <seth@python.org> | ||||
| Co-authored-by: Kirill Podoprigora <kirill.bast9@mail.ru> | ||||
| Co-authored-by: Gregory P. Smith <greg@krypto.org> | ||||
| ---
 | ||||
|  Lib/tarfile.py                                | 103 ++++++++++++------ | ||||
|  Lib/test/test_tarfile.py                      |  42 +++++++ | ||||
|  ...-07-02-13-39-20.gh-issue-121285.hrl-yI.rst |   2 + | ||||
|  3 files changed, 112 insertions(+), 35 deletions(-) | ||||
|  create mode 100644 Misc/NEWS.d/next/Security/2024-07-02-13-39-20.gh-issue-121285.hrl-yI.rst | ||||
| 
 | ||||
| diff --git a/Lib/tarfile.py b/Lib/tarfile.py
 | ||||
| index e1487e3864d44b..0a0f31eca06c04 100755
 | ||||
| --- a/Lib/tarfile.py
 | ||||
| +++ b/Lib/tarfile.py
 | ||||
| @@ -843,6 +843,9 @@ def data_filter(member, dest_path):
 | ||||
|  # Sentinel for replace() defaults, meaning "don't change the attribute" | ||||
|  _KEEP = object() | ||||
|   | ||||
| +# Header length is digits followed by a space.
 | ||||
| +_header_length_prefix_re = re.compile(br"([0-9]{1,20}) ")
 | ||||
| +
 | ||||
|  class TarInfo(object): | ||||
|      """Informational class which holds the details about an | ||||
|         archive member given by a tar header block. | ||||
| @@ -1412,37 +1415,59 @@ def _proc_pax(self, tarfile):
 | ||||
|          else: | ||||
|              pax_headers = tarfile.pax_headers.copy() | ||||
|   | ||||
| -        # Check if the pax header contains a hdrcharset field. This tells us
 | ||||
| -        # the encoding of the path, linkpath, uname and gname fields. Normally,
 | ||||
| -        # these fields are UTF-8 encoded but since POSIX.1-2008 tar
 | ||||
| -        # implementations are allowed to store them as raw binary strings if
 | ||||
| -        # the translation to UTF-8 fails.
 | ||||
| -        match = re.search(br"\d+ hdrcharset=([^\n]+)\n", buf)
 | ||||
| -        if match is not None:
 | ||||
| -            pax_headers["hdrcharset"] = match.group(1).decode("utf-8")
 | ||||
| -
 | ||||
| -        # For the time being, we don't care about anything other than "BINARY".
 | ||||
| -        # The only other value that is currently allowed by the standard is
 | ||||
| -        # "ISO-IR 10646 2000 UTF-8" in other words UTF-8.
 | ||||
| -        hdrcharset = pax_headers.get("hdrcharset")
 | ||||
| -        if hdrcharset == "BINARY":
 | ||||
| -            encoding = tarfile.encoding
 | ||||
| -        else:
 | ||||
| -            encoding = "utf-8"
 | ||||
| -
 | ||||
|          # Parse pax header information. A record looks like that: | ||||
|          # "%d %s=%s\n" % (length, keyword, value). length is the size | ||||
|          # of the complete record including the length field itself and | ||||
| -        # the newline. keyword and value are both UTF-8 encoded strings.
 | ||||
| -        regex = re.compile(br"(\d+) ([^=]+)=")
 | ||||
| +        # the newline.
 | ||||
|          pos = 0 | ||||
| -        while match := regex.match(buf, pos):
 | ||||
| -            length, keyword = match.groups()
 | ||||
| -            length = int(length)
 | ||||
| -            if length == 0:
 | ||||
| +        encoding = None
 | ||||
| +        raw_headers = []
 | ||||
| +        while len(buf) > pos and buf[pos] != 0x00:
 | ||||
| +            if not (match := _header_length_prefix_re.match(buf, pos)):
 | ||||
| +                raise InvalidHeaderError("invalid header")
 | ||||
| +            try:
 | ||||
| +                length = int(match.group(1))
 | ||||
| +            except ValueError:
 | ||||
| +                raise InvalidHeaderError("invalid header")
 | ||||
| +            # Headers must be at least 5 bytes, shortest being '5 x=\n'.
 | ||||
| +            # Value is allowed to be empty.
 | ||||
| +            if length < 5:
 | ||||
| +                raise InvalidHeaderError("invalid header")
 | ||||
| +            if pos + length > len(buf):
 | ||||
| +                raise InvalidHeaderError("invalid header")
 | ||||
| +
 | ||||
| +            header_value_end_offset = match.start(1) + length - 1  # Last byte of the header
 | ||||
| +            keyword_and_value = buf[match.end(1) + 1:header_value_end_offset]
 | ||||
| +            raw_keyword, equals, raw_value = keyword_and_value.partition(b"=")
 | ||||
| +
 | ||||
| +            # Check the framing of the header. The last character must be '\n' (0x0A)
 | ||||
| +            if not raw_keyword or equals != b"=" or buf[header_value_end_offset] != 0x0A:
 | ||||
|                  raise InvalidHeaderError("invalid header") | ||||
| -            value = buf[match.end(2) + 1:match.start(1) + length - 1]
 | ||||
| +            raw_headers.append((length, raw_keyword, raw_value))
 | ||||
| +
 | ||||
| +            # Check if the pax header contains a hdrcharset field. This tells us
 | ||||
| +            # the encoding of the path, linkpath, uname and gname fields. Normally,
 | ||||
| +            # these fields are UTF-8 encoded but since POSIX.1-2008 tar
 | ||||
| +            # implementations are allowed to store them as raw binary strings if
 | ||||
| +            # the translation to UTF-8 fails. For the time being, we don't care about
 | ||||
| +            # anything other than "BINARY". The only other value that is currently
 | ||||
| +            # allowed by the standard is "ISO-IR 10646 2000 UTF-8" in other words UTF-8.
 | ||||
| +            # Note that we only follow the initial 'hdrcharset' setting to preserve
 | ||||
| +            # the initial behavior of the 'tarfile' module.
 | ||||
| +            if raw_keyword == b"hdrcharset" and encoding is None:
 | ||||
| +                if raw_value == b"BINARY":
 | ||||
| +                    encoding = tarfile.encoding
 | ||||
| +                else:  # This branch ensures only the first 'hdrcharset' header is used.
 | ||||
| +                    encoding = "utf-8"
 | ||||
|   | ||||
| +            pos += length
 | ||||
| +
 | ||||
| +        # If no explicit hdrcharset is set, we use UTF-8 as a default.
 | ||||
| +        if encoding is None:
 | ||||
| +            encoding = "utf-8"
 | ||||
| +
 | ||||
| +        # After parsing the raw headers we can decode them to text.
 | ||||
| +        for length, raw_keyword, raw_value in raw_headers:
 | ||||
|              # Normally, we could just use "utf-8" as the encoding and "strict" | ||||
|              # as the error handler, but we better not take the risk. For | ||||
|              # example, GNU tar <= 1.23 is known to store filenames it cannot | ||||
| @@ -1450,17 +1475,16 @@ def _proc_pax(self, tarfile):
 | ||||
|              # hdrcharset=BINARY header). | ||||
|              # We first try the strict standard encoding, and if that fails we | ||||
|              # fall back on the user's encoding and error handler. | ||||
| -            keyword = self._decode_pax_field(keyword, "utf-8", "utf-8",
 | ||||
| +            keyword = self._decode_pax_field(raw_keyword, "utf-8", "utf-8",
 | ||||
|                      tarfile.errors) | ||||
|              if keyword in PAX_NAME_FIELDS: | ||||
| -                value = self._decode_pax_field(value, encoding, tarfile.encoding,
 | ||||
| +                value = self._decode_pax_field(raw_value, encoding, tarfile.encoding,
 | ||||
|                          tarfile.errors) | ||||
|              else: | ||||
| -                value = self._decode_pax_field(value, "utf-8", "utf-8",
 | ||||
| +                value = self._decode_pax_field(raw_value, "utf-8", "utf-8",
 | ||||
|                          tarfile.errors) | ||||
|   | ||||
|              pax_headers[keyword] = value | ||||
| -            pos += length
 | ||||
|   | ||||
|          # Fetch the next header. | ||||
|          try: | ||||
| @@ -1475,7 +1499,7 @@ def _proc_pax(self, tarfile):
 | ||||
|   | ||||
|          elif "GNU.sparse.size" in pax_headers: | ||||
|              # GNU extended sparse format version 0.0. | ||||
| -            self._proc_gnusparse_00(next, pax_headers, buf)
 | ||||
| +            self._proc_gnusparse_00(next, raw_headers)
 | ||||
|   | ||||
|          elif pax_headers.get("GNU.sparse.major") == "1" and pax_headers.get("GNU.sparse.minor") == "0": | ||||
|              # GNU extended sparse format version 1.0. | ||||
| @@ -1497,15 +1521,24 @@ def _proc_pax(self, tarfile):
 | ||||
|   | ||||
|          return next | ||||
|   | ||||
| -    def _proc_gnusparse_00(self, next, pax_headers, buf):
 | ||||
| +    def _proc_gnusparse_00(self, next, raw_headers):
 | ||||
|          """Process a GNU tar extended sparse header, version 0.0. | ||||
|          """ | ||||
|          offsets = [] | ||||
| -        for match in re.finditer(br"\d+ GNU.sparse.offset=(\d+)\n", buf):
 | ||||
| -            offsets.append(int(match.group(1)))
 | ||||
|          numbytes = [] | ||||
| -        for match in re.finditer(br"\d+ GNU.sparse.numbytes=(\d+)\n", buf):
 | ||||
| -            numbytes.append(int(match.group(1)))
 | ||||
| +        for _, keyword, value in raw_headers:
 | ||||
| +            if keyword == b"GNU.sparse.offset":
 | ||||
| +                try:
 | ||||
| +                    offsets.append(int(value.decode()))
 | ||||
| +                except ValueError:
 | ||||
| +                    raise InvalidHeaderError("invalid header")
 | ||||
| +
 | ||||
| +            elif keyword == b"GNU.sparse.numbytes":
 | ||||
| +                try:
 | ||||
| +                    numbytes.append(int(value.decode()))
 | ||||
| +                except ValueError:
 | ||||
| +                    raise InvalidHeaderError("invalid header")
 | ||||
| +
 | ||||
|          next.sparse = list(zip(offsets, numbytes)) | ||||
|   | ||||
|      def _proc_gnusparse_01(self, next, pax_headers): | ||||
| diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
 | ||||
| index 3fbd25e742b181..e28d0311826e2b 100644
 | ||||
| --- a/Lib/test/test_tarfile.py
 | ||||
| +++ b/Lib/test/test_tarfile.py
 | ||||
| @@ -1237,6 +1237,48 @@ def test_pax_number_fields(self):
 | ||||
|          finally: | ||||
|              tar.close() | ||||
|   | ||||
| +    def test_pax_header_bad_formats(self):
 | ||||
| +        # The fields from the pax header have priority over the
 | ||||
| +        # TarInfo.
 | ||||
| +        pax_header_replacements = (
 | ||||
| +            b" foo=bar\n",
 | ||||
| +            b"0 \n",
 | ||||
| +            b"1 \n",
 | ||||
| +            b"2 \n",
 | ||||
| +            b"3 =\n",
 | ||||
| +            b"4 =a\n",
 | ||||
| +            b"1000000 foo=bar\n",
 | ||||
| +            b"0 foo=bar\n",
 | ||||
| +            b"-12 foo=bar\n",
 | ||||
| +            b"000000000000000000000000036 foo=bar\n",
 | ||||
| +        )
 | ||||
| +        pax_headers = {"foo": "bar"}
 | ||||
| +
 | ||||
| +        for replacement in pax_header_replacements:
 | ||||
| +            with self.subTest(header=replacement):
 | ||||
| +                tar = tarfile.open(tmpname, "w", format=tarfile.PAX_FORMAT,
 | ||||
| +                                   encoding="iso8859-1")
 | ||||
| +                try:
 | ||||
| +                    t = tarfile.TarInfo()
 | ||||
| +                    t.name = "pax"  # non-ASCII
 | ||||
| +                    t.uid = 1
 | ||||
| +                    t.pax_headers = pax_headers
 | ||||
| +                    tar.addfile(t)
 | ||||
| +                finally:
 | ||||
| +                    tar.close()
 | ||||
| +
 | ||||
| +                with open(tmpname, "rb") as f:
 | ||||
| +                    data = f.read()
 | ||||
| +                    self.assertIn(b"11 foo=bar\n", data)
 | ||||
| +                    data = data.replace(b"11 foo=bar\n", replacement)
 | ||||
| +
 | ||||
| +                with open(tmpname, "wb") as f:
 | ||||
| +                    f.truncate()
 | ||||
| +                    f.write(data)
 | ||||
| +
 | ||||
| +                with self.assertRaisesRegex(tarfile.ReadError, r"method tar: ReadError\('invalid header'\)"):
 | ||||
| +                    tarfile.open(tmpname, encoding="iso8859-1")
 | ||||
| +
 | ||||
|   | ||||
|  class WriteTestBase(TarTest): | ||||
|      # Put all write tests in here that are supposed to be tested | ||||
| diff --git a/Misc/NEWS.d/next/Security/2024-07-02-13-39-20.gh-issue-121285.hrl-yI.rst b/Misc/NEWS.d/next/Security/2024-07-02-13-39-20.gh-issue-121285.hrl-yI.rst
 | ||||
| new file mode 100644 | ||||
| index 00000000000000..81f918bfe2b255
 | ||||
| --- /dev/null
 | ||||
| +++ b/Misc/NEWS.d/next/Security/2024-07-02-13-39-20.gh-issue-121285.hrl-yI.rst
 | ||||
| @@ -0,0 +1,2 @@
 | ||||
| +Remove backtracking from tarfile header parsing for ``hdrcharset``, PAX, and
 | ||||
| +GNU sparse headers.
 | ||||
| @ -1,293 +0,0 @@ | ||||
| From 9f8d3a48361db210f246b7409490b6d13ee40ae1 Mon Sep 17 00:00:00 2001 | ||||
| From: Victor Stinner <vstinner@python.org> | ||||
| Date: Thu, 31 Oct 2024 19:09:20 +0100 | ||||
| Subject: [PATCH] gh-124651: Quote template strings in `venv` activation | ||||
|  scripts (GH-124712) (GH-126185) | ||||
| 
 | ||||
| (cherry picked from commit d48cc82ed25e26b02eb97c6263d95dcaa1e9111b) | ||||
| ---
 | ||||
|  Lib/test/test_venv.py                         | 81 +++++++++++++++++++ | ||||
|  Lib/venv/__init__.py                          | 42 ++++++++-- | ||||
|  Lib/venv/scripts/common/activate              | 10 +-- | ||||
|  Lib/venv/scripts/posix/activate.csh           |  8 +- | ||||
|  Lib/venv/scripts/posix/activate.fish          |  8 +- | ||||
|  ...-09-28-02-03-04.gh-issue-124651.bLBGtH.rst |  1 + | ||||
|  6 files changed, 132 insertions(+), 18 deletions(-) | ||||
|  create mode 100644 Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst | ||||
| 
 | ||||
| diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
 | ||||
| index feaf978..8926014 100644
 | ||||
| --- a/Lib/test/test_venv.py
 | ||||
| +++ b/Lib/test/test_venv.py
 | ||||
| @@ -17,6 +17,7 @@ import subprocess
 | ||||
|  import sys | ||||
|  import sysconfig | ||||
|  import tempfile | ||||
| +import shlex
 | ||||
|  from test.support import (captured_stdout, captured_stderr, | ||||
|                            skip_if_broken_multiprocessing_synchronize, verbose, | ||||
|                            requires_subprocess, is_emscripten, is_wasi, | ||||
| @@ -97,6 +98,10 @@ class BaseTest(unittest.TestCase):
 | ||||
|              result = f.read() | ||||
|          return result | ||||
|   | ||||
| +    def assertEndsWith(self, string, tail):
 | ||||
| +        if not string.endswith(tail):
 | ||||
| +            self.fail(f"String {string!r} does not end with {tail!r}")
 | ||||
| +
 | ||||
|  class BasicTest(BaseTest): | ||||
|      """Test venv module functionality.""" | ||||
|   | ||||
| @@ -446,6 +451,82 @@ class BasicTest(BaseTest):
 | ||||
|              'import sys; print(sys.executable)']) | ||||
|          self.assertEqual(out.strip(), envpy.encode()) | ||||
|   | ||||
| +    # gh-124651: test quoted strings
 | ||||
| +    @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows')
 | ||||
| +    def test_special_chars_bash(self):
 | ||||
| +        """
 | ||||
| +        Test that the template strings are quoted properly (bash)
 | ||||
| +        """
 | ||||
| +        rmtree(self.env_dir)
 | ||||
| +        bash = shutil.which('bash')
 | ||||
| +        if bash is None:
 | ||||
| +            self.skipTest('bash required for this test')
 | ||||
| +        env_name = '"\';&&$e|\'"'
 | ||||
| +        env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
 | ||||
| +        builder = venv.EnvBuilder(clear=True)
 | ||||
| +        builder.create(env_dir)
 | ||||
| +        activate = os.path.join(env_dir, self.bindir, 'activate')
 | ||||
| +        test_script = os.path.join(self.env_dir, 'test_special_chars.sh')
 | ||||
| +        with open(test_script, "w") as f:
 | ||||
| +            f.write(f'source {shlex.quote(activate)}\n'
 | ||||
| +                    'python -c \'import sys; print(sys.executable)\'\n'
 | ||||
| +                    'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n'
 | ||||
| +                    'deactivate\n')
 | ||||
| +        out, err = check_output([bash, test_script])
 | ||||
| +        lines = out.splitlines()
 | ||||
| +        self.assertTrue(env_name.encode() in lines[0])
 | ||||
| +        self.assertEndsWith(lines[1], env_name.encode())
 | ||||
| +
 | ||||
| +    # gh-124651: test quoted strings
 | ||||
| +    @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows')
 | ||||
| +    def test_special_chars_csh(self):
 | ||||
| +        """
 | ||||
| +        Test that the template strings are quoted properly (csh)
 | ||||
| +        """
 | ||||
| +        rmtree(self.env_dir)
 | ||||
| +        csh = shutil.which('tcsh') or shutil.which('csh')
 | ||||
| +        if csh is None:
 | ||||
| +            self.skipTest('csh required for this test')
 | ||||
| +        env_name = '"\';&&$e|\'"'
 | ||||
| +        env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
 | ||||
| +        builder = venv.EnvBuilder(clear=True)
 | ||||
| +        builder.create(env_dir)
 | ||||
| +        activate = os.path.join(env_dir, self.bindir, 'activate.csh')
 | ||||
| +        test_script = os.path.join(self.env_dir, 'test_special_chars.csh')
 | ||||
| +        with open(test_script, "w") as f:
 | ||||
| +            f.write(f'source {shlex.quote(activate)}\n'
 | ||||
| +                    'python -c \'import sys; print(sys.executable)\'\n'
 | ||||
| +                    'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n'
 | ||||
| +                    'deactivate\n')
 | ||||
| +        out, err = check_output([csh, test_script])
 | ||||
| +        lines = out.splitlines()
 | ||||
| +        self.assertTrue(env_name.encode() in lines[0])
 | ||||
| +        self.assertEndsWith(lines[1], env_name.encode())
 | ||||
| +
 | ||||
| +    # gh-124651: test quoted strings on Windows
 | ||||
| +    @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
 | ||||
| +    def test_special_chars_windows(self):
 | ||||
| +        """
 | ||||
| +        Test that the template strings are quoted properly on Windows
 | ||||
| +        """
 | ||||
| +        rmtree(self.env_dir)
 | ||||
| +        env_name = "'&&^$e"
 | ||||
| +        env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
 | ||||
| +        builder = venv.EnvBuilder(clear=True)
 | ||||
| +        builder.create(env_dir)
 | ||||
| +        activate = os.path.join(env_dir, self.bindir, 'activate.bat')
 | ||||
| +        test_batch = os.path.join(self.env_dir, 'test_special_chars.bat')
 | ||||
| +        with open(test_batch, "w") as f:
 | ||||
| +            f.write('@echo off\n'
 | ||||
| +                    f'"{activate}" & '
 | ||||
| +                    f'{self.exe} -c "import sys; print(sys.executable)" & '
 | ||||
| +                    f'{self.exe} -c "import os; print(os.environ[\'VIRTUAL_ENV\'])" & '
 | ||||
| +                    'deactivate')
 | ||||
| +        out, err = check_output([test_batch])
 | ||||
| +        lines = out.splitlines()
 | ||||
| +        self.assertTrue(env_name.encode() in lines[0])
 | ||||
| +        self.assertEndsWith(lines[1], env_name.encode())
 | ||||
| +
 | ||||
|      @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows') | ||||
|      def test_unicode_in_batch_file(self): | ||||
|          """ | ||||
| diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
 | ||||
| index d5dec4a..aeb522c 100644
 | ||||
| --- a/Lib/venv/__init__.py
 | ||||
| +++ b/Lib/venv/__init__.py
 | ||||
| @@ -11,6 +11,7 @@ import subprocess
 | ||||
|  import sys | ||||
|  import sysconfig | ||||
|  import types | ||||
| +import shlex
 | ||||
|   | ||||
|   | ||||
|  CORE_VENV_DEPS = ('pip',) | ||||
| @@ -422,11 +423,41 @@ class EnvBuilder:
 | ||||
|          :param context: The information for the environment creation request | ||||
|                          being processed. | ||||
|          """ | ||||
| -        text = text.replace('__VENV_DIR__', context.env_dir)
 | ||||
| -        text = text.replace('__VENV_NAME__', context.env_name)
 | ||||
| -        text = text.replace('__VENV_PROMPT__', context.prompt)
 | ||||
| -        text = text.replace('__VENV_BIN_NAME__', context.bin_name)
 | ||||
| -        text = text.replace('__VENV_PYTHON__', context.env_exe)
 | ||||
| +        replacements = {
 | ||||
| +            '__VENV_DIR__': context.env_dir,
 | ||||
| +            '__VENV_NAME__': context.env_name,
 | ||||
| +            '__VENV_PROMPT__': context.prompt,
 | ||||
| +            '__VENV_BIN_NAME__': context.bin_name,
 | ||||
| +            '__VENV_PYTHON__': context.env_exe,
 | ||||
| +        }
 | ||||
| +
 | ||||
| +        def quote_ps1(s):
 | ||||
| +            """
 | ||||
| +            This should satisfy PowerShell quoting rules [1], unless the quoted
 | ||||
| +            string is passed directly to Windows native commands [2].
 | ||||
| +            [1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules
 | ||||
| +            [2]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing#passing-arguments-that-contain-quote-characters
 | ||||
| +            """
 | ||||
| +            s = s.replace("'", "''")
 | ||||
| +            return f"'{s}'"
 | ||||
| +
 | ||||
| +        def quote_bat(s):
 | ||||
| +            return s
 | ||||
| +
 | ||||
| +        # gh-124651: need to quote the template strings properly
 | ||||
| +        quote = shlex.quote
 | ||||
| +        script_path = context.script_path
 | ||||
| +        if script_path.endswith('.ps1'):
 | ||||
| +            quote = quote_ps1
 | ||||
| +        elif script_path.endswith('.bat'):
 | ||||
| +            quote = quote_bat
 | ||||
| +        else:
 | ||||
| +            # fallbacks to POSIX shell compliant quote
 | ||||
| +            quote = shlex.quote
 | ||||
| +
 | ||||
| +        replacements = {key: quote(s) for key, s in replacements.items()}
 | ||||
| +        for key, quoted in replacements.items():
 | ||||
| +            text = text.replace(key, quoted)
 | ||||
|          return text | ||||
|   | ||||
|      def install_scripts(self, context, path): | ||||
| @@ -466,6 +497,7 @@ class EnvBuilder:
 | ||||
|                  with open(srcfile, 'rb') as f: | ||||
|                      data = f.read() | ||||
|                  if not srcfile.endswith(('.exe', '.pdb')): | ||||
| +                    context.script_path = srcfile
 | ||||
|                      try: | ||||
|                          data = data.decode('utf-8') | ||||
|                          data = self.replace_variables(data, context) | ||||
| diff --git a/Lib/venv/scripts/common/activate b/Lib/venv/scripts/common/activate
 | ||||
| index d5914e0..47602ca 100644
 | ||||
| --- a/Lib/venv/scripts/common/activate
 | ||||
| +++ b/Lib/venv/scripts/common/activate
 | ||||
| @@ -39,14 +39,14 @@ deactivate nondestructive
 | ||||
|  if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then | ||||
|      # transform D:\path\to\venv to /d/path/to/venv on MSYS | ||||
|      # and to /cygdrive/d/path/to/venv on Cygwin | ||||
| -    export VIRTUAL_ENV=$(cygpath "__VENV_DIR__")
 | ||||
| +    export VIRTUAL_ENV=$(cygpath __VENV_DIR__)
 | ||||
|  else | ||||
|      # use the path as-is | ||||
| -    export VIRTUAL_ENV="__VENV_DIR__"
 | ||||
| +    export VIRTUAL_ENV=__VENV_DIR__
 | ||||
|  fi | ||||
|   | ||||
|  _OLD_VIRTUAL_PATH="$PATH" | ||||
| -PATH="$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH"
 | ||||
| +PATH="$VIRTUAL_ENV/"__VENV_BIN_NAME__":$PATH"
 | ||||
|  export PATH | ||||
|   | ||||
|  # unset PYTHONHOME if set | ||||
| @@ -59,9 +59,9 @@ fi
 | ||||
|   | ||||
|  if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then | ||||
|      _OLD_VIRTUAL_PS1="${PS1:-}" | ||||
| -    PS1="__VENV_PROMPT__${PS1:-}"
 | ||||
| +    PS1=__VENV_PROMPT__"${PS1:-}"
 | ||||
|      export PS1 | ||||
| -    VIRTUAL_ENV_PROMPT="__VENV_PROMPT__"
 | ||||
| +    VIRTUAL_ENV_PROMPT=__VENV_PROMPT__
 | ||||
|      export VIRTUAL_ENV_PROMPT | ||||
|  fi | ||||
|   | ||||
| diff --git a/Lib/venv/scripts/posix/activate.csh b/Lib/venv/scripts/posix/activate.csh
 | ||||
| index 5e8d66f..08f7929 100644
 | ||||
| --- a/Lib/venv/scripts/posix/activate.csh
 | ||||
| +++ b/Lib/venv/scripts/posix/activate.csh
 | ||||
| @@ -9,17 +9,17 @@ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PA
 | ||||
|  # Unset irrelevant variables. | ||||
|  deactivate nondestructive | ||||
|   | ||||
| -setenv VIRTUAL_ENV "__VENV_DIR__"
 | ||||
| +setenv VIRTUAL_ENV __VENV_DIR__
 | ||||
|   | ||||
|  set _OLD_VIRTUAL_PATH="$PATH" | ||||
| -setenv PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH"
 | ||||
| +setenv PATH "$VIRTUAL_ENV/"__VENV_BIN_NAME__":$PATH"
 | ||||
|   | ||||
|   | ||||
|  set _OLD_VIRTUAL_PROMPT="$prompt" | ||||
|   | ||||
|  if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then | ||||
| -    set prompt = "__VENV_PROMPT__$prompt"
 | ||||
| -    setenv VIRTUAL_ENV_PROMPT "__VENV_PROMPT__"
 | ||||
| +    set prompt = __VENV_PROMPT__"$prompt"
 | ||||
| +    setenv VIRTUAL_ENV_PROMPT __VENV_PROMPT__
 | ||||
|  endif | ||||
|   | ||||
|  alias pydoc python -m pydoc | ||||
| diff --git a/Lib/venv/scripts/posix/activate.fish b/Lib/venv/scripts/posix/activate.fish
 | ||||
| index 91ad644..508cab0 100644
 | ||||
| --- a/Lib/venv/scripts/posix/activate.fish
 | ||||
| +++ b/Lib/venv/scripts/posix/activate.fish
 | ||||
| @@ -33,10 +33,10 @@ end
 | ||||
|  # Unset irrelevant variables. | ||||
|  deactivate nondestructive | ||||
|   | ||||
| -set -gx VIRTUAL_ENV "__VENV_DIR__"
 | ||||
| +set -gx VIRTUAL_ENV __VENV_DIR__
 | ||||
|   | ||||
|  set -gx _OLD_VIRTUAL_PATH $PATH | ||||
| -set -gx PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__" $PATH
 | ||||
| +set -gx PATH "$VIRTUAL_ENV/"__VENV_BIN_NAME__ $PATH
 | ||||
|   | ||||
|  # Unset PYTHONHOME if set. | ||||
|  if set -q PYTHONHOME | ||||
| @@ -56,7 +56,7 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
 | ||||
|          set -l old_status $status | ||||
|   | ||||
|          # Output the venv prompt; color taken from the blue of the Python logo. | ||||
| -        printf "%s%s%s" (set_color 4B8BBE) "__VENV_PROMPT__" (set_color normal)
 | ||||
| +        printf "%s%s%s" (set_color 4B8BBE) __VENV_PROMPT__ (set_color normal)
 | ||||
|   | ||||
|          # Restore the return status of the previous command. | ||||
|          echo "exit $old_status" | . | ||||
| @@ -65,5 +65,5 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
 | ||||
|      end | ||||
|   | ||||
|      set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" | ||||
| -    set -gx VIRTUAL_ENV_PROMPT "__VENV_PROMPT__"
 | ||||
| +    set -gx VIRTUAL_ENV_PROMPT __VENV_PROMPT__
 | ||||
|  end | ||||
| diff --git a/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst b/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst
 | ||||
| new file mode 100644 | ||||
| index 0000000..17fc917
 | ||||
| --- /dev/null
 | ||||
| +++ b/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst
 | ||||
| @@ -0,0 +1 @@
 | ||||
| +Properly quote template strings in :mod:`venv` activation scripts.
 | ||||
| -- 
 | ||||
| 2.47.1 | ||||
| 
 | ||||
| @ -1,62 +0,0 @@ | ||||
| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 | ||||
| From: "Miss Islington (bot)" | ||||
|  <31488909+miss-islington@users.noreply.github.com> | ||||
| Date: Fri, 6 Dec 2024 06:12:40 +0100 | ||||
| Subject: [PATCH] 00445: CVE-2024-12254: Ensure | ||||
|  _SelectorSocketTransport.writelines pauses the protocol if needed | ||||
| 
 | ||||
| Ensure _SelectorSocketTransport.writelines pauses the protocol if it reaches the high water mark as needed. | ||||
| 
 | ||||
| Resolved upstream: https://github.com/python/cpython/issues/127655 | ||||
| 
 | ||||
| Co-authored-by: J. Nick Koston <nick@koston.org> | ||||
| Co-authored-by: Kumar Aditya <kumaraditya@python.org> | ||||
| ---
 | ||||
|  Lib/asyncio/selector_events.py                       |  1 + | ||||
|  Lib/test/test_asyncio/test_selector_events.py        | 12 ++++++++++++ | ||||
|  .../2024-12-05-21-35-19.gh-issue-127655.xpPoOf.rst   |  1 + | ||||
|  3 files changed, 14 insertions(+) | ||||
|  create mode 100644 Misc/NEWS.d/next/Security/2024-12-05-21-35-19.gh-issue-127655.xpPoOf.rst | ||||
| 
 | ||||
| diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py
 | ||||
| index 790711f834..dd79ad18df 100644
 | ||||
| --- a/Lib/asyncio/selector_events.py
 | ||||
| +++ b/Lib/asyncio/selector_events.py
 | ||||
| @@ -1183,6 +1183,7 @@ def writelines(self, list_of_data):
 | ||||
|          # If the entire buffer couldn't be written, register a write handler | ||||
|          if self._buffer: | ||||
|              self._loop._add_writer(self._sock_fd, self._write_ready) | ||||
| +            self._maybe_pause_protocol()
 | ||||
|   | ||||
|      def can_write_eof(self): | ||||
|          return True | ||||
| diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py
 | ||||
| index 47693ea4d3..736c19796e 100644
 | ||||
| --- a/Lib/test/test_asyncio/test_selector_events.py
 | ||||
| +++ b/Lib/test/test_asyncio/test_selector_events.py
 | ||||
| @@ -805,6 +805,18 @@ def test_writelines_send_partial(self):
 | ||||
|          self.assertTrue(self.sock.send.called) | ||||
|          self.assertTrue(self.loop.writers) | ||||
|   | ||||
| +    def test_writelines_pauses_protocol(self):
 | ||||
| +        data = memoryview(b'data')
 | ||||
| +        self.sock.send.return_value = 2
 | ||||
| +        self.sock.send.fileno.return_value = 7
 | ||||
| +
 | ||||
| +        transport = self.socket_transport()
 | ||||
| +        transport._high_water = 1
 | ||||
| +        transport.writelines([data])
 | ||||
| +        self.assertTrue(self.protocol.pause_writing.called)
 | ||||
| +        self.assertTrue(self.sock.send.called)
 | ||||
| +        self.assertTrue(self.loop.writers)
 | ||||
| +
 | ||||
|      @unittest.skipUnless(selector_events._HAS_SENDMSG, 'no sendmsg') | ||||
|      def test_write_sendmsg_full(self): | ||||
|          data = memoryview(b'data') | ||||
| diff --git a/Misc/NEWS.d/next/Security/2024-12-05-21-35-19.gh-issue-127655.xpPoOf.rst b/Misc/NEWS.d/next/Security/2024-12-05-21-35-19.gh-issue-127655.xpPoOf.rst
 | ||||
| new file mode 100644 | ||||
| index 0000000000..76cfc58121
 | ||||
| --- /dev/null
 | ||||
| +++ b/Misc/NEWS.d/next/Security/2024-12-05-21-35-19.gh-issue-127655.xpPoOf.rst
 | ||||
| @@ -0,0 +1 @@
 | ||||
| +Fixed the :class:`!asyncio.selector_events._SelectorSocketTransport` transport not pausing writes for the protocol when the buffer reaches the high water mark when using :meth:`asyncio.WriteTransport.writelines`.
 | ||||
| @ -1,135 +0,0 @@ | ||||
| From dcc3eaef98cd94d6cb6cb0f44bd1c903d04f33b1 Mon Sep 17 00:00:00 2001 | ||||
| From: "Miss Islington (bot)" | ||||
|  <31488909+miss-islington@users.noreply.github.com> | ||||
| Date: Sun, 25 Aug 2024 00:37:11 +0200 | ||||
| Subject: [PATCH] [3.12] gh-123067: Fix quadratic complexity in parsing | ||||
|  "-quoted cookie values with backslashes (GH-123075) (#123104) | ||||
| 
 | ||||
| gh-123067: Fix quadratic complexity in parsing "-quoted cookie values with backslashes (GH-123075) | ||||
| 
 | ||||
| This fixes CVE-2024-7592. | ||||
| (cherry picked from commit 44e458357fca05ca0ae2658d62c8c595b048b5ef) | ||||
| 
 | ||||
| Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> | ||||
| ---
 | ||||
|  Lib/http/cookies.py                           | 34 ++++------------- | ||||
|  Lib/test/test_http_cookies.py                 | 38 +++++++++++++++++++ | ||||
|  ...-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst |  1 + | ||||
|  3 files changed, 47 insertions(+), 26 deletions(-) | ||||
|  create mode 100644 Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst | ||||
| 
 | ||||
| diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py
 | ||||
| index 351faf428a20cd..6b9ed24ad8ec78 100644
 | ||||
| --- a/Lib/http/cookies.py
 | ||||
| +++ b/Lib/http/cookies.py
 | ||||
| @@ -184,8 +184,13 @@ def _quote(str):
 | ||||
|          return '"' + str.translate(_Translator) + '"' | ||||
|   | ||||
|   | ||||
| -_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
 | ||||
| -_QuotePatt = re.compile(r"[\\].")
 | ||||
| +_unquote_sub = re.compile(r'\\(?:([0-3][0-7][0-7])|(.))').sub
 | ||||
| +
 | ||||
| +def _unquote_replace(m):
 | ||||
| +    if m[1]:
 | ||||
| +        return chr(int(m[1], 8))
 | ||||
| +    else:
 | ||||
| +        return m[2]
 | ||||
|   | ||||
|  def _unquote(str): | ||||
|      # If there aren't any doublequotes, | ||||
| @@ -205,30 +210,7 @@ def _unquote(str):
 | ||||
|      #    \012 --> \n | ||||
|      #    \"   --> " | ||||
|      # | ||||
| -    i = 0
 | ||||
| -    n = len(str)
 | ||||
| -    res = []
 | ||||
| -    while 0 <= i < n:
 | ||||
| -        o_match = _OctalPatt.search(str, i)
 | ||||
| -        q_match = _QuotePatt.search(str, i)
 | ||||
| -        if not o_match and not q_match:              # Neither matched
 | ||||
| -            res.append(str[i:])
 | ||||
| -            break
 | ||||
| -        # else:
 | ||||
| -        j = k = -1
 | ||||
| -        if o_match:
 | ||||
| -            j = o_match.start(0)
 | ||||
| -        if q_match:
 | ||||
| -            k = q_match.start(0)
 | ||||
| -        if q_match and (not o_match or k < j):     # QuotePatt matched
 | ||||
| -            res.append(str[i:k])
 | ||||
| -            res.append(str[k+1])
 | ||||
| -            i = k + 2
 | ||||
| -        else:                                      # OctalPatt matched
 | ||||
| -            res.append(str[i:j])
 | ||||
| -            res.append(chr(int(str[j+1:j+4], 8)))
 | ||||
| -            i = j + 4
 | ||||
| -    return _nulljoin(res)
 | ||||
| +    return _unquote_sub(_unquote_replace, str)
 | ||||
|   | ||||
|  # The _getdate() routine is used to set the expiration time in the cookie's HTTP | ||||
|  # header.  By default, _getdate() returns the current time in the appropriate | ||||
| diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py
 | ||||
| index 925c8697f60de6..8879902a6e2f41 100644
 | ||||
| --- a/Lib/test/test_http_cookies.py
 | ||||
| +++ b/Lib/test/test_http_cookies.py
 | ||||
| @@ -5,6 +5,7 @@
 | ||||
|  import doctest | ||||
|  from http import cookies | ||||
|  import pickle | ||||
| +from test import support
 | ||||
|   | ||||
|   | ||||
|  class CookieTests(unittest.TestCase): | ||||
| @@ -58,6 +59,43 @@ def test_basic(self):
 | ||||
|              for k, v in sorted(case['dict'].items()): | ||||
|                  self.assertEqual(C[k].value, v) | ||||
|   | ||||
| +    def test_unquote(self):
 | ||||
| +        cases = [
 | ||||
| +            (r'a="b=\""', 'b="'),
 | ||||
| +            (r'a="b=\\"', 'b=\\'),
 | ||||
| +            (r'a="b=\="', 'b=='),
 | ||||
| +            (r'a="b=\n"', 'b=n'),
 | ||||
| +            (r'a="b=\042"', 'b="'),
 | ||||
| +            (r'a="b=\134"', 'b=\\'),
 | ||||
| +            (r'a="b=\377"', 'b=\xff'),
 | ||||
| +            (r'a="b=\400"', 'b=400'),
 | ||||
| +            (r'a="b=\42"', 'b=42'),
 | ||||
| +            (r'a="b=\\042"', 'b=\\042'),
 | ||||
| +            (r'a="b=\\134"', 'b=\\134'),
 | ||||
| +            (r'a="b=\\\""', 'b=\\"'),
 | ||||
| +            (r'a="b=\\\042"', 'b=\\"'),
 | ||||
| +            (r'a="b=\134\""', 'b=\\"'),
 | ||||
| +            (r'a="b=\134\042"', 'b=\\"'),
 | ||||
| +        ]
 | ||||
| +        for encoded, decoded in cases:
 | ||||
| +            with self.subTest(encoded):
 | ||||
| +                C = cookies.SimpleCookie()
 | ||||
| +                C.load(encoded)
 | ||||
| +                self.assertEqual(C['a'].value, decoded)
 | ||||
| +
 | ||||
| +    @support.requires_resource('cpu')
 | ||||
| +    def test_unquote_large(self):
 | ||||
| +        n = 10**6
 | ||||
| +        for encoded in r'\\', r'\134':
 | ||||
| +            with self.subTest(encoded):
 | ||||
| +                data = 'a="b=' + encoded*n + ';"'
 | ||||
| +                C = cookies.SimpleCookie()
 | ||||
| +                C.load(data)
 | ||||
| +                value = C['a'].value
 | ||||
| +                self.assertEqual(value[:3], 'b=\\')
 | ||||
| +                self.assertEqual(value[-2:], '\\;')
 | ||||
| +                self.assertEqual(len(value), n + 3)
 | ||||
| +
 | ||||
|      def test_load(self): | ||||
|          C = cookies.SimpleCookie() | ||||
|          C.load('Customer="WILE_E_COYOTE"; Version=1; Path=/acme') | ||||
| diff --git a/Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst b/Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst
 | ||||
| new file mode 100644 | ||||
| index 00000000000000..6a234561fe31a3
 | ||||
| --- /dev/null
 | ||||
| +++ b/Misc/NEWS.d/next/Library/2024-08-16-19-13-21.gh-issue-123067.Nx9O4R.rst
 | ||||
| @@ -0,0 +1 @@
 | ||||
| +Fix quadratic complexity in parsing ``"``-quoted cookie values with backslashes by :mod:`http.cookies`.
 | ||||
| @ -1,18 +0,0 @@ | ||||
| -----BEGIN PGP SIGNATURE----- | ||||
| 
 | ||||
| iQKTBAABCgB9FiEEcWlgX2LHUTVtBUomqCHmgOX6YwUFAmayiFtfFIAAAAAALgAo | ||||
| aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDcx | ||||
| Njk2MDVGNjJDNzUxMzU2RDA1NEEyNkE4MjFFNjgwRTVGQTYzMDUACgkQqCHmgOX6 | ||||
| YwUr4g//VyVs9tvbtiSp8pGe8f1gYErEw54r124sL/CBuNii8Irts1j5ymGxcm+l | ||||
| hshPK5UlqRnhd5dCJWFTvLTXa5Ko2R1L3JyyxfGd1hmDuMhrWsDHijI0R7L/mGM5 | ||||
| 6X2LTaadBVNvk8HaNKvR8SEWvo68rdnOuYElFA9ir7uqwjO26ZWz9FfH80YDGwo8 | ||||
| Blef2NYw8rNhiaZMFV0HYV7D+YyUAZnFNfW8M7Fd4oskUyj1tD9J89T9FFLYN09d | ||||
| BcCIf+EdiEfqRpKxH89bW2g52kDrm4jYGONtpyF8eruyS3YwYSbvbuWioBYKmlxC | ||||
| s51mieXz6G325GTZnmPxLek3ywPv6Gil9y0wH3fIr2BsWsmXust4LBpjDGt56Fy6 | ||||
| seokGBg8xzsBSk3iEqNoFmNsy/QOiuCcDejX4XqBDNodOlETQPJb07TkTI2iOmg9 | ||||
| NG4Atiz1HvGVxK68UuK9IIcNHyaWUmH8h4VQFGvc6KV6feP5Nm21Y12PZ5XIqJBO | ||||
| Y8M/VJIJ5koaNPQfnBbbI5YBkUr4BVpIXIpY5LM/L5sUo2C3R7hMi0VGK88HGfSQ | ||||
| KV4JmZgf6RMBNmrWY12sryS1QQ6q3P110GTUGQWB3sxxNbhmfcrK+4viqHc83yDz | ||||
| ifmk33HuqaQGU7OzUMHeNcoCJIPo3H1FpoHOn9wLLCtA1pT+as4= | ||||
| =t0Rk | ||||
| -----END PGP SIGNATURE----- | ||||
							
								
								
									
										18
									
								
								SOURCES/Python-3.12.9.tar.xz.asc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								SOURCES/Python-3.12.9.tar.xz.asc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| -----BEGIN PGP SIGNATURE----- | ||||
| 
 | ||||
| iQKTBAABCgB9FiEEcWlgX2LHUTVtBUomqCHmgOX6YwUFAmeiX7JfFIAAAAAALgAo | ||||
| aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDcx | ||||
| Njk2MDVGNjJDNzUxMzU2RDA1NEEyNkE4MjFFNjgwRTVGQTYzMDUACgkQqCHmgOX6 | ||||
| YwXTqw//VlGJA5CRDfljMwN9BmG2hdXB1B7Lj0PssuAo4A/lH99gb4DRVDS9LNjr | ||||
| 99WdH/fQQovx6rTbtyJnN8Vh7SSduBi/vOc5n5VOXZB0buqR0l+0wu4m43Slu6xP | ||||
| fXO349Hr6585lemU8x54TrP756rSVUhy3T+krUuNDL9W1Wrp2yDCpt4tUoEhNXGw | ||||
| DoYS8MrK/ygLNV/7p2DeMWOHNdbjKNH6rfzl60IAwAp7oANcyoj6Pho960bbeUDo | ||||
| tb47Pw0WWZv3EuITP6bPa8+Z6dj096cFL3AQJ3ap16OduwiaOsGhqTfe4+kbp6ut | ||||
| Gp/1HeIHzPbEV0E5K78RWHuzBYgU1oPGiMjlp7WkA7bP2OSTF7nM4EBkiiihk2qx | ||||
| 3d5VF9wpVRJ4AuR/aWcWcMnvD2ziSWfzZM3Z3VLnTaWYpuRkQp8TTiFr1vHqxMYm | ||||
| p/8AozzBJMfOS6u/Q0WNAdk6x3VB0DXnTAETXQVIrex4DXqX/3WSMWK5/x/OyCh9 | ||||
| ytdreIQYbv1KvlNQJkgpPb7jlUSXp8t9fHCXt4hszhJgtjwIj/+CuSeAgX0bhopV | ||||
| XsqOBseDNhATg38mhwBVaeFKGRpxsKdpxcdqSEGKuhXtEI/hJmkpZGw49gy3xWxB | ||||
| KlgRgKjCPw+BGAIVV9qvdtJzam8a09SKVcslqgF619q0byQoBmo= | ||||
| =1TbP | ||||
| -----END PGP SIGNATURE----- | ||||
| @ -16,11 +16,11 @@ URL: https://www.python.org/ | ||||
| 
 | ||||
| #  WARNING  When rebasing to a new Python version, | ||||
| #           remember to update the python3-docs package as well | ||||
| %global general_version %{pybasever}.5 | ||||
| %global general_version %{pybasever}.9 | ||||
| #global prerel ... | ||||
| %global upstream_version %{general_version}%{?prerel} | ||||
| Version: %{general_version}%{?prerel:~%{prerel}} | ||||
| Release: 2%{?dist}.3 | ||||
| Release: 1%{?dist} | ||||
| License: Python-2.0.1 | ||||
| 
 | ||||
| 
 | ||||
| @ -66,7 +66,7 @@ License: Python-2.0.1 | ||||
| # If the rpmwheels condition is disabled, we use the bundled wheel packages | ||||
| # from Python with the versions below. | ||||
| # This needs to be manually updated when we update Python. | ||||
| %global pip_version 24.2 | ||||
| %global pip_version 24.3.1 | ||||
| %global setuptools_version 67.6.1 | ||||
| %global wheel_version 0.40.0 | ||||
| # All of those also include a list of indirect bundled libs: | ||||
| @ -74,8 +74,8 @@ License: Python-2.0.1 | ||||
| #  $ %%{_rpmconfigdir}/pythonbundles.py <(unzip -p Lib/ensurepip/_bundled/pip-*.whl pip/_vendor/vendor.txt) | ||||
| %global pip_bundled_provides %{expand: | ||||
| Provides: bundled(python3dist(cachecontrol)) = 0.14 | ||||
| Provides: bundled(python3dist(certifi)) = 2024.7.4 | ||||
| Provides: bundled(python3dist(distlib)) = 0.3.8 | ||||
| Provides: bundled(python3dist(certifi)) = 2024.8.30 | ||||
| Provides: bundled(python3dist(distlib)) = 0.3.9 | ||||
| Provides: bundled(python3dist(distro)) = 1.9 | ||||
| Provides: bundled(python3dist(idna)) = 3.7 | ||||
| Provides: bundled(python3dist(msgpack)) = 1.0.8 | ||||
| @ -88,9 +88,9 @@ Provides: bundled(python3dist(resolvelib)) = 1.0.1 | ||||
| Provides: bundled(python3dist(rich)) = 13.7.1 | ||||
| Provides: bundled(python3dist(setuptools)) = 70.3 | ||||
| Provides: bundled(python3dist(tomli)) = 2.0.1 | ||||
| Provides: bundled(python3dist(truststore)) = 0.9.1 | ||||
| Provides: bundled(python3dist(truststore)) = 0.10 | ||||
| Provides: bundled(python3dist(typing-extensions)) = 4.12.2 | ||||
| Provides: bundled(python3dist(urllib3)) = 1.26.18 | ||||
| Provides: bundled(python3dist(urllib3)) = 1.26.20 | ||||
| } | ||||
| # setuptools | ||||
| # vendor.txt files not in .whl | ||||
| @ -329,7 +329,7 @@ Source11: idle3.appdata.xml | ||||
| 
 | ||||
| # (Patches taken from github.com/fedora-python/cpython) | ||||
| 
 | ||||
| # 00251 # cae5a6abc5df08239c85b83e4e250b6f2702e4f5 | ||||
| # 00251 # 6a4ec74157aa01f1ada9f29f30a371cd9e5369e8 | ||||
| # Change user install location | ||||
| # | ||||
| # Set values of base and platbase in sysconfig from /usr | ||||
| @ -378,15 +378,6 @@ Patch371: 00371-revert-bpo-1596321-fix-threading-_shutdown-for-the-main-thread-g | ||||
| # - https://access.redhat.com/articles/7004769 | ||||
| Patch397: 00397-tarfile-filter.patch | ||||
| 
 | ||||
| # 00415 # 83e0fc3ec7bc38055c536f482578a10f6efcc08c | ||||
| # [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. | ||||
| Patch415: 00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch | ||||
| 
 | ||||
| # 00422 # a353cebef737c41420dc7ae2469dd657371b8881 | ||||
| # Fix tests for XMLPullParser with Expat 2.6.0 | ||||
| # | ||||
| @ -394,33 +385,6 @@ Patch415: 00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-par | ||||
| # CVE-2023-52425. Future versions of Expat may be more reactive. | ||||
| Patch422: 00422-fix-tests-for-xmlpullparser-with-expat-2-6-0.patch | ||||
| 
 | ||||
| # 00436 # c76cc2aa3a2c30375ade4859b732ada851cc89ed | ||||
| # [CVE-2024-8088] gh-122905: Sanitize names in zipfile.Path. | ||||
| Patch436: 00436-cve-2024-8088-gh-122905-sanitize-names-in-zipfile-path.patch | ||||
| 
 | ||||
| # 00437 # | ||||
| # CVE-2024-6232: gh-121285: Remove backtracking when parsing tarfile headers | ||||
| # Resolved upstream: https://github.com/python/cpython/issues/121285 | ||||
| Patch437: 00437-CVE-2024-6232.patch | ||||
| 
 | ||||
| # 00443 # | ||||
| # CVE-2024-9287: Virtual environment (venv) activation scripts don't quote paths | ||||
| # Resolved upstream: https://github.com/python/cpython/issues/124651 | ||||
| Patch443: 00443-CVE-2024-9287.patch | ||||
| 
 | ||||
| # 00445 # d1a32daddefad32ceb93155552858c0a0311b23e | ||||
| # CVE-2024-12254: Ensure _SelectorSocketTransport.writelines pauses the protocol if needed | ||||
| # | ||||
| # Ensure _SelectorSocketTransport.writelines pauses the protocol if it reaches the high water mark as needed. | ||||
| # | ||||
| # Resolved upstream: https://github.com/python/cpython/issues/127655 | ||||
| Patch445: 00445-cve-2024-12254-ensure-_selectorsockettransport-writelines-pauses-the-protocol-if-needed.patch | ||||
| 
 | ||||
| # 00453 # | ||||
| # CVE-2024-7592: Denial of Service Vulnerability in http.cookies._unquote() | ||||
| # Resolved upstream: https://github.com/python/cpython/issues/123067 | ||||
| Patch453: 00453-CVE-2024-7592.patch | ||||
| 
 | ||||
| # (New patches go here ^^^) | ||||
| # | ||||
| # When adding new patches to "python" and "python3" in Fedora, EL, etc., | ||||
| @ -1735,17 +1699,19 @@ CheckPython optimized | ||||
| # ====================================================== | ||||
| 
 | ||||
| %changelog | ||||
| * Wed Apr 02 2025 Lumír Balhar <lbalhar@redhat.com> - 3.12.5-2.3 | ||||
| - Security fix for CVE-2024-7592 | ||||
| Resolves: RHEL-85300 | ||||
| * Tue Feb 04 2025 Charalampos Stratakis <cstratak@redhat.com> - 3.12.9-1 | ||||
| - Update to 3.12.9 | ||||
| - Security fix for CVE-2025-0938 | ||||
| Resolves: RHEL-77261 | ||||
| 
 | ||||
| * Tue Dec 03 2024 Charalampos Stratakis <cstratak@redhat.com> - 3.12.5-2.2 | ||||
| * Tue Dec 03 2024 Charalampos Stratakis <cstratak@redhat.com> - 3.12.8-1 | ||||
| - Update to 3.12.8 | ||||
| - Security fix for CVE-2024-9287 and CVE-2024-12254 | ||||
| Resolves: RHEL-64885, RHEL-70316 | ||||
| Resolves: RHEL-64886, RHEL-70320 | ||||
| 
 | ||||
| * Wed Sep 11 2024 Lumír Balhar <lbalhar@redhat.com> - 3.12.5-2.1 | ||||
| - Security fix for CVE-2024-6232 | ||||
| Resolves: RHEL-57415 | ||||
| * 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 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user