Fix CVE-2026-45409: quadratic time complexity in IDNA validation
Add patch CVE-2026-45409.patch to enforce early length limits in
encode(), decode(), and check_label() so that CONTEXTO-heavy
inputs cannot drive per-codepoint validation into quadratic
time. The patch also adapts the upstream test to work with the
v2.5 codec API by using idna.codec.IncrementalEncoder directly
instead of codecs.getincrementalencoder("idna2008").
CVE: CVE-2026-45409
Upstream patches:
- c0dda4501d.patch
- e1cb465b63.patch
Resolves: RHEL-215650
This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent.
Assisted-by: Ymir
Co-authored-by: Tomáš Hrnčiar <thrnciar@redhat.com>
This commit is contained in:
parent
3f661cf699
commit
c86a94e122
173
CVE-2026-45409.patch
Normal file
173
CVE-2026-45409.patch
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
From 320f91cf06a927f48d164ed505e798955ddfd45a 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 1d36686..fe55b5b 100644
|
||||||
|
--- a/idna/core.py
|
||||||
|
+++ b/idna/core.py
|
||||||
|
@@ -342,6 +342,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:
|
||||||
|
@@ -371,6 +380,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 061f129..ff60444 100755
|
||||||
|
--- a/tests/test_idna.py
|
||||||
|
+++ b/tests/test_idna.py
|
||||||
|
@@ -84,6 +84,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 cfdb7a332e69b7664762bc2fdd63e51f8fb75050 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 fe55b5b..48e61d3 100644
|
||||||
|
--- a/idna/core.py
|
||||||
|
+++ b/idna/core.py
|
||||||
|
@@ -238,6 +238,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 ff60444..4195075 100755
|
||||||
|
--- a/tests/test_idna.py
|
||||||
|
+++ b/tests/test_idna.py
|
||||||
|
@@ -97,6 +97,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 c3c00c307f58a72ccfad94b6a51ef40094368c15 Mon Sep 17 00:00:00 2001
|
||||||
|
From: RHEL Packaging Agent <redhat-ymir-agent@redhat.com>
|
||||||
|
Date: Mon, 27 Jul 2026 07:12:18 +0000
|
||||||
|
Subject: [PATCH 3/3] Adapt test to v2.5 codec API: use
|
||||||
|
idna.codec.IncrementalEncoder directly
|
||||||
|
|
||||||
|
In v2.5, the codec is named 'idna' (not 'idna2008') and uses the old-style
|
||||||
|
getregentry() mechanism rather than codecs.register(). Use the codec class
|
||||||
|
directly instead of going through codecs.getincrementalencoder('idna2008').
|
||||||
|
---
|
||||||
|
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 4195075..247f7f9 100755
|
||||||
|
--- a/tests/test_idna.py
|
||||||
|
+++ b/tests/test_idna.py
|
||||||
|
@@ -103,10 +103,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)
|
||||||
|
+ import idna.codec
|
||||||
|
|
||||||
|
payload = "・" * 8000 + "漢"
|
||||||
|
start = time.perf_counter()
|
||||||
|
@@ -115,7 +114,7 @@ class IDNATests(unittest.TestCase):
|
||||||
|
self.assertRaises(idna.IDNAError, idna.ulabel, payload)
|
||||||
|
self.assertRaises(
|
||||||
|
idna.IDNAError,
|
||||||
|
- codecs.getincrementalencoder("idna2008")().encode,
|
||||||
|
+ idna.codec.IncrementalEncoder().encode,
|
||||||
|
payload,
|
||||||
|
True,
|
||||||
|
)
|
||||||
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
Name: python-%{srcname}
|
Name: python-%{srcname}
|
||||||
Version: 2.5
|
Version: 2.5
|
||||||
Release: 7%{?dist}
|
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
|
||||||
@ -25,7 +25,12 @@ Source0: https://pypi.io/packages/source/i/%{srcname}/%{srcname}-%{versio
|
|||||||
# Security fix for CVE-2024-3651
|
# Security fix for CVE-2024-3651
|
||||||
# Upstream: https://github.com/kjd/idna/commit/5beb28b9dd77912c0dd656d8b0fdba3eb80222e7
|
# Upstream: https://github.com/kjd/idna/commit/5beb28b9dd77912c0dd656d8b0fdba3eb80222e7
|
||||||
# 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
|
Patch0: 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
|
||||||
|
Patch1: CVE-2026-45409.patch
|
||||||
|
|
||||||
BuildArch: noarch
|
BuildArch: noarch
|
||||||
|
|
||||||
@ -129,6 +134,10 @@ rm -rf %{srcname}.egg-info
|
|||||||
%endif # with_python3
|
%endif # with_python3
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Mon Jul 27 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.5-8
|
||||||
|
- Security fix for CVE-2026-45409
|
||||||
|
Resolves: RHEL-215650
|
||||||
|
|
||||||
* Thu Apr 25 2024 Lumír Balhar <lbalhar@redhat.com> - 2.5-7
|
* Thu Apr 25 2024 Lumír Balhar <lbalhar@redhat.com> - 2.5-7
|
||||||
- Fix patch application for security fix for CVE-2024-3651
|
- Fix patch application for security fix for CVE-2024-3651
|
||||||
Resolves: RHEL-32703
|
Resolves: RHEL-32703
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user