mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 10:18:25 +00:00
DNSSEC fix for wildcard NSEC records. CVE-2017-15107 applies.
It's OK for NSEC records to be expanded from wildcards, but in that case, the proof of non-existence is only valid starting at the wildcard name, *.<domain> NOT the name expanded from the wildcard. Without this check it's possible for an attacker to craft an NSEC which wrongly proves non-existence in a domain which includes a wildcard for NSEC.
This commit is contained in:
10
CHANGELOG
10
CHANGELOG
@@ -31,6 +31,16 @@ version 2.79
|
|||||||
--bridge-interface=int1,alias1,alias2
|
--bridge-interface=int1,alias1,alias2
|
||||||
Thanks to Neil Jerram for work on this.
|
Thanks to Neil Jerram for work on this.
|
||||||
|
|
||||||
|
Fix for DNSSEC with wildcard-derived NSEC records.
|
||||||
|
It's OK for NSEC records to be expanded from wildcards,
|
||||||
|
but in that case, the proof of non-existence is only valid
|
||||||
|
starting at the wildcard name, *.<domain> NOT the name expanded
|
||||||
|
from the wildcard. Without this check it's possible for an
|
||||||
|
attacker to craft an NSEC which wrongly proves non-existence.
|
||||||
|
Thanks to Ralph Dolmans for finding this, and co-ordinating
|
||||||
|
the vulnerability tracking and fix release.
|
||||||
|
CVE-2017-15107 applies.
|
||||||
|
|
||||||
|
|
||||||
version 2.78
|
version 2.78
|
||||||
Fix logic of appending ".<layer>" to PXE basename. Thanks to Chris
|
Fix logic of appending ".<layer>" to PXE basename. Thanks to Chris
|
||||||
|
|||||||
109
src/dnssec.c
109
src/dnssec.c
@@ -103,15 +103,17 @@ static void from_wire(char *name)
|
|||||||
static int count_labels(char *name)
|
static int count_labels(char *name)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
char *p;
|
||||||
|
|
||||||
if (*name == 0)
|
if (*name == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
for (i = 0; *name; name++)
|
for (p = name, i = 0; *p; p++)
|
||||||
if (*name == '.')
|
if (*p == '.')
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
return i+1;
|
/* Don't count empty first label. */
|
||||||
|
return *name == '.' ? i : i+1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Implement RFC1982 wrapped compare for 32-bit numbers */
|
/* Implement RFC1982 wrapped compare for 32-bit numbers */
|
||||||
@@ -1094,8 +1096,8 @@ static int hostname_cmp(const char *a, const char *b)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, 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, char *workspace2, char *name, int type, int *nons)
|
char *workspace1_in, char *workspace2, char *name, int type, int *nons)
|
||||||
{
|
{
|
||||||
int i, rc, rdlen;
|
int i, rc, rdlen;
|
||||||
unsigned char *p, *psave;
|
unsigned char *p, *psave;
|
||||||
@@ -1108,6 +1110,9 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
|||||||
/* Find NSEC record that proves name doesn't exist */
|
/* Find NSEC record that proves name doesn't exist */
|
||||||
for (i = 0; i < nsec_count; i++)
|
for (i = 0; i < nsec_count; i++)
|
||||||
{
|
{
|
||||||
|
char *workspace1 = workspace1_in;
|
||||||
|
int sig_labels, name_labels;
|
||||||
|
|
||||||
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 0;
|
||||||
@@ -1117,6 +1122,26 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
|
|||||||
if (!extract_name(header, plen, &p, workspace2, 1, 10))
|
if (!extract_name(header, plen, &p, workspace2, 1, 10))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
/* If NSEC comes from wildcard expansion, use original wildcard
|
||||||
|
as name for computation. */
|
||||||
|
sig_labels = *labels[i];
|
||||||
|
name_labels = count_labels(workspace1);
|
||||||
|
|
||||||
|
if (sig_labels < name_labels)
|
||||||
|
{
|
||||||
|
int k;
|
||||||
|
for (k = name_labels - sig_labels; k != 0; k--)
|
||||||
|
{
|
||||||
|
while (*workspace1 != '.' && *workspace1 != 0)
|
||||||
|
workspace1++;
|
||||||
|
if (k != 1 && *workspace1 == '.')
|
||||||
|
workspace1++;
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace1--;
|
||||||
|
*workspace1 = '*';
|
||||||
|
}
|
||||||
|
|
||||||
rc = hostname_cmp(workspace1, name);
|
rc = hostname_cmp(workspace1, name);
|
||||||
|
|
||||||
if (rc == 0)
|
if (rc == 0)
|
||||||
@@ -1514,22 +1539,24 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
|
|||||||
|
|
||||||
static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons)
|
static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons)
|
||||||
{
|
{
|
||||||
static unsigned char **nsecset = NULL;
|
static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
|
||||||
static int nsecset_sz = 0;
|
static int nsecset_sz = 0, rrsig_labels_sz = 0;
|
||||||
|
|
||||||
int type_found = 0;
|
int type_found = 0;
|
||||||
unsigned char *p = skip_questions(header, plen);
|
unsigned char *auth_start, *p = skip_questions(header, plen);
|
||||||
int type, class, rdlen, i, nsecs_found;
|
int type, class, rdlen, i, nsecs_found;
|
||||||
|
|
||||||
/* 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 0;
|
||||||
|
|
||||||
|
auth_start = p;
|
||||||
|
|
||||||
for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--)
|
for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--)
|
||||||
{
|
{
|
||||||
unsigned char *pstart = p;
|
unsigned char *pstart = p;
|
||||||
|
|
||||||
if (!(p = skip_name(p, header, plen, 10)))
|
if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
GETSHORT(type, p);
|
GETSHORT(type, p);
|
||||||
@@ -1548,6 +1575,68 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
|
|||||||
if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
|
if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
if (type == T_NSEC)
|
||||||
|
{
|
||||||
|
/* If we're looking for NSECs, find the corresponding SIGs, to
|
||||||
|
extract the labels value, which we need in case the NSECs
|
||||||
|
are the result of wildcard expansion.
|
||||||
|
Note that the NSEC may not have been validated yet
|
||||||
|
so if there are multiple SIGs, make sure the label value
|
||||||
|
is the same in all, to avoid be duped by a rogue one.
|
||||||
|
If there are no SIGs, that's an error */
|
||||||
|
unsigned char *p1 = auth_start;
|
||||||
|
int res, j, rdlen1, type1, class1;
|
||||||
|
|
||||||
|
if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
rrsig_labels[nsecs_found] = NULL;
|
||||||
|
|
||||||
|
for (j = ntohs(header->nscount); j != 0; j--)
|
||||||
|
{
|
||||||
|
if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
GETSHORT(type1, p1);
|
||||||
|
GETSHORT(class1, p1);
|
||||||
|
p1 += 4; /* TTL */
|
||||||
|
GETSHORT(rdlen1, p1);
|
||||||
|
|
||||||
|
if (!CHECK_LEN(header, p1, plen, rdlen1))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (res == 1 && class1 == qclass && type1 == T_RRSIG)
|
||||||
|
{
|
||||||
|
int type_covered;
|
||||||
|
unsigned char *psav = p1;
|
||||||
|
|
||||||
|
if (rdlen < 18)
|
||||||
|
return 0; /* bad packet */
|
||||||
|
|
||||||
|
GETSHORT(type_covered, p1);
|
||||||
|
|
||||||
|
if (type_covered == T_NSEC)
|
||||||
|
{
|
||||||
|
p1++; /* algo */
|
||||||
|
|
||||||
|
/* labels field must be the same in every SIG we find. */
|
||||||
|
if (!rrsig_labels[nsecs_found])
|
||||||
|
rrsig_labels[nsecs_found] = p1;
|
||||||
|
else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
p1 = psav;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ADD_RDLEN(header, p1, plen, rdlen1))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Must have found at least one sig. */
|
||||||
|
if (!rrsig_labels[nsecs_found])
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
nsecset[nsecs_found++] = pstart;
|
nsecset[nsecs_found++] = pstart;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1556,7 +1645,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, 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);
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user