From 17a0b06ee3cfa43bb9ac4a0cd5186fbc42e090a5 Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Fri, 24 Nov 2023 15:30:09 +0100 Subject: [PATCH] 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. --- pdns/dnsdist.cc | 8 +++-- regression-tests.dnsdist/test_Advanced.py | 43 +++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) 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) -- 2.47.2