diff --git a/SOURCES/bind99-CVE-2017-3136.patch b/SOURCES/bind99-CVE-2017-3136.patch new file mode 100644 index 0000000..f83f93e --- /dev/null +++ b/SOURCES/bind99-CVE-2017-3136.patch @@ -0,0 +1,26 @@ +From d4d151cf34fab415e2823deada3433df7f475c71 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= +Date: Tue, 11 Apr 2017 16:19:08 +0200 +Subject: [PATCH 1/3] 4575. [security] DNS64 with "break-dnssec yes;" + can result in an assertion failure. (CVE-2017-3136) + [RT #44653] + +--- + bin/named/query.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/bin/named/query.c b/bin/named/query.c +index f60078b..6e988f5 100644 +--- a/bin/named/query.c ++++ b/bin/named/query.c +@@ -7324,6 +7324,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype) + result = query_dns64(client, &fname, rdataset, + sigrdataset, dbuf, + DNS_SECTION_ANSWER); ++ noqname = NULL; + dns_rdataset_disassociate(rdataset); + dns_message_puttemprdataset(client->message, &rdataset); + if (result == ISC_R_NOMORE) { +-- +2.9.3 + diff --git a/SOURCES/bind99-CVE-2017-3137.patch b/SOURCES/bind99-CVE-2017-3137.patch new file mode 100644 index 0000000..a0d97e5 --- /dev/null +++ b/SOURCES/bind99-CVE-2017-3137.patch @@ -0,0 +1,1126 @@ +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 + diff --git a/SPECS/bind.spec b/SPECS/bind.spec index 69626bb..28a3d5d 100644 --- a/SPECS/bind.spec +++ b/SPECS/bind.spec @@ -25,7 +25,7 @@ Summary: The Berkeley Internet Name Domain (BIND) DNS (Domain Name System) serv Name: bind License: ISC Version: 9.9.4 -Release: 38%{?PATCHVER}%{?PREVER}%{?dist}.2 +Release: 38%{?PATCHVER}%{?PREVER}%{?dist}.3 Epoch: 32 Url: http://www.isc.org/products/BIND/ Buildroot:%{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -121,6 +121,10 @@ Patch173:bind99-rt43779.patch Patch174:bind99-CVE-2017-3135.patch # ISC 4558 Patch175:bind99-rt44318.patch +# ISC 4575 +Patch176:bind99-CVE-2017-3136.patch +# ISC 4578 +Patch177:bind99-CVE-2017-3137.patch # Native PKCS#11 functionality from 9.10 Patch150:bind-9.9-allow_external_dnskey.patch @@ -416,6 +420,8 @@ tar -xf %{SOURCE48} -C bin/tests/system/geoip/data %patch173 -p1 -b .rt43779 %patch174 -p1 -b .CVE-2017-3135 %patch175 -p1 -b .rt44318 +%patch176 -p1 -b .CVE-2017-3136 +%patch177 -p1 -b .CVE-2017-3137 %if %{PKCS11} cp -r bin/named{,-pkcs11} @@ -1095,6 +1101,10 @@ rm -rf ${RPM_BUILD_ROOT} %endif %changelog +* Wed Apr 12 2017 Petr Menšík - 32:9.9.4-38.3 +- Fix CVE-2017-3136 (ISC change 4575) +- Fix CVE-2017-3137 (ISC change 4578) + * Wed Feb 08 2017 Petr Menšík - 32:9.9.4-38.2 - Fix CVE-2017-3135 (ISC change 4557) - Fix and test caching CNAME before DNAME (ISC change 4558)