diff --git a/CVE-2023-27043-email-parsing-errors.patch b/CVE-2023-27043-email-parsing-errors.patch deleted file mode 100644 index df77785..0000000 --- a/CVE-2023-27043-email-parsing-errors.patch +++ /dev/null @@ -1,461 +0,0 @@ ---- - Doc/library/email.utils.rst | 19 - - Lib/email/utils.py | 151 +++++++- - Lib/test/test_email/test_email.py | 187 +++++++++- - Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8 - 4 files changed, 344 insertions(+), 21 deletions(-) - ---- 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 `. Here's a simple -- example that gets all the recipients of a message:: -+ :meth:`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) - ---- 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 - --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 -+ 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 ']) -+ # 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] - - ---- a/Lib/test/test_email/test_email.py -+++ b/Lib/test/test_email/test_email.py -@@ -16,6 +16,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 -@@ -3288,15 +3289,137 @@ Foo - [('Al Person', 'aperson@dom.ain'), - ('Bud Person', 'bperson@dom.ain')]) - -+ 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" ' -+ 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" , "John Doe" ' -+ 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 R. Mastaler', 'jason@dom.ain')]) -+ for addresses, expected in ( -+ (['"Sürname, Firstname" '], -+ [('Sürname, Firstname', 'to@example.com')]), -+ -+ (['foo: ;'], -+ [('', '')]), -+ -+ (['foo: ;', '"Jason R. Mastaler" '], -+ [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]), -+ -+ ([r'Pete(A nice \) chap) '], -+ [('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 '], -+ [('John Doe (comment)', 'jdoe@machine.example')]), -+ -+ (['"Mary Smith: Personal Account" '], -+ [('Mary Smith: Personal Account', 'smith@home.example')]), -+ -+ (['Undisclosed recipients:;'], -+ [('', '')]), -+ -+ ([r', "Giant; \"Big\" Box" '], -+ [('', '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""" -@@ -3485,6 +3608,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" , "John Doe" ', -+ ' , ') -+ check(r'"Jane \"Doe\"." ', -+ ' ') -+ -+ # 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 , John Doe ', -+ '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): ---- /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. diff --git a/CVE-2023-52425-libexpat-2.6.0-backport.patch b/CVE-2023-52425-libexpat-2.6.0-backport.patch index 334b7b8..25e068f 100644 --- a/CVE-2023-52425-libexpat-2.6.0-backport.patch +++ b/CVE-2023-52425-libexpat-2.6.0-backport.patch @@ -45,7 +45,7 @@ def test_simple_xml_chunk_5(self): self.test_simple_xml(chunk_size=5, flush=True) -@@ -1648,6 +1652,9 @@ class XMLPullParserTest(unittest.TestCas +@@ -1647,6 +1651,9 @@ class XMLPullParserTest(unittest.TestCas self.assert_event_tags(parser, [('end', 'doc')]) diff --git a/CVE-2024-4032-private-IP-addrs.patch b/CVE-2024-4032-private-IP-addrs.patch deleted file mode 100644 index 7aa5e10..0000000 --- a/CVE-2024-4032-private-IP-addrs.patch +++ /dev/null @@ -1,376 +0,0 @@ -From 0740166e60b8cdae9448220beb28721f0126ee03 Mon Sep 17 00:00:00 2001 -From: Petr Viktorin -Date: Wed, 24 Apr 2024 14:29:30 +0200 -Subject: [PATCH 1/2] gh-113171: gh-65056: Fix "private" (non-global) IP - address ranges (GH-113179) (GH-113186) (GH-118177) - -* GH-113171: Fix "private" (non-global) IP address ranges (GH-113179) - -The _private_networks variables, used by various is_private -implementations, were missing some ranges and at the same time had -overly strict ranges (where there are more specific ranges considered -globally reachable by the IANA registries). - -This patch updates the ranges with what was missing or otherwise -incorrect. - -100.64.0.0/10 is left alone, for now, as it's been made special in [1]. - -The _address_exclude_many() call returns 8 networks for IPv4, 121 -networks for IPv6. - -[1] https://github.com/python/cpython/issues/61602 - -* GH-65056: Improve the IP address' is_global/is_private documentation (GH-113186) - -It wasn't clear what the semantics of is_global/is_private are and, when -one gets to the bottom of it, it's not quite so simple (hence the -exceptions listed). - -(cherry picked from commit 2a4cbf17af19a01d942f9579342f77c39fbd23c4) -(cherry picked from commit 40d75c2b7f5c67e254d0a025e0f2e2c7ada7f69f) - ---------- - -(cherry picked from commit f86b17ac511e68192ba71f27e752321a3252cee3) - -Co-authored-by: Jakub Stasiak ---- - Doc/library/ipaddress.rst | 43 +++- - Doc/whatsnew/3.10.rst | 9 - Lib/ipaddress.py | 99 +++++++--- - Lib/test/test_ipaddress.py | 52 +++++ - Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst | 9 - 5 files changed, 187 insertions(+), 25 deletions(-) - create mode 100644 Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst - ---- a/Doc/library/ipaddress.rst -+++ b/Doc/library/ipaddress.rst -@@ -188,18 +188,53 @@ write code that handles both IP versions - - .. attribute:: is_private - -- ``True`` if the address is allocated for private networks. See -+ ``True`` if the address is defined as not globally reachable by - iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -- (for IPv6). -+ (for IPv6) with the following exceptions: -+ -+ * ``is_private`` is ``False`` for the shared address space (``100.64.0.0/10``) -+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: -+ -+ address.is_private == address.ipv4_mapped.is_private -+ -+ ``is_private`` has value opposite to :attr:`is_global`, except for the shared address space -+ (``100.64.0.0/10`` range) where they are both ``False``. -+ -+ .. versionchanged:: 3.10.15 -+ -+ Fixed some false positives and false negatives. -+ -+ * ``192.0.0.0/24`` is considered private with the exception of ``192.0.0.9/32`` and -+ ``192.0.0.10/32`` (previously: only the ``192.0.0.0/29`` sub-range was considered private). -+ * ``64:ff9b:1::/48`` is considered private. -+ * ``2002::/16`` is considered private. -+ * There are exceptions within ``2001::/23`` (otherwise considered private): ``2001:1::1/128``, -+ ``2001:1::2/128``, ``2001:3::/32``, ``2001:4:112::/48``, ``2001:20::/28``, ``2001:30::/28``. -+ The exceptions are not considered private. - - .. attribute:: is_global - -- ``True`` if the address is allocated for public networks. See -+ ``True`` if the address is defined as globally reachable by - iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -- (for IPv6). -+ (for IPv6) with the following exception: -+ -+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: -+ -+ address.is_global == address.ipv4_mapped.is_global -+ -+ ``is_global`` has value opposite to :attr:`is_private`, except for the shared address space -+ (``100.64.0.0/10`` range) where they are both ``False``. - - .. versionadded:: 3.4 - -+ .. versionchanged:: 3.10.15 -+ -+ Fixed some false positives and false negatives, see :attr:`is_private` for details. -+ - .. attribute:: is_unspecified - - ``True`` if the address is unspecified. See :RFC:`5735` (for IPv4) ---- a/Doc/whatsnew/3.10.rst -+++ b/Doc/whatsnew/3.10.rst -@@ -2348,3 +2348,12 @@ tarfile - :exc:`DeprecationWarning`. - In Python 3.14, the default will switch to ``'data'``. - (Contributed by Petr Viktorin in :pep:`706`.) -+ -+Notable changes in 3.10.15 -+========================== -+ -+ipaddress -+--------- -+ -+* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``, -+ ``IPv6Address``, ``IPv4Network`` and ``IPv6Network``. ---- a/Lib/ipaddress.py -+++ b/Lib/ipaddress.py -@@ -1323,18 +1323,41 @@ class IPv4Address(_BaseV4, _BaseAddress) - @property - @functools.lru_cache() - def is_private(self): -- """Test if this address is allocated for private networks. -- -- Returns: -- A boolean, True if the address is reserved per -- iana-ipv4-special-registry. -- -- """ -- return any(self in net for net in self._constants._private_networks) -+ """``True`` if the address is defined as not globally reachable by -+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -+ (for IPv6) with the following exceptions: -+ -+ * ``is_private`` is ``False`` for ``100.64.0.0/10`` -+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: -+ -+ address.is_private == address.ipv4_mapped.is_private -+ -+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10`` -+ IPv4 range where they are both ``False``. -+ """ -+ return ( -+ any(self in net for net in self._constants._private_networks) -+ and all(self not in net for net in self._constants._private_networks_exceptions) -+ ) - - @property - @functools.lru_cache() - def is_global(self): -+ """``True`` if the address is defined as globally reachable by -+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -+ (for IPv6) with the following exception: -+ -+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: -+ -+ address.is_global == address.ipv4_mapped.is_global -+ -+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10`` -+ IPv4 range where they are both ``False``. -+ """ - return self not in self._constants._public_network and not self.is_private - - @property -@@ -1538,13 +1561,15 @@ class _IPv4Constants: - - _public_network = IPv4Network('100.64.0.0/10') - -+ # Not globally reachable address blocks listed on -+ # https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml - _private_networks = [ - IPv4Network('0.0.0.0/8'), - IPv4Network('10.0.0.0/8'), - IPv4Network('127.0.0.0/8'), - IPv4Network('169.254.0.0/16'), - IPv4Network('172.16.0.0/12'), -- IPv4Network('192.0.0.0/29'), -+ IPv4Network('192.0.0.0/24'), - IPv4Network('192.0.0.170/31'), - IPv4Network('192.0.2.0/24'), - IPv4Network('192.168.0.0/16'), -@@ -1555,6 +1580,11 @@ class _IPv4Constants: - IPv4Network('255.255.255.255/32'), - ] - -+ _private_networks_exceptions = [ -+ IPv4Network('192.0.0.9/32'), -+ IPv4Network('192.0.0.10/32'), -+ ] -+ - _reserved_network = IPv4Network('240.0.0.0/4') - - _unspecified_address = IPv4Address('0.0.0.0') -@@ -1996,27 +2026,42 @@ class IPv6Address(_BaseV6, _BaseAddress) - @property - @functools.lru_cache() - def is_private(self): -- """Test if this address is allocated for private networks. -+ """``True`` if the address is defined as not globally reachable by -+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -+ (for IPv6) with the following exceptions: -+ -+ * ``is_private`` is ``False`` for ``100.64.0.0/10`` -+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: - -- Returns: -- A boolean, True if the address is reserved per -- iana-ipv6-special-registry, or is ipv4_mapped and is -- reserved in the iana-ipv4-special-registry. -+ address.is_private == address.ipv4_mapped.is_private - -+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10`` -+ IPv4 range where they are both ``False``. - """ - ipv4_mapped = self.ipv4_mapped - if ipv4_mapped is not None: - return ipv4_mapped.is_private -- return any(self in net for net in self._constants._private_networks) -+ return ( -+ any(self in net for net in self._constants._private_networks) -+ and all(self not in net for net in self._constants._private_networks_exceptions) -+ ) - - @property - def is_global(self): -- """Test if this address is allocated for public networks. -+ """``True`` if the address is defined as globally reachable by -+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ -+ (for IPv6) with the following exception: -+ -+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the -+ semantics of the underlying IPv4 addresses and the following condition holds -+ (see :attr:`IPv6Address.ipv4_mapped`):: - -- Returns: -- A boolean, true if the address is not reserved per -- iana-ipv6-special-registry. -+ address.is_global == address.ipv4_mapped.is_global - -+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10`` -+ IPv4 range where they are both ``False``. - """ - return not self.is_private - -@@ -2257,19 +2302,31 @@ class _IPv6Constants: - - _multicast_network = IPv6Network('ff00::/8') - -+ # Not globally reachable address blocks listed on -+ # https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml - _private_networks = [ - IPv6Network('::1/128'), - IPv6Network('::/128'), - IPv6Network('::ffff:0:0/96'), -+ IPv6Network('64:ff9b:1::/48'), - IPv6Network('100::/64'), - IPv6Network('2001::/23'), -- IPv6Network('2001:2::/48'), - IPv6Network('2001:db8::/32'), -- IPv6Network('2001:10::/28'), -+ # IANA says N/A, let's consider it not globally reachable to be safe -+ IPv6Network('2002::/16'), - IPv6Network('fc00::/7'), - IPv6Network('fe80::/10'), - ] - -+ _private_networks_exceptions = [ -+ IPv6Network('2001:1::1/128'), -+ IPv6Network('2001:1::2/128'), -+ IPv6Network('2001:3::/32'), -+ IPv6Network('2001:4:112::/48'), -+ IPv6Network('2001:20::/28'), -+ IPv6Network('2001:30::/28'), -+ ] -+ - _reserved_networks = [ - IPv6Network('::/8'), IPv6Network('100::/8'), - IPv6Network('200::/7'), IPv6Network('400::/6'), ---- a/Lib/test/test_ipaddress.py -+++ b/Lib/test/test_ipaddress.py -@@ -2263,6 +2263,10 @@ class IpaddrUnitTest(unittest.TestCase): - self.assertEqual(True, ipaddress.ip_address( - '172.31.255.255').is_private) - self.assertEqual(False, ipaddress.ip_address('172.32.0.0').is_private) -+ self.assertFalse(ipaddress.ip_address('192.0.0.0').is_global) -+ self.assertTrue(ipaddress.ip_address('192.0.0.9').is_global) -+ self.assertTrue(ipaddress.ip_address('192.0.0.10').is_global) -+ self.assertFalse(ipaddress.ip_address('192.0.0.255').is_global) - - self.assertEqual(True, - ipaddress.ip_address('169.254.100.200').is_link_local) -@@ -2278,6 +2282,40 @@ class IpaddrUnitTest(unittest.TestCase): - self.assertEqual(False, ipaddress.ip_address('128.0.0.0').is_loopback) - self.assertEqual(True, ipaddress.ip_network('0.0.0.0').is_unspecified) - -+ def testPrivateNetworks(self): -+ self.assertEqual(True, ipaddress.ip_network("0.0.0.0/0").is_private) -+ self.assertEqual(False, ipaddress.ip_network("1.0.0.0/8").is_private) -+ -+ self.assertEqual(True, ipaddress.ip_network("0.0.0.0/8").is_private) -+ self.assertEqual(True, ipaddress.ip_network("10.0.0.0/8").is_private) -+ self.assertEqual(True, ipaddress.ip_network("127.0.0.0/8").is_private) -+ self.assertEqual(True, ipaddress.ip_network("169.254.0.0/16").is_private) -+ self.assertEqual(True, ipaddress.ip_network("172.16.0.0/12").is_private) -+ self.assertEqual(True, ipaddress.ip_network("192.0.0.0/29").is_private) -+ self.assertEqual(False, ipaddress.ip_network("192.0.0.9/32").is_private) -+ self.assertEqual(True, ipaddress.ip_network("192.0.0.170/31").is_private) -+ self.assertEqual(True, ipaddress.ip_network("192.0.2.0/24").is_private) -+ self.assertEqual(True, ipaddress.ip_network("192.168.0.0/16").is_private) -+ self.assertEqual(True, ipaddress.ip_network("198.18.0.0/15").is_private) -+ self.assertEqual(True, ipaddress.ip_network("198.51.100.0/24").is_private) -+ self.assertEqual(True, ipaddress.ip_network("203.0.113.0/24").is_private) -+ self.assertEqual(True, ipaddress.ip_network("240.0.0.0/4").is_private) -+ self.assertEqual(True, ipaddress.ip_network("255.255.255.255/32").is_private) -+ -+ self.assertEqual(False, ipaddress.ip_network("::/0").is_private) -+ self.assertEqual(False, ipaddress.ip_network("::ff/128").is_private) -+ -+ self.assertEqual(True, ipaddress.ip_network("::1/128").is_private) -+ self.assertEqual(True, ipaddress.ip_network("::/128").is_private) -+ self.assertEqual(True, ipaddress.ip_network("::ffff:0:0/96").is_private) -+ self.assertEqual(True, ipaddress.ip_network("100::/64").is_private) -+ self.assertEqual(True, ipaddress.ip_network("2001:2::/48").is_private) -+ self.assertEqual(False, ipaddress.ip_network("2001:3::/48").is_private) -+ self.assertEqual(True, ipaddress.ip_network("2001:db8::/32").is_private) -+ self.assertEqual(True, ipaddress.ip_network("2001:10::/28").is_private) -+ self.assertEqual(True, ipaddress.ip_network("fc00::/7").is_private) -+ self.assertEqual(True, ipaddress.ip_network("fe80::/10").is_private) -+ - def testReservedIpv6(self): - - self.assertEqual(True, ipaddress.ip_network('ffff::').is_multicast) -@@ -2351,6 +2389,20 @@ class IpaddrUnitTest(unittest.TestCase): - self.assertEqual(True, ipaddress.ip_address('0::0').is_unspecified) - self.assertEqual(False, ipaddress.ip_address('::1').is_unspecified) - -+ self.assertFalse(ipaddress.ip_address('64:ff9b:1::').is_global) -+ self.assertFalse(ipaddress.ip_address('2001::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:1::1').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:1::2').is_global) -+ self.assertFalse(ipaddress.ip_address('2001:2::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:3::').is_global) -+ self.assertFalse(ipaddress.ip_address('2001:4::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:4:112::').is_global) -+ self.assertFalse(ipaddress.ip_address('2001:10::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:20::').is_global) -+ self.assertTrue(ipaddress.ip_address('2001:30::').is_global) -+ self.assertFalse(ipaddress.ip_address('2001:40::').is_global) -+ self.assertFalse(ipaddress.ip_address('2002::').is_global) -+ - # some generic IETF reserved addresses - self.assertEqual(True, ipaddress.ip_address('100::').is_reserved) - self.assertEqual(True, ipaddress.ip_network('4000::1/128').is_reserved) ---- /dev/null -+++ b/Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst -@@ -0,0 +1,9 @@ -+Fixed various false positives and false negatives in -+ -+* :attr:`ipaddress.IPv4Address.is_private` (see these docs for details) -+* :attr:`ipaddress.IPv4Address.is_global` -+* :attr:`ipaddress.IPv6Address.is_private` -+* :attr:`ipaddress.IPv6Address.is_global` -+ -+Also in the corresponding :class:`ipaddress.IPv4Network` and :class:`ipaddress.IPv6Network` -+attributes. diff --git a/CVE-2024-6923-email-hdr-inject.patch b/CVE-2024-6923-email-hdr-inject.patch deleted file mode 100644 index fe6cde5..0000000 --- a/CVE-2024-6923-email-hdr-inject.patch +++ /dev/null @@ -1,335 +0,0 @@ -From ff3629b3cedf5e50a7a5f567283778fe5539a11e Mon Sep 17 00:00:00 2001 -From: Petr Viktorin -Date: Wed, 31 Jul 2024 00:19:48 +0200 -Subject: [PATCH] [3.10] gh-121650: Encode newlines in headers, and verify - headers are sound (GH-122233) - -Per RFC 2047: - -> [...] these encoding schemes allow the -> encoding of arbitrary octet values, mail readers that implement this -> decoding should also ensure that display of the decoded data on the -> recipient's terminal will not cause unwanted side-effects - -It seems that the "quoted-word" scheme is a valid way to include -a newline character in a header value, just like we already allow -undecodable bytes or control characters. -They do need to be properly quoted when serialized to text, though. - -This should fail for custom fold() implementations that aren't careful -about newlines. - -(cherry picked from commit 097633981879b3c9de9a1dd120d3aa585ecc2384) - -Co-authored-by: Petr Viktorin -Co-authored-by: Bas Bloemsaat -Co-authored-by: Serhiy Storchaka ---- - Doc/library/email.errors.rst | 6 - Doc/library/email.policy.rst | 18 ++ - Doc/whatsnew/3.10.rst | 12 + - Lib/email/_header_value_parser.py | 12 + - Lib/email/_policybase.py | 8 + - Lib/email/errors.py | 4 - Lib/email/generator.py | 13 +- - Lib/test/test_email/test_generator.py | 62 ++++++++++ - Lib/test/test_email/test_policy.py | 26 ++++ - Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst | 5 - 10 files changed, 162 insertions(+), 4 deletions(-) - create mode 100644 Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst - ---- a/Doc/library/email.errors.rst -+++ b/Doc/library/email.errors.rst -@@ -59,6 +59,12 @@ The following exception classes are defi - :class:`~email.mime.image.MIMEImage`). - - -+.. exception:: HeaderWriteError() -+ -+ Raised when an error occurs when the :mod:`~email.generator` outputs -+ headers. -+ -+ - Here is the list of the defects that the :class:`~email.parser.FeedParser` - can find while parsing messages. Note that the defects are added to the message - where the problem was found, so for example, if a message nested inside a ---- a/Doc/library/email.policy.rst -+++ b/Doc/library/email.policy.rst -@@ -229,6 +229,24 @@ added matters. To illustrate:: - - .. versionadded:: 3.6 - -+ -+ .. attribute:: verify_generated_headers -+ -+ If ``True`` (the default), the generator will raise -+ :exc:`~email.errors.HeaderWriteError` instead of writing a header -+ that is improperly folded or delimited, such that it would -+ be parsed as multiple headers or joined with adjacent data. -+ Such headers can be generated by custom header classes or bugs -+ in the ``email`` module. -+ -+ As it's a security feature, this defaults to ``True`` even in the -+ :class:`~email.policy.Compat32` policy. -+ For backwards compatible, but unsafe, behavior, it must be set to -+ ``False`` explicitly. -+ -+ .. versionadded:: 3.10.15 -+ -+ - The following :class:`Policy` method is intended to be called by code using - the email library to create policy instances with custom settings: - ---- a/Doc/whatsnew/3.10.rst -+++ b/Doc/whatsnew/3.10.rst -@@ -2357,3 +2357,15 @@ ipaddress - - * Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``, - ``IPv6Address``, ``IPv4Network`` and ``IPv6Network``. -+ -+email -+----- -+ -+* Headers with embedded newlines are now quoted on output. -+ -+ The :mod:`~email.generator` will now refuse to serialize (write) headers -+ that are improperly folded or delimited, such that they would be parsed as -+ multiple headers or joined with adjacent data. -+ If you need to turn this safety feature off, -+ set :attr:`~email.policy.Policy.verify_generated_headers`. -+ (Contributed by Bas Bloemsaat and Petr Viktorin in :gh:`121650`.) ---- a/Lib/email/_header_value_parser.py -+++ b/Lib/email/_header_value_parser.py -@@ -92,6 +92,8 @@ TOKEN_ENDS = TSPECIALS | WSP - ASPECIALS = TSPECIALS | set("*'%") - ATTRIBUTE_ENDS = ASPECIALS | WSP - EXTENDED_ATTRIBUTE_ENDS = ATTRIBUTE_ENDS - set('%') -+NLSET = {'\n', '\r'} -+SPECIALSNL = SPECIALS | NLSET - - def quote_string(value): - return '"'+str(value).replace('\\', '\\\\').replace('"', r'\"')+'"' -@@ -2778,9 +2780,13 @@ def _refold_parse_tree(parse_tree, *, po - wrap_as_ew_blocked -= 1 - continue - tstr = str(part) -- if part.token_type == 'ptext' and set(tstr) & SPECIALS: -- # Encode if tstr contains special characters. -- want_encoding = True -+ if not want_encoding: -+ if part.token_type == 'ptext': -+ # Encode if tstr contains special characters. -+ want_encoding = not SPECIALSNL.isdisjoint(tstr) -+ else: -+ # Encode if tstr contains newlines. -+ want_encoding = not NLSET.isdisjoint(tstr) - try: - tstr.encode(encoding) - charset = encoding ---- a/Lib/email/_policybase.py -+++ b/Lib/email/_policybase.py -@@ -157,6 +157,13 @@ class Policy(_PolicyBase, metaclass=abc. - message_factory -- the class to use to create new message objects. - If the value is None, the default is Message. - -+ verify_generated_headers -+ -- if true, the generator verifies that each header -+ they are properly folded, so that a parser won't -+ treat it as multiple headers, start-of-body, or -+ part of another header. -+ This is a check against custom Header & fold() -+ implementations. - """ - - raise_on_defect = False -@@ -165,6 +172,7 @@ class Policy(_PolicyBase, metaclass=abc. - max_line_length = 78 - mangle_from_ = False - message_factory = None -+ verify_generated_headers = True - - def handle_defect(self, obj, defect): - """Based on policy, either raise defect or call register_defect. ---- a/Lib/email/errors.py -+++ b/Lib/email/errors.py -@@ -29,6 +29,10 @@ class CharsetError(MessageError): - """An illegal charset was given.""" - - -+class HeaderWriteError(MessageError): -+ """Error while writing headers.""" -+ -+ - # These are parsing defects which the parser was able to work around. - class MessageDefect(ValueError): - """Base class for a message defect.""" ---- a/Lib/email/generator.py -+++ b/Lib/email/generator.py -@@ -14,12 +14,14 @@ import random - from copy import deepcopy - from io import StringIO, BytesIO - from email.utils import _has_surrogates -+from email.errors import HeaderWriteError - - UNDERSCORE = '_' - NL = '\n' # XXX: no longer used by the code below. - - NLCRE = re.compile(r'\r\n|\r|\n') - fcre = re.compile(r'^From ', re.MULTILINE) -+NEWLINE_WITHOUT_FWSP = re.compile(r'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]') - - - -@@ -223,7 +225,16 @@ class Generator: - - def _write_headers(self, msg): - for h, v in msg.raw_items(): -- self.write(self.policy.fold(h, v)) -+ folded = self.policy.fold(h, v) -+ if self.policy.verify_generated_headers: -+ linesep = self.policy.linesep -+ if not folded.endswith(self.policy.linesep): -+ raise HeaderWriteError( -+ f'folded header does not end with {linesep!r}: {folded!r}') -+ if NEWLINE_WITHOUT_FWSP.search(folded.removesuffix(linesep)): -+ raise HeaderWriteError( -+ f'folded header contains newline: {folded!r}') -+ self.write(folded) - # A blank line always separates headers from body - self.write(self._NL) - ---- a/Lib/test/test_email/test_generator.py -+++ b/Lib/test/test_email/test_generator.py -@@ -6,6 +6,7 @@ from email.message import EmailMessage - from email.generator import Generator, BytesGenerator - from email.headerregistry import Address - from email import policy -+import email.errors - from test.test_email import TestEmailBase, parameterize - - -@@ -216,6 +217,44 @@ class TestGeneratorBase: - g.flatten(msg) - self.assertEqual(s.getvalue(), self.typ(expected)) - -+ def test_keep_encoded_newlines(self): -+ msg = self.msgmaker(self.typ(textwrap.dedent("""\ -+ To: nobody -+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com -+ -+ None -+ """))) -+ expected = textwrap.dedent("""\ -+ To: nobody -+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com -+ -+ None -+ """) -+ s = self.ioclass() -+ g = self.genclass(s, policy=self.policy.clone(max_line_length=80)) -+ g.flatten(msg) -+ self.assertEqual(s.getvalue(), self.typ(expected)) -+ -+ def test_keep_long_encoded_newlines(self): -+ msg = self.msgmaker(self.typ(textwrap.dedent("""\ -+ To: nobody -+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com -+ -+ None -+ """))) -+ expected = textwrap.dedent("""\ -+ To: nobody -+ Subject: Bad subject -+ =?utf-8?q?=0A?=Bcc: -+ injection@example.com -+ -+ None -+ """) -+ s = self.ioclass() -+ g = self.genclass(s, policy=self.policy.clone(max_line_length=30)) -+ g.flatten(msg) -+ self.assertEqual(s.getvalue(), self.typ(expected)) -+ - - class TestGenerator(TestGeneratorBase, TestEmailBase): - -@@ -224,6 +263,29 @@ class TestGenerator(TestGeneratorBase, T - ioclass = io.StringIO - typ = str - -+ def test_verify_generated_headers(self): -+ """gh-121650: by default the generator prevents header injection""" -+ class LiteralHeader(str): -+ name = 'Header' -+ def fold(self, **kwargs): -+ return self -+ -+ for text in ( -+ 'Value\r\nBad Injection\r\n', -+ 'NoNewLine' -+ ): -+ with self.subTest(text=text): -+ message = message_from_string( -+ "Header: Value\r\n\r\nBody", -+ policy=self.policy, -+ ) -+ -+ del message['Header'] -+ message['Header'] = LiteralHeader(text) -+ -+ with self.assertRaises(email.errors.HeaderWriteError): -+ message.as_string() -+ - - class TestBytesGenerator(TestGeneratorBase, TestEmailBase): - ---- a/Lib/test/test_email/test_policy.py -+++ b/Lib/test/test_email/test_policy.py -@@ -26,6 +26,7 @@ class PolicyAPITests(unittest.TestCase): - 'raise_on_defect': False, - 'mangle_from_': True, - 'message_factory': None, -+ 'verify_generated_headers': True, - } - # These default values are the ones set on email.policy.default. - # If any of these defaults change, the docs must be updated. -@@ -277,6 +278,31 @@ class PolicyAPITests(unittest.TestCase): - with self.assertRaises(email.errors.HeaderParseError): - policy.fold("Subject", subject) - -+ def test_verify_generated_headers(self): -+ """Turning protection off allows header injection""" -+ policy = email.policy.default.clone(verify_generated_headers=False) -+ for text in ( -+ 'Header: Value\r\nBad: Injection\r\n', -+ 'Header: NoNewLine' -+ ): -+ with self.subTest(text=text): -+ message = email.message_from_string( -+ "Header: Value\r\n\r\nBody", -+ policy=policy, -+ ) -+ class LiteralHeader(str): -+ name = 'Header' -+ def fold(self, **kwargs): -+ return self -+ -+ del message['Header'] -+ message['Header'] = LiteralHeader(text) -+ -+ self.assertEqual( -+ message.as_string(), -+ f"{text}\nBody", -+ ) -+ - # XXX: Need subclassing tests. - # For adding subclassed objects, make sure the usual rules apply (subclass - # wins), but that the order still works (right overrides left). ---- /dev/null -+++ b/Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst -@@ -0,0 +1,5 @@ -+:mod:`email` headers with embedded newlines are now quoted on output. The -+:mod:`~email.generator` will now refuse to serialize (write) headers that -+are unsafely folded or delimited; see -+:attr:`~email.policy.Policy.verify_generated_headers`. (Contributed by Bas -+Bloemsaat and Petr Viktorin in :gh:`121650`.) diff --git a/CVE-2024-8088-inf-loop-zipfile_Path.patch b/CVE-2024-8088-inf-loop-zipfile_Path.patch deleted file mode 100644 index 5ef5630..0000000 --- a/CVE-2024-8088-inf-loop-zipfile_Path.patch +++ /dev/null @@ -1,136 +0,0 @@ ---- - Lib/test/test_zipfile.py | 75 ++++++++++ - Lib/zipfile.py | 10 + - Misc/NEWS.d/next/Library/2024-08-11-14-08-04.gh-issue-122905.7tDsxA.rst | 1 - Misc/NEWS.d/next/Library/2024-08-26-13-45-20.gh-issue-123270.gXHvNJ.rst | 3 - 4 files changed, 87 insertions(+), 2 deletions(-) - ---- a/Lib/test/test_zipfile.py -+++ b/Lib/test/test_zipfile.py -@@ -3280,6 +3280,81 @@ with zipfile.ZipFile(io.BytesIO(), "w") - zipfile.Path(zf) - zf.extractall(source_path.parent) - -+ def test_malformed_paths(self): -+ """ -+ Path should handle malformed paths gracefully. -+ -+ Paths with leading slashes are not visible. -+ -+ Paths with dots are treated like regular files. -+ """ -+ data = io.BytesIO() -+ zf = zipfile.ZipFile(data, "w") -+ zf.writestr("../parent.txt", b"content") -+ zf.filename = '' -+ root = zipfile.Path(zf) -+ assert list(map(str, root.iterdir())) == ['../'] -+ assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content' -+ -+ def test_unsupported_names(self): -+ """ -+ Path segments with special characters are readable. -+ -+ On some platforms or file systems, characters like -+ ``:`` and ``?`` are not allowed, but they are valid -+ in the zip file. -+ """ -+ data = io.BytesIO() -+ zf = zipfile.ZipFile(data, "w") -+ zf.writestr("path?", b"content") -+ zf.writestr("V: NMS.flac", b"fLaC...") -+ zf.filename = '' -+ root = zipfile.Path(zf) -+ contents = root.iterdir() -+ assert next(contents).name == 'path?' -+ assert next(contents).name == 'V: NMS.flac' -+ assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..." -+ -+ def test_backslash_not_separator(self): -+ """ -+ In a zip file, backslashes are not separators. -+ """ -+ data = io.BytesIO() -+ zf = zipfile.ZipFile(data, "w") -+ zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content") -+ zf.filename = '' -+ root = zipfile.Path(zf) -+ (first,) = root.iterdir() -+ assert not first.is_dir() -+ assert first.name == 'foo\\bar' -+ -+ -+class DirtyZipInfo(zipfile.ZipInfo): -+ """ -+ Bypass name sanitization. -+ """ -+ -+ def __init__(self, filename, *args, **kwargs): -+ super().__init__(filename, *args, **kwargs) -+ self.filename = filename -+ -+ @classmethod -+ def for_name(cls, name, archive): -+ """ -+ Construct the same way that ZipFile.writestr does. -+ -+ TODO: extract this functionality and re-use -+ """ -+ self = cls(filename=name, date_time=time.localtime(time.time())[:6]) -+ self.compress_type = archive.compression -+ self.compress_level = archive.compresslevel -+ if self.filename.endswith('/'): # pragma: no cover -+ self.external_attr = 0o40775 << 16 # drwxrwxr-x -+ self.external_attr |= 0x10 # MS-DOS directory flag -+ else: -+ self.external_attr = 0o600 << 16 # ?rw------- -+ return self -+ - - class StripExtraTests(unittest.TestCase): - # Note: all of the "z" characters are technically invalid, but up ---- a/Lib/zipfile.py -+++ b/Lib/zipfile.py -@@ -9,6 +9,7 @@ import io - import itertools - import os - import posixpath -+import re - import shutil - import stat - import struct -@@ -2151,7 +2152,7 @@ def _parents(path): - def _ancestry(path): - """ - Given a path with elements separated by -- posixpath.sep, generate all elements of that path -+ posixpath.sep, generate all elements of that path. - - >>> list(_ancestry('b/d')) - ['b/d', 'b'] -@@ -2163,9 +2164,14 @@ def _ancestry(path): - ['b'] - >>> list(_ancestry('')) - [] -+ -+ Multiple separators are treated like a single. -+ -+ >>> list(_ancestry('//b//d///f//')) -+ ['//b//d///f', '//b//d', '//b'] - """ - path = path.rstrip(posixpath.sep) -- while path and path != posixpath.sep: -+ while path.rstrip(posixpath.sep): - yield path - path, tail = posixpath.split(path) - ---- /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. ---- /dev/null -+++ b/Misc/NEWS.d/next/Library/2024-08-26-13-45-20.gh-issue-123270.gXHvNJ.rst -@@ -0,0 +1,3 @@ -+Applied a more surgical fix for malformed payloads in :class:`zipfile.Path` -+causing infinite loops (gh-122905) without breaking contents using -+legitimate characters. diff --git a/Python-3.10.14.tar.xz b/Python-3.10.14.tar.xz deleted file mode 100644 index cb7af64..0000000 --- a/Python-3.10.14.tar.xz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9c50481faa8c2832329ba0fc8868d0a606a680fc4f60ec48d26ce8e076751fda -size 19600188 diff --git a/Python-3.10.14.tar.xz.asc b/Python-3.10.14.tar.xz.asc deleted file mode 100644 index b087178..0000000 --- a/Python-3.10.14.tar.xz.asc +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PGP SIGNATURE----- - -iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmX76AkACgkQ/+h0BBaL -2EeKvhAAuN+7X3iFv8tYwUTbKJT9x9fLsADI5wOn5j8xuDiXQCOMzsqqyB1RSdEd -tbCQXg9XJj1bVHc4DY337vUix9jvFcTqbQqlzUm/peX4buY2bKkZu2quti1iWSJf -IN26jDYO2TobPvGdvNiH2Hceqe1dc5tU7iYEfaLR5ImgO4aGgK6x4DiLdmFqo2bk -ZZWZLkXbwenrSdLVmUZLP5Gg2dsfMkbfFpydau9Zk3RVl6mVYATwzJaY9K5otC0K -7kc+nKPwkTxKAjndbznjsVrWK0Xcr4hrlMHs4Re2Nrdqa2mVd1jAAFO5xETJJtd7 -YqL6mQuJ9wQfEEq2QWz1hEi67l8g8VeEgzYQOjZ6pTxwYYt0YDfKBjRtRCWuJ11c -w6Q+pniGcgIHAMkQGjZds88CwAdIiyG7IAIT2ovW+xVxH/JqLPHeRsHMKYx4DPqL -2y23Tchw+gBUvmbwCdObXWL1eq5R3Xz3ikkdX/I6zknmEvgPTi5N59C1IQqh0W/6 -8uMrHOdELz9I5Fd+zGTJ8iyh/wrecMiIx+HOsBTYv/FYbMVnQUshUBOiD70geUb5 -uSeHyxl/P7VK/0phbxOznU4oDot2fHPmZRK3q+K67J9L16q7pEou1AJAw8E7ed5C -Ywf+y2tdxsuqChQK/OA6uuqW6rXjZPuCoG5Bn6YIEuU769LsHcY= -=1PBR ------END PGP SIGNATURE----- diff --git a/Python-3.10.15.tar.xz b/Python-3.10.15.tar.xz new file mode 100644 index 0000000..a9c70e6 --- /dev/null +++ b/Python-3.10.15.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aab0950817735172601879872d937c1e4928a57c409ae02369ec3d91dccebe79 +size 19596540 diff --git a/Python-3.10.15.tar.xz.asc b/Python-3.10.15.tar.xz.asc new file mode 100644 index 0000000..315072a --- /dev/null +++ b/Python-3.10.15.tar.xz.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP SIGNATURE----- + +iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmbboS8ACgkQ/+h0BBaL +2Ecc7BAAmdd+jqs4mNpJg58HgOnLIx3hVBrzn1kyI9AkbxfFGGfm3Gg9Exa/dIph +m1Bt8FogUqOxFnEsFBFTgxh49TCDiUDFzTWYWcrbhtodGFywCmr+0ha6CuEcuuFa +hL0qV7sIJRoVzcdPU6pHh4OcDtdLR0Ws27WiMilrpquw/sWztIiueASZn/kehToD +XM1RTcFtaJeO++cp2tECXRrTU79lzsdpRY/DOyUWWJmLFv0GdrKi4bszKhcYK8x7 +qKleGklFf6AzhGT1A91cRyQ6AEcD3Vnp1Or+agJUwxA0hVuyw6cEmf0+VONqwDMe +M/5bz8xgt6kopfz48mrTJhHg24+6wt6b4kQgwrtUoyucgb+k7ThzwgCj+Wg/Z0Pz +/S+M1hF7I0Ot/PFA3LH5QJADM7nsw5+Rkl68HqQp7s8O9RddPHpCILDIM/AUkUu+ +Xn/1MgPdhhTnA5elyZ2DDDtETUugNu5RILrIRoKonHsZtOQOpOERzUdbzEHCuLv5 +AunaLPWrvxXtEJUKLmyOUfYoI35Gw3/gHYyKTSmo4C1SMYUjke++N7c6vbsvroRG +aUQa/TdAf71zz/r6lHg0vYt+D5FlmFJzB8gCmt6ewKJAO82ls3rr0XjmD1w58sXV +kuwy+53MopEaI1I4D6qIMq/XxNnU2Q63sqKaai8Emx1Yw28Csvw= +=f/SR +-----END PGP SIGNATURE----- diff --git a/fix-sphinx-72.patch b/fix-sphinx-72.patch index dc3d130..68701fe 100644 --- a/fix-sphinx-72.patch +++ b/fix-sphinx-72.patch @@ -915,7 +915,7 @@ Open a new pseudo-terminal pair. Return a pair of file descriptors ``(master, slave)`` for the pty and the tty, respectively. The new file -@@ -2637,7 +2637,7 @@ features: +@@ -2644,7 +2644,7 @@ features: possible and call :func:`lstat` on the result. This does not apply to dangling symlinks or junction points, which will raise the usual exceptions. diff --git a/fix_configure_rst.patch b/fix_configure_rst.patch index 5eca1ee..46067d1 100644 --- a/fix_configure_rst.patch +++ b/fix_configure_rst.patch @@ -29,7 +29,7 @@ Create a Python.framework rather than a traditional Unix install. Optional --- a/Misc/NEWS +++ b/Misc/NEWS -@@ -3731,7 +3731,7 @@ C API +@@ -3810,7 +3810,7 @@ C API ----- - bpo-43795: The list in :ref:`stable-abi-list` now shows the public name diff --git a/gh120226-fix-sendfile-test-kernel-610.patch b/gh120226-fix-sendfile-test-kernel-610.patch new file mode 100644 index 0000000..aebd6b6 --- /dev/null +++ b/gh120226-fix-sendfile-test-kernel-610.patch @@ -0,0 +1,35 @@ +From 1b3f6523a5c83323cdc44031b33a1c062e5dc698 Mon Sep 17 00:00:00 2001 +From: Xi Ruoyao +Date: Fri, 7 Jun 2024 23:51:32 +0800 +Subject: [PATCH] gh-120226: Fix + test_sendfile_close_peer_in_the_middle_of_receiving on Linux >= 6.10 + (GH-120227) + +The worst case is that the kernel buffers 17 pages with a page size of 64k. +(cherry picked from commit a7584245661102a5768c643fbd7db8395fd3c90e) + +Co-authored-by: Xi Ruoyao +--- + Lib/test/test_asyncio/test_sendfile.py | 11 ++++------- + 1 file changed, 4 insertions(+), 7 deletions(-) + +--- a/Lib/test/test_asyncio/test_sendfile.py ++++ b/Lib/test/test_asyncio/test_sendfile.py +@@ -93,13 +93,10 @@ class MyProto(asyncio.Protocol): + + class SendfileBase: + +- # 256 KiB plus small unaligned to buffer chunk +- # Newer versions of Windows seems to have increased its internal +- # buffer and tries to send as much of the data as it can as it +- # has some form of buffering for this which is less than 256KiB +- # on newer server versions and Windows 11. +- # So DATA should be larger than 256 KiB to make this test reliable. +- DATA = b"x" * (1024 * 256 + 1) ++ # Linux >= 6.10 seems buffering up to 17 pages of data. ++ # So DATA should be large enough to make this test reliable even with a ++ # 64 KiB page configuration. ++ DATA = b"x" * (1024 * 17 * 64 + 1) + # Reduce socket buffer size to test on relative small data sets. + BUF_SIZE = 4 * 1024 # 4 KiB + diff --git a/python310.changes b/python310.changes index 1c4ee52..20e1f0c 100644 --- a/python310.changes +++ b/python310.changes @@ -1,3 +1,92 @@ +------------------------------------------------------------------- +Mon Sep 9 13:41:07 UTC 2024 - Matej Cepl + +- Update to 3.10.15: + - Tests + - gh-112769: The tests now correctly compare zlib version + when :const:`zlib.ZLIB_RUNTIME_VERSION` contains + non-integer suffixes. For example zlib-ng defines the + version as ``1.3.0.zlib-ng``. + - gh-117187: Fix XML tests for vanilla Expat <2.6.0. + - gh-100454: Fix SSL tests CI for OpenSSL 3.1+ + - Security + - gh-123678: Upgrade libexpat to 2.6.3 + - gh-121957: Fixed missing audit events around interactive + use of Python, now also properly firing for ``python -i``, + as well as for ``python -m asyncio``. The event in question + is ``cpython.run_stdin``. + - gh-122133: Authenticate the socket connection for the + ``socket.socketpair()`` fallback on platforms where + ``AF_UNIX`` is not available like Windows. Patch by + Gregory P. Smith and Seth Larson + . Reported by Ellie + - gh-121285: Remove backtracking from tarfile header + parsing for ``hdrcharset``, PAX, and GNU sparse headers + (bsc#1230227, CVE-2024-6232). + - gh-118486: :func:`os.mkdir` on Windows now accepts + *mode* of ``0o700`` to restrict the new directory to + the current user. This fixes CVE-2024-4030 affecting + :func:`tempfile.mkdtemp` in scenarios where the base + temporary directory is more permissive than the default. + - gh-116741: Update bundled libexpat to 2.6.2 + - Library + - gh-123693: Use platform-agnostic behavior when computing + ``zipfile.Path.name``. + - gh-123270: Applied a more surgical fix for malformed + payloads in :class:`zipfile.Path` causing infinite loops + (gh-122905) without breaking contents using legitimate + characters (bsc#1229704, CVE-2024-8088). + - gh-123067: Fix quadratic complexity in parsing ``"``-quoted + cookie values with backslashes by :mod:`http.cookies` + (bsc#1229596, CVE-2024-7592). + - gh-122905: :class:`zipfile.Path` objects now sanitize names + from the zipfile. + - gh-121650: :mod:`email` headers with embedded newlines are + now quoted on output. The :mod:`~email.generator` will now + refuse to serialize (write) headers that are unsafely folded + or delimited; see :attr:`~email.policy.Policy.verify_generated_headers`. + (Contributed by Bas Bloemsaat and Petr Viktorin in + gh-121650.; CVE-2024-6923, bsc#1228780). + - gh-113171: Fixed various false positives and false negatives in + * :attr:`ipaddress.IPv4Address.is_private` (see these docs for details) + * :attr:`ipaddress.IPv4Address.is_global` + * :attr:`ipaddress.IPv6Address.is_private` + * :attr:`ipaddress.IPv6Address.is_global` + Also in the corresponding :class:`ipaddress.IPv4Network` and + :class:`ipaddress.IPv6Network` attributes. + Fixes bsc#1226448 (CVE-2024-4032). + - gh-102988: :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 (bsc#1210638). + - gh-67693: Fix :func:`urllib.parse.urlunparse` and + :func:`urllib.parse.urlunsplit` for URIs with path starting with multiple + slashes and no authority. Based on patch by Ashwin Ramaswami. + - Core and Builtins + - gh-112275: A deadlock involving ``pystate.c``'s + ``HEAD_LOCK`` in ``posixmodule.c`` at fork is now + fixed. Patch by ChuBoning based on previous Python 3.12 fix + by Victor Stinner. +- Remove upstreamed patches: + - CVE-2023-27043-email-parsing-errors.patch + - CVE-2024-4032-private-IP-addrs.patch + - CVE-2024-6923-email-hdr-inject.patch + - CVE-2024-8088-inf-loop-zipfile_Path.patch +- Add sphinx-802.patch to overcome working both with the most + recent and older Sphinx versions. + +------------------------------------------------------------------- +Mon Sep 2 09:44:26 UTC 2024 - Matej Cepl + +- Add gh120226-fix-sendfile-test-kernel-610.patch to avoid + failing test_sendfile_close_peer_in_the_middle_of_receiving + tests on Linux >= 6.10 (GH-120227). + ------------------------------------------------------------------- Wed Aug 28 16:54:34 UTC 2024 - Matej Cepl diff --git a/python310.spec b/python310.spec index 6a2f231..48d2192 100644 --- a/python310.spec +++ b/python310.spec @@ -108,7 +108,7 @@ Obsoletes: python39%{?1:-%{1}} # _md5.cpython-38m-x86_64-linux-gnu.so %define dynlib() %{sitedir}/lib-dynload/%{1}.cpython-%{abi_tag}-%{archname}-%{_os}%{?_gnu}%{?armsuffix}.so Name: %{python_pkg_name}%{psuffix} -Version: 3.10.14 +Version: 3.10.15 Release: 0 Summary: Python 3 Interpreter License: Python-2.0 @@ -178,11 +178,6 @@ Patch18: bpo-37596-make-set-marshalling.patch # PATCH-FIX-UPSTREAM gh-78214-marshal_stabilize_FLAG_REF.patch bsc#1213463 mcepl@suse.com # marshal: Stabilize FLAG_REF usage Patch19: gh-78214-marshal_stabilize_FLAG_REF.patch -# PATCH-FIX-UPSTREAM CVE-2023-27043-email-parsing-errors.patch bsc#1210638 mcepl@suse.com -# Detect email address parsing errors and return empty tuple to -# indicate the parsing error (old API), from gh#python/cpython!105127 -# Patch carries a REGRESSION (gh#python/cpython#106669), so it has been also partially REVERTED -Patch20: CVE-2023-27043-email-parsing-errors.patch # PATCH-FIX-UPSTREAM fix-sphinx-72.patch gh#python/cpython#97950 # This is a patch with a lot of PR combined to make the doc work with # sphinx 7.2 @@ -200,18 +195,15 @@ Patch21: fix-sphinx-72.patch # PATCH-FIX-UPSTREAM CVE-2023-52425-libexpat-2.6.0-backport.patch gh#python/cpython#117187 mcepl@suse.com # Make the test suite work with libexpat < 2.6.0 Patch22: CVE-2023-52425-libexpat-2.6.0-backport.patch -# PATCH-FIX-UPSTREAM CVE-2024-4032-private-IP-addrs.patch bsc#1226448 mcepl@suse.com -# rearrange definition of private v global IP addresses -Patch23: CVE-2024-4032-private-IP-addrs.patch # PATCH-FIX-UPSTREAM bso1227999-reproducible-builds.patch bsc#1227999 mcepl@suse.com # reproducibility patches Patch24: bso1227999-reproducible-builds.patch -# PATCH-FIX-UPSTREAM CVE-2024-6923-email-hdr-inject.patch bsc#1228780 mcepl@suse.com -# prevent email header injection, patch from gh#python/cpython!122608 -Patch25: CVE-2024-6923-email-hdr-inject.patch -# PATCH-FIX-UPSTREAM CVE-2024-8088-inf-loop-zipfile_Path.patch bsc#1229704 mcepl@suse.com -# avoid denial of service in zipfile -Patch26: CVE-2024-8088-inf-loop-zipfile_Path.patch +# PATCH-FIX-UPSTREAM gh120226-fix-sendfile-test-kernel-610.patch gh#python/cpython#120226 mcepl@suse.com +# Fix test_sendfile_close_peer_in_the_middle_of_receiving on Linux >= 6.10 (GH-120227) +Patch27: gh120226-fix-sendfile-test-kernel-610.patch +# PATCH-FIX-UPSTREAM sphinx-802.patch mcepl@suse.com +# status_iterator method moved between the Sphinx versions +Patch28: sphinx-802.patch BuildRequires: autoconf-archive BuildRequires: automake BuildRequires: fdupes @@ -486,13 +478,11 @@ other applications. %patch -p1 -P 17 %patch -p1 -P 18 %patch -p1 -P 19 -%patch -p1 -P 20 %patch -p1 -P 21 %patch -p1 -P 22 -%patch -p1 -P 23 %patch -p1 -P 24 -%patch -p1 -P 25 -%patch -p1 -P 26 +%patch -p1 -P 27 +%patch -p1 -P 28 # drop Autoconf version requirement sed -i 's/^AC_PREREQ/dnl AC_PREREQ/' configure.ac diff --git a/sphinx-802.patch b/sphinx-802.patch new file mode 100644 index 0000000..c4600b7 --- /dev/null +++ b/sphinx-802.patch @@ -0,0 +1,21 @@ +--- + Doc/tools/extensions/pyspecific.py | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +--- a/Doc/tools/extensions/pyspecific.py ++++ b/Doc/tools/extensions/pyspecific.py +@@ -27,7 +27,13 @@ try: + except ImportError: + from sphinx.environment import NoUri + from sphinx.locale import _ as sphinx_gettext +-from sphinx.util import status_iterator, logging ++try: ++ from sphinx.util.display import status_iterator ++except ImportError: ++ # This method was moved into sphinx.util.display in Sphinx 6.1.0. Before ++ # that it resided in sphinx.util. ++ from sphinx.util import status_iterator ++from sphinx.util import logging + from sphinx.util.nodes import split_explicit_title + from sphinx.writers.text import TextWriter, TextTranslator + from sphinx.writers.latex import LaTeXTranslator