Skip to content

Commit 2b11306

Browse files
committed
Validate internal PTR UDP responses
Correlate received UDP datagrams with the active PTR request and keep waiting within the original timeout when unrelated responses are received. Refs #2978 Signed-off-by: Gisle Enåsen <6815997+brealorg@users.noreply.github.com>
1 parent 9597c87 commit 2b11306

1 file changed

Lines changed: 187 additions & 17 deletions

File tree

src/resolve.c

Lines changed: 187 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
#include "regex_r.h"
3434
// statis_assert()
3535
#include <assert.h>
36+
#include <poll.h>
37+
#include <time.h>
3638
// TCP_MAX_QUERIES
3739
#include "dnsmasq/config.h"
3840

@@ -285,13 +287,101 @@ int create_socket(bool tcp, struct sockaddr_in *dest)
285287
// Helper macro to reduce code duplication
286288
#define log_resolve_info(host, port, tcp) { log_info("Tried to resolve PTR \"%s\" on 127.0.0.1#%u (%s)", host, port, tcp ? "TCP" : "UDP"); }
287289

290+
// Return CLOCK_MONOTONIC in milliseconds for resolver receive deadlines
291+
static int64_t resolver_monotonic_msec(void)
292+
{
293+
struct timespec now = { 0 };
294+
if(clock_gettime(CLOCK_MONOTONIC, &now) != 0)
295+
return -1;
296+
297+
return (int64_t)now.tv_sec * 1000LL + now.tv_nsec / 1000000LL;
298+
}
299+
300+
// Validate that a UDP DNS response belongs to the active PTR request and
301+
// return a pointer to the first answer record in the received packet.
302+
static bool validate_udp_ptr_response(uint8_t *buf, const size_t response_len,
303+
const uint16_t request_id, const char *host,
304+
struct DNS_HEADER *dns, uint8_t **answer)
305+
{
306+
if(response_len < sizeof(struct DNS_HEADER))
307+
{
308+
log_debug(DEBUG_RESOLVER,
309+
"Discarding short UDP DNS reply while resolving PTR \"%s\" (%zu bytes)",
310+
host, response_len);
311+
return false;
312+
}
313+
314+
memcpy(dns, buf, sizeof(struct DNS_HEADER));
315+
316+
if(dns->id != request_id || dns->qr != 1 || dns->opcode != 0 ||
317+
ntohs(dns->q_count) != 1)
318+
{
319+
log_debug(DEBUG_RESOLVER,
320+
"Discarding unrelated UDP DNS reply while resolving PTR \"%s\" "
321+
"(id %u, expected %u, qr %u, opcode %u, questions %u)",
322+
host, (unsigned int)ntohs(dns->id),
323+
(unsigned int)ntohs(request_id), (unsigned int)dns->qr,
324+
(unsigned int)dns->opcode,
325+
(unsigned int)ntohs(dns->q_count));
326+
return false;
327+
}
328+
329+
const unsigned char *bufend = buf + response_len;
330+
uint8_t *reader = buf + sizeof(struct DNS_HEADER);
331+
uint16_t consumed = 0;
332+
unsigned char *question_name = nameFromDNS(reader, buf, bufend, &consumed);
333+
if(question_name == NULL)
334+
{
335+
log_debug(DEBUG_RESOLVER,
336+
"Discarding malformed UDP DNS question while resolving PTR \"%s\"",
337+
host);
338+
return false;
339+
}
340+
341+
if(consumed > (size_t)(bufend - reader) ||
342+
sizeof(struct QUESTION) > (size_t)(bufend - reader - consumed))
343+
{
344+
free(question_name);
345+
log_debug(DEBUG_RESOLVER,
346+
"Discarding truncated UDP DNS question while resolving PTR \"%s\"",
347+
host);
348+
return false;
349+
}
350+
351+
reader += consumed;
352+
struct QUESTION question = { 0 };
353+
memcpy(&question, reader, sizeof(question));
354+
355+
const bool matches =
356+
strcasecmp((const char *)question_name, host) == 0 &&
357+
ntohs(question.qtype) == T_PTR &&
358+
ntohs(question.qclass) == 1;
359+
360+
if(!matches)
361+
{
362+
log_debug(DEBUG_RESOLVER,
363+
"Discarding UDP DNS reply with mismatched question while resolving "
364+
"PTR \"%s\" (received \"%s\", type %u, class %u)",
365+
host, (const char *)question_name,
366+
(unsigned int)ntohs(question.qtype),
367+
(unsigned int)ntohs(question.qclass));
368+
free(question_name);
369+
return false;
370+
}
371+
372+
free(question_name);
373+
*answer = reader + sizeof(struct QUESTION);
374+
return true;
375+
}
376+
288377
// Perform a name lookup by sending a packet to ourselves
289378
static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *dest,
290379
char hostn[MAXDOMAINLEN], const char *host, const char *ipaddr, bool *truncated)
291380
{
292381
// Initialize request DNS header
293382
struct DNS_HEADER dns = { 0 };
294383
dns.id = (unsigned short) htons(random()); // random query ID
384+
const uint16_t request_id = dns.id;
295385
dns.qr = 0; // This is a query
296386
dns.opcode = 0; // This is a standard query
297387
dns.aa = 0; // Not Authoritative
@@ -314,7 +404,7 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
314404
if(hname == NULL)
315405
{
316406
log_err("Unable to allocate memory for hname");
317-
return NULL;
407+
return false;
318408
}
319409
strncpy(hname, host, hnamelen);
320410
strncat(hname, ".", hnamelen - strlen(hname));
@@ -342,6 +432,9 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
342432
log_debug(DEBUG_RESOLVER, "Resolving PTR \"%s\" on 127.0.0.1#%u (%s)",
343433
host, config.dns.port.v.u16, tcp ? "TCP" : "UDP");
344434

435+
ssize_t response_len = sizeof(buf);
436+
uint8_t *reader = NULL;
437+
345438
// Send the query and receive the answer
346439
if(!tcp)
347440
{
@@ -352,15 +445,92 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
352445
{
353446
log_err("Cannot send UDP DNS query: %s", strsockerr(errno));
354447
log_resolve_info(host, config.dns.port.v.u16, tcp);
355-
return NULL;
448+
return false;
356449
}
357450

358-
// Receive the answer
359-
if(recvfrom (sock, buf, sizeof(buf), 0, (struct sockaddr*)dest, &addrlen) < 0)
451+
// Use one absolute deadline for this request. Mismatched packets are
452+
// discarded without extending the original two-second timeout.
453+
const int64_t start = resolver_monotonic_msec();
454+
if(start < 0)
360455
{
361-
log_err("Cannot receive UDP DNS reply: %s", strsockerr(errno));
456+
log_err("Cannot read resolver monotonic clock: %s", strerror(errno));
362457
log_resolve_info(host, config.dns.port.v.u16, tcp);
363-
return NULL;
458+
return false;
459+
}
460+
const int64_t deadline = start + 2000;
461+
462+
while(true)
463+
{
464+
const int64_t now = resolver_monotonic_msec();
465+
if(now < 0)
466+
{
467+
log_err("Cannot read resolver monotonic clock: %s", strerror(errno));
468+
log_resolve_info(host, config.dns.port.v.u16, tcp);
469+
return false;
470+
}
471+
472+
const int64_t remaining = deadline - now;
473+
if(remaining <= 0)
474+
{
475+
log_err("Cannot receive UDP DNS reply: %s", strsockerr(EAGAIN));
476+
log_resolve_info(host, config.dns.port.v.u16, tcp);
477+
return false;
478+
}
479+
480+
struct pollfd pfd = {
481+
.fd = sock,
482+
.events = POLLIN,
483+
.revents = 0,
484+
};
485+
486+
const int ready = poll(&pfd, 1, (int)remaining);
487+
if(ready < 0)
488+
{
489+
if(errno == EINTR)
490+
continue;
491+
492+
log_err("Cannot wait for UDP DNS reply: %s", strsockerr(errno));
493+
log_resolve_info(host, config.dns.port.v.u16, tcp);
494+
return false;
495+
}
496+
if(ready == 0)
497+
{
498+
log_err("Cannot receive UDP DNS reply: %s", strsockerr(EAGAIN));
499+
log_resolve_info(host, config.dns.port.v.u16, tcp);
500+
return false;
501+
}
502+
503+
struct sockaddr_in source = { 0 };
504+
socklen_t source_len = sizeof(source);
505+
response_len = recvfrom(sock, buf, sizeof(buf), MSG_DONTWAIT,
506+
(struct sockaddr *)&source, &source_len);
507+
if(response_len < 0)
508+
{
509+
if(errno == EAGAIN || errno == EINTR)
510+
continue;
511+
512+
log_err("Cannot receive UDP DNS reply: %s", strsockerr(errno));
513+
log_resolve_info(host, config.dns.port.v.u16, tcp);
514+
return false;
515+
}
516+
517+
if(source_len < sizeof(source) ||
518+
source.sin_family != dest->sin_family ||
519+
source.sin_addr.s_addr != dest->sin_addr.s_addr ||
520+
source.sin_port != dest->sin_port)
521+
{
522+
log_debug(DEBUG_RESOLVER,
523+
"Discarding UDP DNS reply from unexpected source while "
524+
"resolving PTR \"%s\"",
525+
host);
526+
continue;
527+
}
528+
529+
if(!validate_udp_ptr_response(buf, (size_t)response_len,
530+
request_id, host, &dns, &reader))
531+
continue;
532+
533+
break;
364534
}
365535
}
366536
else
@@ -378,7 +548,7 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
378548
{
379549
log_err("Cannot send TCP DNS query: %s", strsockerr(errno));
380550
log_resolve_info(host, config.dns.port.v.u16, tcp);
381-
return NULL;
551+
return false;
382552
}
383553

384554
// Receive the answer, first the length of the message ...
@@ -387,7 +557,7 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
387557
{
388558
log_err("Cannot receive TCP DNS reply (1): %s", strsockerr(errno));
389559
log_resolve_info(host, config.dns.port.v.u16, tcp);
390-
return NULL;
560+
return false;
391561
}
392562
prefix = ntohs(prefix);
393563

@@ -398,22 +568,22 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
398568
{
399569
log_err("Received TCP DNS reply is too long (%u bytes)", prefix);
400570
log_resolve_info(host, config.dns.port.v.u16, tcp);
401-
return NULL;
571+
return false;
402572
}
403573
bzero(buf, prefix + 1);
404574
// ... then the message itself
405575
if(recv(sock, buf, sizeof(buf), 0) < 0)
406576
{
407577
log_err("Cannot receive TCP DNS reply (2): %s", strsockerr(errno));
408578
log_resolve_info(host, config.dns.port.v.u16, tcp);
409-
return NULL;
579+
return false;
410580
}
411-
}
412581

