mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 18:28:25 +00:00
Return INSECURE, rather than BOGUS when DS proved not to exist.
Return INSECURE when validating DNS replies which have RRSIGs, but when a needed DS record in the trust chain is proved not to exist. It's allowed for a zone to set up DNSKEY and RRSIG records first, then add a DS later, completing the chain of trust. Also, since we don't have the infrastructure to track that these non-validated replies have RRSIGS, don't cache them, so we don't provide answers with missing RRSIGS from the cache.
This commit is contained in:
@@ -583,6 +583,7 @@ struct hostsfile {
|
|||||||
#define STAT_NO_NS 10
|
#define STAT_NO_NS 10
|
||||||
#define STAT_NEED_DS_NEG 11
|
#define STAT_NEED_DS_NEG 11
|
||||||
#define STAT_CHASE_CNAME 12
|
#define STAT_CHASE_CNAME 12
|
||||||
|
#define STAT_INSECURE_DS 13
|
||||||
|
|
||||||
#define FREC_NOREBIND 1
|
#define FREC_NOREBIND 1
|
||||||
#define FREC_CHECKING_DISABLED 2
|
#define FREC_CHECKING_DISABLED 2
|
||||||
|
|||||||
@@ -981,7 +981,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
|
|
||||||
/* If we've cached that DS provably doesn't exist, result must be INSECURE */
|
/* If we've cached that DS provably doesn't exist, result must be INSECURE */
|
||||||
if (crecp->flags & F_NEG)
|
if (crecp->flags & F_NEG)
|
||||||
return STAT_INSECURE;
|
return STAT_INSECURE_DS;
|
||||||
|
|
||||||
/* NOTE, we need to find ONE DNSKEY which matches the DS */
|
/* NOTE, we need to find ONE DNSKEY which matches the DS */
|
||||||
for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--)
|
for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--)
|
||||||
|
|||||||
@@ -521,7 +521,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind,
|
static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind,
|
||||||
int no_cache, int cache_secure, int ad_reqd, int do_bit, int added_pheader, int check_subnet, union mysockaddr *query_source)
|
int no_cache, int cache_secure, int bogusanswer, int ad_reqd, int do_bit, int added_pheader,
|
||||||
|
int check_subnet, union mysockaddr *query_source)
|
||||||
{
|
{
|
||||||
unsigned char *pheader, *sizep;
|
unsigned char *pheader, *sizep;
|
||||||
char **sets = 0;
|
char **sets = 0;
|
||||||
@@ -634,7 +635,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
if (no_cache && !(header->hb4 & HB4_CD))
|
if (bogusanswer && !(header->hb4 & HB4_CD))
|
||||||
{
|
{
|
||||||
if (!option_bool(OPT_DNSSEC_DEBUG))
|
if (!option_bool(OPT_DNSSEC_DEBUG))
|
||||||
{
|
{
|
||||||
@@ -786,7 +787,7 @@ void reply_query(int fd, int family, time_t now)
|
|||||||
everything is broken */
|
everything is broken */
|
||||||
if (forward->forwardall == 0 || --forward->forwardall == 1 || RCODE(header) != SERVFAIL)
|
if (forward->forwardall == 0 || --forward->forwardall == 1 || RCODE(header) != SERVFAIL)
|
||||||
{
|
{
|
||||||
int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0;
|
int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
|
||||||
|
|
||||||
if (option_bool(OPT_NO_REBIND))
|
if (option_bool(OPT_NO_REBIND))
|
||||||
check_rebind = !(forward->flags & FREC_NOREBIND);
|
check_rebind = !(forward->flags & FREC_NOREBIND);
|
||||||
@@ -819,7 +820,13 @@ void reply_query(int fd, int family, time_t now)
|
|||||||
else if (forward->flags & FREC_DS_QUERY)
|
else if (forward->flags & FREC_DS_QUERY)
|
||||||
{
|
{
|
||||||
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
|
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
|
||||||
if (status == STAT_NO_DS || status == STAT_NO_NS)
|
/* Provably no DS, everything below is insecure, even if signatures are offered */
|
||||||
|
if (status == STAT_NO_DS)
|
||||||
|
/* We only cache sigs when we've validated a reply.
|
||||||
|
Avoid caching a reply with sigs if there's a vaildated break in the
|
||||||
|
DS chain, so we don't return replies from cache missing sigs. */
|
||||||
|
status = STAT_INSECURE_DS;
|
||||||
|
else if (status == STAT_NO_NS)
|
||||||
status = STAT_BOGUS;
|
status = STAT_BOGUS;
|
||||||
}
|
}
|
||||||
else if (forward->flags & FREC_CHECK_NOSIGN)
|
else if (forward->flags & FREC_CHECK_NOSIGN)
|
||||||
@@ -959,8 +966,14 @@ void reply_query(int fd, int family, time_t now)
|
|||||||
else if (forward->flags & FREC_DS_QUERY)
|
else if (forward->flags & FREC_DS_QUERY)
|
||||||
{
|
{
|
||||||
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
|
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
|
||||||
if (status == STAT_NO_DS || status == STAT_NO_NS)
|
/* Provably no DS, everything below is insecure, even if signatures are offered */
|
||||||
status = STAT_BOGUS;
|
if (status == STAT_NO_DS)
|
||||||
|
/* We only cache sigs when we've validated a reply.
|
||||||
|
Avoid caching a reply with sigs if there's a vaildated break in the
|
||||||
|
DS chain, so we don't return replies from cache missing sigs. */
|
||||||
|
status = STAT_INSECURE_DS;
|
||||||
|
else if (status == STAT_NO_NS)
|
||||||
|
status = STAT_BOGUS;
|
||||||
}
|
}
|
||||||
else if (forward->flags & FREC_CHECK_NOSIGN)
|
else if (forward->flags & FREC_CHECK_NOSIGN)
|
||||||
{
|
{
|
||||||
@@ -985,6 +998,17 @@ void reply_query(int fd, int family, time_t now)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
no_cache_dnssec = 0;
|
||||||
|
|
||||||
|
if (status == STAT_INSECURE_DS)
|
||||||
|
{
|
||||||
|
/* We only cache sigs when we've validated a reply.
|
||||||
|
Avoid caching a reply with sigs if there's a vaildated break in the
|
||||||
|
DS chain, so we don't return replies from cache missing sigs. */
|
||||||
|
status = STAT_INSECURE;
|
||||||
|
no_cache_dnssec = 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (status == STAT_TRUNCATED)
|
if (status == STAT_TRUNCATED)
|
||||||
header->hb3 |= HB3_TC;
|
header->hb3 |= HB3_TC;
|
||||||
else
|
else
|
||||||
@@ -1002,12 +1026,13 @@ void reply_query(int fd, int family, time_t now)
|
|||||||
log_query(F_KEYTAG | F_SECSTAT, "result", NULL, result);
|
log_query(F_KEYTAG | F_SECSTAT, "result", NULL, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
no_cache_dnssec = 0;
|
|
||||||
|
|
||||||
if (status == STAT_SECURE)
|
if (status == STAT_SECURE)
|
||||||
cache_secure = 1;
|
cache_secure = 1;
|
||||||
else if (status == STAT_BOGUS)
|
else if (status == STAT_BOGUS)
|
||||||
no_cache_dnssec = 1;
|
{
|
||||||
|
no_cache_dnssec = 1;
|
||||||
|
bogusanswer = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1017,7 +1042,7 @@ void reply_query(int fd, int family, time_t now)
|
|||||||
else
|
else
|
||||||
header->hb4 &= ~HB4_CD;
|
header->hb4 &= ~HB4_CD;
|
||||||
|
|
||||||
if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure,
|
if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer,
|
||||||
forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION,
|
forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION,
|
||||||
forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source)))
|
forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source)))
|
||||||
{
|
{
|
||||||
@@ -1420,7 +1445,7 @@ static int do_check_sign(struct frec *forward, int status, time_t now, char *nam
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Move toward the root, until we find a signed non-existance of a DS, in which case
|
/* Move down from the root, until we find a signed non-existance of a DS, in which case
|
||||||
an unsigned answer is OK, or we find a signed DS, in which case there should be
|
an unsigned answer is OK, or we find a signed DS, in which case there should be
|
||||||
a signature, and the answer is BOGUS */
|
a signature, and the answer is BOGUS */
|
||||||
static int tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, size_t plen, int class, char *name,
|
static int tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, size_t plen, int class, char *name,
|
||||||
@@ -1570,8 +1595,13 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
|||||||
else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
|
else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
|
||||||
{
|
{
|
||||||
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
|
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
|
||||||
if (status == STAT_NEED_DS && (new_status == STAT_NO_DS || new_status == STAT_NO_NS))
|
if (status == STAT_NEED_DS)
|
||||||
new_status = STAT_BOGUS;
|
{
|
||||||
|
if (new_status == STAT_NO_DS)
|
||||||
|
new_status = STAT_INSECURE_DS;
|
||||||
|
else if (new_status == STAT_NO_NS)
|
||||||
|
new_status = STAT_BOGUS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (status == STAT_CHASE_CNAME)
|
else if (status == STAT_CHASE_CNAME)
|
||||||
new_status = dnssec_chase_cname(now, header, n, name, keyname);
|
new_status = dnssec_chase_cname(now, header, n, name, keyname);
|
||||||
@@ -1630,8 +1660,13 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
|||||||
else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
|
else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
|
||||||
{
|
{
|
||||||
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
|
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
|
||||||
if (status == STAT_NEED_DS && (new_status == STAT_NO_DS || new_status == STAT_NO_NS))
|
if (status == STAT_NEED_DS)
|
||||||
new_status = STAT_BOGUS; /* Validated no DS */
|
{
|
||||||
|
if (new_status == STAT_NO_DS)
|
||||||
|
new_status = STAT_INSECURE_DS;
|
||||||
|
else if (new_status == STAT_NO_NS)
|
||||||
|
new_status = STAT_BOGUS; /* Validated no DS */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (status == STAT_CHASE_CNAME)
|
else if (status == STAT_CHASE_CNAME)
|
||||||
new_status = dnssec_chase_cname(now, header, n, name, keyname);
|
new_status = dnssec_chase_cname(now, header, n, name, keyname);
|
||||||
@@ -1652,7 +1687,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
|
|||||||
goto another_tcp_key;
|
goto another_tcp_key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
free(packet);
|
free(packet);
|
||||||
}
|
}
|
||||||
return new_status;
|
return new_status;
|
||||||
@@ -1673,7 +1708,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
int local_auth = 0;
|
int local_auth = 0;
|
||||||
#endif
|
#endif
|
||||||
int checking_disabled, ad_question, do_bit, added_pheader = 0;
|
int checking_disabled, ad_question, do_bit, added_pheader = 0;
|
||||||
int check_subnet, no_cache_dnssec = 0, cache_secure = 0;
|
int check_subnet, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
|
||||||
size_t m;
|
size_t m;
|
||||||
unsigned short qtype;
|
unsigned short qtype;
|
||||||
unsigned int gotname;
|
unsigned int gotname;
|
||||||
@@ -1941,6 +1976,15 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
int status = tcp_key_recurse(now, STAT_TRUNCATED, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount);
|
int status = tcp_key_recurse(now, STAT_TRUNCATED, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount);
|
||||||
char *result;
|
char *result;
|
||||||
|
|
||||||
|
if (status == STAT_INSECURE_DS)
|
||||||
|
{
|
||||||
|
/* We only cache sigs when we've validated a reply.
|
||||||
|
Avoid caching a reply with sigs if there's a vaildated break in the
|
||||||
|
DS chain, so we don't return replies from cache missing sigs. */
|
||||||
|
status = STAT_INSECURE;
|
||||||
|
no_cache_dnssec = 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (keycount == 0)
|
if (keycount == 0)
|
||||||
{
|
{
|
||||||
result = "ABANDONED";
|
result = "ABANDONED";
|
||||||
@@ -1952,8 +1996,11 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
log_query(F_KEYTAG | F_SECSTAT, "result", NULL, result);
|
log_query(F_KEYTAG | F_SECSTAT, "result", NULL, result);
|
||||||
|
|
||||||
if (status == STAT_BOGUS)
|
if (status == STAT_BOGUS)
|
||||||
no_cache_dnssec = 1;
|
{
|
||||||
|
no_cache_dnssec = 1;
|
||||||
|
bogusanswer = 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (status == STAT_SECURE)
|
if (status == STAT_SECURE)
|
||||||
cache_secure = 1;
|
cache_secure = 1;
|
||||||
}
|
}
|
||||||
@@ -1987,7 +2034,7 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
m = process_reply(header, now, last_server, (unsigned int)m,
|
m = process_reply(header, now, last_server, (unsigned int)m,
|
||||||
option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec,
|
option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, bogusanswer,
|
||||||
cache_secure, ad_question, do_bit, added_pheader, check_subnet, &peer_addr);
|
cache_secure, ad_question, do_bit, added_pheader, check_subnet, &peer_addr);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user