175 lines
6.8 KiB
Diff
175 lines
6.8 KiB
Diff
From 641dad441dbc63145c70e64bf70a4035508d2426 Mon Sep 17 00:00:00 2001
|
||
From: Kim Davies <kim@cynosure.com.au>
|
||
Date: Sun, 10 May 2026 08:47:22 -0700
|
||
Subject: [PATCH 1/3] Merge commit from fork
|
||
|
||
---
|
||
idna/core.py | 14 ++++++++++++++
|
||
tests/test_idna.py | 13 +++++++++++++
|
||
2 files changed, 27 insertions(+)
|
||
|
||
diff --git a/idna/core.py b/idna/core.py
|
||
index 5f54c89..2175d87 100644
|
||
--- a/idna/core.py
|
||
+++ b/idna/core.py
|
||
@@ -347,6 +347,15 @@ def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False):
|
||
s = s.decode("ascii")
|
||
if uts46:
|
||
s = uts46_remap(s, std3_rules, transitional)
|
||
+
|
||
+ # Reject inputs that exceed the maximum DNS domain length up-front.
|
||
+ # Each codepoint in a U-label contributes at least one octet to its
|
||
+ # A-label form, so any input longer than the domain limit cannot
|
||
+ # produce a valid A-domain. Short-circuiting here prevents per-label
|
||
+ # validation from being driven into quadratic time
|
||
+ if len(s) > 254:
|
||
+ raise IDNAError("Domain too long")
|
||
+
|
||
trailing_dot = False
|
||
result = []
|
||
if strict:
|
||
@@ -378,6 +387,11 @@ def decode(s, strict=False, uts46=False, std3_rules=False):
|
||
s = s.decode("ascii")
|
||
if uts46:
|
||
s = uts46_remap(s, std3_rules, False)
|
||
+ # See encode() for rationale; the same bound applies because every
|
||
+ # legal A-domain is at most 254 octets and every codepoint of a
|
||
+ # legal U-domain contributes at least one octet to its A-form.
|
||
+ if len(s) > 254:
|
||
+ raise IDNAError("Domain too long")
|
||
trailing_dot = False
|
||
result = []
|
||
if not strict:
|
||
diff --git a/tests/test_idna.py b/tests/test_idna.py
|
||
index 2d8c450..b32ac8d 100755
|
||
--- a/tests/test_idna.py
|
||
+++ b/tests/test_idna.py
|
||
@@ -78,6 +78,19 @@ class IDNATests(unittest.TestCase):
|
||
self.assertFalse(idna.valid_label_length('a' * 64))
|
||
self.assertRaises(idna.IDNAError, idna.encode, 'a' * 64)
|
||
|
||
+ def test_oversized_input_rejected_promptly(self):
|
||
+ # GHSA-65pc-fj4g-8rjx: encode/decode must reject inputs that
|
||
+ # exceed the maximum DNS domain length before per-codepoint
|
||
+ # validation runs, so labels dominated by CONTEXTO codepoints
|
||
+ # cannot drive validation into quadratic time.
|
||
+ import time
|
||
+
|
||
+ for payload in ("٠" * 8000, "・" * 8000 + "漢"):
|
||
+ start = time.perf_counter()
|
||
+ self.assertRaises(idna.IDNAError, idna.encode, payload)
|
||
+ self.assertRaises(idna.IDNAError, idna.decode, payload)
|
||
+ self.assertLess(time.perf_counter() - start, 1.0)
|
||
+
|
||
def test_check_bidi(self):
|
||
|
||
l = u'\u0061'
|
||
|
||
From 20ecdf5c96d31f2b95a49d5794417ba9e88f2329 Mon Sep 17 00:00:00 2001
|
||
From: metsw24-max <metsw24@gmail.com>
|
||
Date: Mon, 11 May 2026 20:59:30 +0530
|
||
Subject: [PATCH 2/3] Enforce early length limits in check_label
|
||
|
||
---
|
||
idna/core.py | 11 +++++++++++
|
||
tests/test_idna.py | 24 ++++++++++++++++++++++++
|
||
2 files changed, 35 insertions(+)
|
||
|
||
diff --git a/idna/core.py b/idna/core.py
|
||
index 2175d87..c68254a 100644
|
||
--- a/idna/core.py
|
||
+++ b/idna/core.py
|
||
@@ -241,6 +241,17 @@ def check_label(label):
|
||
label = label.decode('utf-8')
|
||
if len(label) == 0:
|
||
raise IDNAError('Empty Label')
|
||
+ # Reject oversized labels before per-codepoint validation runs.
|
||
+ # CONTEXTJ/CONTEXTO checks scan the whole label per codepoint, so an
|
||
+ # uncapped label drives validation into quadratic time
|
||
+ # (GHSA-65pc-fj4g-8rjx / CVE-2024-3651). encode()/decode() cap the
|
||
+ # whole-domain length; this cap protects direct callers of
|
||
+ # alabel/ulabel/check_label and the idna2008 incremental codec.
|
||
+ # Use the whole-domain bound rather than the per-label DNS bound so
|
||
+ # that UTS #46 lenient decoding of labels longer than 63 chars is
|
||
+ # preserved.
|
||
+ if not valid_string_length(label, trailing_dot=True):
|
||
+ raise IDNAError('Label too long')
|
||
|
||
check_nfc(label)
|
||
check_hyphen_ok(label)
|
||
diff --git a/tests/test_idna.py b/tests/test_idna.py
|
||
index b32ac8d..1179ebb 100755
|
||
--- a/tests/test_idna.py
|
||
+++ b/tests/test_idna.py
|
||
@@ -91,6 +91,30 @@ class IDNATests(unittest.TestCase):
|
||
self.assertRaises(idna.IDNAError, idna.decode, payload)
|
||
self.assertLess(time.perf_counter() - start, 1.0)
|
||
|
||
+ def test_oversized_label_rejected_promptly(self):
|
||
+ # The whole-domain cap in encode()/decode() does not cover direct
|
||
+ # callers of alabel/ulabel/check_label, nor the idna2008
|
||
+ # incremental codec which calls alabel/ulabel per label. Without a
|
||
+ # per-label cap, a single oversized CONTEXTO-heavy label still
|
||
+ # drives validation into quadratic time.
|
||
+ import codecs
|
||
+ import time
|
||
+
|
||
+ import idna.codec # noqa: F401 (register the idna2008 codec)
|
||
+
|
||
+ payload = "・" * 8000 + "漢"
|
||
+ start = time.perf_counter()
|
||
+ self.assertRaises(idna.IDNAError, idna.check_label, payload)
|
||
+ self.assertRaises(idna.IDNAError, idna.alabel, payload)
|
||
+ self.assertRaises(idna.IDNAError, idna.ulabel, payload)
|
||
+ self.assertRaises(
|
||
+ idna.IDNAError,
|
||
+ codecs.getincrementalencoder("idna2008")().encode,
|
||
+ payload,
|
||
+ True,
|
||
+ )
|
||
+ self.assertLess(time.perf_counter() - start, 1.0)
|
||
+
|
||
def test_check_bidi(self):
|
||
|
||
l = u'\u0061'
|
||
|
||
From 7afc8765eff9626ae584a9f4980e31ecdf148078 Mon Sep 17 00:00:00 2001
|
||
From: RHEL Packaging Agent <redhat-ymir-agent@redhat.com>
|
||
Date: Mon, 27 Jul 2026 07:12:26 +0000
|
||
Subject: [PATCH 3/3] Adapt test to use IncrementalEncoder directly for v2.10
|
||
compatibility
|
||
|
||
In v2.10, the idna codec is registered as 'idna' not 'idna2008', and
|
||
codecs.register() is not called. Use idna.codec.IncrementalEncoder
|
||
directly instead of codecs.getincrementalencoder('idna2008') to test
|
||
the same functionality without requiring the newer codec registration.
|
||
---
|
||
tests/test_idna.py | 5 ++---
|
||
1 file changed, 2 insertions(+), 3 deletions(-)
|
||
|
||
diff --git a/tests/test_idna.py b/tests/test_idna.py
|
||
index 1179ebb..854de15 100755
|
||
--- a/tests/test_idna.py
|
||
+++ b/tests/test_idna.py
|
||
@@ -97,10 +97,9 @@ class IDNATests(unittest.TestCase):
|
||
# incremental codec which calls alabel/ulabel per label. Without a
|
||
# per-label cap, a single oversized CONTEXTO-heavy label still
|
||
# drives validation into quadratic time.
|
||
- import codecs
|
||
import time
|
||
|
||
- import idna.codec # noqa: F401 (register the idna2008 codec)
|
||
+ from idna.codec import IncrementalEncoder
|
||
|
||
payload = "・" * 8000 + "漢"
|
||
start = time.perf_counter()
|
||
@@ -109,7 +108,7 @@ class IDNATests(unittest.TestCase):
|
||
self.assertRaises(idna.IDNAError, idna.ulabel, payload)
|
||
self.assertRaises(
|
||
idna.IDNAError,
|
||
- codecs.getincrementalencoder("idna2008")().encode,
|
||
+ IncrementalEncoder().encode,
|
||
payload,
|
||
True,
|
||
)
|