Compare commits

...
17 Commits
Author SHA1 Message Date
Simon Kelley 3705ec5592 Relax limits imposed by d/t/functions.d/ip-addr.patterns in Debian autotest. 2024-02-15 09:55:57 +00:00
Simon Kelley b6769234bc Bump Debian version. 2024-02-13 13:49:15 +00:00
Simon Kelley 214a046f47 Merge branch 'dnssec-limit'
This merges security fixes for CVE-2023-50387 and CVE-2023-50868

Keytrap - extreme CPU consumption in the DNSSEC validator.
2024-02-13 13:27:25 +00:00
Simon Kelley b38da6b191 Reverse suppression of ANY query answer logging. 2024-02-13 13:26:24 +00:00
Simon Kelley 9621c16a78 Add CHANGELOG entry for DNSSEC security fixes. 2024-02-12 23:11:35 +00:00
Simon Kelley 3ae7f1ab0d Add --dnssec-limits option. 2024-02-12 23:11:35 +00:00
Simon Kelley 39de57499e Better allocation code for DS digest cache. 2024-02-12 23:11:35 +00:00
Simon Kelley 3c91bca943 Better stats and logging from DNSSEC resource limiting. 2024-02-12 23:11:35 +00:00
Simon Kelley 76bceb06c4 Overhaul data checking in NSEC code. 2024-02-12 23:11:35 +00:00
Simon Kelley 6f23a0a75e Rework validate-by-DS to avoid DoS vuln without arbitrary limits.
By calculating the hash of a DNSKEY once for each digest algo,
we reduce the hashing work from (no. DS) x (no. DNSKEY) to
(no. DNSKEY) x (no. distinct digests)

The number of distinct digests can never be more than 255 and
it's limited by which hashes we implement, so currently only 4.
2024-02-12 23:11:35 +00:00
Simon Kelley 06945c4b77 Update EDE code -> text conversion. 2024-02-12 23:11:35 +00:00
Simon Kelley c5aa221e44 Parameterise work limits for DNSSEC validation. 2024-02-12 23:11:35 +00:00
Simon Kelley bfefd6e38c Fix error introduced in 635bc51cac3d5d7dd49ce9e27149cf7e402b7e79 2024-02-12 23:11:35 +00:00
Simon Kelley 59d30390c9 Measure cryptographic work done by DNSSEC. 2024-02-12 23:11:34 +00:00
Simon Kelley 51471cafa5 Update NSEC3 iterations handling to conform with RFC 9276. 2024-02-12 23:11:34 +00:00
Simon Kelley be73efc020 Update header with new EDE values. 2024-02-12 23:11:34 +00:00
Simon Kelley 40595f80d9 Protection against pathalogical DNSSEC domains.
An attacker can create DNSSEC signed domains which need a lot of
work to verfify. We limit the number of crypto operations to
avoid DoS attacks by CPU exhaustion.
2024-02-12 23:11:34 +00:00
14 changed files with 510 additions and 212 deletions

No files matched your search

