Fix CVE-2026-45409: quadratic time complexity in IDNA validation
Security fix for CVE-2026-45409: IDNA encode/decode and check_label now reject inputs exceeding the maximum DNS domain length before per-codepoint validation runs. This prevents CONTEXTO-heavy labels from driving validation into quadratic time. The patch was adapted for v2.10 compatibility by using IncrementalEncoder directly from idna.codec instead of the 'idna2008' codec name introduced in v3.x. CVE: CVE-2026-45409 Upstream patches: -c0dda4501d.patch -e1cb465b63.patch Resolves: RHEL-215653 This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent. Assisted-by: Ymir
This commit is contained in:
parent
112df0c7ea
commit
cda423ee86
174
CVE-2026-45409.patch
Normal file
174
CVE-2026-45409.patch
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
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,
|
||||||
|
)
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Name: python-%{srcname}
|
Name: python-%{srcname}
|
||||||
Version: 2.10
|
Version: 2.10
|
||||||
Release: 7%{?dist}.1
|
Release: 8%{?dist}
|
||||||
Summary: Internationalized Domain Names in Applications (IDNA)
|
Summary: Internationalized Domain Names in Applications (IDNA)
|
||||||
|
|
||||||
License: BSD and Python and Unicode
|
License: BSD and Python and Unicode
|
||||||
@ -14,6 +14,11 @@ Source0: https://pypi.io/packages/source/i/%{srcname}/%{srcname}-%{versio
|
|||||||
# Tracking bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2274779
|
# Tracking bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2274779
|
||||||
Patch: CVE-2024-3651.patch
|
Patch: CVE-2024-3651.patch
|
||||||
|
|
||||||
|
# Security fix for CVE-2026-45409
|
||||||
|
# Upstream: https://github.com/kjd/idna/commit/c0dda4501df5d91c3181ce6f962dc5de74e82cc1
|
||||||
|
# Upstream: https://github.com/kjd/idna/commit/e1cb465b6376f33306a26f467d197edbcd01c4b9
|
||||||
|
Patch: CVE-2026-45409.patch
|
||||||
|
|
||||||
BuildArch: noarch
|
BuildArch: noarch
|
||||||
|
|
||||||
BuildRequires: python3-devel
|
BuildRequires: python3-devel
|
||||||
@ -65,6 +70,10 @@ rm -rf %{srcname}.egg-info
|
|||||||
%{python3_sitelib}/%{srcname}-%{version}-py%{python3_version}.egg-info
|
%{python3_sitelib}/%{srcname}-%{version}-py%{python3_version}.egg-info
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Mon Jul 27 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.10-8
|
||||||
|
- Security fix for CVE-2026-45409
|
||||||
|
Resolves: RHEL-215653
|
||||||
|
|
||||||
* Tue Apr 23 2024 Lumír Balhar <lbalhar@redhat.com> - 2.10-7.1
|
* Tue Apr 23 2024 Lumír Balhar <lbalhar@redhat.com> - 2.10-7.1
|
||||||
- Security fix for CVE-2024-3651
|
- Security fix for CVE-2024-3651
|
||||||
Resolves: RHEL-33464
|
Resolves: RHEL-33464
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user