Skip to content

Commit 2cd349a

Browse files
authored
Merge pull request python#57 from ActiveState/BE-4055-cve-2024-4032-for-3-7
CVE-2024-4032 Fix "private" (non-global) IP address ranges (pythonGH-113179
2 parents 0f9c294 + 89f48de commit 2cd349a

File tree

6 files changed

+194
-21
lines changed

6 files changed

+194
-21
lines changed

Doc/library/ipaddress.rst

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,18 +168,53 @@ write code that handles both IP versions correctly. Address objects are
168168

169169
.. attribute:: is_private
170170

171-
``True`` if the address is allocated for private networks. See
171+
``True`` if the address is defined as not globally reachable by
172172
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
173-
(for IPv6).
173+
(for IPv6) with the following exceptions:
174+
175+
* ``is_private`` is ``False`` for the shared address space (``100.64.0.0/10``)
176+
* For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
177+
semantics of the underlying IPv4 addresses and the following condition holds
178+
(see :attr:`IPv6Address.ipv4_mapped`)::
179+
180+
address.is_private == address.ipv4_mapped.is_private
181+
182+
``is_private`` has value opposite to :attr:`is_global`, except for the shared address space
183+
(``100.64.0.0/10`` range) where they are both ``False``.
184+
185+
.. versionchanged:: 3.8.20
186+
187+
Fixed some false positives and false negatives.
188+
189+
* ``192.0.0.0/24`` is considered private with the exception of ``192.0.0.9/32`` and
190+
``192.0.0.10/32`` (previously: only the ``192.0.0.0/29`` sub-range was considered private).
191+
* ``64:ff9b:1::/48`` is considered private.
192+
* ``2002::/16`` is considered private.
193+
* There are exceptions within ``2001::/23`` (otherwise considered private): ``2001:1::1/128``,
194+
``2001:1::2/128``, ``2001:3::/32``, ``2001:4:112::/48``, ``2001:20::/28``, ``2001:30::/28``.
195+
The exceptions are not considered private.
174196

175197
.. attribute:: is_global
176198

177-
``True`` if the address is allocated for public networks. See
199+
``True`` if the address is defined as globally reachable by
178200
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
179-
(for IPv6).
201+
(for IPv6) with the following exception:
202+
203+
For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
204+
semantics of the underlying IPv4 addresses and the following condition holds
205+
(see :attr:`IPv6Address.ipv4_mapped`)::
206+
207+
address.is_global == address.ipv4_mapped.is_global
208+
209+
``is_global`` has value opposite to :attr:`is_private`, except for the shared address space
210+
(``100.64.0.0/10`` range) where they are both ``False``.
180211

181212
.. versionadded:: 3.4
182213

214+
.. versionchanged:: 3.8.20
215+
216+
Fixed some false positives and false negatives, see :attr:`is_private` for details.
217+
183218
.. attribute:: is_unspecified
184219

185220
``True`` if the address is unspecified. See :RFC:`5735` (for IPv4)

Doc/tools/susp-ignored.csv

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ library/ipaddress,,:db00,2001:db00::0/24
161161
library/ipaddress,,::,2001:db00::0/24
162162
library/ipaddress,,:db00,2001:db00::0/ffff:ff00::
163163
library/ipaddress,,::,2001:db00::0/ffff:ff00::
164+
library/ipaddress,,:ff9b,64:ff9b:1::/48
165+
library/ipaddress,,::,64:ff9b:1::/48
166+
library/ipaddress,,::,2001::
167+
library/ipaddress,,::,2001:1::
168+
library/ipaddress,,::,2001:3::
169+
library/ipaddress,,::,2001:4:112::
170+
library/ipaddress,,::,2001:20::
171+
library/ipaddress,,::,2001:30::
164172
library/itertools,,:step,elements from seq[start:stop:step]
165173
library/itertools,,:stop,elements from seq[start:stop:step]
166174
library/logging.handlers,,:port,host:port

Doc/whatsnew/3.7.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2650,3 +2650,11 @@ post-handshake TLS encrypted data. Security issue reported as
26502650
<https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-40217>`_ by Aapo
26512651
Oksman. Patch by Gregory P. Smith.
26522652

2653+
Notable changes in 3.7.17.4
2654+
=========================
2655+
2656+
ipaddress
2657+
---------
2658+
2659+
* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``,
2660+
``IPv6Address``, ``IPv4Network`` and ``IPv6Network``.

Lib/ipaddress.py

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,18 +1345,41 @@ def is_reserved(self):
13451345
@property
13461346
@functools.lru_cache()
13471347
def is_private(self):
1348-
"""Test if this address is allocated for private networks.
1348+
"""``True`` if the address is defined as not globally reachable by
1349+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
1350+
(for IPv6) with the following exceptions:
13491351
1350-
Returns:
1351-
A boolean, True if the address is reserved per
1352-
iana-ipv4-special-registry.
1352+
* ``is_private`` is ``False`` for ``100.64.0.0/10``
1353+
* For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
1354+
semantics of the underlying IPv4 addresses and the following condition holds
1355+
(see :attr:`IPv6Address.ipv4_mapped`)::
1356+
1357+
address.is_private == address.ipv4_mapped.is_private
13531358
1359+
``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
1360+
IPv4 range where they are both ``False``.
13541361
"""
1355-
return any(self in net for net in self._constants._private_networks)
1362+
return (
1363+
any(self in net for net in self._constants._private_networks)
1364+
and all(self not in net for net in self._constants._private_networks_exceptions)
1365+
)
13561366

13571367
@property
13581368
@functools.lru_cache()
13591369
def is_global(self):
1370+
"""``True`` if the address is defined as globally reachable by
1371+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
1372+
(for IPv6) with the following exception:
1373+
1374+
For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
1375+
semantics of the underlying IPv4 addresses and the following condition holds
1376+
(see :attr:`IPv6Address.ipv4_mapped`)::
1377+
1378+
address.is_global == address.ipv4_mapped.is_global
1379+
1380+
``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
1381+
IPv4 range where they are both ``False``.
1382+
"""
13601383
return self not in self._constants._public_network and not self.is_private
13611384

13621385
@property
@@ -1557,13 +1580,15 @@ class _IPv4Constants:
15571580

15581581
_public_network = IPv4Network('100.64.0.0/10')
15591582

1583+
# Not globally reachable address blocks listed on
1584+
# https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
15601585
_private_networks = [
15611586
IPv4Network('0.0.0.0/8'),
15621587
IPv4Network('10.0.0.0/8'),
15631588
IPv4Network('127.0.0.0/8'),
15641589
IPv4Network('169.254.0.0/16'),
15651590
IPv4Network('172.16.0.0/12'),
1566-
IPv4Network('192.0.0.0/29'),
1591+
IPv4Network('192.0.0.0/24'),
15671592
IPv4Network('192.0.0.170/31'),
15681593
IPv4Network('192.0.2.0/24'),
15691594
IPv4Network('192.168.0.0/16'),
@@ -1574,6 +1599,11 @@ class _IPv4Constants:
15741599
IPv4Network('255.255.255.255/32'),
15751600
]
15761601

1602+
_private_networks_exceptions = [
1603+
IPv4Network('192.0.0.9/32'),
1604+
IPv4Network('192.0.0.10/32'),
1605+
]
1606+
15771607
_reserved_network = IPv4Network('240.0.0.0/4')
15781608

15791609
_unspecified_address = IPv4Address('0.0.0.0')
@@ -1964,23 +1994,42 @@ def is_site_local(self):
19641994
@property
19651995
@functools.lru_cache()
19661996
def is_private(self):
1967-
"""Test if this address is allocated for private networks.
1997+
"""``True`` if the address is defined as not globally reachable by
1998+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
1999+
(for IPv6) with the following exceptions:
19682000
1969-
Returns:
1970-
A boolean, True if the address is reserved per
1971-
iana-ipv6-special-registry.
2001+
* ``is_private`` is ``False`` for ``100.64.0.0/10``
2002+
* For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
2003+
semantics of the underlying IPv4 addresses and the following condition holds
2004+
(see :attr:`IPv6Address.ipv4_mapped`)::
2005+
2006+
address.is_private == address.ipv4_mapped.is_private
19722007
2008+
``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
2009+
IPv4 range where they are both ``False``.
19732010
"""
1974-
return any(self in net for net in self._constants._private_networks)
2011+
ipv4_mapped = self.ipv4_mapped
2012+
if ipv4_mapped is not None:
2013+
return ipv4_mapped.is_private
2014+
return (
2015+
any(self in net for net in self._constants._private_networks)
2016+
and all(self not in net for net in self._constants._private_networks_exceptions)
2017+
)
19752018

19762019
@property
19772020
def is_global(self):
1978-
"""Test if this address is allocated for public networks.
2021+
"""``True`` if the address is defined as globally reachable by
2022+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
2023+
(for IPv6) with the following exception:
19792024
1980-
Returns:
1981-
A boolean, true if the address is not reserved per
1982-
iana-ipv6-special-registry.
2025+
For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
2026+
semantics of the underlying IPv4 addresses and the following condition holds
2027+
(see :attr:`IPv6Address.ipv4_mapped`)::
2028+
2029+
address.is_global == address.ipv4_mapped.is_global
19832030
2031+
``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
2032+
IPv4 range where they are both ``False``.
19842033
"""
19852034
return not self.is_private
19862035

@@ -2217,19 +2266,31 @@ class _IPv6Constants:
22172266

22182267
_multicast_network = IPv6Network('ff00::/8')
22192268

2269+
# Not globally reachable address blocks listed on
2270+
# https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
22202271
_private_networks = [
22212272
IPv6Network('::1/128'),
22222273
IPv6Network('::/128'),
22232274
IPv6Network('::ffff:0:0/96'),
2275+
IPv6Network('64:ff9b:1::/48'),
22242276
IPv6Network('100::/64'),
22252277
IPv6Network('2001::/23'),
2226-
IPv6Network('2001:2::/48'),
22272278
IPv6Network('2001:db8::/32'),
2228-
IPv6Network('2001:10::/28'),
2279+
# IANA says N/A, let's consider it not globally reachable to be safe
2280+
IPv6Network('2002::/16'),
22292281
IPv6Network('fc00::/7'),
22302282
IPv6Network('fe80::/10'),
22312283
]
22322284

2285+
_private_networks_exceptions = [
2286+
IPv6Network('2001:1::1/128'),
2287+
IPv6Network('2001:1::2/128'),
2288+
IPv6Network('2001:3::/32'),
2289+
IPv6Network('2001:4:112::/48'),
2290+
IPv6Network('2001:20::/28'),
2291+
IPv6Network('2001:30::/28'),
2292+
]
2293+
22332294
_reserved_networks = [
22342295
IPv6Network('::/8'), IPv6Network('100::/8'),
22352296
IPv6Network('200::/7'), IPv6Network('400::/6'),

Lib/test/test_ipaddress.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,6 +1766,10 @@ def testReservedIpv4(self):
17661766
self.assertEqual(True, ipaddress.ip_address(
17671767
'172.31.255.255').is_private)
17681768
self.assertEqual(False, ipaddress.ip_address('172.32.0.0').is_private)
1769+
self.assertFalse(ipaddress.ip_address('192.0.0.0').is_global)
1770+
self.assertTrue(ipaddress.ip_address('192.0.0.9').is_global)
1771+
self.assertTrue(ipaddress.ip_address('192.0.0.10').is_global)
1772+
self.assertFalse(ipaddress.ip_address('192.0.0.255').is_global)
17691773

17701774
self.assertEqual(True,
17711775
ipaddress.ip_address('169.254.100.200').is_link_local)
@@ -1781,6 +1785,40 @@ def testReservedIpv4(self):
17811785
self.assertEqual(False, ipaddress.ip_address('128.0.0.0').is_loopback)
17821786
self.assertEqual(True, ipaddress.ip_network('0.0.0.0').is_unspecified)
17831787

1788+
def testPrivateNetworks(self):
1789+
self.assertEqual(False, ipaddress.ip_network("0.0.0.0/0").is_private)
1790+
self.assertEqual(False, ipaddress.ip_network("1.0.0.0/8").is_private)
1791+
1792+
self.assertEqual(True, ipaddress.ip_network("0.0.0.0/8").is_private)
1793+
self.assertEqual(True, ipaddress.ip_network("10.0.0.0/8").is_private)
1794+
self.assertEqual(True, ipaddress.ip_network("127.0.0.0/8").is_private)
1795+
self.assertEqual(True, ipaddress.ip_network("169.254.0.0/16").is_private)
1796+
self.assertEqual(True, ipaddress.ip_network("172.16.0.0/12").is_private)
1797+
self.assertEqual(True, ipaddress.ip_network("192.0.0.0/29").is_private)
1798+
self.assertEqual(False, ipaddress.ip_network("192.0.0.9/32").is_private)
1799+
self.assertEqual(True, ipaddress.ip_network("192.0.0.170/31").is_private)
1800+
self.assertEqual(True, ipaddress.ip_network("192.0.2.0/24").is_private)
1801+
self.assertEqual(True, ipaddress.ip_network("192.168.0.0/16").is_private)
1802+
self.assertEqual(True, ipaddress.ip_network("198.18.0.0/15").is_private)
1803+
self.assertEqual(True, ipaddress.ip_network("198.51.100.0/24").is_private)
1804+
self.assertEqual(True, ipaddress.ip_network("203.0.113.0/24").is_private)
1805+
self.assertEqual(True, ipaddress.ip_network("240.0.0.0/4").is_private)
1806+
self.assertEqual(True, ipaddress.ip_network("255.255.255.255/32").is_private)
1807+
1808+
self.assertEqual(False, ipaddress.ip_network("::/0").is_private)
1809+
self.assertEqual(False, ipaddress.ip_network("::ff/128").is_private)
1810+
1811+
self.assertEqual(True, ipaddress.ip_network("::1/128").is_private)
1812+
self.assertEqual(True, ipaddress.ip_network("::/128").is_private)
1813+
self.assertEqual(True, ipaddress.ip_network("::ffff:0:0/96").is_private)
1814+
self.assertEqual(True, ipaddress.ip_network("100::/64").is_private)
1815+
self.assertEqual(True, ipaddress.ip_network("2001:2::/48").is_private)
1816+
self.assertEqual(False, ipaddress.ip_network("2001:3::/48").is_private)
1817+
self.assertEqual(True, ipaddress.ip_network("2001:db8::/32").is_private)
1818+
self.assertEqual(True, ipaddress.ip_network("2001:10::/28").is_private)
1819+
self.assertEqual(True, ipaddress.ip_network("fc00::/7").is_private)
1820+
self.assertEqual(True, ipaddress.ip_network("fe80::/10").is_private)
1821+
17841822
def testReservedIpv6(self):
17851823

17861824
self.assertEqual(True, ipaddress.ip_network('ffff::').is_multicast)
@@ -1854,6 +1892,20 @@ def testReservedIpv6(self):
18541892
self.assertEqual(True, ipaddress.ip_address('0::0').is_unspecified)
18551893
self.assertEqual(False, ipaddress.ip_address('::1').is_unspecified)
18561894

1895+
self.assertFalse(ipaddress.ip_address('64:ff9b:1::').is_global)
1896+
self.assertFalse(ipaddress.ip_address('2001::').is_global)
1897+
self.assertTrue(ipaddress.ip_address('2001:1::1').is_global)
1898+
self.assertTrue(ipaddress.ip_address('2001:1::2').is_global)
1899+
self.assertFalse(ipaddress.ip_address('2001:2::').is_global)
1900+
self.assertTrue(ipaddress.ip_address('2001:3::').is_global)
1901+
self.assertFalse(ipaddress.ip_address('2001:4::').is_global)
1902+
self.assertTrue(ipaddress.ip_address('2001:4:112::').is_global)
1903+
self.assertFalse(ipaddress.ip_address('2001:10::').is_global)
1904+
self.assertTrue(ipaddress.ip_address('2001:20::').is_global)
1905+
self.assertTrue(ipaddress.ip_address('2001:30::').is_global)
1906+
self.assertFalse(ipaddress.ip_address('2001:40::').is_global)
1907+
self.assertFalse(ipaddress.ip_address('2002::').is_global)
1908+
18571909
# some generic IETF reserved addresses
18581910
self.assertEqual(True, ipaddress.ip_address('100::').is_reserved)
18591911
self.assertEqual(True, ipaddress.ip_network('4000::1/128').is_reserved)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Fixed various false positives and false negatives in
2+
3+
* :attr:`ipaddress.IPv4Address.is_private` (see these docs for details)
4+
* :attr:`ipaddress.IPv4Address.is_global`
5+
* :attr:`ipaddress.IPv6Address.is_private`
6+
* :attr:`ipaddress.IPv6Address.is_global`
7+
8+
Also in the corresponding :class:`ipaddress.IPv4Network` and :class:`ipaddress.IPv6Network`
9+
attributes.

0 commit comments

Comments
 (0)