DNSSEC-signed zones may contain high iteration-count NSEC3 records, which prove that certain delegations are insecure. Previously, a validating resolver encountering such a delegation processed these iterations up to the number given, which could be a maximum of 65,535. This has been addressed by introducing a processing limit, set at 150. Now, if such an NSEC3 record is encountered, the delegation will be treated as insecure. ISC would like to thank Samy Medjahed/Ap4sh for bringing this vulnerability to our attention. Closes isc-projects/bind9#5708 Backport of MR !935 Resolves-Vulnerability: CVE-2026-1519 Resolves: RHEL-160106
262 lines
8.8 KiB
Diff
262 lines
8.8 KiB
Diff
From 19b300cadcc265af7f6cdee16a32b5a5d9139b38 Mon Sep 17 00:00:00 2001
|
|
From: Matthijs Mekking <matthijs@isc.org>
|
|
Date: Tue, 3 Mar 2026 10:40:36 +0100
|
|
Subject: [PATCH] Check iterations in isdelegation()
|
|
|
|
When looking up an NSEC3 as part of an insecurity proof, check the
|
|
number of iterations. If this is too high, treat the answer as insecure
|
|
by marking the answer with trust level "answer", indicating that they
|
|
did not validate, but could be cached as insecure.
|
|
|
|
(cherry picked from commit 988040a5e02f86f4a8cdb0704e8d501f9082a89c)
|
|
(cherry picked from commit 0d8f3d0b22fb23c5a9138cc48e72ef8ab3d603df)
|
|
|
|
Don't verify already trusted rdatasets
|
|
|
|
If we already marked an rdataset as secure (or it has even stronger
|
|
trust), there is no need to cryptographically verify it again.
|
|
|
|
(cherry picked from commit 0ec08c212022d08c9717f2bc6bd3e8ebd6f034ce)
|
|
(cherry picked from commit e1d47d57e4cb496a8b64ea292467cd05a769d1e6)
|
|
|
|
Check RRset trust in validate_neg_rrset()
|
|
|
|
In many places we only create a validator if the RRset has too low
|
|
trust (the RRset is pending validation, or could not be validated
|
|
before). This check was missing prior to validating negative response
|
|
data.
|
|
|
|
(cherry picked from commit 6ca67f65cd685cf8699540a852c1e3775bd48d64)
|
|
(cherry picked from commit fe40620f47ce7cc10aca33b3d4f0a840f53f708d)
|
|
---
|
|
lib/dns/include/dns/types.h | 2 +-
|
|
lib/dns/validator.c | 96 +++++++++++++++++++++++++++++++------
|
|
2 files changed, 82 insertions(+), 16 deletions(-)
|
|
|
|
diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h
|
|
index 934a641118..1b0f2b71a0 100644
|
|
--- a/lib/dns/include/dns/types.h
|
|
+++ b/lib/dns/include/dns/types.h
|
|
@@ -357,7 +357,7 @@ enum {
|
|
(x) == dns_trust_pending_additional)
|
|
#define DNS_TRUST_GLUE(x) ((x) == dns_trust_glue)
|
|
#define DNS_TRUST_ANSWER(x) ((x) == dns_trust_answer)
|
|
-
|
|
+#define DNS_TRUST_SECURE(x) ((x) >= dns_trust_secure)
|
|
|
|
/*%
|
|
* Name checking severities.
|
|
diff --git a/lib/dns/validator.c b/lib/dns/validator.c
|
|
index 0b257fe874..5bc7bec951 100644
|
|
--- a/lib/dns/validator.c
|
|
+++ b/lib/dns/validator.c
|
|
@@ -262,12 +262,25 @@ dlv_algorithm_supported(dns_validator_t *val) {
|
|
}
|
|
|
|
/*%
|
|
- * Look in the NSEC record returned from a DS query to see if there is
|
|
- * a NS RRset at this name. If it is found we are at a delegation point.
|
|
+ * The isdelegation() function is called as part of seeking the DS record.
|
|
+ * Look in the NSEC or NSEC3 record returned from a DS query to see if the
|
|
+ * record has the NS bitmap set. If so, we are at a delegation point.
|
|
+ *
|
|
+ * If the response contains NSEC3 records with too high iterations, we cannot
|
|
+ * (or rather we are not going to) validate the insecurity proof. Instead we
|
|
+ * are going to treat the message as insecure and just assume the DS was at
|
|
+ * the delegation.
|
|
+ *
|
|
+ * Returns:
|
|
+ *\li #ISC_R_SUCCESS the NS bitmap was set in the NSEC or NSEC3 record, or
|
|
+ * the NSEC3 covers the name (in case of opt-out), or
|
|
+ * we cannot validate the insecurity proof and are going
|
|
+ * to treat the message as isnecure.
|
|
+ *\li #ISC_R_NOTFOUND the NS bitmap was not set,
|
|
*/
|
|
-static bool
|
|
-isdelegation(dns_name_t *name, dns_rdataset_t *rdataset,
|
|
- isc_result_t dbresult)
|
|
+static isc_result_t
|
|
+isdelegation(dns_validator_t *val, dns_name_t *name, dns_rdataset_t *rdataset,
|
|
+ isc_result_t dbresult, const char *caller)
|
|
{
|
|
dns_fixedname_t fixed;
|
|
dns_label_t hashlabel;
|
|
@@ -295,7 +308,7 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset,
|
|
if (result == ISC_R_NOTFOUND)
|
|
goto trynsec3;
|
|
if (result != ISC_R_SUCCESS)
|
|
- return (false);
|
|
+ return (ISC_R_NOTFOUND);
|
|
}
|
|
|
|
INSIST(set.type == dns_rdatatype_nsec);
|
|
@@ -308,7 +321,7 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset,
|
|
dns_rdata_reset(&rdata);
|
|
}
|
|
dns_rdataset_disassociate(&set);
|
|
- return (found);
|
|
+ return (found ? ISC_R_SUCCESS : ISC_R_NOTFOUND);
|
|
|
|
trynsec3:
|
|
/*
|
|
@@ -345,18 +358,33 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset,
|
|
(void)dns_rdata_tostruct(&rdata, &nsec3, NULL);
|
|
if (nsec3.hash != 1)
|
|
continue;
|
|
+
|
|
+ /*
|
|
+ * If there are too many iterations assume bad things
|
|
+ * are happening and bail out early. Treat as if the
|
|
+ * DS was at the delegation.
|
|
+ */
|
|
+ if (nsec3.iterations > DNS_NSEC3_MAXITERATIONS) {
|
|
+ validator_log(val, ISC_LOG_DEBUG(3),
|
|
+ "%s: too many iterations",
|
|
+ caller);
|
|
+ dns_rdataset_disassociate(&set);
|
|
+ return (ISC_R_SUCCESS);
|
|
+ }
|
|
+
|
|
length = isc_iterated_hash(hash, nsec3.hash,
|
|
nsec3.iterations, nsec3.salt,
|
|
nsec3.salt_length,
|
|
name->ndata, name->length);
|
|
if (length != isc_buffer_usedlength(&buffer))
|
|
continue;
|
|
+
|
|
order = memcmp(hash, owner, length);
|
|
if (order == 0) {
|
|
found = dns_nsec3_typepresent(&rdata,
|
|
dns_rdatatype_ns);
|
|
dns_rdataset_disassociate(&set);
|
|
- return (found);
|
|
+ return (found ? ISC_R_SUCCESS : ISC_R_NOTFOUND);
|
|
}
|
|
if ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) == 0)
|
|
continue;
|
|
@@ -370,12 +398,12 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset,
|
|
memcmp(hash, nsec3.next, length) < 0)))
|
|
{
|
|
dns_rdataset_disassociate(&set);
|
|
- return (true);
|
|
+ return (ISC_R_SUCCESS);
|
|
}
|
|
}
|
|
dns_rdataset_disassociate(&set);
|
|
}
|
|
- return (found);
|
|
+ return (found ? ISC_R_SUCCESS : ISC_R_NOTFOUND);
|
|
}
|
|
|
|
/*%
|
|
@@ -597,7 +625,8 @@ dsfetched2(isc_task_t *task, isc_event_t *event) {
|
|
*/
|
|
tname = dns_fixedname_name(&devent->foundname);
|
|
if (eresult != DNS_R_CNAME &&
|
|
- isdelegation(tname, &val->frdataset, eresult)) {
|
|
+ isdelegation(val, tname, &val->frdataset, eresult,
|
|
+ "dsfetched2") == ISC_R_SUCCESS) {
|
|
if (val->mustbesecure) {
|
|
validator_log(val, ISC_LOG_WARNING,
|
|
"must be secure failure, no DS"
|
|
@@ -754,10 +783,13 @@ dsvalidated(isc_task_t *task, isc_event_t *event) {
|
|
dns_trust_totext(val->frdataset.trust));
|
|
have_dsset = (val->frdataset.type == dns_rdatatype_ds);
|
|
name = dns_fixedname_name(&val->fname);
|
|
+
|
|
if ((val->attributes & VALATTR_INSECURITY) != 0 &&
|
|
val->frdataset.covers == dns_rdatatype_ds &&
|
|
NEGATIVE(&val->frdataset) &&
|
|
- isdelegation(name, &val->frdataset, DNS_R_NCACHENXRRSET)) {
|
|
+ isdelegation(val, name, &val->frdataset,
|
|
+ DNS_R_NCACHENXRRSET,
|
|
+ "dsvalidated") == ISC_R_SUCCESS) {
|
|
if (val->mustbesecure) {
|
|
validator_log(val, ISC_LOG_WARNING,
|
|
"must be secure failure, no DS "
|
|
@@ -1524,6 +1556,13 @@ verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata,
|
|
bool ignore = false;
|
|
dns_name_t *wild;
|
|
|
|
+ if (DNS_TRUST_SECURE(val->event->rdataset->trust)) {
|
|
+ /*
|
|
+ * This RRset was already verified before.
|
|
+ */
|
|
+ return ISC_R_SUCCESS;
|
|
+ }
|
|
+
|
|
val->attributes |= VALATTR_TRIEDVERIFY;
|
|
wild = dns_fixedname_initname(&fixed);
|
|
again:
|
|
@@ -2714,6 +2753,19 @@ validate_authority(dns_validator_t *val, bool resume) {
|
|
dns_rdatatype_soa))
|
|
continue;
|
|
}
|
|
+
|
|
+ if (rdataset->type != dns_rdatatype_nsec &&
|
|
+ DNS_TRUST_SECURE(rdataset->trust))
|
|
+ {
|
|
+ /*
|
|
+ * The negative response data is already
|
|
+ * verified. We skip NSEC records, because
|
|
+ * they require special processing in
|
|
+ * authvalidated().
|
|
+ */
|
|
+ continue;
|
|
+ }
|
|
+
|
|
val->currentset = rdataset;
|
|
result = create_validator(val, name, rdataset->type,
|
|
rdataset, sigrdataset,
|
|
@@ -2791,6 +2843,18 @@ validate_ncache(dns_validator_t *val, bool resume) {
|
|
dns_rdatatype_soa))
|
|
continue;
|
|
}
|
|
+
|
|
+ if (rdataset->type != dns_rdatatype_nsec &&
|
|
+ DNS_TRUST_SECURE(rdataset->trust))
|
|
+ {
|
|
+ /*
|
|
+ * The negative response data is already verified.
|
|
+ * We skip NSEC records, because they require special
|
|
+ * processing in authvalidated().
|
|
+ */
|
|
+ continue;
|
|
+ }
|
|
+
|
|
val->currentset = rdataset;
|
|
result = create_validator(val, name, rdataset->type,
|
|
rdataset, sigrdataset,
|
|
@@ -2842,7 +2906,8 @@ nsecvalidate(dns_validator_t *val, bool resume) {
|
|
result = findnsec3proofs(val);
|
|
if (result == DNS_R_NSEC3ITERRANGE) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
- "too many iterations");
|
|
+ "%s: too many iterations",
|
|
+ __func__);
|
|
markanswer(val, "validate_nx (3)");
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
@@ -2877,7 +2942,7 @@ nsecvalidate(dns_validator_t *val, bool resume) {
|
|
result = findnsec3proofs(val);
|
|
if (result == DNS_R_NSEC3ITERRANGE) {
|
|
validator_log(val, ISC_LOG_DEBUG(3),
|
|
- "too many iterations");
|
|
+ "%s: too many iterations", __func__);
|
|
markanswer(val, "validate_nx (4)");
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
@@ -3463,7 +3528,8 @@ proveunsecure(dns_validator_t *val, bool have_ds, bool resume)
|
|
result = DNS_R_NOVALIDSIG;
|
|
goto out;
|
|
}
|
|
- if (isdelegation(tname, &val->frdataset, result)) {
|
|
+ if (isdelegation(val, tname, &val->frdataset, result,
|
|
+ "proveunsecure") == ISC_R_SUCCESS) {
|
|
if (val->mustbesecure) {
|
|
validator_log(val, ISC_LOG_WARNING,
|
|
"must be secure failure, "
|
|
--
|
|
2.53.0
|
|
|