mirror of
http://thekelleys.org.uk/git/dnsmasq.git
synced 2026-11-15 19:13:49 +00:00
Compare commits
17
Commits
v2.90test4
...
v2.90deb2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3705ec5592 | ||
|
|
b6769234bc | ||
|
|
214a046f47 | ||
|
|
b38da6b191 | ||
|
|
9621c16a78 | ||
|
|
3ae7f1ab0d | ||
|
|
39de57499e | ||
|
|
3c91bca943 | ||
|
|
76bceb06c4 | ||
|
|
6f23a0a75e | ||
|
|
06945c4b77 | ||
|
|
c5aa221e44 | ||
|
|
bfefd6e38c | ||
|
|
59d30390c9 | ||
|
|
51471cafa5 | ||
|
|
be73efc020 | ||
|
|
40595f80d9 |
No files matched your search
@@ -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
|
||||||
|
|||||||
Vendored
+11
-1
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+36
@@ -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;
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||||
|
|||||||
+290
-176
@@ -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 DNSSEC_FAIL_NONSEC;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
/* 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 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)
|
|
||||||
{
|
{
|
||||||
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
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
Reference in new issue
Block a user