- Fix that the aggressive negative cache does not insert NSEC

records with overreaching next owner name. Also the result
  is not above the trust anchor's bailiwick. Also RRSIGS are
  not considered valid when an NSEC next owner name is not
  under the signer zone name. Thanks to Qifan Zhang, Palo
  Alto Networks, for the report.
This commit is contained in:
W.C.A. Wijngaards
2026-07-23 16:17:59 +02:00
parent 0735cb28d1
commit 5eb362a6c0
8 changed files with 293 additions and 7 deletions
+18 -3
View File
@@ -938,6 +938,10 @@ void val_neg_addreply(struct val_neg_cache* neg, struct reply_info* rep)
continue;
if(!dname_subdomain_c(rep->rrsets[i]->rk.dname,
zone->name)) continue;
if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC &&
!nsec_nextowner_subdomain(rep->rrsets[i], zone->name)) {
continue; /* nextowner not in zone */
}
/* insert NSEC into this zone's tree */
neg_insert_data(neg, zone, rep->rrsets[i]);
}
@@ -1022,6 +1026,10 @@ void val_neg_addreferral(struct val_neg_cache* neg, struct reply_info* rep,
continue;
if(!dname_subdomain_c(rep->rrsets[i]->rk.dname,
zone->name)) continue;
if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC &&
!nsec_nextowner_subdomain(rep->rrsets[i], zone->name)) {
continue; /* nextowner not in zone */
}
/* insert NSEC into this zone's tree */
neg_insert_data(neg, zone, rep->rrsets[i]);
}
@@ -1110,12 +1118,14 @@ grab_nsec(struct rrset_cache* rrset_cache, uint8_t* qname, size_t qname_len,
* @param rrset_cache: rrset cache
* @param now: to check ttl against
* @param region: where to alloc result
* @param topname: do not look higher than this name, so that the
* result cannot be taken from a zone above the current trust anchor.
* @return rrset or NULL
*/
static struct ub_packed_rrset_key*
neg_find_nsec(struct val_neg_cache* neg_cache, uint8_t* qname, size_t qname_len,
uint16_t qclass, struct rrset_cache* rrset_cache, time_t now,
struct regional* region)
struct regional* region, uint8_t* topname)
{
int labs;
uint32_t flags;
@@ -1133,6 +1143,11 @@ neg_find_nsec(struct val_neg_cache* neg_cache, uint8_t* qname, size_t qname_len,
lock_basic_unlock(&neg_cache->lock);
return NULL;
}
if(topname && !dname_subdomain_c(zone->name, topname)) {
/* Reject NSEC not within trust anchor's bailiwick */
lock_basic_unlock(&neg_cache->lock);
return NULL;
}
/* NSEC only for now */
if(zone->nsec3_hash) {
@@ -1430,7 +1445,7 @@ val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo,
/* Get best available NSEC for qname */
nsec = neg_find_nsec(neg, qinfo->qname, qinfo->qname_len, qinfo->qclass,
rrset_cache, now, region);
rrset_cache, now, region, topname);
/* Matching NSEC, use to generate No Data answer. Not creating answers
* yet for No Data proven using wildcard. */
@@ -1510,7 +1525,7 @@ val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo,
* proof */
if(!(wcrr = neg_find_nsec(neg, wc_qinfo.qname,
wc_qinfo.qname_len, qinfo->qclass,
rrset_cache, now, region)))
rrset_cache, now, region, topname)))
return NULL;
nodata_wc = NULL;
+10
View File
@@ -1641,6 +1641,16 @@ dnskey_verify_rrset_sig(struct regional* region, sldns_buffer* buf,
return sec_status_bogus; /* NSEC3 owner not b32.signer */
}
}
/* NSEC, a next owner that is not under the signer is not allowed.*/
if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_NSEC &&
!nsec_nextowner_subdomain(rrset, signer)) {
verbose(VERB_QUERY, "verify: NSEC next owner overreaches signer name");
*reason = "NSEC next owner overreaches signer name";
if(reason_bogus)
*reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
return sec_status_bogus; /* nextowner overreaching */
}
sigblock = (unsigned char*)signer+signer_len;
if(siglen < 2+18+signer_len+1) {
verbose(VERB_QUERY, "verify: too short, no signature data");
+17
View File
@@ -1384,3 +1384,20 @@ int derive_cname_from_dname(struct ub_packed_rrset_key* cname,
memmove(out+prefix_len, dname_target, dname_target_len);
return 1;
}
int nsec_nextowner_subdomain(struct ub_packed_rrset_key* rrset, uint8_t* name)
{
struct packed_rrset_data* d;
uint8_t* next;
size_t nextlen;
if(ntohs(rrset->rk.type) != LDNS_RR_TYPE_NSEC)
return 0;
d = (struct packed_rrset_data*)rrset->entry.data;
if(!d || d->count == 0)
return 0;
next = d->rr_data[0]+2;
nextlen = dname_valid(next, d->rr_len[0]-2);
if(nextlen == 0)
return 0; /* malformed */
return dname_subdomain_c(next, name);
}
+3
View File
@@ -452,4 +452,7 @@ int derive_cname_from_dname(struct ub_packed_rrset_key* cname,
void rrsig_get_signer(uint8_t* data, size_t len, uint8_t** sname,
size_t* slen);
/** See if the NSEC nextowner name is a subdomain of the name. */
int nsec_nextowner_subdomain(struct ub_packed_rrset_key* rrset, uint8_t* name);
#endif /* VALIDATOR_VAL_UTILS_H */