Skip to content

Commit b137e12

Browse files
encukoufrenzymadness
authored andcommitted
00431: CVE-2024-4032: incorrect IPv4 and IPv6 private ranges
Upstream issue: python#113171 Backported from 3.8.
1 parent bf04a3d commit b137e12

File tree

5 files changed

+186
-21
lines changed

5 files changed

+186
-21
lines changed

Doc/library/ipaddress.rst

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

167167
.. attribute:: is_private
168168

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

173195
.. attribute:: is_global
174196

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

179210
.. versionadded:: 3.4
180211

212+
.. versionchanged:: 3.8.20
213+
214+
Fixed some false positives and false negatives, see :attr:`is_private` for details.
215+
181216
.. attribute:: is_unspecified
182217

183218
``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
@@ -160,6 +160,14 @@ library/ipaddress,,:db00,2001:db00::0/24
160160
library/ipaddress,,::,2001:db00::0/24
161161
library/ipaddress,,:db00,2001:db00::0/ffff:ff00::
162162
library/ipaddress,,::,2001:db00::0/ffff:ff00::
163+
library/ipaddress,,:ff9b,64:ff9b:1::/48
164+
library/ipaddress,,::,64:ff9b:1::/48
165+
library/ipaddress,,::,2001::
166+
library/ipaddress,,::,2001:1::
167+
library/ipaddress,,::,2001:3::
168+
library/ipaddress,,::,2001:4:112::
169+
library/ipaddress,,::,2001:20::
170+
library/ipaddress,,::,2001:30::
163171
library/itertools,,:step,elements from seq[start:stop:step]
164172
library/itertools,,:stop,elements from seq[start:stop:step]
165173
library/logging.handlers,,:port,host:port

Lib/ipaddress.py

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,18 +1302,41 @@ def is_reserved(self):
13021302
@property
13031303
@functools.lru_cache()
13041304
def is_private(self):
1305-
"""Test if this address is allocated for private networks.
1305+
"""``True`` if the address is defined as not globally reachable by
1306+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
1307+
(for IPv6) with the following exceptions:
13061308
1307-
Returns:
1308-
A boolean, True if the address is reserved per
1309-
iana-ipv4-special-registry.
1309+
* ``is_private`` is ``False`` for ``100.64.0.0/10``
1310+
* For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
1311+
semantics of the underlying IPv4 addresses and the following condition holds
1312+
(see :attr:`IPv6Address.ipv4_mapped`)::
1313+
1314+
address.is_private == address.ipv4_mapped.is_private
13101315
1316+
``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
1317+
IPv4 range where they are both ``False``.
13111318
"""
1312-
return any(self in net for net in self._constants._private_networks)
1319+
return (
1320+
any(self in net for net in self._constants._private_networks)
1321+
and all(self not in net for net in self._constants._private_networks_exceptions)
1322+
)
13131323

13141324
@property
13151325
@functools.lru_cache()
13161326
def is_global(self):
1327+
"""``True`` if the address is defined as globally reachable by
1328+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
1329+
(for IPv6) with the following exception:
1330+
1331+
For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
1332+
semantics of the underlying IPv4 addresses and the following condition holds
1333+
(see :attr:`IPv6Address.ipv4_mapped`)::
1334+
1335+
address.is_global == address.ipv4_mapped.is_global
1336+
1337+
``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
1338+
IPv4 range where they are both ``False``.
1339+
"""
13171340
return self not in self._constants._public_network and not self.is_private
13181341

13191342
@property
@@ -1548,13 +1571,15 @@ class _IPv4Constants:
15481571

15491572
_public_network = IPv4Network('100.64.0.0/10')
15501573

1574+
# Not globally reachable address blocks listed on
1575+
# https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
15511576
_private_networks = [
15521577
IPv4Network('0.0.0.0/8'),
15531578
IPv4Network('10.0.0.0/8'),
15541579
IPv4Network('127.0.0.0/8'),
15551580
IPv4Network('169.254.0.0/16'),
15561581
IPv4Network('172.16.0.0/12'),
1557-
IPv4Network('192.0.0.0/29'),
1582+
IPv4Network('192.0.0.0/24'),
15581583
IPv4Network('192.0.0.170/31'),
15591584
IPv4Network('192.0.2.0/24'),
15601585
IPv4Network('192.168.0.0/16'),
@@ -1565,6 +1590,11 @@ class _IPv4Constants:
15651590
IPv4Network('255.255.255.255/32'),
15661591
]
15671592

1593+
_private_networks_exceptions = [
1594+
IPv4Network('192.0.0.9/32'),
1595+
IPv4Network('192.0.0.10/32'),
1596+
]
1597+
15681598
_reserved_network = IPv4Network('240.0.0.0/4')
15691599

15701600
_unspecified_address = IPv4Address('0.0.0.0')
@@ -1953,23 +1983,42 @@ def is_site_local(self):
19531983
@property
19541984
@functools.lru_cache()
19551985
def is_private(self):
1956-
"""Test if this address is allocated for private networks.
1986+
"""``True`` if the address is defined as not globally reachable by
1987+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
1988+
(for IPv6) with the following exceptions:
19571989
1958-
Returns:
1959-
A boolean, True if the address is reserved per
1960-
iana-ipv6-special-registry.
1990+
* ``is_private`` is ``False`` for ``100.64.0.0/10``
1991+
* For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
1992+
semantics of the underlying IPv4 addresses and the following condition holds
1993+
(see :attr:`IPv6Address.ipv4_mapped`)::
1994+
1995+
address.is_private == address.ipv4_mapped.is_private
19611996
1997+
``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
1998+
IPv4 range where they are both ``False``.
19621999
"""
1963-
return any(self in net for net in self._constants._private_networks)
2000+
ipv4_mapped = self.ipv4_mapped
2001+
if ipv4_mapped is not None:
2002+
return ipv4_mapped.is_private
2003+
return (
2004+
any(self in net for net in self._constants._private_networks)
2005+
and all(self not in net for net in self._constants._private_networks_exceptions)
2006+
)
19642007

