mirror of
https://github.com/isc-projects/bind9.git
synced 2026-03-03 05:50:39 -05:00
Reimplement:
4578. [security] Some chaining (CNAME or DNAME) responses to upstream
queries could trigger assertion failures.
(CVE-2017-3137) [RT #44734]
This commit is contained in:
parent
ed5bf0e581
commit
f240f4a5de
4 changed files with 332 additions and 523 deletions
|
|
@ -21,6 +21,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
|
||||
|
|
|
|||
|
|
@ -49,10 +49,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.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.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.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`
|
||||
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ nxdomain a0-1s-cname.tld2s +dnssec @$ns6 # 19
|
|||
drop a3-8.tld2 any @$ns6 # 20 drop
|
||||
|
||||
end_group
|
||||
ckstatsrange $ns3 test1 ns3 22 25
|
||||
ckstatsrange $ns3 test1 ns3 22 27
|
||||
ckstats $ns5 test1 ns5 0
|
||||
ckstats $ns6 test1 ns6 0
|
||||
|
||||
|
|
|
|||
|
|
@ -4555,6 +4555,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);
|
||||
|
||||
|
|
@ -6179,79 +6180,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)
|
||||
|
|
@ -6327,9 +6255,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_result_t result;
|
||||
dns_rbtnode_t *node = NULL;
|
||||
|
|
@ -6337,18 +6264,58 @@ 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 (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 == ISC_R_NOSPACE)
|
||||
return (ISC_TRUE);
|
||||
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
||||
break;
|
||||
default:
|
||||
INSIST(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the owner name matches one in the exclusion list, either exactly
|
||||
* 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);
|
||||
}
|
||||
|
|
@ -6356,7 +6323,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);
|
||||
|
||||
/*
|
||||
|
|
@ -6365,9 +6332,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,
|
||||
|
|
@ -6855,459 +6822,297 @@ 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;
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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 (external) {
|
||||
/*
|
||||
* This data is outside of
|
||||
* our query domain, and
|
||||
* may not be cached.
|
||||
*/
|
||||
rdataset->attributes |=
|
||||
DNS_RDATASETATTR_EXTERNAL;
|
||||
} else if (chaining == 0) {
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* We could add an "else" clause here and
|
||||
* log that we're ignoring this rdataset.
|
||||
*/
|
||||
}
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Look for a DNAME (or its SIG). Anything else is
|
||||
* ignored.
|
||||
*/
|
||||
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))
|
||||
{
|
||||
if (rdataset->rdclass != fctx->res->rdclass) {
|
||||
log_formerr(fctx, "Mismatched class "
|
||||
"in answer");
|
||||
return (DNS_R_FORMERR);
|
||||
if (rdataset->type == type ||
|
||||
type == dns_rdatatype_any)
|
||||
{
|
||||
aname = name;
|
||||
if (type != dns_rdatatype_any) {
|
||||
ardataset = rdataset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (rdataset->type == dns_rdatatype_cname) {
|
||||
cname = name;
|
||||
crdataset = rdataset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/*
|
||||
* Only pass DNAME or RRSIG(DNAME).
|
||||
*/
|
||||
if (rdataset->type != dns_rdatatype_dname &&
|
||||
(rdataset->type != dns_rdatatype_rrsig ||
|
||||
rdataset->covers != dns_rdatatype_dname))
|
||||
case dns_namereln_subdomain:
|
||||
/*
|
||||
* 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 (nlabels >= dname_labels || nlabels < domain_labels)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* We are looking for the shortest DNAME if there
|
||||
* are multiple ones (which there shouldn't be).
|
||||
*/
|
||||
for (rdataset = ISC_LIST_HEAD(name->list);
|
||||
rdataset != NULL;
|
||||
rdataset = ISC_LIST_NEXT(rdataset, link))
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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 (!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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 (external) {
|
||||
rdataset->attributes |=
|
||||
DNS_RDATASETATTR_EXTERNAL;
|
||||
} else if ((chaining == 0) ||
|
||||
(chaining == 1U && synthcname))
|
||||
{
|
||||
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;
|
||||
}
|
||||
dname = name;
|
||||
drdataset = rdataset;
|
||||
dname_labels = nlabels;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
/*
|
||||
* Ensure that we can't ever get chaining == 1
|
||||
* above if we have processed a DNAME.
|
||||
*/
|
||||
if (wanted_chaining && chaining < 2U)
|
||||
chaining += 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
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) {
|
||||
if (dname != NULL) {
|
||||
aname = NULL;
|
||||
ardataset = NULL;
|
||||
cname = NULL;
|
||||
crdataset = NULL;
|
||||
} else if (aname != NULL) {
|
||||
cname = NULL;
|
||||
crdataset = NULL;
|
||||
}
|
||||
|
||||
aa = ISC_TF((message->flags & DNS_MESSAGEFLAG_AA) != 0);
|
||||
trust = aa ? dns_trust_authanswer : dns_trust_answer;
|
||||
|
||||
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);
|
||||
}
|
||||
if ((fctx->type == dns_rdatatype_sig ||
|
||||
fctx->type == dns_rdatatype_rrsig) &&
|
||||
rdataset->type != fctx->type)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
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))
|
||||
{
|
||||
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);
|
||||
}
|
||||
} 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)) {
|
||||
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)) {
|
||||
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)) {
|
||||
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;
|
||||
}
|
||||
chaining = ISC_TRUE;
|
||||
} else {
|
||||
log_formerr(fctx, "reply has no answer");
|
||||
return (DNS_R_FORMERR);
|
||||
}
|
||||
|
|
@ -7320,7 +7125,7 @@ answer_response(fetchctx_t *fctx) {
|
|||
/*
|
||||
* Did chaining end before we got the final answer?
|
||||
*/
|
||||
if (chaining != 0) {
|
||||
if (chaining) {
|
||||
/*
|
||||
* Yes. This may be a negative reply, so hand off
|
||||
* authority section processing to the noanswer code.
|
||||
|
|
@ -7346,11 +7151,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));
|
||||
|
|
@ -7369,12 +7172,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)
|
||||
{
|
||||
|
|
@ -8235,6 +8039,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) {
|
||||
isc_buffer_t b;
|
||||
char code[64];
|
||||
|
|
@ -8296,13 +8101,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 version;
|
||||
isc_boolean_t setnocookie = ISC_FALSE;
|
||||
|
|
@ -8457,6 +8255,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]
|
||||
|
|
|
|||
Loading…
Reference in a new issue