已开启
fix(cve): 修复 CVE-2026-114605 在 python-dns 中的漏洞 #74
infra_team创建于 12 天前
fix(cve): 修复 CVE-2026-114605 在 python-dns 中的漏洞 #74
已开启
共 2 个文件变更+103-2
| @@ -0,0 +1,97 @@ | |||
| 1 | +From b485e460c266540a1a09ca6d8d1d7b685b1d3023 Mon Sep 17 00:00:00 2001 | ||
| 2 | +From: Bob Halley <halley@dnspython.org> | ||
| 3 | +Date: Sat, 26 Sep 2026 13:38:07 -0700 | ||
| 4 | +Subject: [PATCH] fix: CVE-2026-114605 - limit decompression to a chain length | ||
| 5 | + of at most 16 pointers | ||
| 6 | + | ||
| 7 | +A maliciously crafted DNS message may cause dns.name.from_wire() to use | ||
| 8 | +excessive CPU for DNS name decompression. Applications which process | ||
| 9 | +untrusted DNS wire form messages with dnspython may be degraded or DoS'd | ||
| 10 | +by the extra CPU use required. | ||
| 11 | + | ||
| 12 | +Backport of upstream commit b485e460 to dnspython 1.16.0, adapted to the | ||
| 13 | +1.16.0 from_wire(message, current) implementation (the upstream diff | ||
| 14 | +targets the 2.x from_wire_parser() refactor and cannot be applied as-is; | ||
| 15 | +doc/whatsnew.rst does not exist in 1.16.0 and is dropped). The hops | ||
| 16 | +counter already exists in 1.16.0 and is now also used to bound the | ||
| 17 | +compression pointer chain at MAX_COMPRESSION_POINTER_CHAIN = 16. | ||
| 18 | + | ||
| 19 | +Credit: Evgenios Gkritsis, Constantinos Patsakis, George Stergiopoulos | ||
| 20 | + | ||
| 21 | +Upstream-commit: https://github.com/rthalley/dnspython/commit/b485e460c266540a1a09ca6d8d1d7b685b1d3023 | ||
| 22 | +Signed-off-by: infra_team <zhaiwenjie1@huawei.com> | ||
| 23 | +--- | ||
| 24 | + dns/name.py | 9 +++++++++ | ||
| 25 | + tests/test_name.py | 22 ++++++++++++++++++++++ | ||
| 26 | + 2 files changed, 31 insertions(+) | ||
| 27 | + | ||
| 28 | +diff --git a/dns/name.py b/dns/name.py | ||
| 29 | +index 3bc01ac..70b6749 100644 | ||
| 30 | +--- a/dns/name.py | ||
| 31 | ++++ b/dns/name.py | ||
| 32 | +@@ -66,6 +66,10 @@ class BadPointer(dns.exception.FormError): | ||
| 33 | + """A DNS compression pointer points forward instead of backward.""" | ||
| 34 | + | ||
| 35 | + | ||
| 36 | ++class PointerChainTooLong(dns.exception.FormError): | ||
| 37 | ++ """A DNS compression pointer chain was too long.""" | ||
| 38 | ++ | ||
| 39 | ++ | ||
| 40 | + class BadLabelType(dns.exception.FormError): | ||
| 41 | + """The label type in DNS name wire format is unknown.""" | ||
| 42 | + | ||
| 43 | +@@ -957,6 +961,9 @@ def from_text(text, origin=root, idna_codec=None): | ||
| 44 | + return Name(labels) | ||
| 45 | + | ||
| 46 | + | ||
| 47 | ++MAX_COMPRESSION_POINTER_CHAIN = 16 | ||
| 48 | ++ | ||
| 49 | ++ | ||
| 50 | + def from_wire(message, current): | ||
| 51 | + """Convert possibly compressed wire format into a Name. | ||
| 52 | + | ||
| 53 | +@@ -999,6 +1006,8 @@ def from_wire(message, current): | ||
| 54 | + raise BadPointer | ||
| 55 | + biggest_pointer = current | ||
| 56 | + hops += 1 | ||
| 57 | ++ if hops > MAX_COMPRESSION_POINTER_CHAIN: | ||
| 58 | ++ raise PointerChainTooLong | ||
| 59 | + else: | ||
| 60 | + raise BadLabelType | ||
| 61 | + count = message[current] | ||
| 62 | +diff --git a/tests/test_name.py b/tests/test_name.py | ||
| 63 | +index bea30a2..231e577 100644 | ||
| 64 | +--- a/tests/test_name.py | ||
| 65 | ++++ b/tests/test_name.py | ||
| 66 | +@@ -627,6 +627,28 @@ class NameTestCase(unittest.TestCase): | ||
| 67 | + dns.name.from_wire(w, 0) | ||
| 68 | + self.failUnlessRaises(dns.name.BadLabelType, bad) | ||
| 69 | + | ||
| 70 | ++ def testCompressionChainLength(self): | ||
| 71 | ++ # We're going to decode some wire starting two bytes from the end. At this | ||
| 72 | ++ # position is a pointer which points to four bytes from the end, and it points | ||
| 73 | ++ # to one 6 bytes from the end, etc., until finally the 16th pointer points at | ||
| 74 | ++ # offset zero, which is the root label followed by a byte of padding. After | ||
| 75 | ++ # all of this useless pointer hopping, we should get the root name. | ||
| 76 | ++ w = b"\x00\x00\xc0\x00\xc0\x02\xc0\x04\xc0\x06\xc0\x08\xc0\x0a\xc0\x0c\xc0\x0e\xc0\x10\xc0\x12\xc0\x14\xc0\x16\xc0\x18\xc0\x1a\xc0\x1c\xc0\x1e" | ||
| 77 | ++ self.assertEqual(len(w), 34) | ||
| 78 | ++ starts = [c for c in w if c == 0xC0] | ||
| 79 | ++ self.assertEqual(len(starts), dns.name.MAX_COMPRESSION_POINTER_CHAIN) | ||
| 80 | ++ | ||
| 81 | ++ def bad(): | ||
| 82 | ++ # Now we make the wire one pointer longer, which is too long! | ||
| 83 | ++ w1 = w + b"\xc0\x20" | ||
| 84 | ++ self.assertEqual(len(w1), 36) | ||
| 85 | ++ starts = [c for c in w1 if c == 0xC0] | ||
| 86 | ++ self.assertEqual(len(starts), dns.name.MAX_COMPRESSION_POINTER_CHAIN + 1) | ||
| 87 | ++ dns.name.from_wire(w1, 34) | ||
| 88 | ++ | ||
| 89 | ++ self.assertEqual(dns.name.from_wire(w, 32)[0], dns.name.from_text(".")) | ||
| 90 | ++ self.assertRaises(dns.name.PointerChainTooLong, bad) | ||
| 91 | ++ | ||
| 92 | + def testParent1(self): | ||
| 93 | + n = dns.name.from_text('foo.bar.') | ||
| 94 | + self.failUnless(n.parent() == dns.name.from_text('bar.')) | ||
| 95 | +-- | ||
| 96 | +2.39.5 | ||
| 97 | + | ||
| @@ -14,7 +14,7 @@ messages, names, and records. | |||
| 14 | Name: python-dns | 14 | Name: python-dns |
| 15 | Summary: %{sum} | 15 | Summary: %{sum} |
| 16 | Version: 1.16.0 | 16 | Version: 1.16.0 |
| 17 | -Release: 2 | 17 | +Release: 3 |
| 18 | License: MIT | 18 | License: MIT |
| 19 | URL: http://www.dnspython.org/ | 19 | URL: http://www.dnspython.org/ |
| 20 | Source0: http://www.dnspython.org/kits/%{version}/dnspython-%{version}.tar.gz | 20 | Source0: http://www.dnspython.org/kits/%{version}/dnspython-%{version}.tar.gz |
| @@ -23,7 +23,8 @@ BuildArch: noarch | |||
| 23 | Patch0: unicode_label_escapify.patch | 23 | Patch0: unicode_label_escapify.patch |
| 24 | Patch1: collections_abc.patch | 24 | Patch1: collections_abc.patch |
| 25 | Patch2: base64.patch | 25 | Patch2: base64.patch |
| 26 | -Patch3: fix-failed-tests.patch | 26 | +Patch3: fix-failed-tests.patch |
| 27 | +Patch4: backport-CVE-2026-114605-limit-pointer-chain.patch | ||
| 27 | 28 | ||
| 28 | BuildRequires: python2-devel python2-setuptools python2-pycryptodome python2-ecdsa | 29 | BuildRequires: python2-devel python2-setuptools python2-pycryptodome python2-ecdsa |
| 29 | BuildRequires: python3-devel python3-setuptools python3-pycryptodome python3-ecdsa | 30 | BuildRequires: python3-devel python3-setuptools python3-pycryptodome python3-ecdsa |
| @@ -78,6 +79,9 @@ find examples -type f | xargs chmod a-x | |||
| 78 | %doc examples | 79 | %doc examples |
| 79 | 80 | ||
| 80 | %changelog | 81 | %changelog |
| 82 | +* Sat Oct 24 2026 infra_team <zhaiwenjie1@huawei.com> - 1.16.0-3 | ||
| 83 | +- fix CVE-2026-114605: limit DNS name decompression to a chain length of at most 16 pointers | ||
| 84 | + | ||
| 81 | * Tue Jun 15 2021 gaihuiying <gaihuiying1@huawei.com> - 1.16.0-2 | 85 | * Tue Jun 15 2021 gaihuiying <gaihuiying1@huawei.com> - 1.16.0-2 |
| 82 | - remove python2 test | 86 | - remove python2 test |
| 83 | 87 | ||