19652008
@property
19662009
def is_global(self):
1967-
"""Test if this address is allocated for public networks.
2010+
"""``True`` if the address is defined as globally reachable by
2011+
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
2012+
(for IPv6) with the following exception:
19682013
1969-
Returns:
1970-
A boolean, true if the address is not reserved per
1971-
iana-ipv6-special-registry.
2014+
For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
2015+
semantics of the underlying IPv4 addresses and the following condition holds
2016+
(see :attr:`IPv6Address.ipv4_mapped`)::
2017+
2018+
address.is_global == address.ipv4_mapped.is_global
19722019
2020+
``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
2021+
IPv4 range where they are both ``False``.
19732022
"""
19742023
return not self.is_private
19752024

@@ -2236,19 +2285,31 @@ class _IPv6Constants:
22362285

22372286
_multicast_network = IPv6Network('ff00::/8')
22382287

2288+
# Not globally reachable address blocks listed on
2289+
# https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
22392290
_private_networks = [
22402291
IPv6Network('::1/128'),
22412292
IPv6Network('::/128'),
22422293
IPv6Network('::ffff:0:0/96'),
2294+
IPv6Network('64:ff9b:1::/48'),
22432295
IPv6Network('100::/64'),
22442296
IPv6Network('2001::/23'),
2245-
IPv6Network('2001:2::/48'),
22462297
IPv6Network('2001:db8::/32'),
2247-
IPv6Network('2001:10::/28'),
2298+
# IANA says N/A, let's consider it not globally reachable to be safe
2299+
IPv6Network('2002::/16'),
22482300
IPv6Network('fc00::/7'),
22492301
IPv6Network('fe80::/10'),
22502302
]
22512303

2304+
_private_networks_exceptions = [
2305+
IPv6Network('2001:1::1/128'),
2306+
IPv6Network('2001:1::2/128'),
2307+
IPv6Network('2001:3::/32'),
2308+
IPv6Network('2001:4:112::/48'),
2309+
IPv6Network('2001:20::/28'),
2310+
IPv6Network('2001:30::/28'),
2311+
]
2312+
22522313
_reserved_networks = [
22532314
IPv6Network('::/8'), IPv6Network('100::/8'),
22542315
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
@@ -1665,6 +1665,10 @@ def testReservedIpv4(self):
16651665
self.assertEqual(True, ipaddress.ip_address(
16661666
'172.31.255.255').is_private)
16671667
self.assertEqual(False, ipaddress.ip_address('172.32.0.0').is_private)
1668+
self.assertFalse(ipaddress.ip_address('192.0.0.0').is_global)
1669+
self.assertTrue(ipaddress.ip_address('192.0.0.9').is_global)
1670+
self.assertTrue(ipaddress.ip_address('192.0.0.10').is_global)
1671+
self.assertFalse(ipaddress.ip_address('192.0.0.255').is_global)
16681672

16691673
self.assertEqual(True,
16701674
ipaddress.ip_address('169.254.100.200').is_link_local)
@@ -1680,6 +1684,40 @@ def testReservedIpv4(self):
16801684
self.assertEqual(False, ipaddress.ip_address('128.0.0.0').is_loopback)
16811685
self.assertEqual(True, ipaddress.ip_network('0.0.0.0').is_unspecified)
16821686

1687+
def testPrivateNetworks(self):
1688+
self.assertEqual(True, ipaddress.ip_network("0.0.0.0/0").is_private)
1689+
self.assertEqual(False, ipaddress.ip_network("1.0.0.0/8").is_private)
1690+
1691+
self.assertEqual(True, ipaddress.ip_network("0.0.0.0/8").is_private)
1692+
self.assertEqual(True, ipaddress.ip_network("10.0.0.0/8").is_private)
1693+
self.assertEqual(True, ipaddress.ip_network("127.0.0.0/8").is_private)
1694+
self.assertEqual(True, ipaddress.ip_network("169.254.0.0/16").is_private)
1695+
self.assertEqual(True, ipaddress.ip_network("172.16.0.0/12").is_private)
1696+
self.assertEqual(True, ipaddress.ip_network("192.0.0.0/29").is_private)
1697+
self.assertEqual(False, ipaddress.ip_network("192.0.0.9/32").is_private)
1698+
self.assertEqual(True, ipaddress.ip_network("192.0.0.170/31").is_private)
1699+
self.assertEqual(True, ipaddress.ip_network("192.0.2.0/24").is_private)
1700+
self.assertEqual(True, ipaddress.ip_network("192.168.0.0/16").is_private)
1701+
self.assertEqual(True, ipaddress.ip_network("198.18.0.0/15").is_private)
1702+
self.assertEqual(True, ipaddress.ip_network("198.51.100.0/24").is_private)
1703+
self.assertEqual(True, ipaddress.ip_network("203.0.113.0/24").is_private)
1704+
self.assertEqual(True, ipaddress.ip_network("240.0.0.0/4").is_private)
1705+
self.assertEqual(True, ipaddress.ip_network("255.255.255.255/32").is_private)
1706+
1707+
self.assertEqual(False, ipaddress.ip_network("::/0").is_private)
1708+
self.assertEqual(False, ipaddress.ip_network("::ff/128").is_private)
1709+
1710+
self.assertEqual(True, ipaddress.ip_network("::1/128").is_private)
1711+
self.assertEqual(True, ipaddress.ip_network("::/128").is_private)
1712+
self.assertEqual(True, ipaddress.ip_network("::ffff:0:0/96").is_private)
1713+
self.assertEqual(True, ipaddress.ip_network("100::/64").is_private)
1714+
self.assertEqual(True, ipaddress.ip_network("2001:2::/48").is_private)
1715+
self.assertEqual(False, ipaddress.ip_network("2001:3::/48").is_private)
1716+
self.assertEqual(True, ipaddress.ip_network("2001:db8::/32").is_private)
1717+
self.assertEqual(True, ipaddress.ip_network("2001:10::/28").is_private)
1718+
self.assertEqual(True, ipaddress.ip_network("fc00::/7").is_private)
1719+
self.assertEqual(True, ipaddress.ip_network("fe80::/10").is_private)
1720+
16831721
def testReservedIpv6(self):
16841722

16851723
self.assertEqual(True, ipaddress.ip_network('ffff::').is_multicast)
@@ -1753,6 +1791,20 @@ def testReservedIpv6(self):
17531791
self.assertEqual(True, ipaddress.ip_address('0::0').is_unspecified)
17541792
self.assertEqual(False, ipaddress.ip_address('::1').is_unspecified)
17551793

1794+
self.assertFalse(ipaddress.ip_address('64:ff9b:1::').is_global)
1795+
self.assertFalse(ipaddress.ip_address('2001::').is_global)
1796+
self.assertTrue(ipaddress.ip_address('2001:1::1').is_global)
1797+
self.assertTrue(ipaddress.ip_address('2001:1::2').is_global)
1798+
self.assertFalse(ipaddress.ip_address('2001:2::').is_global)
1799+
self.assertTrue(ipaddress.ip_address('2001:3::').is_global)
1800+
self.assertFalse(ipaddress.ip_address('2001:4::').is_global)
1801+
self.assertTrue(ipaddress.ip_address('2001:4:112::').is_global)
1802+
self.assertFalse(ipaddress.ip_address('2001:10::').is_global)
1803+
self.assertTrue(ipaddress.ip_address('2001:20::').is_global)
1804+
self.assertTrue(ipaddress.ip_address('2001:30::').is_global)
1805+
self.assertFalse(ipaddress.ip_address('2001:40::').is_global)
1806+
self.assertFalse(ipaddress.ip_address('2002::').is_global)
1807+
17561808
# some generic IETF reserved addresses
17571809
self.assertEqual(True, ipaddress.ip_address('100::').is_reserved)
17581810
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)