From b34cd8399981324b361ae4f2b8e0eb77444ae0e3 Mon Sep 17 00:00:00 2001 From: Kim Davies Date: Sun, 10 May 2026 08:47:22 -0700 Subject: [PATCH 1/3] Merge commit from fork CVE: CVE-2026-45409 Upstream-Status: Backport [https://github.com/kjd/idna/commit/c0dda4501df5d91c3181ce6f962dc5de74e82cc1] Backport Changes: - Omit the HISTORY.rst hunk because it documents the upstream 3.14 release and is not applicable to the Scarthgap 3.7 source. (cherry picked from commit c0dda4501df5d91c3181ce6f962dc5de74e82cc1) Signed-off-by: Hetvi Thakar --- 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 0dae61a..a549326 100644 --- a/idna/core.py +++ b/idna/core.py @@ -340,6 +340,15 @@ def encode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = raise IDNAError('should pass a unicode string to the function rather than a byte string.') 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: @@ -373,6 +382,11 @@ def decode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = raise IDNAError('Invalid ASCII in A-label') 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 81afb32..5001b48 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 = '\u0061'