Moved origin/cp-main to upstream v2.90 version
Following are the commands used to move to the v2.90
upstream version in the dnsmasq directory:
1. git remote add upstream http://thekelleys.org.uk/git/dnsmasq.git
2. git remote -v show
3. git fetch upstream
4. git diff origin/cp-main v2.90 > ../dnsmasq_to_v2.90.gitdiff
5. patch -p1 < ../dnsmasq_to_v2.90.gitdiff
6. git add . && git commit -m "Moved origin/cp-main to upstream v2.90 version"
7. git diff v2.90 - Should be empty
Change-Id: I167f369cc3c625e7d291b296950fe98aa8f7d513
diff --git a/src/dnssec.c b/src/dnssec.c
index 3ee1e9e..ed2f53f 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -1,5 +1,5 @@
/* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
- and Copyright (c) 2012-2020 Simon Kelley
+ and Copyright (c) 2012-2023 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -24,81 +24,6 @@
#define SERIAL_LT -1
#define SERIAL_GT 1
-/* Convert from presentation format to wire format, in place.
- Also map UC -> LC.
- Note that using extract_name to get presentation format
- then calling to_wire() removes compression and maps case,
- thus generating names in canonical form.
- Calling to_wire followed by from_wire is almost an identity,
- except that the UC remains mapped to LC.
-
- Note that both /000 and '.' are allowed within labels. These get
- represented in presentation format using NAME_ESCAPE as an escape
- character. In theory, if all the characters in a name were /000 or
- '.' or NAME_ESCAPE then all would have to be escaped, so the
- presentation format would be twice as long as the spec (1024).
- The buffers are all declared as 2049 (allowing for the trailing zero)
- for this reason.
-*/
-static int to_wire(char *name)
-{
- unsigned char *l, *p, *q, term;
- int len;
-
- for (l = (unsigned char*)name; *l != 0; l = p)
- {
- for (p = l; *p != '.' && *p != 0; p++)
- if (*p >= 'A' && *p <= 'Z')
- *p = *p - 'A' + 'a';
- else if (*p == NAME_ESCAPE)
- {
- for (q = p; *q; q++)
- *q = *(q+1);
- (*p)--;
- }
- term = *p;
-
- if ((len = p - l) != 0)
- memmove(l+1, l, len);
- *l = len;
-
- p++;
-
- if (term == 0)
- *p = 0;
- }
-
- return l + 1 - (unsigned char *)name;
-}
-
-/* Note: no compression allowed in input. */
-static void from_wire(char *name)
-{
- unsigned char *l, *p, *last;
- int len;
-
- for (last = (unsigned char *)name; *last != 0; last += *last+1);
-
- for (l = (unsigned char *)name; *l != 0; l += len+1)
- {
- len = *l;
- memmove(l, l+1, len);
- for (p = l; p < l + len; p++)
- if (*p == '.' || *p == 0 || *p == NAME_ESCAPE)
- {
- memmove(p+1, p, 1 + last - p);
- len++;
- *p++ = NAME_ESCAPE;
- (*p)++;
- }
-
- l[len] = '.';
- }
-
- if ((char *)l != name)
- *(l-1) = 0;
-}
-
/* Input in presentation format */
static int count_labels(char *name)
{
@@ -215,14 +140,6 @@
return !daemon->dnssec_no_time_check;
}
-/* Check whether today/now is between date_start and date_end */
-static int check_date_range(unsigned long curtime, u32 date_start, u32 date_end)
-{
- /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
- return serial_compare_32(curtime, date_start) == SERIAL_GT
- && serial_compare_32(curtime, date_end) == SERIAL_LT;
-}
-
/* Return bytes of canonicalised rrdata one by one.
Init state->ip with the RR, and state->end with the end of same.
Init state->op to NULL.
@@ -233,7 +150,7 @@
On returning 0, the end has been reached.
*/
struct rdata_state {
- u16 *desc;
+ short *desc;
size_t c;
unsigned char *end, *ip, *op;
char *buff;
@@ -254,7 +171,7 @@
{
d = *(state->desc);
- if (d == (u16)-1)
+ if (d == -1)
{
/* all the bytes to the end. */
if ((state->c = state->end - state->ip) != 0)
@@ -302,7 +219,7 @@
/* Bubble sort the RRset into the canonical order. */
-static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx,
+static int sort_rrset(struct dns_header *header, size_t plen, short *rr_desc, int rrsetidx,
unsigned char **rrset, char *buff1, char *buff2)
{
int swap, i, j;
@@ -339,7 +256,7 @@
is the identity function and we can compare
the RRs directly. If not we compare the
canonicalised RRs one byte at a time. */
- if (*rr_desc == (u16)-1)
+ if (*rr_desc == -1)
{
int rdmin = rdlen1 > rdlen2 ? rdlen2 : rdlen1;
int cmp = memcmp(state1.ip, state2.ip, rdmin);
@@ -507,12 +424,24 @@
return 1;
}
+int dec_counter(int *counter, char *message)
+{
+ if ((*counter)-- == 0)
+ {
+ my_syslog(LOG_WARNING, "limit exceeded: %s", message ? message : _("per-query crypto work"));
+ return 1;
+ }
+
+ return 0;
+}
+
/* Validate a single RRset (class, type, name) in the supplied DNS reply
Return code:
STAT_SECURE if it validates.
STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
(In this case *wildcard_out points to the "body" of the wildcard within name.)
STAT_BOGUS signature is wrong, bad packet.
+ STAT_ABANDONED validation abandoned do to excess resource usage.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
STAT_NEED_DS need DS to complete validation (name is returned in keyname)
@@ -527,14 +456,15 @@
*/
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx,
char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen,
- int algo_in, int keytag_in, unsigned long *ttl_out)
+ int algo_in, int keytag_in, unsigned long *ttl_out, int *validate_counter)
{
unsigned char *p;
- int rdlen, j, name_labels, algo, labels, key_tag;
+ int rdlen, j, name_labels, algo, labels, key_tag, sig_fail_cnt;
struct crec *crecp = NULL;
- u16 *rr_desc = rrfilter_desc(type);
+ short *rr_desc = rrfilter_desc(type);
u32 sig_expiration, sig_inception;
-
+ int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP;
+
unsigned long curtime = time(0);
int time_check = is_check_date(curtime);
@@ -549,7 +479,7 @@
rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);
/* Now try all the sigs to try and find one which validates */
- for (j = 0; j <sigidx; j++)
+ for (sig_fail_cnt = daemon->limit[LIMIT_SIG_FAIL], j = 0; j <sigidx; j++)
{
unsigned char *psav, *sig, *digest;
int i, wire_len, sig_len;
@@ -557,6 +487,8 @@
void *ctx;
char *name_start;
u32 nsigttl, ttl, orig_ttl;
+
+ failflags &= ~DNSSEC_FAIL_NOSIG;
p = sigs[j];
GETLONG(ttl, p);
@@ -574,12 +506,31 @@
if (!extract_name(header, plen, &p, keyname, 1, 0))
return STAT_BOGUS;
- if ((time_check && !check_date_range(curtime, sig_inception, sig_expiration)) ||
- labels > name_labels ||
- !(hash = hash_find(algo_digest_name(algo))) ||
+ if (!time_check)
+ failflags &= ~(DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP);
+ else
+ {
+ /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
+ if (serial_compare_32(curtime, sig_inception) == SERIAL_LT)
+ continue;
+ else
+ failflags &= ~DNSSEC_FAIL_NYV;
+
+ if (serial_compare_32(curtime, sig_expiration) == SERIAL_GT)
+ continue;
+ else
+ failflags &= ~DNSSEC_FAIL_EXP;
+ }
+
+ if (!(hash = hash_find(algo_digest_name(algo))))
+ continue;
+ else
+ failflags &= ~DNSSEC_FAIL_NOKEYSUP;
+
+ if (labels > name_labels ||
!hash_init(hash, &ctx, &digest))
continue;
-
+
/* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
return STAT_NEED_KEY;
@@ -657,7 +608,7 @@
If canonicalisation is not needed, a simple insertion into the hash works.
*/
- if (*rr_desc == (u16)-1)
+ if (*rr_desc == -1)
{
len = htons(rdlen);
hash->update(ctx, 2, (unsigned char *)&len);
@@ -710,13 +661,19 @@
/* namebuff used for workspace above, restore to leave unchanged on exit */
p = (unsigned char*)(rrset[0]);
- extract_name(header, plen, &p, name, 1, 0);
+ if (!extract_name(header, plen, &p, name, 1, 0))
+ return STAT_BOGUS;
if (key)
{
- if (algo_in == algo && keytag_in == key_tag &&
- verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo))
- return STAT_SECURE;
+ if (algo_in == algo && keytag_in == key_tag)
+ {
+ if (dec_counter(validate_counter, NULL))
+ return STAT_ABANDONED;
+
+ if (verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo))
+ return STAT_SECURE;
+ }
}
else
{
@@ -724,13 +681,27 @@
for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
if (crecp->addr.key.algo == algo &&
crecp->addr.key.keytag == key_tag &&
- crecp->uid == (unsigned int)class &&
- verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
- return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE;
+ crecp->uid == (unsigned int)class)
+ {
+ if (dec_counter(validate_counter, NULL))
+ return STAT_ABANDONED;
+
+ if (verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
+ return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE;
+
+ /* An attacker can waste a lot of our CPU by setting up a giant DNSKEY RRSET full of failing
+ keys, all of which we have to try. Since many failing keys is not likely for
+ a legitimate domain, set a limit on how many can fail. */
+ if ((daemon->limit[LIMIT_SIG_FAIL] - (sig_fail_cnt + 1)) > (int)daemon->metrics[METRIC_SIG_FAIL_HWM])
+ daemon->metrics[METRIC_SIG_FAIL_HWM] = daemon->limit[LIMIT_SIG_FAIL] - (sig_fail_cnt + 1);
+ if (dec_counter(&sig_fail_cnt, _("per-RRSet signature fails")))
+ return STAT_ABANDONED;
+ }
}
}
- return STAT_BOGUS;
+ /* If we reach this point, no verifying key was found */
+ return STAT_BOGUS | failflags | DNSSEC_FAIL_NOKEY;
}
@@ -740,38 +711,45 @@
STAT_OK Done, key(s) in cache.
STAT_BOGUS No DNSKEYs found, which can be validated with DS,
or self-sign for DNSKEY RRset is not valid, bad packet.
+ STAT_ABANDONED resource exhaustion.
STAT_NEED_DS DS records to validate a key not found, name in keyname
- STAT_NEED_KEY DNSKEY records to validate a key not found, name in keyname
*/
-int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name,
+ char *keyname, int class, int *validate_counter)
{
- unsigned char *psave, *p = (unsigned char *)(header+1);
+ unsigned char *psave, *p = (unsigned char *)(header+1), *keyaddr;
struct crec *crecp, *recp1;
- int rc, j, qtype, qclass, rdlen, flags, algo, valid, keytag;
+ int rc, j, qtype, qclass, rdlen, flags, algo, keytag, sigcnt, rrcnt;
unsigned long ttl, sig_ttl;
- struct blockdata *key;
union all_addr a;
+ int failflags = DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE;
+ char valid_digest[255];
+ static unsigned char **cached_digest;
+ static size_t cached_digest_size = 0;
- if (ntohs(header->qdcount) != 1 ||
- RCODE(header) == SERVFAIL || RCODE(header) == REFUSED ||
- !extract_name(header, plen, &p, name, 1, 4))
- return STAT_BOGUS;
+ if (ntohs(header->qdcount) != 1 || RCODE(header) != NOERROR || !extract_name(header, plen, &p, name, 1, 4))
+ return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
- if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
- return STAT_BOGUS;
+ if (qtype != T_DNSKEY || qclass != class ||
+ !explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) ||
+ rrcnt == 0)
+ return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
+ if (sigcnt == 0)
+ return STAT_BOGUS | DNSSEC_FAIL_NOSIG;
+
/* See if we have cached a DS record which validates this key */
if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
{
strcpy(keyname, name);
return STAT_NEED_DS;
}
-
+
/* NOTE, we need to find ONE DNSKEY which matches the DS */
- for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--)
+ for (j = ntohs(header->ancount); j != 0; j--)
{
/* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
@@ -782,7 +760,7 @@
GETLONG(ttl, p);
GETSHORT(rdlen, p);
- if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
+ if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */
if (qclass != class || qtype != T_DNSKEY || rc == 2)
@@ -790,172 +768,230 @@
p += rdlen;
continue;
}
-
+
+ if (rdlen < 5)
+ return STAT_BOGUS; /* min 1 byte key! */
+
psave = p;
GETSHORT(flags, p);
if (*p++ != 3)
- return STAT_BOGUS;
- algo = *p++;
- keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
- key = NULL;
-
- /* key must have zone key flag set */
- if (flags & 0x100)
- key = blockdata_alloc((char*)p, rdlen - 4);
-
- p = psave;
-
- if (!ADD_RDLEN(header, p, plen, rdlen))
{
- if (key)
- blockdata_free(key);
- return STAT_BOGUS; /* bad packet */
+ p = psave + rdlen;
+ continue;
}
+ algo = *p++;
+ keyaddr = p;
+ keytag = dnskey_keytag(algo, flags, keyaddr, rdlen - 4);
+
+ p = psave + rdlen;
- /* No zone key flag or malloc failure */
- if (!key)
+ /* key must have zone key flag set */
+ if (!(flags & 0x100))
continue;
+ failflags &= ~DNSSEC_FAIL_NOZONE;
+
+ /* clear digest cache. */
+ memset(valid_digest, 0, sizeof(valid_digest));
+
for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
{
void *ctx;
unsigned char *digest, *ds_digest;
const struct nettle_hash *hash;
- int sigcnt, rrcnt;
+ int wire_len;
+
+ if ((recp1->flags & F_NEG) ||
+ recp1->addr.ds.algo != algo ||
+ recp1->addr.ds.keytag != keytag ||
+ recp1->uid != (unsigned int)class)
+ continue;
+
+ if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))))
+ continue;
+
+ failflags &= ~DNSSEC_FAIL_NODSSUP;
+
+ if (recp1->addr.ds.keylen != (int)hash->digest_size ||
+ !(ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)))
+ continue;
- if (recp1->addr.ds.algo == algo &&
- recp1->addr.ds.keytag == keytag &&
- recp1->uid == (unsigned int)class &&
- (hash = hash_find(ds_digest_name(recp1->addr.ds.digest))) &&
- hash_init(hash, &ctx, &digest))
-
+ if (valid_digest[recp1->addr.ds.digest])
+ digest = cached_digest[recp1->addr.ds.digest];
+ else
{
- int wire_len = to_wire(name);
+ /* computing a hash is a unit of crypto work. */
+ if (dec_counter(validate_counter, NULL))
+ return STAT_ABANDONED;
+
+ if (!hash_init(hash, &ctx, &digest))
+ continue;
+
+ wire_len = to_wire(name);
/* Note that digest may be different between DSs, so
- we can't move this outside the loop. */
+ we can't move this outside the loop. We keep
+ copies of each digest we make for this key,
+ so maximum digest work is O(keys x digests_types)
+ rather then O(keys x DSs) */
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
hash->update(ctx, (unsigned int)rdlen, psave);
hash->digest(ctx, hash->digest_size, digest);
from_wire(name);
-
- if (!(recp1->flags & F_NEG) &&
- recp1->addr.ds.keylen == (int)hash->digest_size &&
- (ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)) &&
- memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
- explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
- sigcnt != 0 && rrcnt != 0 &&
- validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
- NULL, key, rdlen - 4, algo, keytag, &sig_ttl) == STAT_SECURE)
+
+ if (recp1->addr.ds.digest >= cached_digest_size)
{
- valid = 1;
- break;
- }
- }
- }
- blockdata_free(key);
- }
-
- if (valid)
- {
- /* DNSKEY RRset determined to be OK, now cache it. */
- cache_start_insert();
-
- p = skip_questions(header, plen);
-
- for (j = ntohs(header->ancount); j != 0; j--)
- {
- /* Ensure we have type, class TTL and length */
- if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
- return STAT_BOGUS; /* bad packet */
-
- GETSHORT(qtype, p);
- GETSHORT(qclass, p);
- GETLONG(ttl, p);
- GETSHORT(rdlen, p);
-
- /* TTL may be limited by sig. */
- if (sig_ttl < ttl)
- ttl = sig_ttl;
-
- if (!CHECK_LEN(header, p, plen, rdlen))
- return STAT_BOGUS; /* bad packet */
-
- if (qclass == class && rc == 1)
- {
- psave = p;
-
- if (qtype == T_DNSKEY)
- {
- if (rdlen < 4)
- return STAT_BOGUS; /* bad packet */
-
- GETSHORT(flags, p);
- if (*p++ != 3)
- return STAT_BOGUS;
- algo = *p++;
- keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
-
- if ((key = blockdata_alloc((char*)p, rdlen - 4)))
+ unsigned char **new;
+
+ /* whine_malloc zeros memory */
+ if ((new = whine_malloc((recp1->addr.ds.digest + 5) * sizeof(unsigned char *))))
{
- a.key.keylen = rdlen - 4;
- a.key.keydata = key;
- a.key.algo = algo;
- a.key.keytag = keytag;
- a.key.flags = flags;
+ if (cached_digest_size != 0)
+ {
+ memcpy(new, cached_digest, cached_digest_size * sizeof(unsigned char *));
+ free(cached_digest);
+ }
- if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))
- {
- blockdata_free(key);
- return STAT_BOGUS;
- }
- else
- {
- a.log.keytag = keytag;
- a.log.algo = algo;
- if (algo_digest_name(algo))
- log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu");
- else
- log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)");
- }
+ cached_digest_size = recp1->addr.ds.digest + 5;
+ cached_digest = new;
}
}
-
- p = psave;
+
+ if (recp1->addr.ds.digest < cached_digest_size)
+ {
+ if (!cached_digest[recp1->addr.ds.digest])
+ cached_digest[recp1->addr.ds.digest] = whine_malloc(recp1->addr.ds.keylen);
+
+ if (cached_digest[recp1->addr.ds.digest])
+ {
+ memcpy(cached_digest[recp1->addr.ds.digest], digest, recp1->addr.ds.keylen);
+ valid_digest[recp1->addr.ds.digest] = 1;
+ }
+ }
}
+
+ if (memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0)
+ {
+ /* Found the key validated by a DS record.
+ Now check the self-sig for the entire key RRset using that key.
+ Note that validate_rrset() will never return STAT_NEED_KEY here,
+ since we supply the key it will use as an argument. */
+ struct blockdata *key;
+
+ if (!(key = blockdata_alloc((char *)keyaddr, rdlen - 4)))
+ break;
+
+ rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
+ NULL, key, rdlen - 4, algo, keytag, &sig_ttl, validate_counter);
+
+ blockdata_free(key);
+
+ if (STAT_ISEQUAL(rc, STAT_ABANDONED))
+ return rc;
+
+ /* can't validate KEY RRset with this key, see if there's another that
+ will, which is validated by another DS. */
+ if (!STAT_ISEQUAL(rc, STAT_SECURE))
+ break;
+
+ /* DNSKEY RRset determined to be OK, now cache it. */
+ cache_start_insert();
+
+ p = skip_questions(header, plen);
+
+ for (j = ntohs(header->ancount); j != 0; j--)
+ {
+ /* Ensure we have type, class TTL and length */
+ if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+ return STAT_BOGUS; /* bad packet */
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+ GETLONG(ttl, p);
+ GETSHORT(rdlen, p);
+
+ /* TTL may be limited by sig. */
+ if (sig_ttl < ttl)
+ ttl = sig_ttl;
+
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return STAT_BOGUS; /* bad packet */
+
+ psave = p;
- if (!ADD_RDLEN(header, p, plen, rdlen))
- return STAT_BOGUS; /* bad packet */
+ if (qclass == class && rc == 1 && qtype == T_DNSKEY)
+ {
+ if (rdlen < 4)
+ return STAT_BOGUS; /* min 1 byte key! */
+
+ GETSHORT(flags, p);
+ if (*p++ == 3)
+ {
+ algo = *p++;
+ keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
+
+ if (!(key = blockdata_alloc((char*)p, rdlen - 4)))
+ return STAT_BOGUS;
+
+ a.key.keylen = rdlen - 4;
+ a.key.keydata = key;
+ a.key.algo = algo;
+ a.key.keytag = keytag;
+ a.key.flags = flags;
+
+ if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))
+ {
+ blockdata_free(key);
+ return STAT_BOGUS;
+ }
+
+ a.log.keytag = keytag;
+ a.log.algo = algo;
+ if (algo_digest_name(algo))
+ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu", 0);
+ else
+ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)", 0);
+ }
+ }
+
+ p = psave + rdlen;
+ }
+
+ /* commit cache insert. */
+ cache_end_insert();
+ return STAT_OK;
+ }
}
-
- /* commit cache insert. */
- cache_end_insert();
- return STAT_OK;
}
-
- log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
- return STAT_BOGUS;
+
+ log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY", 0);
+ return STAT_BOGUS | failflags;
}
/* The DNS packet is expected to contain the answer to a DS query
- Put all DSs in the answer which are valid into the cache.
+ Put all DSs in the answer which are valid and have hash and signature algos
+ we support into the cache.
Also handles replies which prove that there's no DS at this location,
either because the zone is unsigned or this isn't a zone cut. These are
cached too.
+ If none of the DS's are for supported algos, treat the answer as if
+ it's a proof of no DS at this location. RFC4035 para 5.2.
return codes:
STAT_OK At least one valid DS found and in cache.
STAT_BOGUS no DS in reply or not signed, fails validation, bad packet.
STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname
STAT_NEED_DS DS record needed.
+ STAT_ABANDONED resource exhaustion.
*/
-int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name,
+ char *keyname, int class, int *validate_counter)
{
unsigned char *p = (unsigned char *)(header+1);
- int qtype, qclass, rc, i, neganswer, nons, neg_ttl = 0;
- int aclass, atype, rdlen;
+ int qtype, qclass, rc, i, neganswer = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0;
+ int aclass, atype, rdlen, flags;
unsigned long ttl;
union all_addr a;
@@ -967,38 +1003,51 @@
GETSHORT(qclass, p);
if (qtype != T_DS || qclass != class)
- rc = STAT_BOGUS;
+ return STAT_BOGUS;
+
+ /* A SERVFAIL answer has been seen to a DS query not at start of authority,
+ so treat it as such and continue to search for a DS or proof of no existence
+ further down the tree. */
+ if (RCODE(header) == SERVFAIL)
+ servfail = neganswer = nons = 1;
else
- rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl);
-
- if (rc == STAT_INSECURE)
{
- my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
- rc = STAT_BOGUS;
+ rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter);
+
+ if (STAT_ISEQUAL(rc, STAT_INSECURE))
+ {
+ my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
+ log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure", 0);
+ return STAT_BOGUS | DNSSEC_FAIL_INDET;
+ }
+
+ p = (unsigned char *)(header+1);
+ if (!extract_name(header, plen, &p, name, 1, 4))
+ return STAT_BOGUS;
+
+ p += 4; /* qtype, qclass */
+
+ /* If the key needed to validate the DS is on the same domain as the DS, we'll
+ loop getting nowhere. Stop that now. This can happen of the DS answer comes
+ from the DS's zone, and not the parent zone. */
+ if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))
+ {
+ log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS", 0);
+ return STAT_BOGUS;
+ }
+
+ if (!STAT_ISEQUAL(rc, STAT_SECURE))
+ return rc;
}
- p = (unsigned char *)(header+1);
- extract_name(header, plen, &p, name, 1, 4);
- p += 4; /* qtype, qclass */
-
- /* If the key needed to validate the DS is on the same domain as the DS, we'll
- loop getting nowhere. Stop that now. This can happen of the DS answer comes
- from the DS's zone, and not the parent zone. */
- if (rc == STAT_BOGUS || (rc == STAT_NEED_KEY && hostname_isequal(name, keyname)))
- {
- log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS");
- return STAT_BOGUS;
- }
-
- if (rc != STAT_SECURE)
- return rc;
-
if (!neganswer)
{
cache_start_insert();
for (i = 0; i < ntohs(header->ancount); i++)
{
+ unsigned char *psave;
+
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
return STAT_BOGUS; /* bad packet */
@@ -1009,28 +1058,37 @@
if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */
+
+ psave = p;
if (aclass == class && atype == T_DS && rc == 1)
{
int algo, digest, keytag;
- unsigned char *psave = p;
struct blockdata *key;
- if (rdlen < 4)
- return STAT_BOGUS; /* bad packet */
+ if (rdlen < 5)
+ return STAT_BOGUS; /* min 1 byte digest! */
GETSHORT(keytag, p);
algo = *p++;
digest = *p++;
- if ((key = blockdata_alloc((char*)p, rdlen - 4)))
+ if (!ds_digest_name(digest) || !algo_digest_name(algo))
+ {
+ a.log.keytag = keytag;
+ a.log.algo = algo;
+ a.log.digest = digest;
+ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu (not supported)", 0);
+ neg_ttl = ttl;
+ }
+ else if ((key = blockdata_alloc((char*)p, rdlen - 4)))
{
a.ds.digest = digest;
a.ds.keydata = key;
a.ds.algo = algo;
a.ds.keytag = keytag;
a.ds.keylen = rdlen - 4;
-
+
if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))
{
blockdata_free(key);
@@ -1041,26 +1099,26 @@
a.log.keytag = keytag;
a.log.algo = algo;
a.log.digest = digest;
- if (ds_digest_name(digest) && algo_digest_name(algo))
- log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu");
- else
- log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)");
+ log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu", 0);
+ found_supported = 1;
}
}
-
- p = psave;
}
- if (!ADD_RDLEN(header, p, plen, rdlen))
- return STAT_BOGUS; /* bad packet */
+
+ p = psave + rdlen;
}
cache_end_insert();
+ /* Fall through if no supported algo DS found. */
+ if (found_supported)
+ return STAT_OK;
}
- else
+
+ flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
+
+ if (neganswer)
{
- int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
-
if (RCODE(header) == NXDOMAIN)
flags |= F_NXDOMAIN;
@@ -1068,17 +1126,19 @@
to store presence/absence of NS. */
if (nons)
flags &= ~F_DNSSECOK;
-
- cache_start_insert();
-
- /* Use TTL from NSEC for negative cache entries */
- if (!cache_insert(name, NULL, class, now, neg_ttl, flags))
- return STAT_BOGUS;
-
- cache_end_insert();
-
- log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no DS/cut" : "no DS");
}
+
+ cache_start_insert();
+
+ /* Use TTL from NSEC for negative cache entries */
+ if (!cache_insert(name, NULL, class, now, neg_ttl, flags))
+ return STAT_BOGUS;
+
+ cache_end_insert();
+
+ if (neganswer)
+ log_query(F_NOEXTRA | F_UPSTREAM, name, NULL,
+ servfail ? "SERVFAIL" : (nons ? "no DS/cut" : "no DS"), 0);
return STAT_OK;
}
@@ -1148,6 +1208,7 @@
}
}
+/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count,
char *workspace1_in, char *workspace2, char *name, int type, int *nons)
{
@@ -1167,12 +1228,12 @@
p = nsecs[i];
if (!extract_name(header, plen, &p, workspace1, 1, 10))
- return 0;
+ return DNSSEC_FAIL_BADPACKET;
p += 8; /* class, type, TTL */
GETSHORT(rdlen, p);
psave = p;
- if (!extract_name(header, plen, &p, workspace2, 1, 10))
- return 0;
+ if (!extract_name(header, plen, &p, workspace2, 1, 0))
+ return DNSSEC_FAIL_BADPACKET;
/* If NSEC comes from wildcard expansion, use original wildcard
as name for computation. */
@@ -1200,12 +1261,13 @@
{
/* 4035 para 5.4. Last sentence */
if (type == T_NSEC || type == T_RRSIG)
- return 1;
+ return 0;
/* NSEC with the same name as the RR we're testing, check
that the type in question doesn't appear in the type map */
rdlen -= p - psave;
- /* rdlen is now length of type map, and p points to it */
+ /* rdlen is now length of type map, and p points to it
+ packet checked to be as long as rdlen implies in prove_non_existence() */
/* If we can prove that there's no NS record, return that information. */
if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
@@ -1216,25 +1278,25 @@
/* A CNAME answer would also be valid, so if there's a CNAME is should
have been returned. */
if ((p[2] & (0x80 >> T_CNAME)) != 0)
- return 0;
+ return DNSSEC_FAIL_NONSEC;
/* If the SOA bit is set for a DS record, then we have the
DS from the wrong side of the delegation. For the root DS,
this is expected. */
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
- return 0;
+ return DNSSEC_FAIL_NONSEC;
}
while (rdlen >= 2)
{
if (!CHECK_LEN(header, p, plen, rdlen))
- return 0;
+ return DNSSEC_FAIL_BADPACKET;
if (p[0] == type >> 8)
{
/* Does the NSEC say our type exists? */
if (offset < p[1] && (p[offset+2] & mask) != 0)
- return 0;
+ return DNSSEC_FAIL_NONSEC;
break; /* finished checking */
}
@@ -1243,24 +1305,24 @@
p += p[1];
}
- return 1;
+ return 0;
}
else if (rc == -1)
{
/* Normal case, name falls between NSEC name and next domain name,
wrap around case, name falls between NSEC name (rc == -1) and end */
if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0)
- return 1;
+ return 0;
}
else
{
/* wrap around case, name falls between start and next domain name */
if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 )
- return 1;
+ return 0;
}
}
- return 0;
+ return DNSSEC_FAIL_NONSEC;
}
/* return digest length, or zero on error */
@@ -1334,23 +1396,23 @@
for (i = 0; i < nsec_count; i++)
if ((p = nsecs[i]))
{
- if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
+ if (!extract_name(header, plen, &p, workspace1, 1, 10) ||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
return 0;
p += 8; /* class, type, TTL */
GETSHORT(rdlen, p);
+
psave = p;
+
+ /* packet checked to be as long as implied by rdlen, salt_len and hash_len in prove_non_existence() */
p++; /* algo */
flags = *p++; /* flags */
p += 2; /* iterations */
salt_len = *p++; /* salt_len */
p += salt_len; /* salt */
hash_len = *p++; /* p now points to next hashed name */
-
- if (!CHECK_LEN(header, p, plen, hash_len))
- return 0;
-
+
if (digest_len == base32_len && hash_len == base32_len)
{
int rc = memcmp(workspace2, digest, digest_len);
@@ -1358,7 +1420,8 @@
if (rc == 0)
{
/* We found an NSEC3 whose hashed name exactly matches the query, so
- we just need to check the type map. p points to the RR data for the record. */
+ we just need to check the type map. p points to the RR data for the record.
+ Note we have packet length up to rdlen bytes checked. */
int offset = (type & 0xff) >> 3;
int mask = 0x80 >> (type & 0x07);
@@ -1366,15 +1429,12 @@
p += hash_len; /* skip next-domain hash */
rdlen -= p - psave;
- if (!CHECK_LEN(header, p, plen, rdlen))
- return 0;
-
if (rdlen >= 2 && p[0] == 0)
{
/* If we can prove that there's no NS record, return that information. */
if (nons && (p[2] & (0x80 >> T_NS)) != 0)
*nons = 0;
-
+
/* A CNAME answer would also be valid, so if there's a CNAME is should
have been returned. */
if ((p[2] & (0x80 >> T_CNAME)) != 0)
@@ -1433,8 +1493,9 @@
return 0;
}
-static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
- char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons)
+/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
+static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, char *workspace1,
+ char *workspace2, char *name, int type, char *wildname, int *nons, int *validate_counter)
{
unsigned char *salt, *p, *digest;
int digest_len, i, iterations, salt_len, base32_len, algo = 0;
@@ -1454,7 +1515,7 @@
for (i = 0; i < nsec_count; i++)
{
if (!(p = skip_name(nsecs[i], header, plen, 15)))
- return 0; /* bad packet */
+ return DNSSEC_FAIL_BADPACKET; /* bad packet */
p += 10; /* type, class, TTL, rdlen */
algo = *p++;
@@ -1465,23 +1526,18 @@
/* No usable NSEC3s */
if (i == nsec_count)
- return 0;
+ return DNSSEC_FAIL_NONSEC;
p++; /* flags */
GETSHORT (iterations, p);
- /* Upper-bound iterations, to avoid DoS.
- Strictly, there are lower bounds for small keys, but
- since we don't have key size info here, at least limit
- to the largest bound, for 4096-bit keys. RFC 5155 10.3 */
- if (iterations > 2500)
- return 0;
+ /* Upper-bound iterations, to avoid DoS. RFC 9276 refers. */
+ if (iterations > daemon->limit[LIMIT_NSEC3_ITERS])
+ return DNSSEC_FAIL_NSEC3_ITERS;
salt_len = *p++;
salt = p;
- if (!CHECK_LEN(header, salt, plen, salt_len))
- return 0; /* bad packet */
-
+
/* Now prune so we only have NSEC3 records with same iterations, salt and algo */
for (i = 0; i < nsec_count; i++)
{
@@ -1511,9 +1567,6 @@
if (salt_len != *p++)
continue;
- if (!CHECK_LEN(header, p, plen, salt_len))
- return 0; /* bad packet */
-
if (memcmp(p, salt, salt_len) != 0)
continue;
@@ -1521,11 +1574,14 @@
nsecs[i] = nsec3p;
}
+ if (dec_counter(validate_counter, NULL))
+ return DNSSEC_FAIL_WORK;
+
if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
- return 0;
+ return DNSSEC_FAIL_NONSEC;
if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons, count_labels(name)))
- return 1;
+ return 0;
/* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3"
or an answer inferred from a wildcard record. */
@@ -1540,15 +1596,20 @@
if (wildname && hostname_isequal(closest_encloser, wildname))
break;
+ if (dec_counter(validate_counter, NULL))
+ return DNSSEC_FAIL_WORK;
+
if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)
- return 0;
+ return DNSSEC_FAIL_NONSEC;
for (i = 0; i < nsec_count; i++)
if ((p = nsecs[i]))
{
- if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
- !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
- return 0;
+ if (!extract_name(header, plen, &p, workspace1, 1, 0))
+ return DNSSEC_FAIL_BADPACKET;
+
+ if (!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
+ return DNSSEC_FAIL_NONSEC;
if (digest_len == base32_len &&
memcmp(digest, workspace2, digest_len) == 0)
@@ -1563,14 +1624,17 @@
while ((closest_encloser = strchr(closest_encloser, '.')));
if (!closest_encloser || !next_closest)
- return 0;
+ return DNSSEC_FAIL_NONSEC;
/* Look for NSEC3 that proves the non-existence of the next-closest encloser */
+ if (dec_counter(validate_counter, NULL))
+ return DNSSEC_FAIL_WORK;
+
if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
- return 0;
+ return DNSSEC_FAIL_NONSEC;
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
- return 0;
+ return DNSSEC_FAIL_NONSEC;
/* Finally, check that there's no seat of wildcard synthesis */
if (!wildname)
@@ -1581,17 +1645,22 @@
wildcard--;
*wildcard = '*';
+ if (dec_counter(validate_counter, NULL))
+ return DNSSEC_FAIL_WORK;
+
if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
- return 0;
+ return DNSSEC_FAIL_NONSEC;
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
- return 0;
+ return DNSSEC_FAIL_NONSEC;
}
- return 1;
+ return 0;
}
-static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons, int *nsec_ttl)
+/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
+static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass,
+ char *wildname, int *nons, int *nsec_ttl, int *validate_counter)
{
static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
static int nsecset_sz = 0, rrsig_labels_sz = 0;
@@ -1603,7 +1672,7 @@
/* Move to NS section */
if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
- return 0;
+ return DNSSEC_FAIL_BADPACKET;
auth_start = p;
@@ -1612,13 +1681,16 @@
unsigned char *pstart = p;
if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
- return 0;
+ return DNSSEC_FAIL_BADPACKET;
GETSHORT(type, p);
GETSHORT(class, p);
GETLONG(ttl, p);
GETSHORT(rdlen, p);
-
+
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return DNSSEC_FAIL_BADPACKET;
+
if (class == qclass && (type == T_NSEC || type == T_NSEC3))
{
if (nsec_ttl)
@@ -1631,12 +1703,12 @@
/* No mixed NSECing 'round here, thankyouverymuch */
if (type_found != 0 && type_found != type)
- return 0;
+ return DNSSEC_FAIL_NONSEC;
type_found = type;
if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
- return 0;
+ return DNSSEC_FAIL_BADPACKET;
if (type == T_NSEC)
{
@@ -1651,30 +1723,33 @@
int res, j, rdlen1, type1, class1;
if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
- return 0;
+ return DNSSEC_FAIL_BADPACKET;
rrsig_labels[nsecs_found] = NULL;
for (j = ntohs(header->nscount); j != 0; j--)
{
- if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
- return 0;
+ unsigned char *psav;
+ if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
+ return DNSSEC_FAIL_BADPACKET;
+
GETSHORT(type1, p1);
GETSHORT(class1, p1);
p1 += 4; /* TTL */
GETSHORT(rdlen1, p1);
+ psav = p1;
+
if (!CHECK_LEN(header, p1, plen, rdlen1))
- return 0;
+ return DNSSEC_FAIL_BADPACKET;
if (res == 1 && class1 == qclass && type1 == T_RRSIG)
{
int type_covered;
- unsigned char *psav = p1;
-
+
if (rdlen1 < 18)
- return 0; /* bad packet */
+ return DNSSEC_FAIL_BADPACKET; /* bad packet */
GETSHORT(type_covered, p1);
@@ -1686,33 +1761,56 @@
if (!rrsig_labels[nsecs_found])
rrsig_labels[nsecs_found] = p1;
else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
- return 0;
- }
- p1 = psav;
+ return DNSSEC_FAIL_NONSEC;
+ }
}
- if (!ADD_RDLEN(header, p1, plen, rdlen1))
- return 0;
+ p1 = psav + rdlen1;
}
/* Must have found at least one sig. */
if (!rrsig_labels[nsecs_found])
- return 0;
+ return DNSSEC_FAIL_NONSEC;
+ }
+ else if (type == T_NSEC3)
+ {
+ /* Decode the packet structure enough to check that rdlen is big enough
+ to contain everything other than the type bitmap.
+ (packet checked to be long enough to contain rdlen above)
+ We don't need to do any further length checks in check_nes3_coverage()
+ or prove_non_existence_nsec3() */
+
+ int salt_len, hash_len;
+ unsigned char *psav = p;
+
+ if (rdlen < 5)
+ return DNSSEC_FAIL_BADPACKET;
+
+ p += 4; /* algo, flags, iterations */
+ salt_len = *p++; /* salt_len */
+ if (rdlen < (6 + salt_len))
+ return DNSSEC_FAIL_BADPACKET; /* check up to hash_length */
+
+ p += salt_len; /* salt */
+ hash_len = *p++;
+ if (rdlen < (6 + salt_len + hash_len))
+ return DNSSEC_FAIL_BADPACKET; /* check to end of next hashed name */
+
+ p = psav;
}
nsecset[nsecs_found++] = pstart;
}
- if (!ADD_RDLEN(header, p, plen, rdlen))
- return 0;
+ p += rdlen;
}
if (type_found == T_NSEC)
return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
else if (type_found == T_NSEC3)
- return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);
+ return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons, validate_counter);
else
- return 0;
+ return DNSSEC_FAIL_NONSEC;
}
/* Check signing status of name.
@@ -1807,16 +1905,17 @@
STAT_BOGUS signature is wrong, bad packet, no validation where there should be.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
STAT_NEED_DS need DS to complete validation (name is returned in keyname)
+ STAT_ABANDONED resource exhaustion.
daemon->rr_status points to a char array which corressponds to the RRs in the
- answer and auth sections. This is set to 1 for each RR which is validated, and 0 for any which aren't.
+ answer and auth sections. This is set to >1 for each RR which is validated, and 0 for any which aren't.
When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section.
Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode
- is the nons argument is non-NULL.
+ if the nons argument is non-NULL.
*/
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname,
- int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl)
+ int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_counter)
{
static unsigned char **targets = NULL;
static int target_sz = 0;
@@ -1825,7 +1924,7 @@
int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
int i, j, rc = STAT_INSECURE;
int secure = STAT_SECURE;
-
+ int rc_nsec;
/* extend rr_status if necessary */
if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
{
@@ -1854,7 +1953,7 @@
/* Find all the targets we're looking for answers to.
The zeroth array element is for the query, subsequent ones
- for CNAME targets, unless the query is for a CNAME. */
+ for CNAME targets, unless the query is for a CNAME or ANY. */
if (!expand_workspace(&targets, &target_sz, 0))
return STAT_BOGUS;
@@ -1873,7 +1972,7 @@
if (qtype == T_RRSIG)
return STAT_INSECURE;
- if (qtype != T_CNAME)
+ if (qtype != T_CNAME && qtype != T_ANY)
for (j = ntohs(header->ancount); j != 0; j--)
{
if (!(p1 = skip_name(p1, header, plen, 10)))
@@ -1947,7 +2046,7 @@
{
/* NSEC and NSEC3 records must be signed. We make this assumption elsewhere. */
if (type1 == T_NSEC || type1 == T_NSEC3)
- rc = STAT_INSECURE;
+ return STAT_BOGUS | DNSSEC_FAIL_NOSIG;
else if (nons && i >= ntohs(header->ancount))
/* If we're validating a DS reply, rather than looking for the value of AD bit,
we only care that NSEC and NSEC3 RRs in the auth section are signed.
@@ -1959,15 +2058,16 @@
if (check_unsigned && i < ntohs(header->ancount))
{
rc = zone_status(name, class1, keyname, now);
- if (rc == STAT_SECURE)
- rc = STAT_BOGUS;
+ if (STAT_ISEQUAL(rc, STAT_SECURE))
+ rc = STAT_BOGUS | DNSSEC_FAIL_NOSIG;
+
if (class)
*class = class1; /* Class for NEED_DS or NEED_KEY */
}
else
rc = STAT_INSECURE;
- if (rc != STAT_INSECURE)
+ if (!STAT_ISEQUAL(rc, STAT_INSECURE))
return rc;
}
}
@@ -1978,7 +2078,7 @@
strcpy(daemon->workspacename, keyname);
rc = zone_status(daemon->workspacename, class1, keyname, now);
- if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
+ if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS))
{
if (class)
*class = class1; /* Class for NEED_DS or NEED_KEY */
@@ -1986,13 +2086,13 @@
}
/* Zone is insecure, don't need to validate RRset */
- if (rc == STAT_SECURE)
+ if (STAT_ISEQUAL(rc, STAT_SECURE))
{
unsigned long sig_ttl;
rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
- rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl);
+ rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl, validate_counter);
- if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
+ if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS) || STAT_ISEQUAL(rc, STAT_ABANDONED))
{
if (class)
*class = class1; /* Class for DS or DNSKEY */
@@ -2025,50 +2125,53 @@
Note that we may not yet have validated the NSEC/NSEC3 RRsets.
That's not a problem since if the RRsets later fail
we'll return BOGUS then. */
- if (rc == STAT_SECURE_WILDCARD &&
- !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL))
- return STAT_BOGUS;
+ if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) &&
+ ((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL, validate_counter))) != 0)
+ return (rc_nsec & DNSSEC_FAIL_WORK) ? STAT_ABANDONED : (STAT_BOGUS | rc_nsec);
rc = STAT_SECURE;
}
}
}
- if (rc == STAT_INSECURE)
+ if (STAT_ISEQUAL(rc, STAT_INSECURE))
secure = STAT_INSECURE;
}
/* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */
- if (secure == STAT_SECURE)
- for (j = 0; j <targetidx; j++)
- if ((p2 = targets[j]))
- {
- if (neganswer)
- *neganswer = 1;
-
- if (!extract_name(header, plen, &p2, name, 1, 10))
- return STAT_BOGUS; /* bad packet */
-
- /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
-
- /* For anything other than a DS record, this situation is OK if either
- the answer is in an unsigned zone, or there's a NSEC records. */
- if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl))
- {
- /* Empty DS without NSECS */
- if (qtype == T_DS)
- return STAT_BOGUS;
-
- if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE)
- {
- if (class)
- *class = qclass; /* Class for NEED_DS or NEED_KEY */
- return rc;
- }
-
- return STAT_BOGUS; /* signed zone, no NSECs */
- }
- }
+ for (j = 0; j <targetidx; j++)
+ if ((p2 = targets[j]))
+ {
+ if (neganswer)
+ *neganswer = 1;
+
+ if (!extract_name(header, plen, &p2, name, 1, 10))
+ return STAT_BOGUS; /* bad packet */
+
+ /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
+
+ /* For anything other than a DS record, this situation is OK if either
+ the answer is in an unsigned zone, or there's a NSEC records. */
+ if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl, validate_counter)) != 0)
+ {
+ if (rc_nsec & DNSSEC_FAIL_WORK)
+ return STAT_ABANDONED;
+
+ /* Empty DS without NSECS */
+ if (qtype == T_DS)
+ return STAT_BOGUS | rc_nsec;
+
+ if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) &&
+ !STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
+ {
+ if (class)
+ *class = qclass; /* Class for NEED_DS or NEED_KEY */
+ return rc;
+ }
+
+ return STAT_BOGUS | rc_nsec; /* signed zone, no NSECs */
+ }
+ }
return secure;
}
@@ -2130,4 +2233,33 @@
return ret;
}
+int errflags_to_ede(int status)
+{
+ /* We can end up with more than one flag set for some errors,
+ so this encodes a rough priority so the (eg) No sig is reported
+ before no-unexpired-sig. */
+
+ if (status & DNSSEC_FAIL_NYV)
+ return EDE_SIG_NYV;
+ else if (status & DNSSEC_FAIL_EXP)
+ return EDE_SIG_EXP;
+ else if (status & DNSSEC_FAIL_NOKEYSUP)
+ return EDE_USUPDNSKEY;
+ else if (status & DNSSEC_FAIL_NOZONE)
+ return EDE_NO_ZONEKEY;
+ else if (status & DNSSEC_FAIL_NOKEY)
+ return EDE_NO_DNSKEY;
+ else if (status & DNSSEC_FAIL_NODSSUP)
+ return EDE_USUPDS;
+ else if (status & DNSSEC_FAIL_NSEC3_ITERS)
+ return EDE_UNS_NS3_ITER;
+ else if (status & DNSSEC_FAIL_NONSEC)
+ return EDE_NO_NSEC;
+ else if (status & DNSSEC_FAIL_INDET)
+ return EDE_DNSSEC_IND;
+ else if (status & DNSSEC_FAIL_NOSIG)
+ return EDE_NO_RRSIG;
+ else
+ return EDE_UNSET;
+}
#endif /* HAVE_DNSSEC */