]> git.ipfire.org Git - thirdparty/dnspython.git/commitdiff
Address DoS via the Tudoor mechanism (CVE-2023-29483) (#1044)
authorBob Halley <halley@dnspython.org>
Fri, 9 Feb 2024 19:22:52 +0000 (11:22 -0800)
committerGitHub <noreply@github.com>
Fri, 9 Feb 2024 19:22:52 +0000 (11:22 -0800)
dns/asyncquery.py
dns/nameserver.py
dns/query.py

index 35a355bb6cbf4081ea2379a3b3da346719139a8f..94cb24133172eb0b4c1ee8003567c7acf037756d 100644 (file)
@@ -120,6 +120,8 @@ async def receive_udp(
     request_mac: Optional[bytes] = b"",
     ignore_trailing: bool = False,
     raise_on_truncation: bool = False,
+    ignore_errors: bool = False,
+    query: Optional[dns.message.Message] = None,
 ) -> Any:
     """Read a DNS message from a UDP socket.
 
@@ -133,22 +135,30 @@ async def receive_udp(
     """
 
     wire = b""
-    while 1:
+    while True:
         (wire, from_address) = await sock.recvfrom(65535, _timeout(expiration))
-        if _matches_destination(
+        if not _matches_destination(
             sock.family, from_address, destination, ignore_unexpected
         ):
-            break
-    received_time = time.time()
-    r = dns.message.from_wire(
-        wire,
-        keyring=keyring,
-        request_mac=request_mac,
-        one_rr_per_rrset=one_rr_per_rrset,
-        ignore_trailing=ignore_trailing,
-        raise_on_truncation=raise_on_truncation,
-    )
-    return (r, received_time, from_address)
+            continue
+        received_time = time.time()
+        try:
+            r = dns.message.from_wire(
+                wire,
+                keyring=keyring,
+                request_mac=request_mac,
+                one_rr_per_rrset=one_rr_per_rrset,
+                ignore_trailing=ignore_trailing,
+                raise_on_truncation=raise_on_truncation,
+            )
+        except Exception:
+            if ignore_errors:
+                continue
+            else:
+                raise
+        if ignore_errors and query is not None and not query.is_response(r):
+            continue
+        return (r, received_time, from_address)
 
 
 async def udp(
@@ -164,6 +174,7 @@ async def udp(
     raise_on_truncation: bool = False,
     sock: Optional[dns.asyncbackend.DatagramSocket] = None,
     backend: Optional[dns.asyncbackend.Backend] = None,
+    ignore_errors: bool = False,
 ) -> dns.message.Message:
     """Return the response obtained after sending a query via UDP.
 
@@ -205,9 +216,13 @@ async def udp(
             q.mac,
             ignore_trailing,
             raise_on_truncation,
+            ignore_errors,
+            q,
         )
         r.time = received_time - begin_time
-        if not q.is_response(r):
+        # We don't need to check q.is_response() if we are in ignore_errors mode
+        # as receive_udp() will have checked it.
+        if not (ignore_errors or q.is_response(r)):
             raise BadResponse
         return r
 
@@ -225,6 +240,7 @@ async def udp_with_fallback(
     udp_sock: Optional[dns.asyncbackend.DatagramSocket] = None,
     tcp_sock: Optional[dns.asyncbackend.StreamSocket] = None,
     backend: Optional[dns.asyncbackend.Backend] = None,
+    ignore_errors: bool = False,
 ) -> Tuple[dns.message.Message, bool]:
     """Return the response to the query, trying UDP first and falling back
     to TCP if UDP results in a truncated response.
@@ -260,6 +276,7 @@ async def udp_with_fallback(
             True,
             udp_sock,
             backend,
+            ignore_errors,
         )
         return (response, False)
     except dns.message.Truncated:
index a1fb54987ad97a2e656de008f2ec1d40ba9d821d..0c494c18bb6c06499d02ad150be47a6b25923585 100644 (file)
@@ -115,6 +115,7 @@ class Do53Nameserver(AddressAndPortNameserver):
                 raise_on_truncation=True,
                 one_rr_per_rrset=one_rr_per_rrset,
                 ignore_trailing=ignore_trailing,
+                ignore_errors=True,
             )
         return response
 
@@ -153,6 +154,7 @@ class Do53Nameserver(AddressAndPortNameserver):
                 backend=backend,
                 one_rr_per_rrset=one_rr_per_rrset,
                 ignore_trailing=ignore_trailing,
+                ignore_errors=True,
             )
         return response
 
index d4bd6b92ceb97927d00b4a1ef9e9ce8150c1dbd9..bdd251e752a4df491cd869d80741b76fca49e5f1 100644 (file)
@@ -569,6 +569,8 @@ def receive_udp(
     request_mac: Optional[bytes] = b"",
     ignore_trailing: bool = False,
     raise_on_truncation: bool = False,
+    ignore_errors: bool = False,
+    query: Optional[dns.message.Message] = None,
 ) -> Any:
     """Read a DNS message from a UDP socket.
 
@@ -609,28 +611,44 @@ def receive_udp(
     ``(dns.message.Message, float, tuple)``
     tuple of the received message, the received time, and the address where
     the message arrived from.
+
+    *ignore_errors*, a ``bool``.  If various format errors or response
+    mismatches occur, ignore them and keep listening for a valid response.
+    The default is ``False``.
+
+    *query*, a ``dns.message.Message`` or ``None``.  If not ``None`` and
+    *ignore_errors* is ``True``, check that the received message is a response
+    to this query, and if not keep listening for a valid response.
     """
 
     wire = b""
     while True:
         (wire, from_address) = _udp_recv(sock, 65535, expiration)
-        if _matches_destination(
+        if not _matches_destination(
             sock.family, from_address, destination, ignore_unexpected
         ):
-            break
-    received_time = time.time()
-    r = dns.message.from_wire(
-        wire,
-        keyring=keyring,
-        request_mac=request_mac,
-        one_rr_per_rrset=one_rr_per_rrset,
-        ignore_trailing=ignore_trailing,
-        raise_on_truncation=raise_on_truncation,
-    )
-    if destination:
-        return (r, received_time)
-    else:
-        return (r, received_time, from_address)
+            continue
+        received_time = time.time()
+        try:
+            r = dns.message.from_wire(
+                wire,
+                keyring=keyring,
+                request_mac=request_mac,
+                one_rr_per_rrset=one_rr_per_rrset,
+                ignore_trailing=ignore_trailing,
+                raise_on_truncation=raise_on_truncation,
+            )
+        except Exception:
+            if ignore_errors:
+                continue
+            else:
+                raise
+        if ignore_errors and query is not None and not query.is_response(r):
+            continue
+        if destination:
+            return (r, received_time)
+        else:
+            return (r, received_time, from_address)
 
 
 def udp(
@@ -645,6 +663,7 @@ def udp(
     ignore_trailing: bool = False,
     raise_on_truncation: bool = False,
     sock: Optional[Any] = None,
+    ignore_errors: bool = False,
 ) -> dns.message.Message:
     """Return the response obtained after sending a query via UDP.
 
@@ -681,6 +700,10 @@ def udp(
     if a socket is provided, it must be a nonblocking datagram socket,
     and the *source* and *source_port* are ignored.
 
+    *ignore_errors*, a ``bool``.  If various format errors or response
+    mismatches occur, ignore them and keep listening for a valid response.
+    The default is ``False``.
+
     Returns a ``dns.message.Message``.
     """
 
@@ -705,9 +728,13 @@ def udp(
             q.mac,
             ignore_trailing,
             raise_on_truncation,
+            ignore_errors,
+            q,
         )
         r.time = received_time - begin_time
-        if not q.is_response(r):
+        # We don't need to check q.is_response() if we are in ignore_errors mode
+        # as receive_udp() will have checked it.
+        if not (ignore_errors or q.is_response(r)):
             raise BadResponse
         return r
     assert (
@@ -727,48 +754,50 @@ def udp_with_fallback(
     ignore_trailing: bool = False,
     udp_sock: Optional[Any] = None,
     tcp_sock: Optional[Any] = None,
+    ignore_errors: bool = False,
 ) -> Tuple[dns.message.Message, bool]:
     """Return the response to the query, trying UDP first and falling back
     to TCP if UDP results in a truncated response.
 
     *q*, a ``dns.message.Message``, the query to send
 
-    *where*, a ``str`` containing an IPv4 or IPv6 address,  where
-    to send the message.
+    *where*, a ``str`` containing an IPv4 or IPv6 address,  where to send the message.
 
-    *timeout*, a ``float`` or ``None``, the number of seconds to wait before the
-    query times out.  If ``None``, the default, wait forever.
+    *timeout*, a ``float`` or ``None``, the number of seconds to wait before the query
+    times out.  If ``None``, the default, wait forever.
 
     *port*, an ``int``, the port send the message to.  The default is 53.
 
-    *source*, a ``str`` containing an IPv4 or IPv6 address, specifying
-    the source address.  The default is the wildcard address.
+    *source*, a ``str`` containing an IPv4 or IPv6 address, specifying the source
+    address.  The default is the wildcard address.
 
-    *source_port*, an ``int``, the port from which to send the message.
-    The default is 0.
+    *source_port*, an ``int``, the port from which to send the message. The default is
+    0.
 
-    *ignore_unexpected*, a ``bool``.  If ``True``, ignore responses from
-    unexpected sources.
+    *ignore_unexpected*, a ``bool``.  If ``True``, ignore responses from unexpected
+    sources.
 
-    *one_rr_per_rrset*, a ``bool``.  If ``True``, put each RR into its own
-    RRset.
+    *one_rr_per_rrset*, a ``bool``.  If ``True``, put each RR into its own RRset.
 
-    *ignore_trailing*, a ``bool``.  If ``True``, ignore trailing
-    junk at end of the received message.
+    *ignore_trailing*, a ``bool``.  If ``True``, ignore trailing junk at end of the
+    received message.
 
-    *udp_sock*, a ``socket.socket``, or ``None``, the socket to use for the
-    UDP query.  If ``None``, the default, a socket is created.  Note that
-    if a socket is provided, it must be a nonblocking datagram socket,
-    and the *source* and *source_port* are ignored for the UDP query.
+    *udp_sock*, a ``socket.socket``, or ``None``, the socket to use for the UDP query.
+    If ``None``, the default, a socket is created.  Note that if a socket is provided,
+    it must be a nonblocking datagram socket, and the *source* and *source_port* are
+    ignored for the UDP query.
 
     *tcp_sock*, a ``socket.socket``, or ``None``, the connected socket to use for the
-    TCP query.  If ``None``, the default, a socket is created.  Note that
-    if a socket is provided, it must be a nonblocking connected stream
-    socket, and *where*, *source* and *source_port* are ignored for the TCP
-    query.
+    TCP query.  If ``None``, the default, a socket is created.  Note that if a socket is
+    provided, it must be a nonblocking connected stream socket, and *where*, *source*
+    and *source_port* are ignored for the TCP query.
+
+    *ignore_errors*, a ``bool``.  If various format errors or response mismatches occur
+    while listening for UDP, ignore them and keep listening for a valid response. The
+    default is ``False``.
 
-    Returns a (``dns.message.Message``, tcp) tuple where tcp is ``True``
-    if and only if TCP was used.
+    Returns a (``dns.message.Message``, tcp) tuple where tcp is ``True`` if and only if
+    TCP was used.
     """
     try:
         response = udp(
@@ -783,6 +812,7 @@ def udp_with_fallback(
             ignore_trailing,
             True,
             udp_sock,
+            ignore_errors,
         )
         return (response, False)
     except dns.message.Truncated: