From 115bec4b27fb23c917f1a33f50727c422c52a15e Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Tue, 14 Jan 2025 11:13:33 -0600 Subject: [PATCH 1/6] GH-128840: Limit the number of parts in IPv6 address parsing --- Lib/ipaddress.py | 7 +++++-- Lib/test/test_ipaddress.py | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 703fa289dda1fb..135b35d023a4f1 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1661,7 +1661,11 @@ def _ip_int_from_string(cls, ip_str): if not ip_str: raise AddressValueError('Address cannot be empty') - parts = ip_str.split(':') + # We want to allow more parts than the max to be 'split' + # to preserve the correct error message when there are + # too many parts combined with '::' + _max_parts = cls._HEXTET_COUNT + 1 + parts = ip_str.split(':', maxsplit=_max_parts) # An IPv6 address needs at least 2 colons (3 parts). _min_parts = 3 @@ -1681,7 +1685,6 @@ def _ip_int_from_string(cls, ip_str): # An IPv6 address can't have more than 8 colons (9 parts). # The extra colon comes from using the "::" notation for a single # leading or trailing zero part. - _max_parts = cls._HEXTET_COUNT + 1 if len(parts) > _max_parts: msg = "At most %d colons permitted in %r" % (_max_parts-1, ip_str) raise AddressValueError(msg) diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index b1ac2b94f41b38..c1b672caba2d94 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -416,6 +416,8 @@ def assertBadSplit(addr): # A trailing IPv4 address is two parts assertBadSplit("9:8:7:6:5:4:3:42.42.42.42%scope") assertBadSplit("7:6:5:4:3:42.42.42.42%scope") + # Long IPv6 address + assertBadSplit(("0:" * 10000) + "0") def test_bad_address_split_v6_too_many_parts_with_double_colon(self): def assertBadSplit(addr): From 0afa53888deb87a727e2ba2235c1c0077a366ea8 Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Tue, 14 Jan 2025 11:19:16 -0600 Subject: [PATCH 2/6] Add news entry --- .../next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst diff --git a/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst b/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst new file mode 100644 index 00000000000000..e197ee9c6677d8 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst @@ -0,0 +1 @@ +Limit the number of parts split during IPv6 address parsing. From 3e055b46a472d86a279d3e3976d951f5025390af Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Tue, 14 Jan 2025 12:14:18 -0600 Subject: [PATCH 3/6] Fix location of new test case --- Lib/test/test_ipaddress.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index c1b672caba2d94..37aa3b3fc706d1 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -396,6 +396,8 @@ def assertBadSplit(addr): assertBadSplit("8:7:6:5:4:3:2:1::%scope") # A trailing IPv4 address is two parts assertBadSplit("10:9:8:7:6:5:4:3:42.42.42.42%scope") + # Long IPv6 address + assertBadSplit(("0:" * 10000) + "0") def test_bad_address_split_v6_too_many_parts(self): def assertBadSplit(addr): @@ -416,8 +418,6 @@ def assertBadSplit(addr): # A trailing IPv4 address is two parts assertBadSplit("9:8:7:6:5:4:3:42.42.42.42%scope") assertBadSplit("7:6:5:4:3:42.42.42.42%scope") - # Long IPv6 address - assertBadSplit(("0:" * 10000) + "0") def test_bad_address_split_v6_too_many_parts_with_double_colon(self): def assertBadSplit(addr): From e65928735aae806d1343d7b14802fb6dd0fb6b64 Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Tue, 14 Jan 2025 16:10:29 -0600 Subject: [PATCH 4/6] Update with more descriptive news --- .../Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst b/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst index e197ee9c6677d8..fbfeb355262bbb 100644 --- a/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst +++ b/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst @@ -1 +1,3 @@ -Limit the number of parts split during IPv6 address parsing. +Limit the number of splitting on colons (``:``) that will occur while parsing +an IPv6 address. This prevents excessive memory consumption and potential +denial-of-service when parsing a large IPv6 address. From e1e69174a279157e0be73b045ae302381b954e4e Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Wed, 15 Jan 2025 10:49:49 -0600 Subject: [PATCH 5/6] Limit length of IP address string to 39 --- Lib/ipaddress.py | 3 +++ Lib/test/test_ipaddress.py | 11 ++++++++++- .../2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst | 5 ++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index 135b35d023a4f1..67a8d578a238ce 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1660,6 +1660,9 @@ def _ip_int_from_string(cls, ip_str): """ if not ip_str: raise AddressValueError('Address cannot be empty') + if len(ip_str) > 39: + msg = "At most 39 characters expected in %r" % (ip_str,) + raise AddressValueError(msg) # We want to allow more parts than the max to be 'split' # to preserve the correct error message when there are diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index 37aa3b3fc706d1..21602de4024369 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -396,8 +396,17 @@ def assertBadSplit(addr): assertBadSplit("8:7:6:5:4:3:2:1::%scope") # A trailing IPv4 address is two parts assertBadSplit("10:9:8:7:6:5:4:3:42.42.42.42%scope") + + def test_bad_address_split_v6_too_long(self): + def assertBadSplit(addr): + msg = "At most 39 characters expected in %r" + with self.assertAddressError(msg, addr.split('%')[0]): + ipaddress.IPv6Address(addr) + # Long IPv6 address - assertBadSplit(("0:" * 10000) + "0") + long_addr = ("0:" * 10000) + "0" + assertBadSplit(long_addr) + assertBadSplit(long_addr + "%zoneid") def test_bad_address_split_v6_too_many_parts(self): def assertBadSplit(addr): diff --git a/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst b/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst index fbfeb355262bbb..362b43c67ee83b 100644 --- a/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst +++ b/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst @@ -1,3 +1,2 @@ -Limit the number of splitting on colons (``:``) that will occur while parsing -an IPv6 address. This prevents excessive memory consumption and potential -denial-of-service when parsing a large IPv6 address. +Short-circuit the processing of long IPv6 addresses early to prevent excessive +memory consumption and a minor denial-of-service. From 9401977fbbb1c18ea678bbb2ba6e3fa0f944f687 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Fri, 23 May 2025 19:27:37 -0700 Subject: [PATCH 6/6] Elide the looong value in the exception message. --- Lib/ipaddress.py | 3 ++- Lib/test/test_ipaddress.py | 4 ++-- .../Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index bee7fb3463b671..69e933a6541f78 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1661,7 +1661,8 @@ def _ip_int_from_string(cls, ip_str): if not ip_str: raise AddressValueError('Address cannot be empty') if len(ip_str) > 39: - msg = "At most 39 characters expected in %r" % (ip_str,) + msg = ("At most 39 characters expected in " + f"{ip_str[:14]!r}({len(ip_str)-28} chars elided){ip_str[-14:]!r}") raise AddressValueError(msg) # We want to allow more parts than the max to be 'split' diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index d95b87a7463dfe..ee95454e64bb0d 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -399,8 +399,8 @@ def assertBadSplit(addr): def test_bad_address_split_v6_too_long(self): def assertBadSplit(addr): - msg = "At most 39 characters expected in %r" - with self.assertAddressError(msg, addr.split('%')[0]): + msg = r"At most 39 characters expected in %s" + with self.assertAddressError(msg, repr(re.escape(addr[:14]))): ipaddress.IPv6Address(addr) # Long IPv6 address diff --git a/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst b/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst index 362b43c67ee83b..b57ec3e70dcc5f 100644 --- a/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst +++ b/Misc/NEWS.d/next/Security/2025-01-14-11-19-07.gh-issue-128840.M1doZW.rst @@ -1,2 +1,2 @@ -Short-circuit the processing of long IPv6 addresses early to prevent excessive +Short-circuit the processing of long IPv6 addresses early in :mod:`ipaddress` to prevent excessive memory consumption and a minor denial-of-service. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy