From: Remi Gacogne Date: Fri, 24 Nov 2023 14:30:09 +0000 (+0100) Subject: dnsdist: Detect and dismiss truncated UDP responses from a backend X-Git-Tag: rec-5.0.0-rc1~11^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=17a0b06ee3cfa43bb9ac4a0cd5186fbc42e090a5;p=thirdparty%2Fpdns.git dnsdist: Detect and dismiss truncated UDP responses from a backend 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. --- diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index a5c1d88735..0e92d99823 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -756,7 +756,8 @@ void responderThread(std::shared_ptr 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 sockets; sockets.reserve(dss->sockets.size()); @@ -785,14 +786,15 @@ void responderThread(std::shared_ptr 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(got) < sizeof(dnsheader)) { + if (got < 0 || static_cast(got) < sizeof(dnsheader) || static_cast(got) == (initialBufferSize + 1)) { continue; } diff --git a/regression-tests.dnsdist/test_Advanced.py b/regression-tests.dnsdist/test_Advanced.py index 65354bb444..babf7399b3 100644 --- a/regression-tests.dnsdist/test_Advanced.py +++ b/regression-tests.dnsdist/test_Advanced.py @@ -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)