From 93aec4d3d80a0d1cdb6553f70f35a2e2cb1fbaa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= Date: Tue, 11 Apr 2017 16:19:51 +0200 Subject: [PATCH 2/3] 4578. [security] Some chaining (CNAME or DNAME) responses to upstream queries could trigger assertion failures. (CVE-2017-3137) [RT #44734] (including part of commit fea8a9d) --- bin/tests/system/dname/ans3/ans.pl | 16 +- bin/tests/system/dname/ns1/root.db | 2 +- bin/tests/system/dname/ns2/example.db | 3 +- bin/tests/system/dname/tests.sh | 17 +- lib/dns/name.c | 2 - lib/dns/resolver.c | 850 +++++++++++++--------------------- 6 files changed, 349 insertions(+), 541 deletions(-) diff --git a/bin/tests/system/dname/ans3/ans.pl b/bin/tests/system/dname/ans3/ans.pl index 271fc7d..af338fe 100644 --- a/bin/tests/system/dname/ans3/ans.pl +++ b/bin/tests/system/dname/ans3/ans.pl @@ -1,10 +1,18 @@ #!/usr/bin/env perl # -# Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") # -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. use strict; use warnings; diff --git a/bin/tests/system/dname/ns1/root.db b/bin/tests/system/dname/ns1/root.db index 2e84ae0..3d55ace 100644 --- a/bin/tests/system/dname/ns1/root.db +++ b/bin/tests/system/dname/ns1/root.db @@ -1,4 +1,4 @@ -; Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") +; Copyright (C) 2011, 2017 Internet Systems Consortium, Inc. ("ISC") ; ; Permission to use, copy, modify, and/or distribute this software for any ; purpose with or without fee is hereby granted, provided that the above diff --git a/bin/tests/system/dname/ns2/example.db b/bin/tests/system/dname/ns2/example.db index 4289134..c0193de 100644 --- a/bin/tests/system/dname/ns2/example.db +++ b/bin/tests/system/dname/ns2/example.db @@ -1,4 +1,4 @@ -; Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") +; Copyright (C) 2011, 2017 Internet Systems Consortium, Inc. ("ISC") ; ; Permission to use, copy, modify, and/or distribute this software for any ; purpose with or without fee is hereby granted, provided that the above @@ -29,6 +29,7 @@ a.short A 10.0.0.1 short-dname DNAME short a.longlonglonglonglonglonglonglonglonglonglonglonglong A 10.0.0.2 long-dname DNAME longlonglonglonglonglonglonglonglonglonglonglonglong +toolong-dname DNAME longlonglonglonglonglonglonglonglonglonglonglonglong cname CNAME a.cnamedname cnamedname DNAME target a.target A 10.0.0.3 diff --git a/bin/tests/system/dname/tests.sh b/bin/tests/system/dname/tests.sh index 6dc9e88..1487bd9 100644 --- a/bin/tests/system/dname/tests.sh +++ b/bin/tests/system/dname/tests.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (C) 2011, 2012 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2011, 2012, 2017 Internet Systems Consortium, Inc. ("ISC") # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -57,10 +57,19 @@ grep "status: YXDOMAIN" dig.out.ns2.toolong > /dev/null || ret=1 if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` -echo "I:checking (too) long dname from recursive" +echo "I:checking (too) long dname from recursive with cached DNAME" ret=0 -$DIG 01234567890123456789012345678901234567890123456789.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.long-dname.example @10.53.0.4 a -p 5300 > dig.out.ns4.toolong || ret=1 -grep "status: YXDOMAIN" dig.out.ns4.toolong > /dev/null || ret=1 +$DIG 01234567890123456789012345678901234567890123456789.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.long-dname.example @10.53.0.4 a -p 5300 > dig.out.ns4.cachedtoolong || ret=1 +grep "status: YXDOMAIN" dig.out.ns4.cachedtoolong > /dev/null || ret=1 +grep '^long-dname\.example\..*DNAME.*long' dig.out.ns4.cachedtoolong > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:checking (too) long dname from recursive without cached DNAME" +ret=0 +$DIG 01234567890123456789012345678901234567890123456789.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglong.toolong-dname.example @10.53.0.4 a -p 5300 > dig.out.ns4.uncachedtoolong || ret=1 +grep "status: YXDOMAIN" dig.out.ns4.uncachedtoolong > /dev/null || ret=1 +grep '^toolong-dname\.example\..*DNAME.*long' dig.out.ns4.uncachedtoolong > /dev/null || ret=1 if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` diff --git a/lib/dns/name.c b/lib/dns/name.c index 93173ee..d02e713 100644 --- a/lib/dns/name.c +++ b/lib/dns/name.c @@ -2119,11 +2119,9 @@ dns_name_split(dns_name_t *name, unsigned int suffixlabels, REQUIRE(prefix != NULL || suffix != NULL); REQUIRE(prefix == NULL || (VALID_NAME(prefix) && - prefix->buffer != NULL && BINDABLE(prefix))); REQUIRE(suffix == NULL || (VALID_NAME(suffix) && - suffix->buffer != NULL && BINDABLE(suffix))); splitlabel = name->labels - suffixlabels; diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index c3607fa..860a792 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -3817,6 +3817,7 @@ is_lame(fetchctx_t *fctx) { isc_result_t result; if (message->rcode != dns_rcode_noerror && + message->rcode != dns_rcode_yxdomain && message->rcode != dns_rcode_nxdomain) return (ISC_FALSE); @@ -5386,79 +5387,6 @@ chase_additional(fetchctx_t *fctx) { goto again; } -static inline isc_result_t -cname_target(dns_rdataset_t *rdataset, dns_name_t *tname) { - isc_result_t result; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdata_cname_t cname; - - result = dns_rdataset_first(rdataset); - if (result != ISC_R_SUCCESS) - return (result); - dns_rdataset_current(rdataset, &rdata); - result = dns_rdata_tostruct(&rdata, &cname, NULL); - if (result != ISC_R_SUCCESS) - return (result); - dns_name_init(tname, NULL); - dns_name_clone(&cname.cname, tname); - dns_rdata_freestruct(&cname); - - return (ISC_R_SUCCESS); -} - -/*% - * Construct the synthesised CNAME from the existing QNAME and - * the DNAME RR and store it in 'target'. - */ -static inline isc_result_t -dname_target(dns_rdataset_t *rdataset, dns_name_t *qname, - unsigned int nlabels, dns_name_t *target) -{ - isc_result_t result; - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdata_dname_t dname; - dns_fixedname_t prefix; - - /* - * Get the target name of the DNAME. - */ - result = dns_rdataset_first(rdataset); - if (result != ISC_R_SUCCESS) - return (result); - dns_rdataset_current(rdataset, &rdata); - result = dns_rdata_tostruct(&rdata, &dname, NULL); - if (result != ISC_R_SUCCESS) - return (result); - - dns_fixedname_init(&prefix); - dns_name_split(qname, nlabels, dns_fixedname_name(&prefix), NULL); - result = dns_name_concatenate(dns_fixedname_name(&prefix), - &dname.dname, target, NULL); - dns_rdata_freestruct(&dname); - return (result); -} - -/*% - * Check if it was possible to construct 'qname' from 'lastcname' - * and 'rdataset'. - */ -static inline isc_result_t -fromdname(dns_rdataset_t *rdataset, dns_name_t *lastcname, - unsigned int nlabels, const dns_name_t *qname) -{ - dns_fixedname_t fixed; - isc_result_t result; - dns_name_t *target; - - dns_fixedname_init(&fixed); - target = dns_fixedname_name(&fixed); - result = dname_target(rdataset, lastcname, nlabels, target); - if (result != ISC_R_SUCCESS || !dns_name_equal(qname, target)) - return (ISC_R_NOTFOUND); - - return (ISC_R_SUCCESS); -} - static isc_boolean_t is_answeraddress_allowed(dns_view_t *view, dns_name_t *name, dns_rdataset_t *rdataset) @@ -5534,9 +5462,8 @@ is_answeraddress_allowed(dns_view_t *view, dns_name_t *name, } static isc_boolean_t -is_answertarget_allowed(dns_view_t *view, dns_name_t *name, - dns_rdatatype_t type, dns_name_t *tname, - dns_name_t *domain) +is_answertarget_allowed(fetchctx_t *fctx, dns_name_t *qname, dns_name_t *rname, + dns_rdataset_t *rdataset, isc_boolean_t *chainingp) { isc_result_t result; dns_rbtnode_t *node = NULL; @@ -5544,8 +5471,57 @@ is_answertarget_allowed(dns_view_t *view, dns_name_t *name, char tnamebuf[DNS_NAME_FORMATSIZE]; char classbuf[64]; char typebuf[64]; + dns_name_t *tname = NULL; + dns_rdata_cname_t cname; + dns_rdata_dname_t dname; + dns_view_t *view = fctx->res->view; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned int nlabels; + dns_fixedname_t fixed; + dns_name_t prefix; + + REQUIRE(rdataset != NULL); + REQUIRE(rdataset->type == dns_rdatatype_cname || + rdataset->type == dns_rdatatype_dname); + + /* + * By default, we allow any target name. + * If newqname != NULL we also need to extract the newqname. + */ + if (chainingp == NULL && view->denyanswernames == NULL) + return (ISC_TRUE); + + result = dns_rdataset_first(rdataset); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdataset_current(rdataset, &rdata); + switch (rdataset->type) { + case dns_rdatatype_cname: + result = dns_rdata_tostruct(&rdata, &cname, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + tname = &cname.cname; + break; + case dns_rdatatype_dname: + result = dns_rdata_tostruct(&rdata, &dname, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_name_init(&prefix, NULL); + dns_fixedname_init(&fixed); + tname = dns_fixedname_name(&fixed); + nlabels = dns_name_countlabels(qname) - + dns_name_countlabels(rname); + dns_name_split(qname, nlabels, &prefix, NULL); + result = dns_name_concatenate(&prefix, &dname.dname, tname, + NULL); + if (result == DNS_R_NAMETOOLONG) + return (ISC_TRUE); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + break; + default: + INSIST(0); + } + + if (chainingp != NULL) + *chainingp = ISC_TRUE; - /* By default, we allow any target name. */ if (view->denyanswernames == NULL) return (ISC_TRUE); @@ -5554,8 +5530,8 @@ is_answertarget_allowed(dns_view_t *view, dns_name_t *name, * or partially, allow it. */ if (view->answernames_exclude != NULL) { - result = dns_rbt_findnode(view->answernames_exclude, name, NULL, - &node, NULL, 0, NULL, NULL); + result = dns_rbt_findnode(view->answernames_exclude, qname, + NULL, &node, NULL, 0, NULL, NULL); if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) return (ISC_TRUE); } @@ -5563,7 +5539,7 @@ is_answertarget_allowed(dns_view_t *view, dns_name_t *name, /* * If the target name is a subdomain of the search domain, allow it. */ - if (dns_name_issubdomain(tname, domain)) + if (dns_name_issubdomain(tname, &fctx->domain)) return (ISC_TRUE); /* @@ -5572,9 +5548,9 @@ is_answertarget_allowed(dns_view_t *view, dns_name_t *name, result = dns_rbt_findnode(view->denyanswernames, tname, NULL, &node, NULL, 0, NULL, NULL); if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { - dns_name_format(name, qnamebuf, sizeof(qnamebuf)); + dns_name_format(qname, qnamebuf, sizeof(qnamebuf)); dns_name_format(tname, tnamebuf, sizeof(tnamebuf)); - dns_rdatatype_format(type, typebuf, sizeof(typebuf)); + dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf)); dns_rdataclass_format(view->rdclass, classbuf, sizeof(classbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, @@ -6057,473 +6033,301 @@ noanswer_response(fetchctx_t *fctx, dns_name_t *oqname, return (ISC_R_SUCCESS); } +static isc_boolean_t +validinanswer(dns_rdataset_t *rdataset, fetchctx_t *fctx) { + if (rdataset->type == dns_rdatatype_nsec3) { + /* + * NSEC3 records are not allowed to + * appear in the answer section. + */ + log_formerr(fctx, "NSEC3 in answer"); + return (ISC_FALSE); + } + if (rdataset->type == dns_rdatatype_tkey) { + /* + * TKEY is not a valid record in a + * response to any query we can make. + */ + log_formerr(fctx, "TKEY in answer"); + return (ISC_FALSE); + } + if (rdataset->rdclass != fctx->res->rdclass) { + log_formerr(fctx, "Mismatched class in answer"); + return (ISC_FALSE); + } + return (ISC_TRUE); +} + static isc_result_t answer_response(fetchctx_t *fctx) { isc_result_t result; - dns_message_t *message; - dns_name_t *name, *dname = NULL, *qname, tname, *ns_name; - dns_name_t *cname = NULL, *lastcname = NULL; - dns_rdataset_t *rdataset, *ns_rdataset; - isc_boolean_t done, external, aa, found, want_chaining; - isc_boolean_t have_answer, found_cname, found_dname, found_type; - isc_boolean_t wanted_chaining; - unsigned int aflag, chaining; + dns_message_t *message = NULL; + dns_name_t *name = NULL, *qname = NULL, *ns_name = NULL; + dns_name_t *aname = NULL, *cname = NULL, *dname = NULL; + dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL; + dns_rdataset_t *ardataset = NULL, *crdataset = NULL; + dns_rdataset_t *drdataset = NULL, *ns_rdataset = NULL; + isc_boolean_t done = ISC_FALSE, aa; + unsigned int dname_labels, domain_labels; + isc_boolean_t chaining = ISC_FALSE; dns_rdatatype_t type; - dns_fixedname_t fdname, fqname; - dns_view_t *view; + dns_view_t *view = NULL; + dns_trust_t trust; + + REQUIRE(VALID_FCTX(fctx)); FCTXTRACE("answer_response"); message = fctx->rmessage; + qname = &fctx->name; + view = fctx->res->view; + type = fctx->type; /* - * Examine the answer section, marking those rdatasets which are - * part of the answer and should be cached. + * There can be multiple RRSIG and SIG records at a name so + * we treat these types as a subset of ANY. */ + if (type == dns_rdatatype_rrsig || type == dns_rdatatype_sig) { + type = dns_rdatatype_any; + } - done = ISC_FALSE; - found_cname = ISC_FALSE; - found_dname = ISC_FALSE; - found_type = ISC_FALSE; - have_answer = ISC_FALSE; - want_chaining = ISC_FALSE; - chaining = 0; - POST(want_chaining); - if ((message->flags & DNS_MESSAGEFLAG_AA) != 0) - aa = ISC_TRUE; - else - aa = ISC_FALSE; - qname = &fctx->name; - type = fctx->type; - view = fctx->res->view; - result = dns_message_firstname(message, DNS_SECTION_ANSWER); - while (!done && result == ISC_R_SUCCESS) { - dns_namereln_t namereln, lastreln; - int order, lastorder; - unsigned int nlabels, lastnlabels; + /* + * Bigger than any valid DNAME label count. + */ + dname_labels = dns_name_countlabels(qname); + domain_labels = dns_name_countlabels(&fctx->domain); + + /* + * Perform a single pass looking for the answer, cname or covering + * dname. + */ + for (result = dns_message_firstname(message, DNS_SECTION_ANSWER); + result == ISC_R_SUCCESS; + result = dns_message_nextname(message, DNS_SECTION_ANSWER)) + { + int order; + unsigned int nlabels; + dns_namereln_t namereln; name = NULL; dns_message_currentname(message, DNS_SECTION_ANSWER, &name); - external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain)); namereln = dns_name_fullcompare(qname, name, &order, &nlabels); - - if (namereln == dns_namereln_equal) { - wanted_chaining = ISC_FALSE; + switch (namereln) { + case dns_namereln_equal: for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; - rdataset = ISC_LIST_NEXT(rdataset, link)) { - found = ISC_FALSE; - want_chaining = ISC_FALSE; - aflag = 0; - if (rdataset->type == dns_rdatatype_nsec3) { - /* - * NSEC3 records are not allowed to - * appear in the answer section. - */ - log_formerr(fctx, "NSEC3 in answer"); - return (DNS_R_FORMERR); - } - if (rdataset->type == dns_rdatatype_tkey) { - /* - * TKEY is not a valid record in a - * response to any query we can make. - */ - log_formerr(fctx, "TKEY in answer"); - return (DNS_R_FORMERR); - } - if (rdataset->rdclass != fctx->res->rdclass) { - log_formerr(fctx, "Mismatched class " - "in answer"); - return (DNS_R_FORMERR); - } - - /* - * Apply filters, if given, on answers to reject - * a malicious attempt of rebinding. - */ - if ((rdataset->type == dns_rdatatype_a || - rdataset->type == dns_rdatatype_aaaa) && - !is_answeraddress_allowed(view, name, - rdataset)) { - return (DNS_R_SERVFAIL); - } - - if (rdataset->type == type && !found_cname) { - /* - * We've found an ordinary answer. - */ - found = ISC_TRUE; - found_type = ISC_TRUE; - done = ISC_TRUE; - aflag = DNS_RDATASETATTR_ANSWER; - } else if (type == dns_rdatatype_any) { - /* - * We've found an answer matching - * an ANY query. There may be - * more. - */ - found = ISC_TRUE; - aflag = DNS_RDATASETATTR_ANSWER; - } else if (rdataset->type == dns_rdatatype_rrsig - && rdataset->covers == type - && !found_cname) { - /* - * We've found a signature that - * covers the type we're looking for. - */ - found = ISC_TRUE; - found_type = ISC_TRUE; - aflag = DNS_RDATASETATTR_ANSWERSIG; - } else if (rdataset->type == - dns_rdatatype_cname - && !found_type) { - /* - * We're looking for something else, - * but we found a CNAME. - * - * Getting a CNAME response for some - * query types is an error, see - * RFC 4035, Section 2.5. - */ - if (type == dns_rdatatype_rrsig || - type == dns_rdatatype_key || - type == dns_rdatatype_nsec) { - char buf[DNS_RDATATYPE_FORMATSIZE]; - dns_rdatatype_format(fctx->type, - buf, sizeof(buf)); - log_formerr(fctx, - "CNAME response " - "for %s RR", buf); - return (DNS_R_FORMERR); - } - found = ISC_TRUE; - found_cname = ISC_TRUE; - want_chaining = ISC_TRUE; - aflag = DNS_RDATASETATTR_ANSWER; - result = cname_target(rdataset, - &tname); - if (result != ISC_R_SUCCESS) - return (result); - /* Apply filters on the target name. */ - if (!is_answertarget_allowed(view, - name, - rdataset->type, - &tname, - &fctx->domain)) { - return (DNS_R_SERVFAIL); + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (rdataset->type == type || + type == dns_rdatatype_any) + { + aname = name; + if (type != dns_rdatatype_any) { + ardataset = rdataset; } - lastcname = name; - } else if (rdataset->type == dns_rdatatype_rrsig - && rdataset->covers == - dns_rdatatype_cname - && !found_type) { - /* - * We're looking for something else, - * but we found a SIG CNAME. - */ - found = ISC_TRUE; - found_cname = ISC_TRUE; - aflag = DNS_RDATASETATTR_ANSWERSIG; + break; } - - if (found) { - /* - * We've found an answer to our - * question. - */ - name->attributes |= - DNS_NAMEATTR_CACHE; - rdataset->attributes |= - DNS_RDATASETATTR_CACHE; - rdataset->trust = dns_trust_answer; - if (chaining == 0) { - /* - * This data is "the" answer - * to our question only if - * we're not chaining (i.e. - * if we haven't followed - * a CNAME or DNAME). - */ - INSIST(!external); - /* - * Don't use found_cname here - * as we have just set it - * above. - */ - if (cname == NULL && - !found_dname && - aflag == - DNS_RDATASETATTR_ANSWER) - { - have_answer = ISC_TRUE; - if (found_cname && - cname == NULL) - cname = name; - name->attributes |= - DNS_NAMEATTR_ANSWER; - } - rdataset->attributes |= aflag; - if (aa) - rdataset->trust = - dns_trust_authanswer; - } else if (external) { - /* - * This data is outside of - * our query domain, and - * may not be cached. - */ - rdataset->attributes |= - DNS_RDATASETATTR_EXTERNAL; - } - - /* - * Mark any additional data related - * to this rdataset. - */ - (void)dns_rdataset_additionaldata( - rdataset, - check_related, - fctx); - - /* - * CNAME chaining. - */ - if (want_chaining) { - wanted_chaining = ISC_TRUE; - name->attributes |= - DNS_NAMEATTR_CHAINING; - rdataset->attributes |= - DNS_RDATASETATTR_CHAINING; - qname = &tname; - } + if (rdataset->type == dns_rdatatype_cname) { + cname = name; + crdataset = rdataset; + break; } - /* - * We could add an "else" clause here and - * log that we're ignoring this rdataset. - */ } + break; + + case dns_namereln_subdomain: /* - * If wanted_chaining is true, we've done - * some chaining as the result of processing - * this node, and thus we need to set - * chaining to true. - * - * We don't set chaining inside of the - * rdataset loop because doing that would - * cause us to ignore the signatures of - * CNAMEs. + * In-scope DNAME records must have at least + * as many labels as the domain being queried. + * They also must be less that qname's labels + * and any previously found dname. */ - if (wanted_chaining && chaining < 2U) - chaining++; - } else { - dns_rdataset_t *dnameset = NULL; - isc_boolean_t synthcname = ISC_FALSE; - - if (lastcname != NULL) { - lastreln = dns_name_fullcompare(lastcname, - name, - &lastorder, - &lastnlabels); - if (lastreln == dns_namereln_subdomain && - lastnlabels == dns_name_countlabels(name)) - synthcname = ISC_TRUE; + if (nlabels >= dname_labels || nlabels < domain_labels) + { + continue; } /* - * Look for a DNAME (or its SIG). Anything else is - * ignored. + * We are looking for the shortest DNAME if there + * are multiple ones (which there shouldn't be). */ - wanted_chaining = ISC_FALSE; for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { - if (rdataset->rdclass != fctx->res->rdclass) { - log_formerr(fctx, "Mismatched class " - "in answer"); - return (DNS_R_FORMERR); - } - - /* - * Only pass DNAME or RRSIG(DNAME). - */ - if (rdataset->type != dns_rdatatype_dname && - (rdataset->type != dns_rdatatype_rrsig || - rdataset->covers != dns_rdatatype_dname)) + if (rdataset->type != dns_rdatatype_dname) { continue; - - /* - * If we're not chaining, then the DNAME and - * its signature should not be external. - */ - if (chaining == 0 && external) { - char qbuf[DNS_NAME_FORMATSIZE]; - char obuf[DNS_NAME_FORMATSIZE]; - - dns_name_format(name, qbuf, - sizeof(qbuf)); - dns_name_format(&fctx->domain, obuf, - sizeof(obuf)); - log_formerr(fctx, "external DNAME or " - "RRSIG covering DNAME " - "in answer: %s is " - "not in %s", qbuf, obuf); - return (DNS_R_FORMERR); - } - - /* - * If DNAME + synthetic CNAME then the - * namereln is dns_namereln_subdomain. - */ - if (namereln != dns_namereln_subdomain && - !synthcname) - { - char qbuf[DNS_NAME_FORMATSIZE]; - char obuf[DNS_NAME_FORMATSIZE]; - - dns_name_format(qname, qbuf, - sizeof(qbuf)); - dns_name_format(name, obuf, - sizeof(obuf)); - log_formerr(fctx, "unrelated DNAME " - "in answer: %s is " - "not in %s", qbuf, obuf); - return (DNS_R_FORMERR); } + dname = name; + drdataset = rdataset; + dname_labels = nlabels; + break; + } + break; + default: + break; + } + } - aflag = 0; - if (rdataset->type == dns_rdatatype_dname) { - want_chaining = ISC_TRUE; - POST(want_chaining); - aflag = DNS_RDATASETATTR_ANSWER; - dns_fixedname_init(&fdname); - dname = dns_fixedname_name(&fdname); - if (synthcname) { - result = fromdname(rdataset, - lastcname, - lastnlabels, - qname); - } else { - result = dname_target(rdataset, - qname, - nlabels, - dname); - } - if (result == ISC_R_NOSPACE) { - /* - * We can't construct the - * DNAME target. Do not - * try to continue. - */ - want_chaining = ISC_FALSE; - POST(want_chaining); - } else if (result != ISC_R_SUCCESS) - return (result); - else - dnameset = rdataset; + if (dname != NULL) { + aname = NULL; + ardataset = NULL; + cname = NULL; + crdataset = NULL; + } else if (aname != NULL) { + cname = NULL; + crdataset = NULL; + } - if (!synthcname && - !is_answertarget_allowed(view, - qname, rdataset->type, - dname, &fctx->domain)) - { - return (DNS_R_SERVFAIL); - } - } else { - /* - * We've found a signature that - * covers the DNAME. - */ - aflag = DNS_RDATASETATTR_ANSWERSIG; - } + aa = ISC_TF((message->flags & DNS_MESSAGEFLAG_AA) != 0); + trust = aa ? dns_trust_authanswer : dns_trust_answer; - /* - * We've found an answer to our - * question. - */ - name->attributes |= DNS_NAMEATTR_CACHE; - rdataset->attributes |= DNS_RDATASETATTR_CACHE; - rdataset->trust = dns_trust_answer; - /* - * If we are not chaining or the first CNAME - * is a synthesised CNAME before the DNAME. - */ - if ((chaining == 0) || - (chaining == 1U && synthcname)) - { - /* - * This data is "the" answer to - * our question only if we're - * not chaining. - */ - INSIST(!external); - if (aflag == DNS_RDATASETATTR_ANSWER) { - have_answer = ISC_TRUE; - found_dname = ISC_TRUE; - if (cname != NULL && - synthcname) - { - cname->attributes &= - ~DNS_NAMEATTR_ANSWER; - } - name->attributes |= - DNS_NAMEATTR_ANSWER; - } - rdataset->attributes |= aflag; - if (aa) - rdataset->trust = - dns_trust_authanswer; - } else if (external) { - rdataset->attributes |= - DNS_RDATASETATTR_EXTERNAL; - } + if (aname != NULL && type == dns_rdatatype_any) { + for (rdataset = ISC_LIST_HEAD(aname->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (!validinanswer(rdataset, fctx)) { + return (DNS_R_FORMERR); } - - /* - * DNAME chaining. - */ - if (dnameset != NULL) { - if (!synthcname) { - /* - * Copy the dname into the qname fixed - * name. - * - * Although we check for failure of the - * copy operation, in practice it - * should never fail since we already - * know that the result fits in a - * fixedname. - */ - dns_fixedname_init(&fqname); - qname = dns_fixedname_name(&fqname); - result = dns_name_copy(dname, qname, - NULL); - if (result != ISC_R_SUCCESS) - return (result); - } - wanted_chaining = ISC_TRUE; - name->attributes |= DNS_NAMEATTR_CHAINING; - dnameset->attributes |= - DNS_RDATASETATTR_CHAINING; + if ((fctx->type == dns_rdatatype_sig || + fctx->type == dns_rdatatype_rrsig) && + rdataset->type != fctx->type) + { + continue; } - /* - * Ensure that we can't ever get chaining == 1 - * above if we have processed a DNAME. - */ - if (wanted_chaining && chaining < 2U) - chaining += 2; + if ((rdataset->type == dns_rdatatype_a || + rdataset->type == dns_rdatatype_aaaa) && + !is_answeraddress_allowed(view, aname, rdataset)) + { + return (DNS_R_SERVFAIL); + } + if ((rdataset->type == dns_rdatatype_cname || + rdataset->type == dns_rdatatype_dname) && + !is_answertarget_allowed(fctx, qname, aname, + rdataset, NULL)) + { + return (DNS_R_SERVFAIL); + } + aname->attributes |= DNS_NAMEATTR_CACHE; + aname->attributes |= DNS_NAMEATTR_ANSWER; + rdataset->attributes |= DNS_RDATASETATTR_ANSWER; + rdataset->attributes |= DNS_RDATASETATTR_CACHE; + rdataset->trust = trust; + (void)dns_rdataset_additionaldata(rdataset, + check_related, + fctx); } - result = dns_message_nextname(message, DNS_SECTION_ANSWER); - } - if (result == ISC_R_NOMORE) - result = ISC_R_SUCCESS; - if (result != ISC_R_SUCCESS) - return (result); - - /* - * We should have found an answer. - */ - if (!have_answer) { + } else if (aname != NULL) { + if (!validinanswer(ardataset, fctx)) + return (DNS_R_FORMERR); + if ((ardataset->type == dns_rdatatype_a || + ardataset->type == dns_rdatatype_aaaa) && + !is_answeraddress_allowed(view, aname, ardataset)) { + return (DNS_R_SERVFAIL); + } + if ((ardataset->type == dns_rdatatype_cname || + ardataset->type == dns_rdatatype_dname) && + !is_answertarget_allowed(fctx, qname, aname, ardataset, + NULL)) + { + return (DNS_R_SERVFAIL); + } + aname->attributes |= DNS_NAMEATTR_CACHE; + aname->attributes |= DNS_NAMEATTR_ANSWER; + ardataset->attributes |= DNS_RDATASETATTR_ANSWER; + ardataset->attributes |= DNS_RDATASETATTR_CACHE; + ardataset->trust = trust; + (void)dns_rdataset_additionaldata(ardataset, check_related, + fctx); + for (sigrdataset = ISC_LIST_HEAD(aname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) { + if (!validinanswer(sigrdataset, fctx)) + return (DNS_R_FORMERR); + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != type) + continue; + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = trust; + break; + } + } else if (cname != NULL) { + if (!validinanswer(crdataset, fctx)) { + return (DNS_R_FORMERR); + } + if (type == dns_rdatatype_rrsig || type == dns_rdatatype_key || + type == dns_rdatatype_nsec) + { + char buf[DNS_RDATATYPE_FORMATSIZE]; + dns_rdatatype_format(type, buf, sizeof(buf)); + log_formerr(fctx, "CNAME response for %s RR", buf); + return (DNS_R_FORMERR); + } + if (!is_answertarget_allowed(fctx, qname, cname, crdataset, + NULL)) + { + return (DNS_R_SERVFAIL); + } + cname->attributes |= DNS_NAMEATTR_CACHE; + cname->attributes |= DNS_NAMEATTR_ANSWER; + cname->attributes |= DNS_NAMEATTR_CHAINING; + crdataset->attributes |= DNS_RDATASETATTR_ANSWER; + crdataset->attributes |= DNS_RDATASETATTR_CACHE; + crdataset->attributes |= DNS_RDATASETATTR_CHAINING; + crdataset->trust = trust; + for (sigrdataset = ISC_LIST_HEAD(cname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (!validinanswer(sigrdataset, fctx)) { + return (DNS_R_FORMERR); + } + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != dns_rdatatype_cname) + { + continue; + } + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = trust; + break; + } + chaining = ISC_TRUE; + } else if (dname != NULL) { + if (!validinanswer(drdataset, fctx)) { + return (DNS_R_FORMERR); + } + if (!is_answertarget_allowed(fctx, qname, dname, drdataset, + &chaining)) { + return (DNS_R_SERVFAIL); + } + dname->attributes |= DNS_NAMEATTR_CACHE; + dname->attributes |= DNS_NAMEATTR_ANSWER; + dname->attributes |= DNS_NAMEATTR_CHAINING; + drdataset->attributes |= DNS_RDATASETATTR_ANSWER; + drdataset->attributes |= DNS_RDATASETATTR_CACHE; + drdataset->attributes |= DNS_RDATASETATTR_CHAINING; + drdataset->trust = trust; + for (sigrdataset = ISC_LIST_HEAD(dname->list); + sigrdataset != NULL; + sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) + { + if (!validinanswer(sigrdataset, fctx)) { + return (DNS_R_FORMERR); + } + if (sigrdataset->type != dns_rdatatype_rrsig || + sigrdataset->covers != dns_rdatatype_dname) + { + continue; + } + sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG; + sigrdataset->attributes |= DNS_RDATASETATTR_CACHE; + sigrdataset->trust = trust; + break; + } + } else { log_formerr(fctx, "reply has no answer"); return (DNS_R_FORMERR); } @@ -6536,14 +6340,8 @@ answer_response(fetchctx_t *fctx) { /* * Did chaining end before we got the final answer? */ - if (chaining != 0) { - /* - * Yes. This may be a negative reply, so hand off - * authority section processing to the noanswer code. - * If it isn't a noanswer response, no harm will be - * done. - */ - return (noanswer_response(fctx, qname, 0)); + if (chaining) { + return (ISC_R_SUCCESS); } /* @@ -6562,11 +6360,9 @@ answer_response(fetchctx_t *fctx) { * We expect there to be only one owner name for all the rdatasets * in this section, and we expect that it is not external. */ - done = ISC_FALSE; - ns_name = NULL; - ns_rdataset = NULL; result = dns_message_firstname(message, DNS_SECTION_AUTHORITY); while (!done && result == ISC_R_SUCCESS) { + isc_boolean_t external; name = NULL; dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name); external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain)); @@ -6585,12 +6381,13 @@ answer_response(fetchctx_t *fctx) { DNS_NAMEATTR_CACHE; rdataset->attributes |= DNS_RDATASETATTR_CACHE; - if (aa && chaining == 0) + if (aa && !chaining) { rdataset->trust = dns_trust_authauthority; - else + } else { rdataset->trust = dns_trust_additional; + } if (rdataset->type == dns_rdatatype_ns) { @@ -7249,6 +7046,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) { * Is the remote server broken, or does it dislike us? */ if (message->rcode != dns_rcode_noerror && + message->rcode != dns_rcode_yxdomain && message->rcode != dns_rcode_nxdomain) { if (((message->rcode == dns_rcode_formerr || message->rcode == dns_rcode_notimp) || @@ -7293,13 +7091,6 @@ resquery_response(isc_task_t *task, isc_event_t *event) { log_formerr(fctx, "server sent FORMERR"); result = DNS_R_FORMERR; } - } else if (message->rcode == dns_rcode_yxdomain) { - /* - * DNAME mapping failed because the new name - * was too long. There's no chance of success - * for this fetch. - */ - result = DNS_R_YXDOMAIN; } else if (message->rcode == dns_rcode_badvers) { unsigned int flags, mask; unsigned int version; @@ -7404,6 +7195,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) { */ if (message->counts[DNS_SECTION_ANSWER] > 0 && (message->rcode == dns_rcode_noerror || + message->rcode == dns_rcode_yxdomain || message->rcode == dns_rcode_nxdomain)) { /* * [normal case] -- 2.9.3