forked from pool/python39
- Security
- gh-135034: Fixes multiple issues that allowed tarfile
extraction filters (filter="data" and filter="tar") to be
bypassed using crafted symlinks and hard links.
- Addresses CVE-2024-12718 (bsc#1244056), CVE-2025-4138
(bsc#1244059), CVE-2025-4330 (bsc#1244060), and
CVE-2025-4517 (bsc#1244032).
- gh-133767: Fix use-after-free in the “unicode-escape”
decoder with a non-“strict” error handler (CVE-2025-4516,
bsc#1243273).
- gh-128840: Short-circuit the processing of long IPv6
addresses early in ipaddress to prevent excessive memory
consumption and a minor denial-of-service.
- gh-80222: Fix bug in the folding of quoted strings
when flattening an email message using a modern email
policy. Previously when a quoted string was folded so
that it spanned more than one line, the surrounding
quotes and internal escapes would be omitted. This could
theoretically be used to spoof header lines using a
carefully constructed quoted string if the resulting
rendered email was transmitted or re-parsed.
- Library
- gh-128840: Fix parsing long IPv6 addresses with embedded
IPv4 address.
- gh-134062: ipaddress: fix collisions in __hash__() for
IPv4Network and IPv6Network objects.
- gh-123409: Fix ipaddress.IPv6Address.reverse_pointer output
according to RFC 3596, §2.5. Patch by Bénédikt Tran.
- bpo-43633: Improve the textual representation of
OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python39?expand=0&rev=233
397 lines
18 KiB
Diff
397 lines
18 KiB
Diff
From 5e58376d424fb951966277e5d46cf0b11d860ef3 Mon Sep 17 00:00:00 2001
|
|
From: Petr Viktorin <encukou@gmail.com>
|
|
Date: Wed, 24 Apr 2024 14:29:30 +0200
|
|
Subject: [PATCH 1/3] 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 <jakub@stasiak.at>
|
|
---
|
|
Doc/library/ipaddress.rst | 43 +++-
|
|
Doc/tools/susp-ignored.csv | 8
|
|
Doc/whatsnew/3.9.rst | 9
|
|
Lib/ipaddress.py | 107 +++++++---
|
|
Lib/test/test_ipaddress.py | 52 ++++
|
|
Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst | 9
|
|
6 files changed, 201 insertions(+), 27 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.9.20
|
|
+
|
|
+ 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.9.20
|
|
+
|
|
+ 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/tools/susp-ignored.csv
|
|
+++ b/Doc/tools/susp-ignored.csv
|
|
@@ -169,6 +169,14 @@ library/ipaddress,,:db00,2001:db00::0/24
|
|
library/ipaddress,,::,2001:db00::0/24
|
|
library/ipaddress,,:db00,2001:db00::0/ffff:ff00::
|
|
library/ipaddress,,::,2001:db00::0/ffff:ff00::
|
|
+library/ipaddress,,:ff9b,64:ff9b:1::/48
|
|
+library/ipaddress,,::,64:ff9b:1::/48
|
|
+library/ipaddress,,::,2001::
|
|
+library/ipaddress,,::,2001:1::
|
|
+library/ipaddress,,::,2001:3::
|
|
+library/ipaddress,,::,2001:4:112::
|
|
+library/ipaddress,,::,2001:20::
|
|
+library/ipaddress,,::,2001:30::
|
|
library/itertools,,:step,elements from seq[start:stop:step]
|
|
library/itertools,,:stop,elements from seq[start:stop:step]
|
|
library/itertools,,::,kernel = tuple(kernel)[::-1]
|
|
--- a/Doc/whatsnew/3.9.rst
|
|
+++ b/Doc/whatsnew/3.9.rst
|
|
@@ -1616,3 +1616,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.9.20
|
|
+=========================
|
|
+
|
|
+ipaddress
|
|
+---------
|
|
+
|
|
+* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``,
|
|
+ ``IPv6Address``, ``IPv4Network`` and ``IPv6Network``.
|
|
--- a/Lib/ipaddress.py
|
|
+++ b/Lib/ipaddress.py
|
|
@@ -1322,18 +1322,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
|
|
@@ -1537,13 +1560,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'),
|
|
@@ -1554,6 +1579,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')
|
|
@@ -1995,23 +2025,42 @@ class IPv6Address(_BaseV6, _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-ipv6-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``.
|
|
+ """
|
|
+ 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)
|
|
+ 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
|
|
|
|
@@ -2252,19 +2301,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.
|