mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-10-05 02:57:19 +00:00
Details: https://nvd.nist.gov/vuln/detail/cve-2026-47180 Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com> Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
111 lines
4.9 KiB
Diff
111 lines
4.9 KiB
Diff
From 7ffd3a90230cb99bcae4350fcb8498c48044f6a6 Mon Sep 17 00:00:00 2001
|
|
From: "J. Nick Koston" <nick@koston.org>
|
|
Date: Sun, 17 May 2026 19:48:44 -0700
|
|
Subject: [PATCH] fix: bound DNS compression-pointer chain depth in DNSIncoming
|
|
(#1719)
|
|
|
|
(cherry picked from commit f9e23592137f30fdf7ef710dba065da31c79b1cf)
|
|
|
|
CVE: CVE-2026-47180
|
|
Upstream-Status: Backport [https://github.com/python-zeroconf/python-zeroconf/commit/f9e23592137f30fdf7ef710dba065da31c79b1cf]
|
|
Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
|
|
---
|
|
src/zeroconf/_protocol/incoming.pxd | 2 +-
|
|
src/zeroconf/_protocol/incoming.py | 14 ++++++++++----
|
|
tests/test_protocol.py | 22 ++++++++++++++++++++++
|
|
3 files changed, 33 insertions(+), 5 deletions(-)
|
|
|
|
diff --git a/src/zeroconf/_protocol/incoming.pxd b/src/zeroconf/_protocol/incoming.pxd
|
|
index feaa2a0..eface8d 100644
|
|
--- a/src/zeroconf/_protocol/incoming.pxd
|
|
+++ b/src/zeroconf/_protocol/incoming.pxd
|
|
@@ -83,7 +83,7 @@ cdef class DNSIncoming:
|
|
link_py_int=object,
|
|
linked_labels=cython.list
|
|
)
|
|
- cdef unsigned int _decode_labels_at_offset(self, unsigned int off, cython.list labels, cython.set seen_pointers)
|
|
+ cdef unsigned int _decode_labels_at_offset(self, unsigned int off, cython.list labels, cython.set seen_pointers, unsigned int depth)
|
|
|
|
@cython.locals(offset="unsigned int")
|
|
cdef void _read_header(self)
|
|
diff --git a/src/zeroconf/_protocol/incoming.py b/src/zeroconf/_protocol/incoming.py
|
|
index 2d977b6..d772f47 100644
|
|
--- a/src/zeroconf/_protocol/incoming.py
|
|
+++ b/src/zeroconf/_protocol/incoming.py
|
|
@@ -60,7 +60,7 @@ DNS_COMPRESSION_POINTER_LEN = 2
|
|
MAX_DNS_LABELS = 128
|
|
MAX_NAME_LENGTH = 253
|
|
|
|
-DECODE_EXCEPTIONS = (IndexError, struct.error, IncomingDecodeError)
|
|
+DECODE_EXCEPTIONS = (IndexError, struct.error, IncomingDecodeError, RecursionError)
|
|
|
|
|
|
_seen_logs: dict[str, int | tuple] = {}
|
|
@@ -409,7 +409,7 @@ class DNSIncoming:
|
|
labels: list[str] = []
|
|
seen_pointers: set[int] = set()
|
|
original_offset = self.offset
|
|
- self.offset = self._decode_labels_at_offset(original_offset, labels, seen_pointers)
|
|
+ self.offset = self._decode_labels_at_offset(original_offset, labels, seen_pointers, 0)
|
|
self._name_cache[original_offset] = labels
|
|
name = ".".join(labels) + "."
|
|
if len(name) > MAX_NAME_LENGTH:
|
|
@@ -418,8 +418,14 @@ class DNSIncoming:
|
|
)
|
|
return name
|
|
|
|
- def _decode_labels_at_offset(self, off: _int, labels: list[str], seen_pointers: set[int]) -> int:
|
|
+ def _decode_labels_at_offset(
|
|
+ self, off: _int, labels: list[str], seen_pointers: set[int], depth: _int
|
|
+ ) -> int:
|
|
# This is a tight loop that is called frequently, small optimizations can make a difference.
|
|
+ if depth > MAX_DNS_LABELS:
|
|
+ raise IncomingDecodeError(
|
|
+ f"DNS compression pointer chain exceeds {MAX_DNS_LABELS} at {off} from {self.source}"
|
|
+ )
|
|
view = self.view
|
|
while off < self._data_len:
|
|
length = view[off]
|
|
@@ -457,7 +463,7 @@ class DNSIncoming:
|
|
if not linked_labels:
|
|
linked_labels = []
|
|
seen_pointers.add(link_py_int)
|
|
- self._decode_labels_at_offset(link, linked_labels, seen_pointers)
|
|
+ self._decode_labels_at_offset(link, linked_labels, seen_pointers, depth + 1)
|
|
self._name_cache[link_py_int] = linked_labels
|
|
labels.extend(linked_labels)
|
|
if len(labels) > MAX_DNS_LABELS:
|
|
diff --git a/tests/test_protocol.py b/tests/test_protocol.py
|
|
index edd87c2..bac2b44 100644
|
|
--- a/tests/test_protocol.py
|
|
+++ b/tests/test_protocol.py
|
|
@@ -1011,6 +1011,28 @@ def test_label_compression_attack():
|
|
assert len(parsed.answers()) == 1
|
|
|
|
|
|
+def test_dns_compression_pointer_chain_depth_attack() -> None:
|
|
+ """Test our wire parser rejects deeply chained compression pointers without recursing."""
|
|
+ # Build a packet with one question whose name is a 1500-deep chain of forward
|
|
+ # compression pointers, ending in a root label. Each pointer is 2 bytes,
|
|
+ # so chain length easily exceeds CPython's default recursion limit.
|
|
+ header = b"\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"
|
|
+ # Question at offset 12: pointer to offset 18 (past the question's type/class).
|
|
+ question_name = bytes([0xC0, 18])
|
|
+ question_type_class = b"\x00\x01\x00\x01"
|
|
+ chain_depth = 1500
|
|
+ chain = bytearray()
|
|
+ for i in range(chain_depth):
|
|
+ target = 18 + 2 * (i + 1)
|
|
+ chain.append(0xC0 | (target >> 8))
|
|
+ chain.append(target & 0xFF)
|
|
+ chain.append(0x00)
|
|
+ packet = header + question_name + question_type_class + bytes(chain)
|
|
+ parsed = r.DNSIncoming(packet, ("1.2.3.4", 5353))
|
|
+ assert parsed.valid is False
|
|
+ assert parsed.questions == []
|
|
+
|
|
+
|
|
def test_dns_compression_loop_attack():
|
|
"""Test our wire parser does not loop forever when dns compression is in a loop."""
|
|
packet = (
|