Handle malformed DNS replies better.

If we detect that that reply from usptream is malformed,
transform it into a SERVFAIL reply before sending to the
original requestor.
This commit is contained in:
Simon Kelley
2022-11-26 22:19:29 +00:00
parent e3068ed111
commit e939b45c9f
3 changed files with 32 additions and 17 deletions

View File

@@ -59,6 +59,9 @@ version 2.88
needed is O(n^2). Handle this case more intelligently. needed is O(n^2). Handle this case more intelligently.
Thanks to Ye Zhou for spotting the problem and an initial patch. Thanks to Ye Zhou for spotting the problem and an initial patch.
If we detect that a DNS reply from upstream is malformed don't
return it to the requestor; send a SEVFAIL rcode instead.
version 2.87 version 2.87
Allow arbitrary prefix lengths in --rev-server and Allow arbitrary prefix lengths in --rev-server and

View File

@@ -821,12 +821,22 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
n = rrfilter(header, n, RRFILTER_AAAA); n = rrfilter(header, n, RRFILTER_AAAA);
} }
if (extract_addresses(header, n, daemon->namebuff, now, ipsets, nftsets, is_sign, check_rebind, no_cache, cache_secure, &doctored)) switch (extract_addresses(header, n, daemon->namebuff, now, ipsets, nftsets, is_sign, check_rebind, no_cache, cache_secure, &doctored))
{ {
case 1:
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
munged = 1; munged = 1;
cache_secure = 0; cache_secure = 0;
ede = EDE_BLOCKED; ede = EDE_BLOCKED;
break;
/* extract_addresses() found a malformed answer. */
case 2:
munged = 1;
SET_RCODE(header, SERVFAIL);
cache_secure = 0;
ede = EDE_OTHER;
break;
} }
if (doctored) if (doctored)

View File

