|
|
429959 |
From b09d07ee2aa891c5b8dd2469c4a73c9dd61e2384 Mon Sep 17 00:00:00 2001
|
|
|
429959 |
From: Simon Kelley <simon@thekelleys.org.uk>
|
|
|
429959 |
Date: Wed, 25 Oct 2017 17:48:19 +0100
|
|
|
429959 |
Subject: [PATCH 1/2] Fix caching logic for validated answers.
|
|
|
429959 |
|
|
|
429959 |
The current logic is naive in the case that there is more than
|
|
|
429959 |
one RRset in an answer (Typically, when a non-CNAME query is answered
|
|
|
429959 |
by one or more CNAME RRs, and then then an answer RRset.)
|
|
|
429959 |
|
|
|
429959 |
If all the RRsets validate, then they are cached and marked as validated,
|
|
|
429959 |
but if any RRset doesn't validate, then the AD flag is not set (good) and
|
|
|
429959 |
ALL the RRsets are cached marked as not validated.
|
|
|
429959 |
|
|
|
429959 |
This breaks when, eg, the answer contains a validated CNAME, pointing
|
|
|
429959 |
to a non-validated answer. A subsequent query for the CNAME without do
|
|
|
429959 |
will get an answer with the AD flag wrongly reset, and worse, the same
|
|
|
429959 |
query with do will get a cached answer without RRSIGS, rather than
|
|
|
429959 |
being forwarded.
|
|
|
429959 |
|
|
|
429959 |
The code now records the validation of individual RRsets and that
|
|
|
429959 |
is used to correctly set the "validated" bits in the cache entries.
|
|
|
429959 |
|
|
|
429959 |
(cherry picked from commit a6004d7f17687ac2455f724d0b57098c413f128d)
|
|
|
429959 |
---
|
|
|
429959 |
src/dnsmasq.c | 2 +
|
|
|
429959 |
src/dnsmasq.h | 5 +-
|
|
|
429959 |
src/dnssec.c | 174 +++++++++++++++++++++++++++++---------------------
|
|
|
429959 |
src/forward.c | 19 ++++--
|
|
|
429959 |
src/rfc1035.c | 58 ++++++++++++-----
|
|
|
429959 |
5 files changed, 162 insertions(+), 96 deletions(-)
|
|
|
429959 |
|
|
|
429959 |
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
|
|
|
429959 |
index 50b2029..f3d2671 100644
|
|
|
429959 |
--- a/src/dnsmasq.c
|
|
|
429959 |
+++ b/src/dnsmasq.c
|
|
|
429959 |
@@ -118,6 +118,8 @@ int main (int argc, char **argv)
|
|
|
429959 |
daemon->namebuff = safe_malloc(MAXDNAME * 2);
|
|
|
429959 |
daemon->keyname = safe_malloc(MAXDNAME * 2);
|
|
|
429959 |
daemon->workspacename = safe_malloc(MAXDNAME * 2);
|
|
|
429959 |
+ /* one char flag per possible RR in answer section. */
|
|
|
429959 |
+ daemon->rr_status = safe_malloc(256);
|
|
|
429959 |
}
|
|
|
429959 |
#endif
|
|
|
429959 |
|
|
|
429959 |
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
|
|
|
429959 |
index 5a68162..89d138a 100644
|
|
|
429959 |
--- a/src/dnsmasq.h
|
|
|
429959 |
+++ b/src/dnsmasq.h
|
|
|
429959 |
@@ -1006,6 +1006,7 @@ extern struct daemon {
|
|
|
429959 |
#ifdef HAVE_DNSSEC
|
|
|
429959 |
char *keyname; /* MAXDNAME size buffer */
|
|
|
429959 |
char *workspacename; /* ditto */
|
|
|
429959 |
+ char *rr_status; /* 256 bytes as flags for individual RRs */
|
|
|
429959 |
#endif
|
|
|
429959 |
unsigned int local_answer, queries_forwarded, auth_answer;
|
|
|
429959 |
struct frec *frec_list;
|
|
|
429959 |
@@ -1118,7 +1119,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen,
|
|
|
429959 |
unsigned long local_ttl);
|
|
|
429959 |
int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff,
|
|
|
429959 |
time_t now, char **ipsets, int is_sign, int checkrebind,
|
|
|
429959 |
- int no_cache, int secure, int *doctored);
|
|
|
429959 |
+ int no_cache_dnssec, int secure, int *doctored, char *rr_status);
|
|
|
429959 |
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|
|
429959 |
struct in_addr local_addr, struct in_addr local_netmask,
|
|
|
429959 |
time_t now, int ad_reqd, int do_bit, int have_pseudoheader);
|
|
|
429959 |
@@ -1151,7 +1152,7 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char
|
|
|
429959 |
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
|
|
|
429959 |
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
|
|
|
429959 |
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
|
|
|
429959 |
- int check_unsigned, int *neganswer, int *nons);
|
|
|
429959 |
+ int check_unsigned, int *neganswer, int *nons, char *rr_status);
|
|
|
429959 |
int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen);
|
|
|
429959 |
size_t filter_rrsigs(struct dns_header *header, size_t plen);
|
|
|
429959 |
unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name);
|
|
|
429959 |
diff --git a/src/dnssec.c b/src/dnssec.c
|
|
|
429959 |
index f45c804..3121eb1 100644
|
|
|
429959 |
--- a/src/dnssec.c
|
|
|
429959 |
+++ b/src/dnssec.c
|
|
|
429959 |
@@ -1177,8 +1177,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
|
|
|
429959 |
if (qtype != T_DS || qclass != class)
|
|
|
429959 |
rc = STAT_BOGUS;
|
|
|
429959 |
else
|
|
|
429959 |
- rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons);
|
|
|
429959 |
- /* Note dnssec_validate_reply() will have cached positive answers */
|
|
|
429959 |
+ rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, NULL);
|
|
|
429959 |
|
|
|
429959 |
if (rc == STAT_INSECURE)
|
|
|
429959 |
rc = STAT_BOGUS;
|
|
|
429959 |
@@ -1962,18 +1961,25 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
|
|
|
429959 |
STAT_INSECURE at least one RRset not validated, because in unsigned zone.
|
|
|
429959 |
STAT_BOGUS signature is wrong, bad packet, no validation where there should be.
|
|
|
429959 |
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
|
|
|
429959 |
- STAT_NEED_DS need DS to complete validation (name is returned in keyname)
|
|
|
429959 |
+ STAT_NEED_DS need DS to complete validation (name is returned in keyname)
|
|
|
429959 |
+
|
|
|
429959 |
+ If non-NULL, rr_status points to a char array which corressponds to the RRs in the
|
|
|
429959 |
+ answer section (only). This is set to 1 for each RR which is validated, and 0 for any which aren't.
|
|
|
429959 |
*/
|
|
|
429959 |
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname,
|
|
|
429959 |
- int *class, int check_unsigned, int *neganswer, int *nons)
|
|
|
429959 |
+ int *class, int check_unsigned, int *neganswer, int *nons, char *rr_status)
|
|
|
429959 |
{
|
|
|
429959 |
static unsigned char **targets = NULL;
|
|
|
429959 |
static int target_sz = 0;
|
|
|
429959 |
|
|
|
429959 |
unsigned char *ans_start, *p1, *p2;
|
|
|
429959 |
- int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype, targetidx;
|
|
|
429959 |
+ int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
|
|
|
429959 |
int i, j, rc;
|
|
|
429959 |
+ int secure = STAT_SECURE;
|
|
|
429959 |
|
|
|
429959 |
+ if (rr_status)
|
|
|
429959 |
+ memset(rr_status, 0, ntohs(header->ancount));
|
|
|
429959 |
+
|
|
|
429959 |
if (neganswer)
|
|
|
429959 |
*neganswer = 0;
|
|
|
429959 |
|
|
|
429959 |
@@ -2030,7 +2036,10 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|
|
429959 |
|
|
|
429959 |
for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
|
|
|
429959 |
{
|
|
|
429959 |
- if (!extract_name(header, plen, &p1, name, 1, 10))
|
|
|
429959 |
+ if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1))
|
|
|
429959 |
+ return STAT_BOGUS;
|
|
|
429959 |
+
|
|
|
429959 |
+ if (!extract_name(header, plen, &p1, name, 1, 10))
|
|
|
429959 |
return STAT_BOGUS; /* bad packet */
|
|
|
429959 |
|
|
|
429959 |
GETSHORT(type1, p1);
|
|
|
429959 |
@@ -2039,106 +2048,125 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|
|
429959 |
GETSHORT(rdlen1, p1);
|
|
|
429959 |
|
|
|
429959 |
/* Don't try and validate RRSIGs! */
|
|
|
429959 |
- if (type1 != T_RRSIG)
|
|
|
429959 |
+ if (type1 == T_RRSIG)
|
|
|
429959 |
+ continue;
|
|
|
429959 |
+
|
|
|
429959 |
+ /* Check if we've done this RRset already */
|
|
|
429959 |
+ for (p2 = ans_start, j = 0; j < i; j++)
|
|
|
429959 |
{
|
|
|
429959 |
- /* Check if we've done this RRset already */
|
|
|
429959 |
- for (p2 = ans_start, j = 0; j < i; j++)
|
|
|
429959 |
- {
|
|
|
429959 |
- if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
|
|
|
429959 |
- return STAT_BOGUS; /* bad packet */
|
|
|
429959 |
-
|
|
|
429959 |
- GETSHORT(type2, p2);
|
|
|
429959 |
- GETSHORT(class2, p2);
|
|
|
429959 |
- p2 += 4; /* TTL */
|
|
|
429959 |
- GETSHORT(rdlen2, p2);
|
|
|
429959 |
-
|
|
|
429959 |
- if (type2 == type1 && class2 == class1 && rc == 1)
|
|
|
429959 |
- break; /* Done it before: name, type, class all match. */
|
|
|
429959 |
-
|
|
|
429959 |
- if (!ADD_RDLEN(header, p2, plen, rdlen2))
|
|
|
429959 |
- return STAT_BOGUS;
|
|
|
429959 |
- }
|
|
|
429959 |
+ if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
|
|
|
429959 |
+ return STAT_BOGUS; /* bad packet */
|
|
|
429959 |
+
|
|
|
429959 |
+ GETSHORT(type2, p2);
|
|
|
429959 |
+ GETSHORT(class2, p2);
|
|
|
429959 |
+ p2 += 4; /* TTL */
|
|
|
429959 |
+ GETSHORT(rdlen2, p2);
|
|
|
429959 |
+
|
|
|
429959 |
+ if (type2 == type1 && class2 == class1 && rc == 1)
|
|
|
429959 |
+ break; /* Done it before: name, type, class all match. */
|
|
|
429959 |
|
|
|
429959 |
+ if (!ADD_RDLEN(header, p2, plen, rdlen2))
|
|
|
429959 |
+ return STAT_BOGUS;
|
|
|
429959 |
+ }
|
|
|
429959 |
+
|
|
|
429959 |
+ if (j != i)
|
|
|
429959 |
+ {
|
|
|
429959 |
+ /* Done already: copy the validation status */
|
|
|
429959 |
+ if (rr_status && (i < ntohs(header->ancount)))
|
|
|
429959 |
+ rr_status[i] = rr_status[j];
|
|
|
429959 |
+ }
|
|
|
429959 |
+ else
|
|
|
429959 |
+ {
|
|
|
429959 |
/* Not done, validate now */
|
|
|
429959 |
- if (j == i)
|
|
|
429959 |
+ int sigcnt, rrcnt;
|
|
|
429959 |
+ char *wildname;
|
|
|
429959 |
+
|
|
|
429959 |
+ if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
|
|
|
429959 |
+ return STAT_BOGUS;
|
|
|
429959 |
+
|
|
|
429959 |
+ /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */
|
|
|
429959 |
+ if (sigcnt == 0)
|
|
|
429959 |
{
|
|
|
429959 |
- int sigcnt, rrcnt;
|
|
|
429959 |
- char *wildname;
|
|
|
429959 |
-
|
|
|
429959 |
- if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
|
|
|
429959 |
- return STAT_BOGUS;
|
|
|
429959 |
-
|
|
|
429959 |
- /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */
|
|
|
429959 |
- if (sigcnt == 0)
|
|
|
429959 |
+ if (check_unsigned)
|
|
|
429959 |
{
|
|
|
429959 |
- if (check_unsigned)
|
|
|
429959 |
- {
|
|
|
429959 |
- rc = zone_status(name, class1, keyname, now);
|
|
|
429959 |
- if (rc == STAT_SECURE)
|
|
|
429959 |
- rc = STAT_BOGUS;
|
|
|
429959 |
- if (class)
|
|
|
429959 |
- *class = class1; /* Class for NEED_DS or NEED_KEY */
|
|
|
429959 |
- }
|
|
|
429959 |
- else
|
|
|
429959 |
- rc = STAT_INSECURE;
|
|
|
429959 |
-
|
|
|
429959 |
- return rc;
|
|
|
429959 |
+ rc = zone_status(name, class1, keyname, now);
|
|
|
429959 |
+ if (rc == STAT_SECURE)
|
|
|
429959 |
+ rc = STAT_BOGUS;
|
|
|
429959 |
+ if (class)
|
|
|
429959 |
+ *class = class1; /* Class for NEED_DS or NEED_KEY */
|
|
|
429959 |
}
|
|
|
429959 |
+ else
|
|
|
429959 |
+ rc = STAT_INSECURE;
|
|
|
429959 |
|
|
|
429959 |
+ if (rc != STAT_INSECURE)
|
|
|
429959 |
+ return rc;
|
|
|
429959 |
+ }
|
|
|
429959 |
+ else
|
|
|
429959 |
+ {
|
|
|
429959 |
/* explore_rrset() gives us key name from sigs in keyname.
|
|
|
429959 |
Can't overwrite name here. */
|
|
|
429959 |
strcpy(daemon->workspacename, keyname);
|
|
|
429959 |
rc = zone_status(daemon->workspacename, class1, keyname, now);
|
|
|
429959 |
-
|
|
|
429959 |
- if (rc != STAT_SECURE)
|
|
|
429959 |
+
|
|
|
429959 |
+ if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
|
|
|
429959 |
{
|
|
|
429959 |
/* Zone is insecure, don't need to validate RRset */
|
|
|
429959 |
if (class)
|
|
|
429959 |
*class = class1; /* Class for NEED_DS or NEED_KEY */
|
|
|
429959 |
return rc;
|
|
|
429959 |
- }
|
|
|
429959 |
-
|
|
|
429959 |
- rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0);
|
|
|
429959 |
+ }
|
|
|
429959 |
|
|
|
429959 |
- if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
|
|
|
429959 |
- {
|
|
|
429959 |
- if (class)
|
|
|
429959 |
- *class = class1; /* Class for DS or DNSKEY */
|
|
|
429959 |
- return rc;
|
|
|
429959 |
- }
|
|
|
429959 |
- else
|
|
|
429959 |
+ /* Zone is insecure, don't need to validate RRset */
|
|
|
429959 |
+ if (rc == STAT_SECURE)
|
|
|
429959 |
{
|
|
|
429959 |
+ rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
|
|
|
429959 |
+ rrcnt, name, keyname, &wildname, NULL, 0, 0, 0);
|
|
|
429959 |
+
|
|
|
429959 |
+ if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
|
|
|
429959 |
+ {
|
|
|
429959 |
+ if (class)
|
|
|
429959 |
+ *class = class1; /* Class for DS or DNSKEY */
|
|
|
429959 |
+ return rc;
|
|
|
429959 |
+ }
|
|
|
429959 |
+
|
|
|
429959 |
/* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
|
|
|
429959 |
-
|
|
|
429959 |
+
|
|
|
429959 |
+ /* Note that RR is validated */
|
|
|
429959 |
+ if (rr_status && (i < ntohs(header->ancount)))
|
|
|
429959 |
+ rr_status[i] = 1;
|
|
|
429959 |
+
|
|
|
429959 |
/* Note if we've validated either the answer to the question
|
|
|
429959 |
or the target of a CNAME. Any not noted will need NSEC or
|
|
|
429959 |
to be in unsigned space. */
|
|
|
429959 |
-
|
|
|
429959 |
for (j = 0; j
|
|
|
429959 |
if ((p2 = targets[j]))
|
|
|
429959 |
{
|
|
|
429959 |
- if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
|
|
|
429959 |
+ int rc1;
|
|
|
429959 |
+ if (!(rc1 = extract_name(header, plen, &p2, name, 0, 10)))
|
|
|
429959 |
return STAT_BOGUS; /* bad packet */
|
|
|
429959 |
|
|
|
429959 |
- if (class1 == qclass && rc == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
|
|
|
429959 |
+ if (class1 == qclass && rc1 == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
|
|
|
429959 |
targets[j] = NULL;
|
|
|
429959 |
}
|
|
|
429959 |
-
|
|
|
429959 |
- /* An attacker replay a wildcard answer with a different
|
|
|
429959 |
- answer and overlay a genuine RR. To prove this
|
|
|
429959 |
- hasn't happened, the answer must prove that
|
|
|
429959 |
- the gennuine record doesn't exist. Check that here.
|
|
|
429959 |
- Note that we may not yet have validated the NSEC/NSEC3 RRsets.
|
|
|
429959 |
- That's not a problem since if the RRsets later fail
|
|
|
429959 |
- we'll return BOGUS then. */
|
|
|
429959 |
- if (rc == STAT_SECURE_WILDCARD && !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL))
|
|
|
429959 |
+
|
|
|
429959 |
+ /* An attacker replay a wildcard answer with a different
|
|
|
429959 |
+ answer and overlay a genuine RR. To prove this
|
|
|
429959 |
+ hasn't happened, the answer must prove that
|
|
|
429959 |
+ the genuine record doesn't exist. Check that here.
|
|
|
429959 |
+ Note that we may not yet have validated the NSEC/NSEC3 RRsets.
|
|
|
429959 |
+ That's not a problem since if the RRsets later fail
|
|
|
429959 |
+ we'll return BOGUS then. */
|
|
|
429959 |
+ if (rc == STAT_SECURE_WILDCARD &&
|
|
|
429959 |
+ !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL))
|
|
|
429959 |
return STAT_BOGUS;
|
|
|
429959 |
+
|
|
|
429959 |
+ rc = STAT_SECURE;
|
|
|
429959 |
}
|
|
|
429959 |
}
|
|
|
429959 |
}
|
|
|
429959 |
|
|
|
429959 |
- if (!ADD_RDLEN(header, p1, plen, rdlen1))
|
|
|
429959 |
- return STAT_BOGUS;
|
|
|
429959 |
+ if (rc == STAT_INSECURE)
|
|
|
429959 |
+ secure = STAT_INSECURE;
|
|
|
429959 |
}
|
|
|
429959 |
|
|
|
429959 |
/* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */
|
|
|
429959 |
@@ -2172,7 +2200,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|
|
429959 |
}
|
|
|
429959 |
}
|
|
|
429959 |
|
|
|
429959 |
- return STAT_SECURE;
|
|
|
429959 |
+ return secure;
|
|
|
429959 |
}
|
|
|
429959 |
|
|
|
429959 |
|
|
|
429959 |
diff --git a/src/forward.c b/src/forward.c
|
|
|
429959 |
index a729c06..245c448 100644
|
|
|
429959 |
--- a/src/forward.c
|
|
|
429959 |
+++ b/src/forward.c
|
|
|
429959 |
@@ -561,7 +561,8 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
|
|
429959 |
char **sets = 0;
|
|
|
429959 |
int munged = 0, is_sign;
|
|
|
429959 |
size_t plen;
|
|
|
429959 |
-
|
|
|
429959 |
+ char *rr_status = NULL;
|
|
|
429959 |
+
|
|
|
429959 |
(void)ad_reqd;
|
|
|
429959 |
(void)do_bit;
|
|
|
429959 |
(void)bogusanswer;
|
|
|
429959 |
@@ -651,6 +652,11 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
|
|
429959 |
server->flags |= SERV_WARNED_RECURSIVE;
|
|
|
429959 |
}
|
|
|
429959 |
|
|
|
429959 |
+#ifdef HAVE_DNSSEC
|
|
|
429959 |
+ if (option_bool(OPT_DNSSEC_VALID))
|
|
|
429959 |
+ rr_status = daemon->rr_status;
|
|
|
429959 |
+#endif
|
|
|
429959 |
+
|
|
|
429959 |
if (daemon->bogus_addr && RCODE(header) != NXDOMAIN &&
|
|
|
429959 |
check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now))
|
|
|
429959 |
{
|
|
|
429959 |
@@ -676,7 +682,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
|
|
429959 |
cache_secure = 0;
|
|
|
429959 |
}
|
|
|
429959 |
|
|
|
429959 |
- if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored))
|
|
|
429959 |
+ if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored, rr_status))
|
|
|
429959 |
{
|
|
|
429959 |
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
|
|
|
429959 |
munged = 1;
|
|
|
429959 |
@@ -856,7 +862,7 @@ void reply_query(int fd, int family, time_t now)
|
|
|
429959 |
if (forward->forwardall == 0 || --forward->forwardall == 1 || RCODE(header) != REFUSED)
|
|
|
429959 |
{
|
|
|
429959 |
int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
|
|
|
429959 |
-
|
|
|
429959 |
+
|
|
|
429959 |
if (option_bool(OPT_NO_REBIND))
|
|
|
429959 |
check_rebind = !(forward->flags & FREC_NOREBIND);
|
|
|
429959 |
|
|
|
429959 |
@@ -896,7 +902,8 @@ void reply_query(int fd, int family, time_t now)
|
|
|
429959 |
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
|
|
|
429959 |
else
|
|
|
429959 |
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class,
|
|
|
429959 |
- option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL);
|
|
|
429959 |
+ option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC),
|
|
|
429959 |
+ NULL, NULL, daemon->rr_status);
|
|
|
429959 |
}
|
|
|
429959 |
|
|
|
429959 |
/* Can't validate, as we're missing key data. Put this
|
|
|
429959 |
@@ -1480,7 +1487,9 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
|
|
429959 |
else if (status == STAT_NEED_DS)
|
|
|
429959 |
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
|
|
|
429959 |
else
|
|
|
429959 |
- new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL);
|
|
|
429959 |
+ new_status = dnssec_validate_reply(now, header, n, name, keyname, &class,
|
|
|
429959 |
+ option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC),
|
|
|
429959 |
+ NULL, NULL, daemon->rr_status);
|
|
|
429959 |
|
|
|
429959 |
if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY)
|
|
|
429959 |
break;
|
|
|
429959 |
diff --git a/src/rfc1035.c b/src/rfc1035.c
|
|
|
429959 |
index f78b5cb..607412f 100644
|
|
|
429959 |
--- a/src/rfc1035.c
|
|
|
429959 |
+++ b/src/rfc1035.c
|
|
|
429959 |
@@ -571,7 +571,8 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *doc
|
|
|
429959 |
expired and cleaned out that way.
|
|
|
429959 |
Return 1 if we reject an address because it look like part of dns-rebinding attack. */
|
|
|
429959 |
int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now,
|
|
|
429959 |
- char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec, int secure, int *doctored)
|
|
|
429959 |
+ char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec,
|
|
|
429959 |
+ int secure, int *doctored, char *rr_status)
|
|
|
429959 |
{
|
|
|
429959 |
unsigned char *p, *p1, *endrr, *namep;
|
|
|
429959 |
int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
|
|
|
429959 |
@@ -582,6 +583,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|
|
429959 |
#else
|
|
|
429959 |
(void)ipsets; /* unused */
|
|
|
429959 |
#endif
|
|
|
429959 |
+
|
|
|
429959 |
|
|
|
429959 |
cache_start_insert();
|
|
|
429959 |
|
|
|
429959 |
@@ -590,10 +592,16 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|
|
429959 |
{
|
|
|
429959 |
searched_soa = 1;
|
|
|
429959 |
ttl = find_soa(header, qlen, name, doctored);
|
|
|
429959 |
-#ifdef HAVE_DNSSEC
|
|
|
429959 |
- if (*doctored && secure)
|
|
|
429959 |
- return 0;
|
|
|
429959 |
-#endif
|
|
|
429959 |
+
|
|
|
429959 |
+ if (*doctored)
|
|
|
429959 |
+ {
|
|
|
429959 |
+ if (secure)
|
|
|
429959 |
+ return 0;
|
|
|
429959 |
+ if (rr_status)
|
|
|
429959 |
+ for (i = 0; i < ntohs(header->ancount); i++)
|
|
|
429959 |
+ if (rr_status[i])
|
|
|
429959 |
+ return 0;
|
|
|
429959 |
+ }
|
|
|
429959 |
}
|
|
|
429959 |
|
|
|
429959 |
/* go through the questions. */
|
|
|
429959 |
@@ -604,7 +612,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|
|
429959 |
int found = 0, cname_count = CNAME_CHAIN;
|
|
|
429959 |
struct crec *cpp = NULL;
|
|
|
429959 |
int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
|
|
|
429959 |
- int secflag = secure ? F_DNSSECOK : 0;
|
|
|
429959 |
+ int cname_short = 0;
|
|
|
429959 |
unsigned long cttl = ULONG_MAX, attl;
|
|
|
429959 |
|
|
|
429959 |
namep = p;
|
|
|
429959 |
@@ -632,8 +640,9 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|
|
429959 |
if (!(p1 = skip_questions(header, qlen)))
|
|
|
429959 |
return 0;
|
|
|
429959 |
|
|
|
429959 |
- for (j = ntohs(header->ancount); j != 0; j--)
|
|
|
429959 |
+ for (j = 0; j < ntohs(header->ancount); j++)
|
|
|
429959 |
{
|
|
|
429959 |
+ int secflag = 0;
|
|
|
429959 |
unsigned char *tmp = namep;
|
|
|
429959 |
/* the loop body overwrites the original name, so get it back here. */
|
|
|
429959 |
if (!extract_name(header, qlen, &tmp, name, 1, 0) ||
|
|
|
429959 |
@@ -659,11 +668,21 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|
|
429959 |
{
|
|
|
429959 |
if (!extract_name(header, qlen, &p1, name, 1, 0))
|
|
|
429959 |
return 0;
|
|
|
429959 |
-
|
|
|
429959 |
+
|
|
|
429959 |
+ if (rr_status && rr_status[j])
|
|
|
429959 |
+ {
|
|
|
429959 |
+ /* validated RR anywhere in CNAME chain, don't cache. */
|
|
|
429959 |
+ if (cname_short || aqtype == T_CNAME)
|
|
|
429959 |
+ return 0;
|
|
|
429959 |
+
|
|
|
429959 |
+ secflag = F_DNSSECOK;
|
|
|
429959 |
+ }
|
|
|
429959 |
+
|
|
|
429959 |
if (aqtype == T_CNAME)
|
|
|
429959 |
{
|
|
|
429959 |
- if (!cname_count-- || secure)
|
|
|
429959 |
- return 0; /* looped CNAMES, or DNSSEC, which we can't cache. */
|
|
|
429959 |
+ if (!cname_count--)
|
|
|
429959 |
+ return 0; /* looped CNAMES, we can't cache. */
|
|
|
429959 |
+ cname_short = 1;
|
|
|
429959 |
goto cname_loop;
|
|
|
429959 |
}
|
|
|
429959 |
|
|
|
429959 |
@@ -685,7 +704,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|
|
429959 |
ttl = find_soa(header, qlen, NULL, doctored);
|
|
|
429959 |
}
|
|
|
429959 |
if (ttl)
|
|
|
429959 |
- cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | secflag);
|
|
|
429959 |
+ cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | (secure ? F_DNSSECOK : 0));
|
|
|
429959 |
}
|
|
|
429959 |
}
|
|
|
429959 |
else
|
|
|
429959 |
@@ -713,8 +732,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|
|
429959 |
if (!(p1 = skip_questions(header, qlen)))
|
|
|
429959 |
return 0;
|
|
|
429959 |
|
|
|
429959 |
- for (j = ntohs(header->ancount); j != 0; j--)
|
|
|
429959 |
+ for (j = 0; j < ntohs(header->ancount); j++)
|
|
|
429959 |
{
|
|
|
429959 |
+ int secflag = 0;
|
|
|
429959 |
+
|
|
|
429959 |
if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
|
|
|
429959 |
return 0; /* bad packet */
|
|
|
429959 |
|
|
|
429959 |
@@ -731,6 +752,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|
|
429959 |
|
|
|
429959 |
if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == qtype))
|
|
|
429959 |
{
|
|
|
429959 |
+#ifdef HAVE_DNSSEC
|
|
|
429959 |
+ if (rr_status && rr_status[j])
|
|
|
429959 |
+ secflag = F_DNSSECOK;
|
|
|
429959 |
+#endif
|
|
|
429959 |
if (aqtype == T_CNAME)
|
|
|
429959 |
{
|
|
|
429959 |
if (!cname_count--)
|
|
|
429959 |
@@ -822,7 +847,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|
|
429959 |
pointing at this, inherit its TTL */
|
|
|
429959 |
if (ttl || cpp)
|
|
|
429959 |
{
|
|
|
429959 |
- newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | secflag);
|
|
|
429959 |
+ newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));
|
|
|
429959 |
if (newc && cpp)
|
|
|
429959 |
{
|
|
|
429959 |
cpp->addr.cname.target.cache = newc;
|
|
|
429959 |
@@ -937,7 +962,7 @@ int check_for_local_domain(char *name, time_t now)
|
|
|
429959 |
/* Note: the call to cache_find_by_name is intended to find any record which matches
|
|
|
429959 |
ie A, AAAA, CNAME. */
|
|
|
429959 |
|
|
|
429959 |
- if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME |F_NO_RR)) &&
|
|
|
429959 |
+ if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_NO_RR)) &&
|
|
|
429959 |
(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
|
|
|
429959 |
return 1;
|
|
|
429959 |
|
|
|
429959 |
@@ -1689,8 +1714,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
|
|
|
429959 |
|
|
|
429959 |
if (qtype == T_CNAME || qtype == T_ANY)
|
|
|
429959 |
{
|
|
|
429959 |
- if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) &&
|
|
|
429959 |
- (qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | (dryrun ? F_NO_RR : 0)))))
|
|
|
429959 |
+ if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME | (dryrun ? F_NO_RR : 0))) &&
|
|
|
429959 |
+ (qtype == T_CNAME || (crecp->flags & F_CONFIG)) &&
|
|
|
429959 |
+ ((crecp->flags & F_CONFIG) || !do_bit || !(crecp->flags & F_DNSSECOK)))
|
|
|
429959 |
{
|
|
|
429959 |
if (!(crecp->flags & F_DNSSECOK))
|
|
|
429959 |
sec_data = 0;
|
|
|
429959 |
--
|
|
|
429959 |
2.20.1
|
|
|
429959 |
|