mirror of
http://thekelleys.org.uk/git/dnsmasq.git
synced 2026-10-22 11:46:09 +00:00
Fix a corner-case in DNSSEC validation with wildcards.
If we have a wildcard record *.example.com and recieve a query for a.example.com then that's OK, but we have to check that there isn't an actual a.example.com record. The corner case is when we get a query for *.example.com in that case the non-existence check is not required, but was being done. Thanks to Jan Breig for spotting this.
This commit is contained in:
3 files changed
+36
-28
No files matched your search
@@ -1,3 +1,13 @@
|
|||||||
|
version 2.93
|
||||||
|
Fix a corner-case in DNSSEC validation with wildcards. If we have
|
||||||
|
a wildcard record *.example.com and recieve a query for
|
||||||
|
a.example.com then that's OK, but we have to check that there isn't
|
||||||
|
an actual a.example.com record. The corner case is when we get a
|
||||||
|
query for *.example.com in that case the non-existence check
|
||||||
|
is not required, was being done. Thanks to Jan Breig for
|
||||||
|
spotting this.
|
||||||
|
|
||||||
|
|
||||||
version 2.92
|
version 2.92
|
||||||
Redesign the interaction between DNSSEC validation and per-domain
|
Redesign the interaction between DNSSEC validation and per-domain
|
||||||
servers, specified as --server=/<domain>/<ip-address>. This should
|
servers, specified as --server=/<domain>/<ip-address>. This should
|
||||||
|
|||||||
+3
-4
@@ -761,10 +761,9 @@ struct dyndir {
|
|||||||
#define STAT_NEED_DS 0x40000
|
#define STAT_NEED_DS 0x40000
|
||||||
#define STAT_NEED_KEY 0x50000
|
#define STAT_NEED_KEY 0x50000
|
||||||
#define STAT_TRUNCATED 0x60000
|
#define STAT_TRUNCATED 0x60000
|
||||||
#define STAT_SECURE_WILDCARD 0x70000
|
#define STAT_OK 0x70000
|
||||||
#define STAT_OK 0x80000
|
#define STAT_ABANDONED 0x80000
|
||||||
#define STAT_ABANDONED 0x90000
|
#define STAT_ASYNC 0x90000
|
||||||
#define STAT_ASYNC 0xa0000
|
|
||||||
|
|
||||||
#define DNSSEC_FAIL_NYV 0x0001 /* key not yet valid */
|
#define DNSSEC_FAIL_NYV 0x0001 /* key not yet valid */
|
||||||
#define DNSSEC_FAIL_EXP 0x0002 /* key expired */
|
#define DNSSEC_FAIL_EXP 0x0002 /* key expired */
|
||||||
|
|||||||
+23
-24
@@ -438,8 +438,6 @@ int dec_counter(int *counter, char *message)
|
|||||||
/* 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.
|
|
||||||
(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_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)
|
||||||
@@ -455,7 +453,7 @@ int dec_counter(int *counter, char *message)
|
|||||||
ttl_out is the floor on TTL, based on TTL and orig_ttl and expiration of sig used to validate.
|
ttl_out is the floor on TTL, based on TTL and orig_ttl and expiration of sig used to validate.
|
||||||
*/
|
*/
|
||||||
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, int *wildcard_offset_out, struct blockdata *key, int keylen,
|
||||||
int algo_in, int keytag_in, unsigned long *ttl_out, int *validate_counter)
|
int algo_in, int keytag_in, unsigned long *ttl_out, int *validate_counter)
|
||||||
{
|
{
|
||||||
unsigned char *p;
|
unsigned char *p;
|
||||||
@@ -468,8 +466,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
unsigned long curtime = time(0);
|
unsigned long curtime = time(0);
|
||||||
int time_check = is_check_date(curtime);
|
int time_check = is_check_date(curtime);
|
||||||
|
|
||||||
if (wildcard_out)
|
if (wildcard_offset_out)
|
||||||
*wildcard_out = NULL;
|
*wildcard_offset_out = 0;
|
||||||
|
|
||||||
name_labels = count_labels(name); /* For 4035 5.3.2 check */
|
name_labels = count_labels(name); /* For 4035 5.3.2 check */
|
||||||
|
|
||||||
@@ -574,7 +572,9 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
name_start = name;
|
name_start = name;
|
||||||
|
|
||||||
/* if more labels than in RRsig name, hash *.<no labels in rrsig labels field> 4035 5.3.2 */
|
/* if more labels than in RRsig name, hash *.<no labels in rrsig labels field> 4035 5.3.2 */
|
||||||
if (labels < name_labels)
|
/* If the name is already the wildcard, we're not going to change it. */
|
||||||
|
if (labels < name_labels &&
|
||||||
|
!(name_labels - labels == 1 && name_start[0] == '*' && name_start[1] == '.'))
|
||||||
{
|
{
|
||||||
for (j = name_labels - labels; j != 0; j--)
|
for (j = name_labels - labels; j != 0; j--)
|
||||||
{
|
{
|
||||||
@@ -584,11 +584,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
name_start++;
|
name_start++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wildcard_out)
|
if (wildcard_offset_out)
|
||||||
*wildcard_out = name_start+1;
|
*wildcard_offset_out = name_start - name + 1;
|
||||||
|
|
||||||
name_start--;
|
name_start--;
|
||||||
*name_start = '*';
|
*name_start = '*';
|
||||||
}
|
}
|
||||||
|
|
||||||
wire_len = to_wire(name_start);
|
wire_len = to_wire(name_start);
|
||||||
@@ -687,7 +687,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
return STAT_ABANDONED;
|
return STAT_ABANDONED;
|
||||||
|
|
||||||
if (verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
|
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 STAT_SECURE;
|
||||||
|
|
||||||
/* An attacker can waste a lot of our CPU by setting up a giant DNSKEY RRSET full of failing
|
/* 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
|
keys, all of which we have to try. Since many failing keys is not likely for
|
||||||
@@ -1545,7 +1545,7 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
|
|||||||
|
|
||||||
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
|
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
|
||||||
static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, char *workspace1,
|
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)
|
char *workspace2, char *name, int type, int wild_offset, 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;
|
||||||
@@ -1643,9 +1643,9 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
|||||||
if (*closest_encloser == '.')
|
if (*closest_encloser == '.')
|
||||||
closest_encloser++;
|
closest_encloser++;
|
||||||
|
|
||||||
if (wildname && hostname_isequal(closest_encloser, wildname))
|
if (wild_offset != 0 && name - closest_encloser == wild_offset)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (dec_counter(validate_counter, NULL))
|
if (dec_counter(validate_counter, NULL))
|
||||||
return DNSSEC_FAIL_WORK;
|
return DNSSEC_FAIL_WORK;
|
||||||
|
|
||||||
@@ -1687,7 +1687,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
|||||||
return DNSSEC_FAIL_NONSEC;
|
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 (wild_offset == 0)
|
||||||
{
|
{
|
||||||
if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)
|
if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)
|
||||||
return DNSSEC_FAIL_NONSEC;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
@@ -1710,7 +1710,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
|||||||
|
|
||||||
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
|
/* 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,
|
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)
|
int wild_offset, int *nons, int *nsec_ttl, int *validate_counter)
|
||||||
{
|
{
|
||||||
static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
|
static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
|
||||||
static int nsecset_sz = 0, rrsig_labels_sz = 0;
|
static int nsecset_sz = 0, rrsig_labels_sz = 0;
|
||||||
@@ -1858,7 +1858,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
|
|||||||
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, validate_counter);
|
return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wild_offset, nons, validate_counter);
|
||||||
else
|
else
|
||||||
return DNSSEC_FAIL_NONSEC;
|
return DNSSEC_FAIL_NONSEC;
|
||||||
}
|
}
|
||||||
@@ -2183,8 +2183,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Not done, validate now */
|
/* Not done, validate now */
|
||||||
int sigcnt, rrcnt;
|
int sigcnt, rrcnt, wild_offset;
|
||||||
char *wildname;
|
|
||||||
|
|
||||||
if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
|
if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
|
||||||
return STAT_BOGUS;
|
return STAT_BOGUS;
|
||||||
@@ -2238,7 +2237,7 @@ 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, validate_counter);
|
rrcnt, name, keyname, &wild_offset, 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) || STAT_ISEQUAL(rc, STAT_ABANDONED))
|
if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS) || STAT_ISEQUAL(rc, STAT_ABANDONED))
|
||||||
{
|
{
|
||||||
@@ -2247,7 +2246,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
|
/* rc is now STAT_SECURE */
|
||||||
|
|
||||||
/* Note that RR is validated */
|
/* Note that RR is validated */
|
||||||
daemon->rr_status[i] = sig_ttl;
|
daemon->rr_status[i] = sig_ttl;
|
||||||
@@ -2273,8 +2272,8 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
Note that we may not yet have validated the NSEC/NSEC3 RRsets.
|
Note that we may not yet have validated the NSEC/NSEC3 RRsets.
|
||||||
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 (wild_offset != 0 &&
|
||||||
((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL, validate_counter))) != 0)
|
((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wild_offset, NULL, NULL, validate_counter))) != 0)
|
||||||
return (rc_nsec & DNSSEC_FAIL_WORK) ? STAT_ABANDONED : (STAT_BOGUS | rc_nsec);
|
return (rc_nsec & DNSSEC_FAIL_WORK) ? STAT_ABANDONED : (STAT_BOGUS | rc_nsec);
|
||||||
|
|
||||||
rc = STAT_SECURE;
|
rc = STAT_SECURE;
|
||||||
@@ -2302,7 +2301,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
the answer is in an unsigned zone, or there's NSEC records.
|
the answer is in an unsigned zone, or there's NSEC records.
|
||||||
For a DS record, we return INSECURE, which almost always turns
|
For a DS record, we return INSECURE, which almost always turns
|
||||||
into BOGUS in the caller. */
|
into BOGUS in the caller. */
|
||||||
if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl, validate_counter)) != 0)
|
if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, 0, nons, nsec_ttl, validate_counter)) != 0)
|
||||||
{
|
{
|
||||||
if (rc_nsec & DNSSEC_FAIL_WORK)
|
if (rc_nsec & DNSSEC_FAIL_WORK)
|
||||||
return STAT_ABANDONED;
|
return STAT_ABANDONED;
|
||||||
|
|||||||
Reference in new issue
Block a user