From 22acbaae97c3698005e69555eb4ebccc168b2fff Mon Sep 17 00:00:00 2001 From: metsw24-max Date: Mon, 11 May 2026 20:59:30 +0530 Subject: [PATCH 3/3] Enforce early length limits in check_label CVE: CVE-2026-45409 Upstream-Status: Backport [https://github.com/kjd/idna/commit/e1cb465b6376f33306a26f467d197edbcd01c4b9] (cherry picked from commit e1cb465b6376f33306a26f467d197edbcd01c4b9) Signed-off-by: Hetvi Thakar --- 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 4a9fc75..26bb9fa 100644 --- a/idna/core.py +++ b/idna/core.py @@ -230,6 +230,17 @@ def check_label(label: Union[str, bytes, bytearray]) -> None: 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 5001b48..2dc0892 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 = '\u0061'