413-
// Parse the reply
414-
memcpy(&dns, buf, sizeof(struct DNS_HEADER));
415-
// Move ahead of the dns header and the query field
416-
uint8_t *reader = &buf[len];
582+
// Preserve the existing TCP parser behavior. The PoC hardens the UDP
583+
// path where sequential lookups share one datagram socket.
584+
memcpy(&dns, buf, sizeof(struct DNS_HEADER));
585+
reader = &buf[len];
586+
}
417587

418588
// Log the status of the query
419589
log_debug(DEBUG_RESOLVER, "DNS query for PTR \"%s\" returned status %s (%i)",
@@ -425,14 +595,14 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
425595
log_debug(DEBUG_RESOLVER, " --> DNS response truncated");
426596
if(truncated != NULL)
427597
*truncated = true;
428-
return NULL;
598+
return false;
429599
}
430600

431601
// Start reading answers
432602
uint16_t stop = 0;
433603
bool have_name = false;
434604
struct RES_RECORD answers[20] = { 0 };
435-
const unsigned char *bufend = buf + sizeof(buf);
605+
const unsigned char *bufend = tcp ? buf + sizeof(buf) : buf + response_len;
436606
for(uint16_t i = 0; i < min(ntohs(dns.ans_count), ArraySize(answers)); i++)
437607
{
438608
// Ensure the read pointer still points within the receive

0 commit comments

Comments
 (0)