Skip to content

Commit 2976e94

Browse files
encukoujstasiak
authored andcommitted
[CVE-2024-4032] Fix "private" (non-global) IP address ranges
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 (gh#python#61602). The _address_exclude_many() call returns 8 networks for IPv4, 121 networks for IPv6. For 0.0.0.0/0, both 0.0.0.0 and 255.225.255.255 are private, but one is in 0.0.0.0/8 ("This network") and the other in 255.255.255.255/32 ("Limited broadcast"). Add IPv6 addresses to suspignore.csv That's a lot of semicolons! Fixes: gh#python#113171 Fixes: bsc#1226448 (CVE-2024-4032) From-PR: gh#python/cpython!113179 From-PR: gh#python/cpython!113186 From-PR: gh#python/cpython!118177 Co-authored-by: Jakub Stasiak <[email protected]> Patch: CVE-2024-4032-private-IP-addrs.patch
1 parent e701181 commit 2976e94

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)