@@ -538,7 +538,9 @@ static int print_txt(struct dns_header *header, const size_t qlen, char *name,
/* Note that the following code can create CNAME chains that don't point to a real record, /* Note that the following code can create CNAME chains that don't point to a real record,
either because of lack of memory, or lack of SOA records. These are treated by the cache code as either because of lack of memory, or lack of SOA records. These are treated by the cache code as
expired and cleaned out that way. expired and cleaned out that way.
Return 1 if we reject an address because it look like part of dns-rebinding attack. */ Return 1 if we reject an address because it look like part of dns-rebinding attack.
Return 2 if the packet is malformed.
*/
int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now,
struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, int check_rebind, struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, int check_rebind,
int no_cache_dnssec, int secure, int *doctored) int no_cache_dnssec, int secure, int *doctored)
@@ -589,7 +591,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
namep = p = (unsigned char *)(header+1); namep = p = (unsigned char *)(header+1);
if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4)) if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4))
return 0; /* bad packet */ return 2; /* bad packet */
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
@@ -607,13 +609,13 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{ {
cname_loop: cname_loop:
if (!(p1 = skip_questions(header, qlen))) if (!(p1 = skip_questions(header, qlen)))
return 0; return 2;
for (j = 0; j < ntohs(header->ancount); j++) for (j = 0; j < ntohs(header->ancount); j++)
{ {
int secflag = 0; int secflag = 0;
if (!(res = extract_name(header, qlen, &p1, name, 0, 10))) if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
return 0; /* bad packet */ return 2; /* bad packet */
GETSHORT(aqtype, p1); GETSHORT(aqtype, p1);
GETSHORT(aqclass, p1); GETSHORT(aqclass, p1);
@@ -651,7 +653,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL, 0); log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL, 0);
if (!extract_name(header, qlen, &p1, name, 1, 0)) if (!extract_name(header, qlen, &p1, name, 1, 0))
return 0; return 2;
if (aqtype == T_CNAME) if (aqtype == T_CNAME)
{ {
@@ -677,7 +679,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
p1 = endrr; p1 = endrr;
if (!CHECK_LEN(header, p1, qlen, 0)) if (!CHECK_LEN(header, p1, qlen, 0))
return 0; /* bad packet */ return 2; /* bad packet */
} }
} }
@@ -722,14 +724,14 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
cname_loop1: cname_loop1:
if (!(p1 = skip_questions(header, qlen))) if (!(p1 = skip_questions(header, qlen)))
return 0; return 2;
for (j = 0; j < ntohs(header->ancount); j++) for (j = 0; j < ntohs(header->ancount); j++)
{ {
int secflag = 0; int secflag = 0;
if (!(res = extract_name(header, qlen, &p1, name, 0, 10))) if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
return 0; /* bad packet */ return 2; /* bad packet */
GETSHORT(aqtype, p1); GETSHORT(aqtype, p1);
GETSHORT(aqclass, p1); GETSHORT(aqclass, p1);
@@ -747,7 +749,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{ {
p1 = endrr; p1 = endrr;
if (!CHECK_LEN(header, p1, qlen, 0)) if (!CHECK_LEN(header, p1, qlen, 0))
return 0; /* bad packet */ return 2; /* bad packet */
continue; continue;
} }
@@ -790,7 +792,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
namep = p1; namep = p1;
if (!extract_name(header, qlen, &p1, name, 1, 0)) if (!extract_name(header, qlen, &p1, name, 1, 0))
return 0; return 2;
if (qtype != T_CNAME) if (qtype != T_CNAME)
goto cname_loop1; goto cname_loop1;
@@ -813,25 +815,25 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
unsigned char *tmp = namep; unsigned char *tmp = namep;
if (!CHECK_LEN(header, p1, qlen, 6)) if (!CHECK_LEN(header, p1, qlen, 6))
return 0; /* bad packet */ return 2; /* bad packet */
GETSHORT(addr.srv.priority, p1); GETSHORT(addr.srv.priority, p1);
GETSHORT(addr.srv.weight, p1); GETSHORT(addr.srv.weight, p1);
GETSHORT(addr.srv.srvport, p1); GETSHORT(addr.srv.srvport, p1);
if (!extract_name(header, qlen, &p1, name, 1, 0)) if (!extract_name(header, qlen, &p1, name, 1, 0))
return 0; return 2;
addr.srv.targetlen = strlen(name) + 1; /* include terminating zero */ addr.srv.targetlen = strlen(name) + 1; /* include terminating zero */
if (!(addr.srv.target = blockdata_alloc(name, addr.srv.targetlen))) if (!(addr.srv.target = blockdata_alloc(name, addr.srv.targetlen)))
return 0; return 0;
/* we overwrote the original name, so get it back here. */ /* we overwrote the original name, so get it back here. */
if (!extract_name(header, qlen, &tmp, name, 1, 0)) if (!extract_name(header, qlen, &tmp, name, 1, 0))
return 0; return 2;
} }
else if (flags & (F_IPV4 | F_IPV6)) else if (flags & (F_IPV4 | F_IPV6))
{ {
/* copy address into aligned storage */ /* copy address into aligned storage */
if (!CHECK_LEN(header, p1, qlen, addrlen)) if (!CHECK_LEN(header, p1, qlen, addrlen))
return 0; /* bad packet */ return 2; /* bad packet */
memcpy(&addr, p1, addrlen); memcpy(&addr, p1, addrlen);
/* check for returned address in private space */ /* check for returned address in private space */
@@ -875,7 +877,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (aqtype == T_TXT) if (aqtype == T_TXT)
{ {
if (!print_txt(header, qlen, name, p1, ardlen, secflag)) if (!print_txt(header, qlen, name, p1, ardlen, secflag))
return 0; return 2;
} }
else else
log_query(flags | F_FORWARD | secflag | F_UPSTREAM, name, &addr, NULL, aqtype); log_query(flags | F_FORWARD | secflag | F_UPSTREAM, name, &addr, NULL, aqtype);
@@ -883,7 +885,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
p1 = endrr; p1 = endrr;
if (!CHECK_LEN(header, p1, qlen, 0)) if (!CHECK_LEN(header, p1, qlen, 0))
return 0; /* bad packet */ return 2; /* bad packet */
} }
if (!found && (qtype != T_ANY || (flags & F_NXDOMAIN))) if (!found && (qtype != T_ANY || (flags & F_NXDOMAIN)))