]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Detect and dismiss truncated UDP responses from a backend
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 24 Nov 2023 14:30:09 +0000 (15:30 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 24 Nov 2023 14:41:20 +0000 (15:41 +0100)
Until now we would not have detected if the response was larger than
our buffer (4096 bytes or larger in some cases), which could have
led to parsing errors or even forwarding a corrupted response.

pdns/dnsdist.cc
regression-tests.dnsdist/test_Advanced.py

index a5c1d88735ecd0440c4970b6923c989371232f66..0e92d9982329e3daaeca1d3bfe6262eb2263acf4 100644 (file)
@@ -756,7 +756,8 @@ void responderThread(std::shared_ptr<DownstreamState> dss)
   auto localRespRuleActions = g_respruleactions.getLocal();
   auto localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.getLocal();
   const size_t initialBufferSize = getInitialUDPPacketBufferSize();
-  PacketBuffer response(initialBufferSize);
+  /* allocate one more byte so we can detect truncation */
+  PacketBuffer response(initialBufferSize + 1);
   uint16_t queryId = 0;
   std::vector<int> sockets;
   sockets.reserve(dss->sockets.size());
@@ -785,14 +786,15 @@ void responderThread(std::shared_ptr<DownstreamState> dss)
       }
 
       for (const auto& fd : sockets) {
-        response.resize(initialBufferSize);
+        /* allocate one more byte so we can detect truncation */
+        response.resize(initialBufferSize + 1);
         ssize_t got = recv(fd, response.data(), response.size(), 0);
 
         if (got == 0 && dss->isStopped()) {
           break;
         }
 
-        if (got < 0 || static_cast<size_t>(got) < sizeof(dnsheader)) {
+        if (got < 0 || static_cast<size_t>(got) < sizeof(dnsheader) || static_cast<size_t>(got) == (initialBufferSize + 1)) {
           continue;
         }
 
index 65354bb444b365c61684fc4f179520832595c793..babf7399b3516fd88b112a01c7d8e033246a8a33 100644 (file)
@@ -867,3 +867,46 @@ class TestFlagsOnTimeout(DNSDistTest):
 
         self.assertEqual(len(timeouts), 2)
         self.assertEqual(len(queries), 2)
+
+class TestTruncatedUDPLargeAnswers(DNSDistTest):
+    _config_template = """
+    newServer{address="127.0.0.1:%d"}
+    """
+    def testVeryLargeAnswer(self):
+        """
+        Advanced: Check that UDP responses that are too large for our buffer are dismissed
+        """
+        name = 'very-large-answer-dismissed.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'TXT', 'IN')
+        response = dns.message.make_response(query)
+        # we prepare a large answer
+        content = ''
+        for i in range(31):
+            if len(content) > 0:
+                content = content + ' '
+            content = content + 'A' * 255
+        # pad up to 8192
+        content = content + ' ' + 'B' * 170
+
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.TXT,
+                                    content)
+        response.answer.append(rrset)
+        self.assertEqual(len(response.to_wire()), 8192)
+
+        # TCP should be OK
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, response)
+
+        # UDP should  never get an answer, because dnsdist will not be able to get it from the backend
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertFalse(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)