Skip to content

Commit d315722

Browse files
authored
gh-98433: Fix quadratic time idna decoding. (#99092)
There was an unnecessary quadratic loop in idna decoding. This restores the behavior to linear. This also adds an early length check in IDNA decoding to outright reject huge inputs early on given the ultimate result is defined to be 63 or fewer characters.
1 parent 9430d27 commit d315722

File tree

3 files changed

+45
-17
lines changed

3 files changed

+45
-17
lines changed

Lib/encodings/idna.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,21 @@ def nameprep(label):
3939

4040
# Check bidi
4141
RandAL = [stringprep.in_table_d1(x) for x in label]
42-
for c in RandAL:
43-
if c:
44-
# There is a RandAL char in the string. Must perform further
45-
# tests:
46-
# 1) The characters in section 5.8 MUST be prohibited.
47-
# This is table C.8, which was already checked
48-
# 2) If a string contains any RandALCat character, the string
49-
# MUST NOT contain any LCat character.
50-
if any(stringprep.in_table_d2(x) for x in label):
51-
raise UnicodeError("Violation of BIDI requirement 2")
52-
53-
# 3) If a string contains any RandALCat character, a
54-
# RandALCat character MUST be the first character of the
55-
# string, and a RandALCat character MUST be the last
56-
# character of the string.
57-
if not RandAL[0] or not RandAL[-1]:
58-
raise UnicodeError("Violation of BIDI requirement 3")
42+
if any(RandAL):
43+
# There is a RandAL char in the string. Must perform further
44+
# tests:
45+
# 1) The characters in section 5.8 MUST be prohibited.
46+
# This is table C.8, which was already checked
47+
# 2) If a string contains any RandALCat character, the string
48+
# MUST NOT contain any LCat character.
49+
if any(stringprep.in_table_d2(x) for x in label):
50+
raise UnicodeError("Violation of BIDI requirement 2")
51+
# 3) If a string contains any RandALCat character, a
52+
# RandALCat character MUST be the first character of the
53+
# string, and a RandALCat character MUST be the last
54+
# character of the string.
55+
if not RandAL[0] or not RandAL[-1]:
56+
raise UnicodeError("Violation of BIDI requirement 3")
5957

6058
return label
6159

@@ -103,6 +101,16 @@ def ToASCII(label):
103101
raise UnicodeError("label empty or too long")
104102

105103
def ToUnicode(label):
104+
if len(label) > 1024:
105+
# Protection from https://github.com/python/cpython/issues/98433.
106+
# https://datatracker.ietf.org/doc/html/rfc5894#section-6
107+
# doesn't specify a label size limit prior to NAMEPREP. But having
108+
# one makes practical sense.
109+
# This leaves ample room for nameprep() to remove Nothing characters
110+
# per https://www.rfc-editor.org/rfc/rfc3454#section-3.1 while still
111+
# preventing us from wasting time decoding a big thing that'll just
112+
# hit the actual <= 63 length limit in Step 6.
113+
raise UnicodeError("label way too long")
106114
# Step 1: Check for ASCII
107115
if isinstance(label, bytes):
108116
pure_ascii = True

Lib/test/test_codecs.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,6 +1552,12 @@ def test_builtin_encode(self):
15521552
self.assertEqual("pyth\xf6n.org".encode("idna"), b"xn--pythn-mua.org")
15531553
self.assertEqual("pyth\xf6n.org.".encode("idna"), b"xn--pythn-mua.org.")
15541554

1555+
def test_builtin_decode_length_limit(self):
1556+
with self.assertRaisesRegex(UnicodeError, "way too long"):
1557+
(b"xn--016c"+b"a"*1100).decode("idna")
1558+
with self.assertRaisesRegex(UnicodeError, "too long"):
1559+
(b"xn--016c"+b"a"*70).decode("idna")
1560+
15551561
def test_stream(self):
15561562
r = codecs.getreader("idna")(io.BytesIO(b"abc"))
15571563
r.read(3)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
The IDNA codec decoder used on DNS hostnames by :mod:`socket` or :mod:`asyncio`
2+
related name resolution functions no longer involves a quadratic algorithm.
3+
This prevents a potential CPU denial of service if an out-of-spec excessive
4+
length hostname involving bidirectional characters were decoded. Some protocols
5+
such as :mod:`urllib` http ``3xx`` redirects potentially allow for an attacker
6+
to supply such a name.
7+
8+
Individual labels within an IDNA encoded DNS name will now raise an error early
9+
during IDNA decoding if they are longer than 1024 unicode characters given that
10+
each decoded DNS label must be 63 or fewer characters and the entire decoded
11+
DNS name is limited to 255. Only an application presenting a hostname or label
12+
consisting primarily of :rfc:`3454` section 3.1 "Nothing" characters to be
13+
removed would run into of this new limit. See also :rfc:`5894` section 6 and
14+
:rfc:`3491`.

0 commit comments

Comments
 (0)