Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 1 | From 7602be276a73a6eb5431c5acd9718e68a55e8b61 Mon Sep 17 00:00:00 2001 |
| 2 | From: Mark Andrews <marka@isc.org> |
| 3 | Date: Mon, 29 Feb 2016 07:16:48 +1100 |
| 4 | Subject: [PATCH] Part 2 of: 4319. [security] Fix resolver assertion |
| 5 | failure due to improper DNAME handling when parsing |
| 6 | fetch reply messages. (CVE-2016-1286) [RT #41753] |
| 7 | |
| 8 | (cherry picked from commit 2de89ee9de8c8da9dc153a754b02dcdbb7fe2374) |
| 9 | Signed-off-by: Sona Sarmadi <sona.sarmadi@enea.com> |
| 10 | --- |
| 11 | lib/dns/resolver.c | 192 ++++++++++++++++++++++++++--------------------------- |
| 12 | 1 file changed, 93 insertions(+), 99 deletions(-) |
| 13 | |
| 14 | diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c |
| 15 | index 70aba87..41e9df4 100644 |
| 16 | --- a/lib/dns/resolver.c |
| 17 | +++ b/lib/dns/resolver.c |
| 18 | @@ -6074,14 +6074,11 @@ cname_target(dns_rdataset_t *rdataset, dns_name_t *tname) { |
| 19 | } |
| 20 | |
| 21 | static inline isc_result_t |
| 22 | -dname_target(fetchctx_t *fctx, dns_rdataset_t *rdataset, dns_name_t *qname, |
| 23 | - dns_name_t *oname, dns_fixedname_t *fixeddname) |
| 24 | +dname_target(dns_rdataset_t *rdataset, dns_name_t *qname, |
| 25 | + unsigned int nlabels, dns_fixedname_t *fixeddname) |
| 26 | { |
| 27 | isc_result_t result; |
| 28 | dns_rdata_t rdata = DNS_RDATA_INIT; |
| 29 | - unsigned int nlabels; |
| 30 | - int order; |
| 31 | - dns_namereln_t namereln; |
| 32 | dns_rdata_dname_t dname; |
| 33 | dns_fixedname_t prefix; |
| 34 | |
| 35 | @@ -6096,21 +6093,6 @@ dname_target(fetchctx_t *fctx, dns_rdataset_t *rdataset, dns_name_t *qname, |
| 36 | if (result != ISC_R_SUCCESS) |
| 37 | return (result); |
| 38 | |
| 39 | - /* |
| 40 | - * Get the prefix of qname. |
| 41 | - */ |
| 42 | - namereln = dns_name_fullcompare(qname, oname, &order, &nlabels); |
| 43 | - if (namereln != dns_namereln_subdomain) { |
| 44 | - char qbuf[DNS_NAME_FORMATSIZE]; |
| 45 | - char obuf[DNS_NAME_FORMATSIZE]; |
| 46 | - |
| 47 | - dns_rdata_freestruct(&dname); |
| 48 | - dns_name_format(qname, qbuf, sizeof(qbuf)); |
| 49 | - dns_name_format(oname, obuf, sizeof(obuf)); |
| 50 | - log_formerr(fctx, "unrelated DNAME in answer: " |
| 51 | - "%s is not in %s", qbuf, obuf); |
| 52 | - return (DNS_R_FORMERR); |
| 53 | - } |
| 54 | dns_fixedname_init(&prefix); |
| 55 | dns_name_split(qname, nlabels, dns_fixedname_name(&prefix), NULL); |
| 56 | dns_fixedname_init(fixeddname); |
| 57 | @@ -6736,13 +6718,13 @@ static isc_result_t |
| 58 | answer_response(fetchctx_t *fctx) { |
| 59 | isc_result_t result; |
| 60 | dns_message_t *message; |
| 61 | - dns_name_t *name, *qname, tname, *ns_name; |
| 62 | + dns_name_t *name, *dname, *qname, tname, *ns_name; |
| 63 | dns_rdataset_t *rdataset, *ns_rdataset; |
| 64 | isc_boolean_t done, external, chaining, aa, found, want_chaining; |
| 65 | isc_boolean_t have_answer, found_cname, found_type, wanted_chaining; |
| 66 | unsigned int aflag; |
| 67 | dns_rdatatype_t type; |
| 68 | - dns_fixedname_t dname, fqname; |
| 69 | + dns_fixedname_t fdname, fqname; |
| 70 | dns_view_t *view; |
| 71 | |
| 72 | FCTXTRACE("answer_response"); |
| 73 | @@ -6770,10 +6752,15 @@ answer_response(fetchctx_t *fctx) { |
| 74 | view = fctx->res->view; |
| 75 | result = dns_message_firstname(message, DNS_SECTION_ANSWER); |
| 76 | while (!done && result == ISC_R_SUCCESS) { |
| 77 | + dns_namereln_t namereln; |
| 78 | + int order; |
| 79 | + unsigned int nlabels; |
| 80 | + |
| 81 | name = NULL; |
| 82 | dns_message_currentname(message, DNS_SECTION_ANSWER, &name); |
| 83 | external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain)); |
| 84 | - if (dns_name_equal(name, qname)) { |
| 85 | + namereln = dns_name_fullcompare(qname, name, &order, &nlabels); |
| 86 | + if (namereln == dns_namereln_equal) { |
| 87 | wanted_chaining = ISC_FALSE; |
| 88 | for (rdataset = ISC_LIST_HEAD(name->list); |
| 89 | rdataset != NULL; |
| 90 | @@ -6898,10 +6885,11 @@ answer_response(fetchctx_t *fctx) { |
| 91 | */ |
| 92 | INSIST(!external); |
| 93 | if (aflag == |
| 94 | - DNS_RDATASETATTR_ANSWER) |
| 95 | + DNS_RDATASETATTR_ANSWER) { |
| 96 | have_answer = ISC_TRUE; |
| 97 | - name->attributes |= |
| 98 | - DNS_NAMEATTR_ANSWER; |
| 99 | + name->attributes |= |
| 100 | + DNS_NAMEATTR_ANSWER; |
| 101 | + } |
| 102 | rdataset->attributes |= aflag; |
| 103 | if (aa) |
| 104 | rdataset->trust = |
| 105 | @@ -6956,6 +6944,8 @@ answer_response(fetchctx_t *fctx) { |
| 106 | if (wanted_chaining) |
| 107 | chaining = ISC_TRUE; |
| 108 | } else { |
| 109 | + dns_rdataset_t *dnameset = NULL; |
| 110 | + |
| 111 | /* |
| 112 | * Look for a DNAME (or its SIG). Anything else is |
| 113 | * ignored. |
| 114 | @@ -6963,10 +6953,8 @@ answer_response(fetchctx_t *fctx) { |
| 115 | wanted_chaining = ISC_FALSE; |
| 116 | for (rdataset = ISC_LIST_HEAD(name->list); |
| 117 | rdataset != NULL; |
| 118 | - rdataset = ISC_LIST_NEXT(rdataset, link)) { |
| 119 | - isc_boolean_t found_dname = ISC_FALSE; |
| 120 | - dns_name_t *dname_name; |
| 121 | - |
| 122 | + rdataset = ISC_LIST_NEXT(rdataset, link)) |
| 123 | + { |
| 124 | /* |
| 125 | * Only pass DNAME or RRSIG(DNAME). |
| 126 | */ |
| 127 | @@ -6980,20 +6968,41 @@ answer_response(fetchctx_t *fctx) { |
| 128 | * its signature should not be external. |
| 129 | */ |
| 130 | if (!chaining && external) { |
| 131 | - log_formerr(fctx, "external DNAME"); |
| 132 | + char qbuf[DNS_NAME_FORMATSIZE]; |
| 133 | + char obuf[DNS_NAME_FORMATSIZE]; |
| 134 | + |
| 135 | + dns_name_format(name, qbuf, |
| 136 | + sizeof(qbuf)); |
| 137 | + dns_name_format(&fctx->domain, obuf, |
| 138 | + sizeof(obuf)); |
| 139 | + log_formerr(fctx, "external DNAME or " |
| 140 | + "RRSIG covering DNAME " |
| 141 | + "in answer: %s is " |
| 142 | + "not in %s", qbuf, obuf); |
| 143 | + return (DNS_R_FORMERR); |
| 144 | + } |
| 145 | + |
| 146 | + if (namereln != dns_namereln_subdomain) { |
| 147 | + char qbuf[DNS_NAME_FORMATSIZE]; |
| 148 | + char obuf[DNS_NAME_FORMATSIZE]; |
| 149 | + |
| 150 | + dns_name_format(qname, qbuf, |
| 151 | + sizeof(qbuf)); |
| 152 | + dns_name_format(name, obuf, |
| 153 | + sizeof(obuf)); |
| 154 | + log_formerr(fctx, "unrelated DNAME " |
| 155 | + "in answer: %s is " |
| 156 | + "not in %s", qbuf, obuf); |
| 157 | return (DNS_R_FORMERR); |
| 158 | } |
| 159 | |
| 160 | - found = ISC_FALSE; |
| 161 | aflag = 0; |
| 162 | if (rdataset->type == dns_rdatatype_dname) { |
| 163 | - found = ISC_TRUE; |
| 164 | want_chaining = ISC_TRUE; |
| 165 | POST(want_chaining); |
| 166 | aflag = DNS_RDATASETATTR_ANSWER; |
| 167 | - result = dname_target(fctx, rdataset, |
| 168 | - qname, name, |
| 169 | - &dname); |
| 170 | + result = dname_target(rdataset, qname, |
| 171 | + nlabels, &fdname); |
| 172 | if (result == ISC_R_NOSPACE) { |
| 173 | /* |
| 174 | * We can't construct the |
| 175 | @@ -7005,14 +7014,12 @@ answer_response(fetchctx_t *fctx) { |
| 176 | } else if (result != ISC_R_SUCCESS) |
| 177 | return (result); |
| 178 | else |
| 179 | - found_dname = ISC_TRUE; |
| 180 | + dnameset = rdataset; |
| 181 | |
| 182 | - dname_name = dns_fixedname_name(&dname); |
| 183 | + dname = dns_fixedname_name(&fdname); |
| 184 | if (!is_answertarget_allowed(view, |
| 185 | - qname, |
| 186 | - rdataset->type, |
| 187 | - dname_name, |
| 188 | - &fctx->domain)) { |
| 189 | + qname, rdataset->type, |
| 190 | + dname, &fctx->domain)) { |
| 191 | return (DNS_R_SERVFAIL); |
| 192 | } |
| 193 | } else { |
| 194 | @@ -7020,73 +7027,60 @@ answer_response(fetchctx_t *fctx) { |
| 195 | * We've found a signature that |
| 196 | * covers the DNAME. |
| 197 | */ |
| 198 | - found = ISC_TRUE; |
| 199 | aflag = DNS_RDATASETATTR_ANSWERSIG; |
| 200 | } |
| 201 | |
| 202 | - if (found) { |
| 203 | + /* |
| 204 | + * We've found an answer to our |
| 205 | + * question. |
| 206 | + */ |
| 207 | + name->attributes |= DNS_NAMEATTR_CACHE; |
| 208 | + rdataset->attributes |= DNS_RDATASETATTR_CACHE; |
| 209 | + rdataset->trust = dns_trust_answer; |
| 210 | + if (!chaining) { |
| 211 | /* |
| 212 | - * We've found an answer to our |
| 213 | - * question. |
| 214 | + * This data is "the" answer to |
| 215 | + * our question only if we're |
| 216 | + * not chaining. |
| 217 | */ |
| 218 | - name->attributes |= |
| 219 | - DNS_NAMEATTR_CACHE; |
| 220 | - rdataset->attributes |= |
| 221 | - DNS_RDATASETATTR_CACHE; |
| 222 | - rdataset->trust = dns_trust_answer; |
| 223 | - if (!chaining) { |
| 224 | - /* |
| 225 | - * This data is "the" answer |
| 226 | - * to our question only if |
| 227 | - * we're not chaining. |
| 228 | - */ |
| 229 | - INSIST(!external); |
| 230 | - if (aflag == |
| 231 | - DNS_RDATASETATTR_ANSWER) |
| 232 | - have_answer = ISC_TRUE; |
| 233 | + INSIST(!external); |
| 234 | + if (aflag == DNS_RDATASETATTR_ANSWER) { |
| 235 | + have_answer = ISC_TRUE; |
| 236 | name->attributes |= |
| 237 | DNS_NAMEATTR_ANSWER; |
| 238 | - rdataset->attributes |= aflag; |
| 239 | - if (aa) |
| 240 | - rdataset->trust = |
| 241 | - dns_trust_authanswer; |
| 242 | - } else if (external) { |
| 243 | - rdataset->attributes |= |
| 244 | - DNS_RDATASETATTR_EXTERNAL; |
| 245 | - } |
| 246 | - |
| 247 | - /* |
| 248 | - * DNAME chaining. |
| 249 | - */ |
| 250 | - if (found_dname) { |
| 251 | - /* |
| 252 | - * Copy the dname into the |
| 253 | - * qname fixed name. |
| 254 | - * |
| 255 | - * Although we check for |
| 256 | - * failure of the copy |
| 257 | - * operation, in practice it |
| 258 | - * should never fail since |
| 259 | - * we already know that the |
| 260 | - * result fits in a fixedname. |
| 261 | - */ |
| 262 | - dns_fixedname_init(&fqname); |
| 263 | - result = dns_name_copy( |
| 264 | - dns_fixedname_name(&dname), |
| 265 | - dns_fixedname_name(&fqname), |
| 266 | - NULL); |
| 267 | - if (result != ISC_R_SUCCESS) |
| 268 | - return (result); |
| 269 | - wanted_chaining = ISC_TRUE; |
| 270 | - name->attributes |= |
| 271 | - DNS_NAMEATTR_CHAINING; |
| 272 | - rdataset->attributes |= |
| 273 | - DNS_RDATASETATTR_CHAINING; |
| 274 | - qname = dns_fixedname_name( |
| 275 | - &fqname); |
| 276 | } |
| 277 | + rdataset->attributes |= aflag; |
| 278 | + if (aa) |
| 279 | + rdataset->trust = |
| 280 | + dns_trust_authanswer; |
| 281 | + } else if (external) { |
| 282 | + rdataset->attributes |= |
| 283 | + DNS_RDATASETATTR_EXTERNAL; |
| 284 | } |
| 285 | } |
| 286 | + |
| 287 | + /* |
| 288 | + * DNAME chaining. |
| 289 | + */ |
| 290 | + if (dnameset != NULL) { |
| 291 | + /* |
| 292 | + * Copy the dname into the qname fixed name. |
| 293 | + * |
| 294 | + * Although we check for failure of the copy |
| 295 | + * operation, in practice it should never fail |
| 296 | + * since we already know that the result fits |
| 297 | + * in a fixedname. |
| 298 | + */ |
| 299 | + dns_fixedname_init(&fqname); |
| 300 | + qname = dns_fixedname_name(&fqname); |
| 301 | + result = dns_name_copy(dname, qname, NULL); |
| 302 | + if (result != ISC_R_SUCCESS) |
| 303 | + return (result); |
| 304 | + wanted_chaining = ISC_TRUE; |
| 305 | + name->attributes |= DNS_NAMEATTR_CHAINING; |
| 306 | + dnameset->attributes |= |
| 307 | + DNS_RDATASETATTR_CHAINING; |
| 308 | + } |
| 309 | if (wanted_chaining) |
| 310 | chaining = ISC_TRUE; |
| 311 | } |
| 312 | -- |
| 313 | 1.9.1 |
| 314 | |