From: Matthijs Mekking Date: Fri, 27 Mar 2026 09:48:21 +0000 (+0100) Subject: When caching names, check for CNAME RRsets X-Git-Tag: v9.21.23~54^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=69a560fff189f3295a4501a51676ad7e7d0479e3;p=thirdparty%2Fbind9.git When caching names, check for CNAME RRsets CNAME and other record types cannot coexist. DNSSEC records are the exceptions to this rule. If the answer contains a name with a CNAME, remove existing RRsets at the same name from the cache. If the answer contains a name without a CNAME, remove the CNAME RRset at the same name from the cache. --- diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index e0575ab8a83..faf69ba5881 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -69,6 +69,7 @@ #include #include #include +#include #include #include #include @@ -5521,6 +5522,99 @@ getrrsig(dns_name_t *name, dns_rdatatype_t type) { return NULL; } +static void +delete_rrset(fetchctx_t *fctx, dns_name_t *name, dns_rdatatype_t type) { + isc_result_t result; + dns_dbnode_t *node = NULL; + + result = dns_db_findnode(fctx->cache, name, false, &node); + if (result != ISC_R_SUCCESS) { + return; + } + + dns_db_deleterdataset(fctx->cache, node, NULL, type, 0); + dns_db_deleterdataset(fctx->cache, node, NULL, dns_rdatatype_rrsig, + type); + dns_db_detachnode(&node); +} + +/* + * When caching a CNAME, evict other RRsets at the same owner name, + * according to the RFC specifications. + * + * RFC 1034, 3.6.2: Aliases and canonical names + * If a CNAME RR is present at a node, no other data should be + * present. + * RFC 2181, 10.1: CNAME resource records + * An alias name (label of a CNAME record) may, + * if DNSSEC is in use, have SIG, NXT, and KEY RRs, but may have no + * other data. + * RFC 2535, 2.3.5: Special Considerations with CNAME + * RFC 4034, 3: The RRSIG Resource Record + * Because every authoritative RRset in a zone must be protected by a + * digital signature, RRSIG RRs must be present for names containing a + * CNAME RR. This is a change to the traditional DNS specification + * [RFC1034], which stated that if a CNAME is present for a name, it is + * the only type allowed at that name. + * RFC 4034, 4: The NSEC Resource Record + * Because every authoritative name in a zone must be part of the NSEC + * chain, NSEC RRs must be present for names containing a CNAME RR. + * This is a change to the traditional DNS specification [RFC1034], + * which stated that if a CNAME is present for a name, it is the only + * type allowed at that name. + * + * So types allowed next to CNAME are: KEY, SIG, NXT, RRSIG, and NSEC. + */ +static void +evict_cname_other(fetchctx_t *fctx, dns_name_t *name) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdatasetiter_t *rdsiter = NULL; + + result = dns_db_findnode(fctx->cache, name, false, &node); + if (result != ISC_R_SUCCESS) { + return; + } + + result = dns_db_allrdatasets(fctx->cache, node, NULL, 0, 0, &rdsiter); + if (result != ISC_R_SUCCESS) { + dns_db_detachnode(&node); + return; + } + + DNS_RDATASETITER_FOREACH(rdsiter) { + dns_rdataset_t rdataset = DNS_RDATASET_INIT; + dns_rdatasetiter_current(rdsiter, &rdataset); + if (rdataset.type == dns_rdatatype_nsec || + rdataset.type == dns_rdatatype_nxt || + rdataset.type == dns_rdatatype_key) + { + /* KEY, NSEC and NXT records are allowed */ + dns_rdataset_disassociate(&rdataset); + continue; + } + if (dns_rdatatype_issig(rdataset.type)) { + /* Signatures will be deleted together below */ + dns_rdataset_disassociate(&rdataset); + continue; + } + if (rdataset.type == dns_rdatatype_none) { + /* Negative type. */ + dns_rdataset_disassociate(&rdataset); + continue; + } + + dns_db_deleterdataset(fctx->cache, node, NULL, rdataset.type, + 0); + dns_db_deleterdataset(fctx->cache, node, NULL, + dns_rdatatype_rrsig, rdataset.type); + dns_rdataset_disassociate(&rdataset); + } + + dns_rdatasetiter_destroy(&rdsiter); + dns_db_detachnode(&node); +} + static isc_result_t cache_rrset(fetchctx_t *fctx, isc_stdtime_t now, dns_name_t *name, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, @@ -5575,6 +5669,21 @@ cache_rrset(fetchctx_t *fctx, isc_stdtime_t now, dns_name_t *name, result = dns_db_findnode(fctx->cache, name, true, &node); } + /* + * Evict CNAME records, according to the RFC rules (see + * evict_cname_other). + * + * Note that a signature is tied to the type it covers and is deleted + * along with the covered RRset in 'delete_rrset()'. + */ + if (!dns_rdataset_matchestype(rdataset, dns_rdatatype_cname) && + !dns_rdataset_matchestype(rdataset, dns_rdatatype_key) && + !dns_rdataset_matchestype(rdataset, dns_rdatatype_nsec) && + !dns_rdataset_matchestype(rdataset, dns_rdatatype_nxt)) + { + delete_rrset(fctx, name, dns_rdatatype_cname); + } + if (result == ISC_R_SUCCESS) { result = dns_db_addrdataset(fctx->cache, node, NULL, now, rdataset, options | equalok, added); @@ -5611,22 +5720,6 @@ cache_rrset(fetchctx_t *fctx, isc_stdtime_t now, dns_name_t *name, return result; } -static void -delete_rrset(fetchctx_t *fctx, dns_name_t *name, dns_rdatatype_t type) { - isc_result_t result; - dns_dbnode_t *node = NULL; - - result = dns_db_findnode(fctx->cache, name, false, &node); - if (result != ISC_R_SUCCESS) { - return; - } - - dns_db_deleterdataset(fctx->cache, node, NULL, type, 0); - dns_db_deleterdataset(fctx->cache, node, NULL, dns_rdatatype_rrsig, - type); - dns_db_detachnode(&node); -} - static void fctx_cacheauthority(fetchctx_t *fctx, dns_message_t *message, isc_stdtime_t now) { @@ -6271,6 +6364,14 @@ rctx_cachename(respctx_t *rctx, dns_message_t *message, dns_name_t *name) { goto cleanup; } + /* + * If CNAME, delete other RRsets at the same name + * from the cache. + */ + if (rdataset->type == dns_rdatatype_cname) { + evict_cname_other(fctx, name); + } + /* Find the signature for this rdataset */ sigrdataset = getrrsig(name, rdataset->type);