+32
View File
@@ -32,6 +32,38 @@ version 2.90
--filter-rr=ANY has a special meaning: it filters the --filter-rr=ANY has a special meaning: it filters the
answers to queries for the ANY RR-type. answers to queries for the ANY RR-type.
Add limits on the resources used to do DNSSEC validation.
DNSSEC introduces a potential CPU DoS, because a crafted domain
can force a validator to a large number of cryptographic
operations whilst attempting to do validation. When using TCP
transport a DNSKEY RRset contain thousands of members and any
RRset can have thousands of signatures. The potential number
of signature validations to follow the RFC for validation
for one RRset is the cross product of the keys and signatures,
so millions. In practice, the actual numbers are much lower,
so attacks can be mitigated by limiting the amount of
cryptographic "work" to a much lower amount. The actual
limits are number a signature validation fails per RRset(20),
number of signature validations and hash computations
per query(200), number of sub-queries to fetch DS and DNSKEY
RRsets per query(40), and the number of iterations in a
NSEC3 record(150). These values are sensible, but there is, as yet,
no standardisation on the values for a "conforming" domain, so a
new option --dnssec-limit is provided should they need to be altered.
The algorithm to validate DS records has also been altered to reduce
the maximum work from cross product of the number of DS records and
number of DNSKEYs to the cross product of the number of DS records
and supported DS digest types. As the number of DS digest types
is in single figures, this reduces the exposure.
Credit is due to Elias Heftrig, Haya Schulmann, Niklas Vogel,
and Michael Waidner from the German National Research Center for
Applied Cybersecurity ATHENE for finding this vulnerability.
CVE 2023-50387 and CVE 2023-50868 apply.
Note that the is a security vulnerablity only when DNSSEC validation
is enabled.
version 2.89 version 2.89
Fix bug introduced in 2.88 (commit fe91134b) which can result Fix bug introduced in 2.88 (commit fe91134b) which can result
+11 -1
View File
@@ -1,9 +1,19 @@
dnsmasq (2.90~test4-1) experimental; urgency=medium dnsmasq (2.90-2) unstable; urgency=medium
[ Sven Geuer ]
* Relax limits imposed by d/t/functions.d/ip-addr.patterns to allow for
successful tests on ci.debian.net.
-- Simon Kelley <simon@thekelleys.org.uk> Wed, 14 Feb 2024 11:33:14 +0000
dnsmasq (2.90-1) unstable; urgency=medium
[ Simon Kelley ] [ Simon Kelley ]
* New upstream. (closes: #1033165) * New upstream. (closes: #1033165)
* Move hard-coding of Lua version from the upstream Makefile * Move hard-coding of Lua version from the upstream Makefile
to d/rules. to d/rules.
* Security fixes for Keytrap - DNSSEC validation CPU exhaustion.
CVE-2023-50387 and CVE-2023-50868
[ Sven Geuer ] [ Sven Geuer ]
* Introduce autokpgtests per d/tests/* (closes: #1034135). * Introduce autokpgtests per d/tests/* (closes: #1034135).
* Switch to dpkg-source 3.0 (quilt) format (closes: #1007041). * Switch to dpkg-source 3.0 (quilt) format (closes: #1007041).
+1 -1
View File
@@ -1,4 +1,4 @@
?: veth1@if?: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 *: veth1@if*: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether ??:??:??:??:??:?? brd ff:ff:ff:ff:ff:ff link-netnsid 0 link/ether ??:??:??:??:??:?? brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.142.?/29 brd 192.168.142.7 scope global dynamic veth1 inet 192.168.142.?/29 brd 192.168.142.7 scope global dynamic veth1
valid_lft 3[56][0-9][0-9]sec preferred_lft 3[56][0-9][0-9]sec valid_lft 3[56][0-9][0-9]sec preferred_lft 3[56][0-9][0-9]sec
+9
View File
@@ -931,6 +931,15 @@ Authenticated Data bit correctly in all cases is not technically possible. If th
when using this option, then the cache should be disabled using --cache-size=0. In most cases, enabling DNSSEC validation when using this option, then the cache should be disabled using --cache-size=0. In most cases, enabling DNSSEC validation
within dnsmasq is a better option. See --dnssec for details. within dnsmasq is a better option. See --dnssec for details.
.TP .TP
.B --dnssec-limits=<limit>[,<limit>.......]
Override the default resource limits applied to DNSSEC validation. Cryptographic operations are expensive and crafted domains
can DoS a DNSSEC validator by forcing it to do hundreds of thousands of such operations. To avoid this, the dnsmasq validation code
applies limits on how much work will be expended in validation. If any of the limits are exceeded, the validation will fail and the
domain treated as BOGUS. There are four limits, in order(default values in parens): number a signature validation fails per RRset(20), number of signature validations and
hash computations per query(200), number of sub-queries to fetch DS and DNSKEY RRsets per query(40), and the number of iterations in a NSEC3 record(150).
The maximum values reached during validation are stored, and dumped as part of the stats generated by SIGUSR1. Supplying a limit value of 0 leaves the default in place, so
\fB--dnssec-limits=0,0,20\fP sets the number of sub-queries to 20 whilst leaving the other limits at default values.
.TP
.B --dnssec-debug .B --dnssec-debug
Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries, Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries,
and don't convert replies which do not validate to responses with and don't convert replies which do not validate to responses with
+37 -1
View File
@@ -834,7 +834,18 @@ void cache_end_insert(void)
if (daemon->pipe_to_parent != -1) if (daemon->pipe_to_parent != -1)
{ {
ssize_t m = -1; ssize_t m = -1;
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0); read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
#ifdef HAVE_DNSSEC
/* Sneak out possibly updated crypto HWM values. */
m = daemon->metrics[METRIC_CRYPTO_HWM];
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
m = daemon->metrics[METRIC_SIG_FAIL_HWM];
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
m = daemon->metrics[METRIC_WORK_HWM];
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
#endif
} }
new_chain = NULL; new_chain = NULL;
@@ -853,7 +864,7 @@ int cache_recv_insert(time_t now, int fd)
cache_start_insert(); cache_start_insert();
while(1) while (1)
{ {
if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1)) if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
@@ -861,6 +872,21 @@ int cache_recv_insert(time_t now, int fd)
if (m == -1) if (m == -1)
{ {
#ifdef HAVE_DNSSEC
/* Sneak in possibly updated crypto HWM. */
if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
return 0;
if (m > daemon->metrics[METRIC_CRYPTO_HWM])
daemon->metrics[METRIC_CRYPTO_HWM] = m;
if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
return 0;
if (m > daemon->metrics[METRIC_SIG_FAIL_HWM])
daemon->metrics[METRIC_SIG_FAIL_HWM] = m;
if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
return 0;
if (m > daemon->metrics[METRIC_WORK_HWM])
daemon->metrics[METRIC_WORK_HWM] = m;
#endif
cache_end_insert(); cache_end_insert();
return 1; return 1;
} }
@@ -1893,6 +1919,11 @@ void dump_cache(time_t now)
#ifdef HAVE_AUTH #ifdef HAVE_AUTH
my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]); my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
#endif #endif
#ifdef HAVE_DNSSEC
my_syslog(LOG_INFO, _("DNSSEC per-query subqueries HWM %u"), daemon->metrics[METRIC_WORK_HWM]);
my_syslog(LOG_INFO, _("DNSSEC per-query crypto work HWM %u"), daemon->metrics[METRIC_CRYPTO_HWM]);
my_syslog(LOG_INFO, _("DNSSEC per-RRSet signature fails HWM %u"), daemon->metrics[METRIC_SIG_FAIL_HWM]);
#endif
blockdata_report(); blockdata_report();
my_syslog(LOG_INFO, _("child processes for TCP requests: in use %zu, highest since last SIGUSR1 %zu, max allowed %zu."), my_syslog(LOG_INFO, _("child processes for TCP requests: in use %zu, highest since last SIGUSR1 %zu, max allowed %zu."),
@@ -2052,6 +2083,11 @@ static char *edestr(int ede)
case EDE_NO_AUTH: return "no reachable authority"; case EDE_NO_AUTH: return "no reachable authority";
case EDE_NETERR: return "network error"; case EDE_NETERR: return "network error";
case EDE_INVALID_DATA: return "invalid data"; case EDE_INVALID_DATA: return "invalid data";
case EDE_SIG_E_B_V: return "signature expired before valid";
case EDE_TOO_EARLY: return "too early";
case EDE_UNS_NS3_ITER: return "unsupported NSEC3 iterations value";
case EDE_UNABLE_POLICY: return "uanble to conform to policy";
case EDE_SYNTHESIZED: return "synthesized";
default: return "unknown"; default: return "unknown";
} }
} }
+4 -1
View File
@@ -22,7 +22,10 @@
#define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */ #define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */
#define SAFE_PKTSZ 1232 /* "go anywhere" UDP packet size, see https://dnsflagday.net/2020/ */ #define SAFE_PKTSZ 1232 /* "go anywhere" UDP packet size, see https://dnsflagday.net/2020/ */
#define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */ #define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */
#define DNSSEC_WORK 50 /* Max number of queries to validate one question */ #define DNSSEC_LIMIT_WORK 40 /* Max number of queries to validate one question */
#define DNSSEC_LIMIT_SIG_FAIL 20 /* Number of signature that can fail to validate in one answer */
#define DNSSEC_LIMIT_CRYPTO 200 /* max no. of crypto operations to validate one query. */
#define DNSSEC_LIMIT_NSEC3_ITERS 150 /* Max. number if iterations allowed in NSEC3 record. */
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */ #define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
#define SMALL_PORT_RANGE 30 /* If DNS port range is smaller than this, use different allocation. */ #define SMALL_PORT_RANGE 30 /* If DNS port range is smaller than this, use different allocation. */
#define FORWARD_TEST 50 /* try all servers every 50 queries */ #define FORWARD_TEST 50 /* try all servers every 50 queries */
+5 -2
View File
@@ -112,8 +112,11 @@
#define EDE_NO_AUTH 22 /* No Reachable Authority */ #define EDE_NO_AUTH 22 /* No Reachable Authority */
#define EDE_NETERR 23 /* Network error */ #define EDE_NETERR 23 /* Network error */
#define EDE_INVALID_DATA 24 /* Invalid Data */ #define EDE_INVALID_DATA 24 /* Invalid Data */
#define EDE_SIG_E_B_V 25 /* Signature Expired before Valid */
#define EDE_TOO_EARLY 26 /* To Early */
#define EDE_UNS_NS3_ITER 27 /* Unsupported NSEC3 Iterations Value */
#define EDE_UNABLE_POLICY 28 /* Unable to conform to policy */
#define EDE_SYNTHESIZED 29 /* Synthesized */
struct dns_header { struct dns_header {
+16 -4
View File
@@ -757,6 +757,9 @@ struct dyndir {
#define DNSSEC_FAIL_NONSEC 0x0040 /* No NSEC */ #define DNSSEC_FAIL_NONSEC 0x0040 /* No NSEC */
#define DNSSEC_FAIL_NODSSUP 0x0080 /* no supported DS algo. */ #define DNSSEC_FAIL_NODSSUP 0x0080 /* no supported DS algo. */
#define DNSSEC_FAIL_NOKEY 0x0100 /* no DNSKEY */ #define DNSSEC_FAIL_NOKEY 0x0100 /* no DNSKEY */
#define DNSSEC_FAIL_NSEC3_ITERS 0x0200 /* too many iterations in NSEC3 */
#define DNSSEC_FAIL_BADPACKET 0x0400 /* bad packet */
#define DNSSEC_FAIL_WORK 0x0800 /* too much crypto */
#define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b)) #define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b))
@@ -794,7 +797,7 @@ struct frec {
struct blockdata *stash; /* Saved reply, whilst we validate */ struct blockdata *stash; /* Saved reply, whilst we validate */
size_t stash_len; size_t stash_len;
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
int class, work_counter; int class, work_counter, validate_counter;
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
struct frec *next_dependent; /* list of above. */ struct frec *next_dependent; /* list of above. */
struct frec *blocking_query; /* Query which is blocking us. */ struct frec *blocking_query; /* Query which is blocking us. */
@@ -831,6 +834,12 @@ struct frec {
#define LEASE_HAVE_HWADDR 128 /* Have set hwaddress */ #define LEASE_HAVE_HWADDR 128 /* Have set hwaddress */
#define LEASE_EXP_CHANGED 256 /* Lease expiry time changed */ #define LEASE_EXP_CHANGED 256 /* Lease expiry time changed */
#define LIMIT_SIG_FAIL 0
#define LIMIT_CRYPTO 1
#define LIMIT_WORK 2
#define LIMIT_NSEC3_ITERS 3
#define LIMIT_MAX 4
struct dhcp_lease { struct dhcp_lease {
int clid_len; /* length of client identifier */ int clid_len; /* length of client identifier */
unsigned char *clid; /* clientid */ unsigned char *clid; /* clientid */
@@ -1240,6 +1249,7 @@ extern struct daemon {
int rr_status_sz; int rr_status_sz;
int dnssec_no_time_check; int dnssec_no_time_check;
int back_to_the_future; int back_to_the_future;
int limit[LIMIT_MAX];
#endif #endif
struct frec *frec_list; struct frec *frec_list;
struct frec_src *free_frec_src; struct frec_src *free_frec_src;
@@ -1415,10 +1425,12 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
/* dnssec.c */ /* dnssec.c */
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz); size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz);
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,
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); char *keyname, int class, int *validate_count);
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name,
char *keyname, int class, int *validate_count);
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, 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 check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_count);
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen); int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);
size_t filter_rrsigs(struct dns_header *header, size_t plen); size_t filter_rrsigs(struct dns_header *header, size_t plen);
int setup_timestamp(void); int setup_timestamp(void);
+289 -175
View File
@@ -424,12 +424,24 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
return 1; 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 /* Validate a single RRset (class, type, name) in the supplied DNS reply
Return code: Return code:
STAT_SECURE if it validates. STAT_SECURE if it validates.
STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion. 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.) (In this case *wildcard_out points to the "body" of the wildcard within name.)
STAT_BOGUS signature is wrong, bad packet. 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_KEY need DNSKEY to complete validation (name is returned in keyname)
STAT_NEED_DS need DS to complete validation (name is returned in keyname) STAT_NEED_DS need DS to complete validation (name is returned in keyname)
@@ -444,10 +456,10 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
*/ */
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, 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, 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; 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; struct crec *crecp = NULL;
short *rr_desc = rrfilter_desc(type); short *rr_desc = rrfilter_desc(type);
u32 sig_expiration, sig_inception; u32 sig_expiration, sig_inception;
@@ -467,7 +479,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname); rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);
/* Now try all the sigs to try and find one which validates */ /* 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; unsigned char *psav, *sig, *digest;
int i, wire_len, sig_len; int i, wire_len, sig_len;
@@ -654,19 +666,37 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
if (key) if (key)
{ {
if (algo_in == algo && keytag_in == key_tag && if (algo_in == algo && keytag_in == key_tag)
verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo)) {
if (dec_counter(validate_counter, NULL))
return STAT_ABANDONED;
if (verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo))
return STAT_SECURE; return STAT_SECURE;
} }
}
else else
{ {
/* iterate through all possible keys 4035 5.3.1 */ /* iterate through all possible keys 4035 5.3.1 */
for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
if (crecp->addr.key.algo == algo && if (crecp->addr.key.algo == algo &&
crecp->addr.key.keytag == key_tag && crecp->addr.key.keytag == key_tag &&
crecp->uid == (unsigned int)class && crecp->uid == (unsigned int)class)
verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo)) {
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; 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;
}
} }
} }
@@ -681,30 +711,36 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
STAT_OK Done, key(s) in cache. STAT_OK Done, key(s) in cache.
STAT_BOGUS No DNSKEYs found, which can be validated with DS, STAT_BOGUS No DNSKEYs found, which can be validated with DS,
or self-sign for DNSKEY RRset is not valid, bad packet. 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_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; 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; unsigned long ttl, sig_ttl;
struct blockdata *key;
union all_addr a; union all_addr a;
int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE | DNSSEC_FAIL_NOKEY; 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 || if (ntohs(header->qdcount) != 1 || RCODE(header) != NOERROR || !extract_name(header, plen, &p, name, 1, 4))
RCODE(header) == SERVFAIL || RCODE(header) == REFUSED ||
!extract_name(header, plen, &p, name, 1, 4))
return STAT_BOGUS | DNSSEC_FAIL_NOKEY; return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0) 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; 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 */ /* See if we have cached a DS record which validates this key */
if (!(crecp = cache_find_by_name(NULL, name, now, F_DS))) if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
{ {
@@ -713,7 +749,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
} }
/* 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 (j = ntohs(header->ancount); j != 0; j--)
{ {
/* Ensure we have type, class TTL and length */ /* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10))) if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
@@ -724,7 +760,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
GETLONG(ttl, p); GETLONG(ttl, p);
GETSHORT(rdlen, 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 */ return STAT_BOGUS; /* bad packet */
if (qclass != class || qtype != T_DNSKEY || rc == 2) if (qclass != class || qtype != T_DNSKEY || rc == 2)
@@ -733,97 +769,133 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
continue; continue;
} }
if (rdlen < 5)
return STAT_BOGUS; /* min 1 byte key! */
psave = p; psave = p;
GETSHORT(flags, p); GETSHORT(flags, p);
if (*p++ != 3) if (*p++ != 3)
return STAT_BOGUS | DNSSEC_FAIL_NOKEY; {
p = psave + rdlen;
continue;
}
algo = *p++; algo = *p++;
keytag = dnskey_keytag(algo, flags, p, rdlen - 4); keyaddr = p;
key = NULL; keytag = dnskey_keytag(algo, flags, keyaddr, rdlen - 4);
p = psave + rdlen;
/* key must have zone key flag set */ /* key must have zone key flag set */
if (flags & 0x100) if (!(flags & 0x100))
{
key = blockdata_alloc((char*)p, rdlen - 4);
failflags &= ~DNSSEC_FAIL_NOZONE;
}
p = psave;
if (!ADD_RDLEN(header, p, plen, rdlen))
{
if (key)
blockdata_free(key);
return STAT_BOGUS; /* bad packet */
}
/* No zone key flag or malloc failure */
if (!key)
continue; 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)) for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
{ {
void *ctx; void *ctx;
unsigned char *digest, *ds_digest; unsigned char *digest, *ds_digest;
const struct nettle_hash *hash; const struct nettle_hash *hash;
int sigcnt, rrcnt;
int wire_len; int wire_len;
if (recp1->addr.ds.algo == algo && if ((recp1->flags & F_NEG) ||
recp1->addr.ds.keytag == keytag && recp1->addr.ds.algo != algo ||
recp1->uid == (unsigned int)class) recp1->addr.ds.keytag != keytag ||
{ recp1->uid != (unsigned int)class)
failflags &= ~DNSSEC_FAIL_NOKEY; continue;
if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest)))) if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))))
continue; continue;
else
failflags &= ~DNSSEC_FAIL_NODSSUP; 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 (valid_digest[recp1->addr.ds.digest])
digest = cached_digest[recp1->addr.ds.digest];
else
{
/* computing a hash is a unit of crypto work. */
if (dec_counter(validate_counter, NULL))
return STAT_ABANDONED;
if (!hash_init(hash, &ctx, &digest)) if (!hash_init(hash, &ctx, &digest))
continue; continue;
wire_len = to_wire(name); wire_len = to_wire(name);
/* Note that digest may be different between DSs, so /* 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)wire_len, (unsigned char *)name);
hash->update(ctx, (unsigned int)rdlen, psave); hash->update(ctx, (unsigned int)rdlen, psave);
hash->digest(ctx, hash->digest_size, digest); hash->digest(ctx, hash->digest_size, digest);
from_wire(name); from_wire(name);
if (!(recp1->flags & F_NEG) && if (recp1->addr.ds.digest >= cached_digest_size)
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) &&
rrcnt != 0)
{ {
if (sigcnt == 0) unsigned char **new;
continue;
else /* whine_malloc zeros memory */
failflags &= ~DNSSEC_FAIL_NOSIG; if ((new = whine_malloc((recp1->addr.ds.digest + 5) * sizeof(unsigned char *))))
{
if (cached_digest_size != 0)
{
memcpy(new, cached_digest, cached_digest_size * sizeof(unsigned char *));
free(cached_digest);
}
cached_digest_size = recp1->addr.ds.digest + 5;
cached_digest = new;
}
}
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, rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
NULL, key, rdlen - 4, algo, keytag, &sig_ttl); NULL, key, rdlen - 4, algo, keytag, &sig_ttl, validate_counter);
failflags &= rc;
if (STAT_ISEQUAL(rc, STAT_SECURE))
{
valid = 1;
break;
}
}
}
}
blockdata_free(key); blockdata_free(key);
}
if (valid) 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. */ /* DNSKEY RRset determined to be OK, now cache it. */
cache_start_insert(); cache_start_insert();
@@ -847,23 +919,22 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
if (!CHECK_LEN(header, p, plen, rdlen)) if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
if (qclass == class && rc == 1)
{
psave = p; psave = p;
if (qtype == T_DNSKEY) if (qclass == class && rc == 1 && qtype == T_DNSKEY)
{ {
if (rdlen < 4) if (rdlen < 4)
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* min 1 byte key! */
GETSHORT(flags, p); GETSHORT(flags, p);
if (*p++ != 3) if (*p++ == 3)
return STAT_BOGUS; {
algo = *p++; algo = *p++;
keytag = dnskey_keytag(algo, flags, p, rdlen - 4); keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
if ((key = blockdata_alloc((char*)p, rdlen - 4))) if (!(key = blockdata_alloc((char*)p, rdlen - 4)))
{ return STAT_BOGUS;
a.key.keylen = rdlen - 4; a.key.keylen = rdlen - 4;
a.key.keydata = key; a.key.keydata = key;
a.key.algo = algo; a.key.algo = algo;
@@ -875,8 +946,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
blockdata_free(key); blockdata_free(key);
return STAT_BOGUS; return STAT_BOGUS;
} }
else
{
a.log.keytag = keytag; a.log.keytag = keytag;
a.log.algo = algo; a.log.algo = algo;
if (algo_digest_name(algo)) if (algo_digest_name(algo))
@@ -885,19 +955,16 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)", 0); log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)", 0);
} }
} }
}
p = psave; p = psave + rdlen;
}
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */
} }
/* commit cache insert. */ /* commit cache insert. */
cache_end_insert(); cache_end_insert();
return STAT_OK; return STAT_OK;
} }
}
}
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY", 0); log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY", 0);
return STAT_BOGUS | failflags; return STAT_BOGUS | failflags;
@@ -916,9 +983,11 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
STAT_BOGUS no DS in reply or not signed, fails validation, bad packet. 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_KEY DNSKEY records to validate a DS not found, name in keyname
STAT_NEED_DS DS record needed. 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); unsigned char *p = (unsigned char *)(header+1);
int qtype, qclass, rc, i, neganswer = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0; int qtype, qclass, rc, i, neganswer = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0;
@@ -943,7 +1012,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
servfail = neganswer = nons = 1; servfail = neganswer = nons = 1;
else else
{ {
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl); rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter);
if (STAT_ISEQUAL(rc, STAT_INSECURE)) if (STAT_ISEQUAL(rc, STAT_INSECURE))
{ {
@@ -977,6 +1046,8 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
for (i = 0; i < ntohs(header->ancount); i++) for (i = 0; i < ntohs(header->ancount); i++)
{ {
unsigned char *psave;
if (!(rc = extract_name(header, plen, &p, name, 0, 10))) if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
@@ -988,14 +1059,15 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
if (!CHECK_LEN(header, p, plen, rdlen)) if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
psave = p;
if (aclass == class && atype == T_DS && rc == 1) if (aclass == class && atype == T_DS && rc == 1)
{ {
int algo, digest, keytag; int algo, digest, keytag;
unsigned char *psave = p;
struct blockdata *key; struct blockdata *key;
if (rdlen < 4) if (rdlen < 5)
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* min 1 byte digest! */
GETSHORT(keytag, p); GETSHORT(keytag, p);
algo = *p++; algo = *p++;
@@ -1006,7 +1078,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
a.log.keytag = keytag; a.log.keytag = keytag;
a.log.algo = algo; a.log.algo = algo;
a.log.digest = digest; a.log.digest = digest;
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)", 0); log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu (not supported)", 0);
neg_ttl = ttl; neg_ttl = ttl;
} }
else if ((key = blockdata_alloc((char*)p, rdlen - 4))) else if ((key = blockdata_alloc((char*)p, rdlen - 4)))
@@ -1027,16 +1099,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
a.log.keytag = keytag; a.log.keytag = keytag;
a.log.algo = algo; a.log.algo = algo;
a.log.digest = digest; a.log.digest = digest;
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu", 0); log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu", 0);
found_supported = 1; found_supported = 1;
} }
} }
p = psave;
} }
if (!ADD_RDLEN(header, p, plen, rdlen)) p = psave + rdlen;
return STAT_BOGUS; /* bad packet */
} }
cache_end_insert(); cache_end_insert();
@@ -1139,6 +1208,7 @@ static int hostname_cmp(const char *a, const char *b)
} }
} }
/* 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, 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) char *workspace1_in, char *workspace2, char *name, int type, int *nons)
{ {
@@ -1158,12 +1228,12 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
p = nsecs[i]; p = nsecs[i];
if (!extract_name(header, plen, &p, workspace1, 1, 10)) if (!extract_name(header, plen, &p, workspace1, 1, 10))
return 0; return DNSSEC_FAIL_BADPACKET;
p += 8; /* class, type, TTL */ p += 8; /* class, type, TTL */
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
psave = p; psave = p;
if (!extract_name(header, plen, &p, workspace2, 1, 10)) if (!extract_name(header, plen, &p, workspace2, 1, 0))
return 0; return DNSSEC_FAIL_BADPACKET;
/* If NSEC comes from wildcard expansion, use original wildcard /* If NSEC comes from wildcard expansion, use original wildcard
as name for computation. */ as name for computation. */
@@ -1191,12 +1261,13 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
{ {
/* 4035 para 5.4. Last sentence */ /* 4035 para 5.4. Last sentence */
if (type == T_NSEC || type == T_RRSIG) if (type == T_NSEC || type == T_RRSIG)
return 1; return 0;
/* NSEC with the same name as the RR we're testing, check /* NSEC with the same name as the RR we're testing, check
that the type in question doesn't appear in the type map */ that the type in question doesn't appear in the type map */
rdlen -= p - psave; 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 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) if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
@@ -1207,25 +1278,25 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
/* A CNAME answer would also be valid, so if there's a CNAME is should /* A CNAME answer would also be valid, so if there's a CNAME is should
have been returned. */ have been returned. */
if ((p[2] & (0x80 >> T_CNAME)) != 0) 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 /* 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, DS from the wrong side of the delegation. For the root DS,
this is expected. */ this is expected. */
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0) if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
return 0; return DNSSEC_FAIL_NONSEC;
} }
while (rdlen >= 2) while (rdlen >= 2)
{ {
if (!CHECK_LEN(header, p, plen, rdlen)) if (!CHECK_LEN(header, p, plen, rdlen))
return 0; return DNSSEC_FAIL_BADPACKET;
if (p[0] == type >> 8) if (p[0] == type >> 8)
{ {
/* Does the NSEC say our type exists? */ /* Does the NSEC say our type exists? */
if (offset < p[1] && (p[offset+2] & mask) != 0) if (offset < p[1] && (p[offset+2] & mask) != 0)
return 0; return DNSSEC_FAIL_NONSEC;
break; /* finished checking */ break; /* finished checking */
} }
@@ -1234,24 +1305,24 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
p += p[1]; p += p[1];
} }
return 1; return 0;
} }
else if (rc == -1) else if (rc == -1)
{ {
/* Normal case, name falls between NSEC name and next domain name, /* Normal case, name falls between NSEC name and next domain name,
wrap around case, name falls between NSEC name (rc == -1) and end */ wrap around case, name falls between NSEC name (rc == -1) and end */
if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0) if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0)
return 1; return 0;
} }
else else
{ {
/* wrap around case, name falls between start and next domain name */ /* wrap around case, name falls between start and next domain name */
if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 ) 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 */ /* return digest length, or zero on error */
@@ -1325,13 +1396,16 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
for (i = 0; i < nsec_count; i++) for (i = 0; i < nsec_count; i++)
if ((p = nsecs[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))) !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
return 0; return 0;
p += 8; /* class, type, TTL */ p += 8; /* class, type, TTL */
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
psave = p; psave = p;
/* packet checked to be as long as implied by rdlen, salt_len and hash_len in prove_non_existence() */
p++; /* algo */ p++; /* algo */
flags = *p++; /* flags */ flags = *p++; /* flags */
p += 2; /* iterations */ p += 2; /* iterations */
@@ -1339,9 +1413,6 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
p += salt_len; /* salt */ p += salt_len; /* salt */
hash_len = *p++; /* p now points to next hashed name */ 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) if (digest_len == base32_len && hash_len == base32_len)
{ {
int rc = memcmp(workspace2, digest, digest_len); int rc = memcmp(workspace2, digest, digest_len);
@@ -1349,7 +1420,8 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
if (rc == 0) if (rc == 0)
{ {
/* We found an NSEC3 whose hashed name exactly matches the query, so /* 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 offset = (type & 0xff) >> 3;
int mask = 0x80 >> (type & 0x07); int mask = 0x80 >> (type & 0x07);
@@ -1357,9 +1429,6 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
p += hash_len; /* skip next-domain hash */ p += hash_len; /* skip next-domain hash */
rdlen -= p - psave; rdlen -= p - psave;
if (!CHECK_LEN(header, p, plen, rdlen))
return 0;
if (rdlen >= 2 && p[0] == 0) if (rdlen >= 2 && p[0] == 0)
{ {
/* If we can prove that there's no NS record, return that information. */ /* If we can prove that there's no NS record, return that information. */
@@ -1424,8 +1493,9 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
return 0; return 0;
} }
static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, /* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons) 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; unsigned char *salt, *p, *digest;
int digest_len, i, iterations, salt_len, base32_len, algo = 0; int digest_len, i, iterations, salt_len, base32_len, algo = 0;
@@ -1445,7 +1515,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
for (i = 0; i < nsec_count; i++) for (i = 0; i < nsec_count; i++)
{ {
if (!(p = skip_name(nsecs[i], header, plen, 15))) 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 */ p += 10; /* type, class, TTL, rdlen */
algo = *p++; algo = *p++;
@@ -1456,22 +1526,17 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
/* No usable NSEC3s */ /* No usable NSEC3s */
if (i == nsec_count) if (i == nsec_count)
return 0; return DNSSEC_FAIL_NONSEC;
p++; /* flags */ p++; /* flags */
GETSHORT (iterations, p); GETSHORT (iterations, p);
/* Upper-bound iterations, to avoid DoS. /* Upper-bound iterations, to avoid DoS. RFC 9276 refers. */
Strictly, there are lower bounds for small keys, but if (iterations > daemon->limit[LIMIT_NSEC3_ITERS])
since we don't have key size info here, at least limit return DNSSEC_FAIL_NSEC3_ITERS;
to the largest bound, for 4096-bit keys. RFC 5155 10.3 */
if (iterations > 2500)
return 0;
salt_len = *p++; salt_len = *p++;
salt = 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 */ /* Now prune so we only have NSEC3 records with same iterations, salt and algo */
for (i = 0; i < nsec_count; i++) for (i = 0; i < nsec_count; i++)
@@ -1502,9 +1567,6 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
if (salt_len != *p++) if (salt_len != *p++)
continue; continue;
if (!CHECK_LEN(header, p, plen, salt_len))
return 0; /* bad packet */
if (memcmp(p, salt, salt_len) != 0) if (memcmp(p, salt, salt_len) != 0)
continue; continue;
@@ -1512,11 +1574,14 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
nsecs[i] = nsec3p; 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) 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))) 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" /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3"
or an answer inferred from a wildcard record. */ or an answer inferred from a wildcard record. */
@@ -1531,15 +1596,20 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
if (wildname && hostname_isequal(closest_encloser, wildname)) if (wildname && hostname_isequal(closest_encloser, wildname))
break; 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) 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++) for (i = 0; i < nsec_count; i++)
if ((p = nsecs[i])) if ((p = nsecs[i]))
{ {
if (!extract_name(header, plen, &p, workspace1, 1, 0) || if (!extract_name(header, plen, &p, workspace1, 1, 0))
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) return DNSSEC_FAIL_BADPACKET;
return 0;
if (!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
return DNSSEC_FAIL_NONSEC;
if (digest_len == base32_len && if (digest_len == base32_len &&
memcmp(digest, workspace2, digest_len) == 0) memcmp(digest, workspace2, digest_len) == 0)
@@ -1554,14 +1624,17 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
while ((closest_encloser = strchr(closest_encloser, '.'))); while ((closest_encloser = strchr(closest_encloser, '.')));
if (!closest_encloser || !next_closest) if (!closest_encloser || !next_closest)
return 0; return DNSSEC_FAIL_NONSEC;
/* Look for NSEC3 that proves the non-existence of the next-closest encloser */ /* 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) 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)) 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 */ /* Finally, check that there's no seat of wildcard synthesis */
if (!wildname) if (!wildname)
@@ -1572,17 +1645,22 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
wildcard--; wildcard--;
*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) 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)) 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 unsigned char **nsecset = NULL, **rrsig_labels = NULL;
static int nsecset_sz = 0, rrsig_labels_sz = 0; static int nsecset_sz = 0, rrsig_labels_sz = 0;
@@ -1594,7 +1672,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
/* Move to NS section */ /* Move to NS section */
if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen))) if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
return 0; return DNSSEC_FAIL_BADPACKET;
auth_start = p; auth_start = p;
@@ -1603,13 +1681,16 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
unsigned char *pstart = p; unsigned char *pstart = p;
if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10)) if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
return 0; return DNSSEC_FAIL_BADPACKET;
GETSHORT(type, p); GETSHORT(type, p);
GETSHORT(class, p); GETSHORT(class, p);
GETLONG(ttl, p); GETLONG(ttl, p);
GETSHORT(rdlen, 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 (class == qclass && (type == T_NSEC || type == T_NSEC3))
{ {
if (nsec_ttl) if (nsec_ttl)
@@ -1622,12 +1703,12 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
/* No mixed NSECing 'round here, thankyouverymuch */ /* No mixed NSECing 'round here, thankyouverymuch */
if (type_found != 0 && type_found != type) if (type_found != 0 && type_found != type)
return 0; return DNSSEC_FAIL_NONSEC;
type_found = type; type_found = type;
if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found)) if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
return 0; return DNSSEC_FAIL_BADPACKET;
if (type == T_NSEC) if (type == T_NSEC)
{ {
@@ -1642,30 +1723,33 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
int res, j, rdlen1, type1, class1; int res, j, rdlen1, type1, class1;
if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found)) if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
return 0; return DNSSEC_FAIL_BADPACKET;
rrsig_labels[nsecs_found] = NULL; rrsig_labels[nsecs_found] = NULL;
for (j = ntohs(header->nscount); j != 0; j--) for (j = ntohs(header->nscount); j != 0; j--)
{ {
unsigned char *psav;
if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10))) if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
return 0; return DNSSEC_FAIL_BADPACKET;
GETSHORT(type1, p1); GETSHORT(type1, p1);
GETSHORT(class1, p1); GETSHORT(class1, p1);
p1 += 4; /* TTL */ p1 += 4; /* TTL */
GETSHORT(rdlen1, p1); GETSHORT(rdlen1, p1);
psav = p1;
if (!CHECK_LEN(header, p1, plen, rdlen1)) if (!CHECK_LEN(header, p1, plen, rdlen1))
return 0; return DNSSEC_FAIL_BADPACKET;
if (res == 1 && class1 == qclass && type1 == T_RRSIG) if (res == 1 && class1 == qclass && type1 == T_RRSIG)
{ {
int type_covered; int type_covered;
unsigned char *psav = p1;
if (rdlen1 < 18) if (rdlen1 < 18)
return 0; /* bad packet */ return DNSSEC_FAIL_BADPACKET; /* bad packet */
GETSHORT(type_covered, p1); GETSHORT(type_covered, p1);
@@ -1677,33 +1761,56 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
if (!rrsig_labels[nsecs_found]) if (!rrsig_labels[nsecs_found])
rrsig_labels[nsecs_found] = p1; rrsig_labels[nsecs_found] = p1;
else if (*rrsig_labels[nsecs_found] != *p1) /* algo */ else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
return 0; return DNSSEC_FAIL_NONSEC;
} }
p1 = psav;
} }
if (!ADD_RDLEN(header, p1, plen, rdlen1)) p1 = psav + rdlen1;
return 0;
} }
/* Must have found at least one sig. */ /* Must have found at least one sig. */
if (!rrsig_labels[nsecs_found]) 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; nsecset[nsecs_found++] = pstart;
} }
if (!ADD_RDLEN(header, p, plen, rdlen)) p += rdlen;
return 0;
} }
if (type_found == T_NSEC) if (type_found == T_NSEC)
return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons); return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
else if (type_found == T_NSEC3) 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 else
return 0; return DNSSEC_FAIL_NONSEC;
} }
/* Check signing status of name. /* Check signing status of name.
@@ -1798,6 +1905,7 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
STAT_BOGUS signature is wrong, bad packet, no validation where there should be. 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_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_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 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.
@@ -1807,7 +1915,7 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
if 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 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 unsigned char **targets = NULL;
static int target_sz = 0; static int target_sz = 0;
@@ -1816,7 +1924,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx; int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
int i, j, rc = STAT_INSECURE; int i, j, rc = STAT_INSECURE;
int secure = STAT_SECURE; int secure = STAT_SECURE;
int rc_nsec;
/* extend rr_status if necessary */ /* extend rr_status if necessary */
if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount)) if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
{ {
@@ -1982,9 +2090,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
{ {
unsigned long sig_ttl; unsigned long sig_ttl;
rc = validate_rrset(now, header, plen, class1, type1, sigcnt, 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 (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(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) if (class)
*class = class1; /* Class for DS or DNSKEY */ *class = class1; /* Class for DS or DNSKEY */
@@ -2018,8 +2126,8 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
That's not a problem since if the RRsets later fail That's not a problem since if the RRsets later fail
we'll return BOGUS then. */ we'll return BOGUS then. */
if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) && if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) &&
!prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL)) ((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL, validate_counter))) != 0)
return STAT_BOGUS | DNSSEC_FAIL_NONSEC; return (rc_nsec & DNSSEC_FAIL_WORK) ? STAT_ABANDONED : (STAT_BOGUS | rc_nsec);
rc = STAT_SECURE; rc = STAT_SECURE;
} }
@@ -2044,20 +2152,24 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
/* For anything other than a DS record, this situation is OK if either /* 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. */ 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)) 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 */ /* Empty DS without NSECS */
if (qtype == T_DS) if (qtype == T_DS)
return STAT_BOGUS | DNSSEC_FAIL_NONSEC; return STAT_BOGUS | rc_nsec;
if (!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE)) if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) &&
!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
{ {
if (class) if (class)
*class = qclass; /* Class for NEED_DS or NEED_KEY */ *class = qclass; /* Class for NEED_DS or NEED_KEY */
return rc; return rc;
} }
return STAT_BOGUS | DNSSEC_FAIL_NONSEC; /* signed zone, no NSECs */ return STAT_BOGUS | rc_nsec; /* signed zone, no NSECs */
} }
} }
@@ -2139,6 +2251,8 @@ int errflags_to_ede(int status)
return EDE_NO_DNSKEY; return EDE_NO_DNSKEY;
else if (status & DNSSEC_FAIL_NODSSUP) else if (status & DNSSEC_FAIL_NODSSUP)
return EDE_USUPDS; return EDE_USUPDS;
else if (status & DNSSEC_FAIL_NSEC3_ITERS)
return EDE_UNS_NS3_ITER;
else if (status & DNSSEC_FAIL_NONSEC) else if (status & DNSSEC_FAIL_NONSEC)
return EDE_NO_NSEC; return EDE_NO_NSEC;
else if (status & DNSSEC_FAIL_INDET) else if (status & DNSSEC_FAIL_INDET)
+73 -25
View File
@@ -338,7 +338,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
if (ad_reqd) if (ad_reqd)
forward->flags |= FREC_AD_QUESTION; forward->flags |= FREC_AD_QUESTION;
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
forward->work_counter = DNSSEC_WORK; forward->work_counter = daemon->limit[LIMIT_WORK];
forward->validate_counter = daemon->limit[LIMIT_CRYPTO];
if (do_bit) if (do_bit)
forward->flags |= FREC_DO_QUESTION; forward->flags |= FREC_DO_QUESTION;
#endif #endif
@@ -892,6 +893,9 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
static void dnssec_validate(struct frec *forward, struct dns_header *header, static void dnssec_validate(struct frec *forward, struct dns_header *header,
ssize_t plen, int status, time_t now) ssize_t plen, int status, time_t now)
{ {
struct frec *orig;
int log_resource = 0;
daemon->log_display_id = forward->frec_src.log_id; daemon->log_display_id = forward->frec_src.log_id;
/* We've had a reply already, which we're validating. Ignore this duplicate */ /* We've had a reply already, which we're validating. Ignore this duplicate */
@@ -917,26 +921,27 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
} }
} }
/* Find the original query that started it all.... */
for (orig = forward; orig->dependent; orig = orig->dependent);
/* As soon as anything returns BOGUS, we stop and unwind, to do otherwise /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
would invite infinite loops, since the answers to DNSKEY and DS queries would invite infinite loops, since the answers to DNSKEY and DS queries
will not be cached, so they'll be repeated. */ will not be cached, so they'll be repeated. */
if (!STAT_ISEQUAL(status, STAT_BOGUS) && !STAT_ISEQUAL(status, STAT_TRUNCATED) && !STAT_ISEQUAL(status, STAT_ABANDONED)) if (!STAT_ISEQUAL(status, STAT_BOGUS) && !STAT_ISEQUAL(status, STAT_TRUNCATED) && !STAT_ISEQUAL(status, STAT_ABANDONED))
{ {
if (forward->flags & FREC_DNSKEY_QUERY) if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class); status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
else if (forward->flags & FREC_DS_QUERY) else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class); status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
else else
status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class, status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class,
!option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC), !option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC),
NULL, NULL, NULL); NULL, NULL, NULL, &orig->validate_counter);
#ifdef HAVE_DUMPFILE
if (STAT_ISEQUAL(status, STAT_BOGUS))
dump_packet_udp((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS,
header, (size_t)plen, &forward->sentto->addr, NULL, -daemon->port);
#endif
} }
if (STAT_ISEQUAL(status, STAT_ABANDONED))
log_resource = 1;
/* Can't validate, as we're missing key data. Put this /* Can't validate, as we're missing key data. Put this
answer aside, whilst we get that. */ answer aside, whilst we get that. */
if (STAT_ISEQUAL(status, STAT_NEED_DS) || STAT_ISEQUAL(status, STAT_NEED_KEY)) if (STAT_ISEQUAL(status, STAT_NEED_DS) || STAT_ISEQUAL(status, STAT_NEED_KEY))
@@ -979,18 +984,19 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
return; return;
} }
} }
else if (orig->work_counter-- == 0)
{
my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries"));
log_resource = 1;
}
else else
{ {
struct server *server; struct server *server;
struct frec *orig;
void *hash; void *hash;
size_t nn; size_t nn;
int serverind, fd; int serverind, fd;
struct randfd_list *rfds = NULL; struct randfd_list *rfds = NULL;
/* Find the original query that started it all.... */
for (orig = forward; orig->dependent; orig = orig->dependent);
/* Make sure we don't expire and free the orig frec during the /* Make sure we don't expire and free the orig frec during the
allocation of a new one: third arg of get_new_frec() does that. */ allocation of a new one: third arg of get_new_frec() does that. */
if ((serverind = dnssec_server(forward->sentto, daemon->keyname, NULL, NULL)) != -1 && if ((serverind = dnssec_server(forward->sentto, daemon->keyname, NULL, NULL)) != -1 &&
@@ -999,7 +1005,6 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
daemon->keyname, forward->class, daemon->keyname, forward->class,
STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz)) && STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz)) &&
(hash = hash_questions(header, nn, daemon->namebuff)) && (hash = hash_questions(header, nn, daemon->namebuff)) &&
--orig->work_counter != 0 &&
(fd = allocate_rfd(&rfds, server)) != -1 && (fd = allocate_rfd(&rfds, server)) != -1 &&
(new = get_new_frec(now, server, 1))) (new = get_new_frec(now, server, 1)))
{ {
@@ -1065,6 +1070,21 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
status = STAT_ABANDONED; status = STAT_ABANDONED;
} }
if (log_resource)
{
/* Log the actual validation that made us barf. */
unsigned char *p = (unsigned char *)(header+1);
if (extract_name(header, plen, &p, daemon->namebuff, 0, 4) == 1)
my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."),
daemon->namebuff[0] ? daemon->namebuff : ".");
}
#ifdef HAVE_DUMPFILE
if (STAT_ISEQUAL(status, STAT_BOGUS) || STAT_ISEQUAL(status, STAT_ABANDONED))
dump_packet_udp((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS,
header, (size_t)plen, &forward->sentto->addr, NULL, -daemon->port);
#endif
/* Validated original answer, all done. */ /* Validated original answer, all done. */
if (!forward->dependent) if (!forward->dependent)
return_reply(now, forward, header, plen, status); return_reply(now, forward, header, plen, status);
@@ -1337,6 +1357,12 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
log_query(F_SECSTAT, domain, &a, result, 0); log_query(F_SECSTAT, domain, &a, result, 0);
} }
} }
if ((daemon->limit[LIMIT_CRYPTO] - forward->validate_counter) > (int)daemon->metrics[METRIC_CRYPTO_HWM])
daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit[LIMIT_CRYPTO] - forward->validate_counter;
if ((daemon->limit[LIMIT_WORK] - forward->work_counter) > (int)daemon->metrics[METRIC_WORK_HWM])
daemon->metrics[METRIC_WORK_HWM] = daemon->limit[LIMIT_WORK] - forward->work_counter;
#endif #endif
if (option_bool(OPT_NO_REBIND)) if (option_bool(OPT_NO_REBIND))
@@ -2018,7 +2044,7 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
/* Recurse down the key hierarchy */ /* Recurse down the key hierarchy */
static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n,
int class, char *name, char *keyname, struct server *server, int class, char *name, char *keyname, struct server *server,
int have_mark, unsigned int mark, int *keycount) int have_mark, unsigned int mark, int *keycount, int *validatecount)
{ {
int first, last, start, new_status; int first, last, start, new_status;
unsigned char *packet = NULL; unsigned char *packet = NULL;
@@ -2030,20 +2056,34 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
int log_save; int log_save;
/* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
if (--(*keycount) == 0) if (STAT_ISEQUAL(status, STAT_NEED_KEY))
new_status = STAT_ABANDONED; new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class, validatecount);
else if (STAT_ISEQUAL(status, STAT_NEED_KEY))
new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
else if (STAT_ISEQUAL(status, STAT_NEED_DS)) else if (STAT_ISEQUAL(status, STAT_NEED_DS))
new_status = dnssec_validate_ds(now, header, n, name, keyname, class); new_status = dnssec_validate_ds(now, header, n, name, keyname, class, validatecount);
else else
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, new_status = dnssec_validate_reply(now, header, n, name, keyname, &class,
!option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC), !option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC),
NULL, NULL, NULL); NULL, NULL, NULL, validatecount);
if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY)) if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY) && !STAT_ISEQUAL(new_status, STAT_ABANDONED))
break; break;
if ((*keycount)-- == 0)
{
my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries"));
new_status = STAT_ABANDONED;
}
if (STAT_ISEQUAL(new_status, STAT_ABANDONED))
{
/* Log the actual validation that made us barf. */
unsigned char *p = (unsigned char *)(header+1);
if (extract_name(header, n, &p, daemon->namebuff, 0, 4) == 1)
my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."),
daemon->namebuff[0] ? daemon->namebuff : ".");
break;
}
/* Can't validate because we need a key/DS whose name now in keyname. /* Can't validate because we need a key/DS whose name now in keyname.
Make query for same, and recurse to validate */ Make query for same, and recurse to validate */
if (!packet) if (!packet)
@@ -2074,7 +2114,8 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, keyname, &server->addr, log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, keyname, &server->addr,
STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0); STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0);
new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount); new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server,
have_mark, mark, keycount, validatecount);
daemon->log_display_id = log_save; daemon->log_display_id = log_save;
@@ -2379,9 +2420,10 @@ unsigned char *tcp_request(int confd, time_t now,
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (master->flags & SERV_DO_DNSSEC)) if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (master->flags & SERV_DO_DNSSEC))
{ {
int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ int keycount = daemon->limit[LIMIT_WORK]; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
int validatecount = daemon->limit[LIMIT_CRYPTO];
int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname,
serv, have_mark, mark, &keycount); serv, have_mark, mark, &keycount, &validatecount);
char *result, *domain = "result"; char *result, *domain = "result";
union all_addr a; union all_addr a;
@@ -2407,6 +2449,12 @@ unsigned char *tcp_request(int confd, time_t now,
} }
log_query(F_SECSTAT, domain, &a, result, 0); log_query(F_SECSTAT, domain, &a, result, 0);
if ((daemon->limit[LIMIT_CRYPTO] - validatecount) > (int)daemon->metrics[METRIC_CRYPTO_HWM])
daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit[LIMIT_CRYPTO] - validatecount;
if ((daemon->limit[LIMIT_WORK] - keycount) > (int)daemon->metrics[METRIC_WORK_HWM])
daemon->metrics[METRIC_WORK_HWM] = daemon->limit[LIMIT_WORK] - keycount;
} }
#endif #endif
+3
View File
@@ -24,6 +24,9 @@ const char * metric_names[] = {
"dns_local_answered", "dns_local_answered",
"dns_stale_answered", "dns_stale_answered",
"dns_unanswered", "dns_unanswered",
"dnssec_max_crypto_use",
"dnssec_max_sig_fail",
"dnssec_max_work",
"bootp", "bootp",
"pxe", "pxe",
"dhcp_ack", "dhcp_ack",
+3
View File
@@ -23,6 +23,9 @@ enum {
METRIC_DNS_LOCAL_ANSWERED, METRIC_DNS_LOCAL_ANSWERED,
METRIC_DNS_STALE_ANSWERED, METRIC_DNS_STALE_ANSWERED,
METRIC_DNS_UNANSWERED_QUERY, METRIC_DNS_UNANSWERED_QUERY,
METRIC_CRYPTO_HWM,
METRIC_SIG_FAIL_HWM,
METRIC_WORK_HWM,
METRIC_BOOTP, METRIC_BOOTP,
METRIC_PXE, METRIC_PXE,
METRIC_DHCPACK, METRIC_DHCPACK,
+27 -1
View File
@@ -191,6 +191,7 @@ struct myoption {
#define LOPT_NO_DHCP6 382 #define LOPT_NO_DHCP6 382
#define LOPT_NO_DHCP4 383 #define LOPT_NO_DHCP4 383
#define LOPT_MAX_PROCS 384 #define LOPT_MAX_PROCS 384
#define LOPT_DNSSEC_LIMITS 385
#ifdef HAVE_GETOPT_LONG #ifdef HAVE_GETOPT_LONG
static const struct option opts[] = static const struct option opts[] =
@@ -364,6 +365,7 @@ static const struct myoption opts[] =
{ "dnssec-check-unsigned", 2, 0, LOPT_DNSSEC_CHECK }, { "dnssec-check-unsigned", 2, 0, LOPT_DNSSEC_CHECK },
{ "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME }, { "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME },
{ "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP }, { "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP },
{ "dnssec-limits", 1, 0, LOPT_DNSSEC_LIMITS },
{ "dhcp-relay", 1, 0, LOPT_RELAY }, { "dhcp-relay", 1, 0, LOPT_RELAY },
{ "ra-param", 1, 0, LOPT_RA_PARAM }, { "ra-param", 1, 0, LOPT_RA_PARAM },
{ "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP }, { "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP },
@@ -568,6 +570,7 @@ static struct {
{ LOPT_DNSSEC_CHECK, ARG_DUP, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL }, { LOPT_DNSSEC_CHECK, ARG_DUP, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL },
{ LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL }, { LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL },
{ LOPT_DNSSEC_STAMP, ARG_ONE, "<path>", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL }, { LOPT_DNSSEC_STAMP, ARG_ONE, "<path>", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL },
{ LOPT_DNSSEC_LIMITS, ARG_ONE, "<limit>,..", gettext_noop("Set resource limits for DNSSEC validation"), NULL },
{ LOPT_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>]", gettext_noop("Set MTU, priority, resend-interval and router-lifetime"), NULL }, { LOPT_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>]", gettext_noop("Set MTU, priority, resend-interval and router-lifetime"), NULL },
{ LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL }, { LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL },
{ LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL }, { LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL },
@@ -5258,6 +5261,24 @@ err:
} }
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
case LOPT_DNSSEC_LIMITS:
{
int lim, val;
for (lim = LIMIT_SIG_FAIL; arg && lim < LIMIT_MAX ; lim++, arg = comma)
{
comma = split(arg);
if (!atoi_check(arg, &val))
ret_err(gen_err);
if (val != 0)
daemon->limit[lim] = val;
}
break;
}
case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */ case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */
daemon->timestamp_file = opt_string_alloc(arg); daemon->timestamp_file = opt_string_alloc(arg);
break; break;
@@ -5869,7 +5890,12 @@ void read_opts(int argc, char **argv, char *compile_opts)
daemon->randport_limit = 1; daemon->randport_limit = 1;
daemon->host_index = SRC_AH; daemon->host_index = SRC_AH;
daemon->max_procs = MAX_PROCS; daemon->max_procs = MAX_PROCS;
daemon->max_procs_used = 0; #ifdef HAVE_DNSSEC
daemon->limit[LIMIT_SIG_FAIL] = DNSSEC_LIMIT_SIG_FAIL;
daemon->limit[LIMIT_CRYPTO] = DNSSEC_LIMIT_CRYPTO;
daemon->limit[LIMIT_WORK] = DNSSEC_LIMIT_WORK;
daemon->limit[LIMIT_NSEC3_ITERS] = DNSSEC_LIMIT_NSEC3_ITERS;
#endif
/* See comment above make_servers(). Optimises server-read code. */ /* See comment above make_servers(). Optimises server-read code. */
mark_servers(0); mark_servers(0);
-1
View File
@@ -875,7 +875,6 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (!option_bool(OPT_DNSSEC_VALID) || aqtype != T_RRSIG) if (!option_bool(OPT_DNSSEC_VALID) || aqtype != T_RRSIG)
#endif #endif
if (qtype != T_ANY)
log_query(secflag | F_FORWARD | F_UPSTREAM | F_RRNAME, name, NULL, NULL, aqtype); log_query(secflag | F_FORWARD | F_UPSTREAM | F_RRNAME, name, NULL, NULL, aqtype);
} }
else if (!(flags & F_NXDOMAIN)) else if (!(flags & F_NXDOMAIN))