1 From 4fe6744a220eddd3f1749b40cac3dfc510787de6 Mon Sep 17 00:00:00 2001
2 From: Simon Kelley <simon@thekelleys.org.uk>
3 Date: Fri, 19 Jan 2018 12:26:08 +0000
4 Subject: [PATCH] DNSSEC fix for wildcard NSEC records. CVE-2017-15107
7 It's OK for NSEC records to be expanded from wildcards,
8 but in that case, the proof of non-existence is only valid
9 starting at the wildcard name, *.<domain> NOT the name expanded
10 from the wildcard. Without this check it's possible for an
11 attacker to craft an NSEC which wrongly proves non-existence
12 in a domain which includes a wildcard for NSEC.
14 src/dnssec.c | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
15 2 files changed, 114 insertions(+), 15 deletions(-)
19 @@ -424,15 +424,17 @@ static void from_wire(char *name)
20 static int count_labels(char *name)
29 - for (i = 0; *name; name++)
31 + for (p = name, i = 0; *p; p++)
36 + /* Don't count empty first label. */
37 + return *name == '.' ? i : i+1;
40 /* Implement RFC1982 wrapped compare for 32-bit numbers */
41 @@ -1412,8 +1414,8 @@ static int hostname_cmp(const char *a, c
45 -static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
46 - char *workspace1, char *workspace2, char *name, int type, int *nons)
47 +static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count,
48 + char *workspace1_in, char *workspace2, char *name, int type, int *nons)
51 unsigned char *p, *psave;
52 @@ -1426,6 +1428,9 @@ static int prove_non_existence_nsec(stru
53 /* Find NSEC record that proves name doesn't exist */
54 for (i = 0; i < nsec_count; i++)
56 + char *workspace1 = workspace1_in;
57 + int sig_labels, name_labels;
60 if (!extract_name(header, plen, &p, workspace1, 1, 10))
62 @@ -1434,7 +1439,27 @@ static int prove_non_existence_nsec(stru
64 if (!extract_name(header, plen, &p, workspace2, 1, 10))
68 + /* If NSEC comes from wildcard expansion, use original wildcard
69 + as name for computation. */
70 + sig_labels = *labels[i];
71 + name_labels = count_labels(workspace1);
73 + if (sig_labels < name_labels)
76 + for (k = name_labels - sig_labels; k != 0; k--)
78 + while (*workspace1 != '.' && *workspace1 != 0)
80 + if (k != 1 && *workspace1 == '.')
88 rc = hostname_cmp(workspace1, name);
91 @@ -1832,24 +1857,26 @@ static int prove_non_existence_nsec3(str
93 static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons)
95 - static unsigned char **nsecset = NULL;
96 - static int nsecset_sz = 0;
97 + static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
98 + static int nsecset_sz = 0, rrsig_labels_sz = 0;
101 - unsigned char *p = skip_questions(header, plen);
102 + unsigned char *auth_start, *p = skip_questions(header, plen);
103 int type, class, rdlen, i, nsecs_found;
105 /* Move to NS section */
106 if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
111 for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--)
113 unsigned char *pstart = p;
115 - if (!(p = skip_name(p, header, plen, 10)))
116 + if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
123 @@ -1866,7 +1893,69 @@ static int prove_non_existence(struct dn
124 if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
127 - nsecset[nsecs_found++] = pstart;
128 + if (type == T_NSEC)
130 + /* If we're looking for NSECs, find the corresponding SIGs, to
131 + extract the labels value, which we need in case the NSECs
132 + are the result of wildcard expansion.
133 + Note that the NSEC may not have been validated yet
134 + so if there are multiple SIGs, make sure the label value
135 + is the same in all, to avoid be duped by a rogue one.
136 + If there are no SIGs, that's an error */
137 + unsigned char *p1 = auth_start;
138 + int res, j, rdlen1, type1, class1;
140 + if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
143 + rrsig_labels[nsecs_found] = NULL;
145 + for (j = ntohs(header->nscount); j != 0; j--)
147 + if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
150 + GETSHORT(type1, p1);
151 + GETSHORT(class1, p1);
153 + GETSHORT(rdlen1, p1);
155 + if (!CHECK_LEN(header, p1, plen, rdlen1))
158 + if (res == 1 && class1 == qclass && type1 == T_RRSIG)
161 + unsigned char *psav = p1;
164 + return 0; /* bad packet */
166 + GETSHORT(type_covered, p1);
168 + if (type_covered == T_NSEC)
172 + /* labels field must be the same in every SIG we find. */
173 + if (!rrsig_labels[nsecs_found])
174 + rrsig_labels[nsecs_found] = p1;
175 + else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
181 + if (!ADD_RDLEN(header, p1, plen, rdlen1))
185 + /* Must have found at least one sig. */
186 + if (!rrsig_labels[nsecs_found])
190 + nsecset[nsecs_found++] = pstart;
193 if (!ADD_RDLEN(header, p, plen, rdlen))
194 @@ -1874,7 +1963,7 @@ static int prove_non_existence(struct dn
197 if (type_found == T_NSEC)
198 - return prove_non_existence_nsec(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
199 + return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
200 else if (type_found == T_NSEC3)
201 return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);