1
0
forked from rpms/bind

import CS git bind-9.11.36-16.el8_10.14

This commit is contained in:
AlmaLinux RelEng Bot 2026-08-13 14:37:08 -04:00
parent 3bc8ad52b2
commit 704934c53c
7 changed files with 1275 additions and 1 deletions

View File

@ -0,0 +1,64 @@
From 6e7215866cf1b9d92b7cdbb5e1990f0f7581336a Mon Sep 17 00:00:00 2001
From: Evan Hunt <each@isc.org>
Date: Wed, 1 Jul 2026 23:24:09 -0700
Subject: [PATCH] Check NSEC3 signer matches the owning zone
When validating NSEC3 records, reject any signature whose signer field
does not match the zone owning the NSEC3.
This ensures that a child zone cannot impersonate its parent and forge
NXDOMAIN responses for sibling domains.
Fixes: isc-projects/bind9#5874
(cherry picked from commit 6e5066bb1f0f12d090e8707adb7d6ccf74f8012b)
---
lib/dns/dnssec.c | 19 +++++++++++++++++--
lib/dns/result.c | 2 +-
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index b6b2405..8dae801 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -428,10 +428,25 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
}
/*
- * NS, SOA and DNSSKEY records are signed by their owner.
- * DS records are signed by the parent.
+ * NS, SOA and DNSKEY records are signed by their owners.
+ * NSEC3 records are signed by the apex, exactly one level up
+ * from their owner names.
+ * DS records are signed by the parent zone.
*/
switch (set->type) {
+ case dns_rdatatype_nsec3: {
+ dns_name_t apex = DNS_NAME_INITEMPTY;
+ labels = dns_name_countlabels(name);
+ if (labels <= 1) {
+ inc_stat(dns_dnssecstats_fail);
+ return DNS_R_INVALIDNSEC3;
+ }
+ dns_name_split(name, labels - 1, NULL, &apex);
+ if (!dns_name_equal(&apex, &sig.signer)) {
+ inc_stat(dns_dnssecstats_fail);
+ return DNS_R_SIGINVALID;
+ }
+ } break;
case dns_rdatatype_ns:
case dns_rdatatype_soa:
case dns_rdatatype_dnskey:
diff --git a/lib/dns/result.c b/lib/dns/result.c
index 24aa01e..45ac114 100644
--- a/lib/dns/result.c
+++ b/lib/dns/result.c
@@ -148,7 +148,7 @@ static const char *text[DNS_R_NRESULTS] = {
"covering NSEC record returned", /*%< 101 DNS_R_COVERINGNSEC */
"MX is an address", /*%< 102 DNS_R_MXISADDRESS */
"duplicate query", /*%< 103 DNS_R_DUPLICATE */
- "invalid NSEC3 owner name (wildcard)", /*%< 104 DNS_R_INVALIDNSEC3 */
+ "invalid NSEC3 owner name", /*%< 104 DNS_R_INVALIDNSEC3 */
"not master", /*%< 105 DNS_R_NOTMASTER */
"broken trust chain", /*%< 106 DNS_R_BROKENCHAIN */

View File

@ -0,0 +1,318 @@
From a323885e23d763255a6abea0ae7752449c0c2cde Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@sury.org>
Date: Tue, 23 Jun 2026 10:59:38 +0200
Subject: [PATCH] Make the dns_slabheaders in the cache reference counted
Instead of only reference counting the enclosing qpcnode, add the
reference counting directly to the slabheaders. The reference is
incremented when an rdataset is bound to the header and decremented when
the rdataset is disassociated, so a stale slabheader can be removed from
the node's down chain as soon as its own reference count reaches zero,
instead of waiting for the whole qpcnode to become unreferenced.
Building on that, clean up the ancient headers eagerly: mark_ancient()
is made idempotent, releases the header's own (container) reference and
reaps the stale headers from the node's down chain as soon as their
references reach zero. A header evicted over the per-name type limit is
expired only after the new rdataset has been bound, so the bind's
increment always precedes mark_ancient()'s decrement.
Because a header can now be reclaimed independently of its node, the
rdataset iterators must keep the header they are positioned on alive:
each iterator takes a reference on its current header and releases it
when it advances or is destroyed. Iteration otherwise stays lazy and
re-reads the node on every step, so it still observes records added to
the node while the iterator is live, as zone signing requires.
The slab headers are shared with the zone databases, so the matching
increment is added to every bind path. The noqname/closest proofs hand
out rdatasets backed by bare slabs that have no header, so they are
given a separate dns_rdataproof_rdatasetmethods that leaves the
reference count untouched.
(cherry picked from commit 2dabf117e1264fd13fb33096f87e78a039fd1c6c)
---
lib/dns/include/dns/rdataslab.h | 1 +
lib/dns/rbtdb.c | 105 +++++++++++++++++++++++++++-----
2 files changed, 90 insertions(+), 16 deletions(-)
diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h
index 40c40a8..f353f03 100644
--- a/lib/dns/include/dns/rdataslab.h
+++ b/lib/dns/include/dns/rdataslab.h
@@ -44,6 +44,7 @@
#include <stdbool.h>
#include <isc/lang.h>
+#include <isc/refcount.h>
#include <dns/types.h>
diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c
index 388ffdf..7ec553e 100644
--- a/lib/dns/rbtdb.c
+++ b/lib/dns/rbtdb.c
@@ -441,6 +441,7 @@ struct noqname {
typedef struct acachectl acachectl_t;
typedef struct rdatasetheader {
+ isc_refcount_t references;
/*%
* Locked by the owning node's lock.
*/
@@ -1760,6 +1761,8 @@ init_rdataset(dns_rbtdb_t *rbtdb, rdatasetheader_t *h) {
h->next_is_relative = 0;
h->node_is_relative = 0;
+ isc_refcount_init(&h->references, 1);
+
#if TRACE_HEADER
if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in)
fprintf(stderr, "initialized header: %p\n", h);
@@ -1885,6 +1888,9 @@ rollback_node(dns_rbtnode_t *node, rbtdb_serial_t serial) {
node->dirty = 1;
}
+static void
+clean_stale_headers(dns_rbtdb_t *rbtdb, isc_mem_t *mctx, rdatasetheader_t *top);
+
static inline void
mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
@@ -1897,26 +1903,37 @@ mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
header->attributes |= RDATASET_ATTR_ANCIENT;
header->node->dirty = 1;
+ isc_refcount_decrement(&header->references, NULL);
+
/*
- * If we have not been counted then there is nothing to do.
+ * If this header has been counted, move it to the stale stats bucket.
*/
- if ((header->attributes & RDATASET_ATTR_STATCOUNT) == 0)
- return;
-
- if (EXISTS(header))
+ if ((header->attributes & RDATASET_ATTR_STATCOUNT) != 0 &&
+ EXISTS(header))
+ {
update_rrsetstats(rbtdb, header, true);
+ }
+
+ clean_stale_headers(rbtdb, rbtdb->common.mctx, header);
}
static inline void
clean_stale_headers(dns_rbtdb_t *rbtdb, isc_mem_t *mctx, rdatasetheader_t *top)
{
rdatasetheader_t *d, *down_next;
+ rdatasetheader_t *down_parent = top;
for (d = top->down; d != NULL; d = down_next) {
down_next = d->down;
- free_rdataset(rbtdb, mctx, d);
+ d->next = down_parent;
+
+ if (isc_refcount_current(&d->references) == 0) {
+ free_rdataset(rbtdb, mctx, d);
+ down_parent->down = down_next;
+ } else {
+ down_parent = d;
+ }
}
- top->down = NULL;
}
static inline void
@@ -1932,6 +1949,7 @@ clean_cache_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) {
for (current = node->data; current != NULL; current = top_next) {
top_next = current->next;
clean_stale_headers(rbtdb, mctx, current);
+ INSIST(current->down == NULL);
/*
* If current is nonexistent or stale, we can clean it up.
*/
@@ -3469,6 +3487,8 @@ bind_rdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, rdatasetheader_t *header,
if (rdataset == NULL)
return;
+ isc_refcount_increment(&header->references, NULL);
+
new_reference(rbtdb, node, locktype);
INSIST(rdataset->methods == NULL); /* We must be disassociated. */
@@ -6386,6 +6406,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
bool header_nx;
bool newheader_nx;
bool merge;
+ bool do_expireheader = false;
dns_rdatatype_t rdtype, covers;
rbtdb_rdatatype_t negtype, sigtype;
dns_trust_t trust;
@@ -6918,6 +6939,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
}
if (IS_CACHE(rbtdb) && overmaxtype(rbtdb, ntypes)) {
+ do_expireheader = true;
if (expireheader == NULL) {
expireheader = newheader;
}
@@ -6931,9 +6953,6 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
*/
expireheader = newheader;
}
-
- set_ttl(rbtdb, expireheader, 0);
- mark_header_ancient(rbtdb, expireheader);
}
}
}
@@ -6954,6 +6973,15 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
isc_rwlocktype_write, addedrdataset);
}
+ /*
+ * We need to delay the expiration of the header until we are bound to
+ * it to prevent decrement-then-increment on the header references.
+ */
+ if (do_expireheader) {
+ set_ttl(rbtdb, expireheader, 0);
+ mark_header_ancient(rbtdb, expireheader);
+ }
+
return (ISC_R_SUCCESS);
}
@@ -9228,6 +9256,12 @@ rdataset_disassociate(dns_rdataset_t *rdataset) {
dns_db_t *db = rdataset->private1;
dns_dbnode_t *node = rdataset->private2;
+ if (rdataset->methods == &rdataset_methods) {
+ rdatasetheader_t *header = rdataset->private3;
+ header--;
+ isc_refcount_decrement(&header->references, NULL);
+ }
+
detachnode(db, &node);
}
@@ -9339,6 +9373,11 @@ rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
dns_dbnode_t *cloned_node = NULL;
attachnode(db, node, &cloned_node);
+ if (source->methods == &rdataset_methods) {
+ rdatasetheader_t *header = source->private3;
+ header--;
+ isc_refcount_increment(&header->references, NULL);
+ }
INSIST(!ISC_LINK_LINKED(target, link));
*target = *source;
ISC_LINK_INIT(target, link);
@@ -9504,6 +9543,11 @@ rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) {
rbtiterator = (rbtdb_rdatasetiter_t *)(*iteratorp);
+ if (rbtiterator->current != NULL) {
+ isc_refcount_decrement(&rbtiterator->current->references, NULL);
+ rbtiterator->current = NULL;
+ }
+
if (rbtiterator->common.version != NULL)
closeversion(rbtiterator->common.db,
&rbtiterator->common.version, false);
@@ -9561,9 +9605,18 @@ rdatasetiter_first(dns_rdatasetiter_t *iterator) {
break;
}
+ if (header != NULL) {
+ isc_refcount_increment0(&header->references, NULL);
+ }
+
NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
isc_rwlocktype_read);
+ if (rbtiterator->current != NULL) {
+ isc_refcount_decrement(&rbtiterator->current->references, NULL);
+ rbtiterator->current = NULL;
+ }
+
rbtiterator->current = header;
if (header == NULL)
@@ -9637,9 +9690,18 @@ rdatasetiter_next(dns_rdatasetiter_t *iterator) {
}
}
+ if (header != NULL) {
+ isc_refcount_increment0(&header->references, NULL);
+ }
+
NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
isc_rwlocktype_read);
+ if (rbtiterator->current != NULL) {
+ isc_refcount_decrement(&rbtiterator->current->references, NULL);
+ rbtiterator->current = NULL;
+ }
+
rbtiterator->current = header;
if (header == NULL)
@@ -10249,6 +10311,19 @@ rdataset_getadditional(dns_rdataset_t *rdataset, dns_rdatasetadditional_t type,
return (result);
}
+static void
+free_acache_cbarg(isc_mem_t *mctx, acache_cbarg_t **cbargp) {
+ acache_cbarg_t *cbarg;
+
+ REQUIRE(cbargp != NULL && *cbargp != NULL);
+
+ cbarg = *cbargp;
+ isc_refcount_decrement(&cbarg->header->references, NULL);
+ isc_mem_put(mctx, cbarg, sizeof(*cbarg));
+
+ *cbargp = NULL;
+}
+
static void
acache_callback(dns_acacheentry_t *entry, void **arg) {
dns_rbtdb_t *rbtdb;
@@ -10288,7 +10363,7 @@ acache_callback(dns_acacheentry_t *entry, void **arg) {
acarray[count].entry = NULL;
INSIST(acarray[count].cbarg == cbarg);
acarray[count].cbarg = NULL;
- isc_mem_put(rbtdb->common.mctx, cbarg, sizeof(acache_cbarg_t));
+ free_acache_cbarg(rbtdb->common.mctx, &cbarg);
dns_acache_detachentry(&entry);
}
@@ -10317,9 +10392,7 @@ acache_cancelentry(isc_mem_t *mctx, dns_acacheentry_t *entry,
dns_db_detach(&cbarg->db);
}
- isc_mem_put(mctx, cbarg, sizeof(acache_cbarg_t));
-
- *cbargp = NULL;
+ free_acache_cbarg(mctx, cbargp);
}
static isc_result_t
@@ -10355,6 +10428,7 @@ rdataset_setadditional(dns_rdataset_t *rdataset, dns_rdatasetadditional_t type,
newcbarg = isc_mem_get(rbtdb->common.mctx, sizeof(*newcbarg));
if (newcbarg == NULL)
return (ISC_R_NOMEMORY);
+ isc_refcount_increment(&header->references, NULL);
newcbarg->type = type;
newcbarg->count = count;
newcbarg->header = header;
@@ -10448,8 +10522,7 @@ rdataset_setadditional(dns_rdataset_t *rdataset, dns_rdatasetadditional_t type,
} else {
dns_db_detachnode((dns_db_t *)rbtdb, &newcbarg->node);
dns_db_detach(&newcbarg->db);
- isc_mem_put(rbtdb->common.mctx, newcbarg,
- sizeof(*newcbarg));
+ free_acache_cbarg(rbtdb->common.mctx, &newcbarg);
}
}

View File

@ -0,0 +1,168 @@
From 9c1f5b579a7a4c338be8319a40927c83a0b4f886 Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Tue, 14 Apr 2026 13:46:22 +1000
Subject: [PATCH] Test RRSIG record parsing
In particular test that labels and signer fields are consistent.
(cherry picked from commit 5a95e64731afe63d348d272cc4d3b2f9847150c2)
(cherry picked from commit 5f3b7aef42be50630ecc349a6dbc22acf96d7413)
Fix failing dnssec-signzone -M sub-test
Restore example.db to the default contents which does not result
in too many records causing the test to fail.
(cherry picked from commit 8b11c48ed044844e26eaaca4315868231014dbb7)
---
bin/tests/system/dnssec/tests.sh | 1 +
lib/dns/tests/rdata_test.c | 114 +++++++++++++++++++++++++++++++
2 files changed, 115 insertions(+)
diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh
index 1eb97f42c5..e5cb97924e 100644
--- a/bin/tests/system/dnssec/tests.sh
+++ b/bin/tests/system/dnssec/tests.sh
@@ -1793,6 +1793,7 @@ echo_i "checking TTLs are capped by dnssec-signzone -M ($n)"
ret=0
(
cd signer
+cp example.db.in example.db
$SIGNER -O full -f signer.out.8 -S -M 30 -o example example.db > /dev/null 2>&1
) || ret=1
awk '/^;/ { next; } $2 > 30 { exit 1; }' signer/signer.out.8 || ret=1
diff --git a/lib/dns/tests/rdata_test.c b/lib/dns/tests/rdata_test.c
index fe01e48fa3..1cde60b1d8 100644
--- a/lib/dns/tests/rdata_test.c
+++ b/lib/dns/tests/rdata_test.c
@@ -2356,6 +2356,119 @@ rkey(void **state) {
dns_rdatatype_rkey, sizeof(dns_rdata_rkey_t));
}
+static void
+rrsig(void **state) {
+ text_ok_t text_ok[] = {
+ TEXT_VALID("SOA 8 0 86400 20260426170000 20260413160000 54393 "
+ ". "
+ "tFbcoVP8MnpecUquJ/aj+XeNgV7ts9GSHVkXaXRJrJ/"
+ "TEkOZApVG0F6E "
+ "9sYpxGk2ItweLL43ujioGj0HWwZDRR+vbur+O/"
+ "dIdheiig1VvU+9HXLi "
+ "QOViY9Kc64ixdyJhYCC5K+bO1qsHxd+"
+ "KJXOaxyHbqchYkDFy4PL6qftE "
+ "VaLkueRgjXgOsq/"
+ "NxvCXDgAa5xy0+3Sl0myxIs8rJ5KeXfJQFe7qxgaw "
+ "VjJsJTKw8neOTw2rQfLaigWu2LIWw+"
+ "IyVrLjZJdLqGkiLBGd1w4X3U12 "
+ "fFxoY3eqzNgBEtduoGKPZ/"
+ "NpP9cuKJORJ18283aV8hR4WO91VR0q1zcM jLwqUg=="),
+ /* labels too short for signer */
+ TEXT_INVALID("SOA 8 0 86400 20260426170000 20260413160000 "
+ "54393 example. "
+ "tFbcoVP8MnpecUquJ/aj+XeNgV7ts9GSHVkXaXRJrJ/"
+ "TEkOZApVG0F6E "
+ "9sYpxGk2ItweLL43ujioGj0HWwZDRR+vbur+O/"
+ "dIdheiig1VvU+9HXLi "
+ "QOViY9Kc64ixdyJhYCC5K+bO1qsHxd+"
+ "KJXOaxyHbqchYkDFy4PL6qftE "
+ "VaLkueRgjXgOsq/"
+ "NxvCXDgAa5xy0+3Sl0myxIs8rJ5KeXfJQFe7qxgaw "
+ "VjJsJTKw8neOTw2rQfLaigWu2LIWw+"
+ "IyVrLjZJdLqGkiLBGd1w4X3U12 "
+ "fFxoY3eqzNgBEtduoGKPZ/"
+ "NpP9cuKJORJ18283aV8hR4WO91VR0q1zcM jLwqUg=="),
+ /*
+ * Sentinel.
+ */
+ TEXT_SENTINEL()
+ };
+ wire_ok_t wire_ok[] = {
+ WIRE_VALID(0x00, 0x06, 0x08, 0x00, 0x00, 0x01, 0x51, 0x80, 0x69,
+ 0xee, 0x44, 0x90, 0x69, 0xdd, 0x13, 0x00, 0xd4, 0x79,
+ 0x00, 0xb4, 0x56, 0xdc, 0xa1, 0x53, 0xfc, 0x32, 0x7a,
+ 0x5e, 0x71, 0x4a, 0xae, 0x27, 0xf6, 0xa3, 0xf9, 0x77,
+ 0x8d, 0x81, 0x5e, 0xed, 0xb3, 0xd1, 0x92, 0x1d, 0x59,
+ 0x17, 0x69, 0x74, 0x49, 0xac, 0x9f, 0xd3, 0x12, 0x43,
+ 0x99, 0x02, 0x95, 0x46, 0xd0, 0x5e, 0x84, 0xf6, 0xc6,
+ 0x29, 0xc4, 0x69, 0x36, 0x22, 0xdc, 0x1e, 0x2c, 0xbe,
+ 0x37, 0xba, 0x38, 0xa8, 0x1a, 0x3d, 0x07, 0x5b, 0x06,
+ 0x43, 0x45, 0x1f, 0xaf, 0x6e, 0xea, 0xfe, 0x3b, 0xf7,
+ 0x48, 0x76, 0x17, 0xa2, 0x8a, 0x0d, 0x55, 0xbd, 0x4f,
+ 0xbd, 0x1d, 0x72, 0xe2, 0x40, 0xe5, 0x62, 0x63, 0xd2,
+ 0x9c, 0xeb, 0x88, 0xb1, 0x77, 0x22, 0x61, 0x60, 0x20,
+ 0xb9, 0x2b, 0xe6, 0xce, 0xd6, 0xab, 0x07, 0xc5, 0xdf,
+ 0x8a, 0x25, 0x73, 0x9a, 0xc7, 0x21, 0xdb, 0xa9, 0xc8,
+ 0x58, 0x90, 0x31, 0x72, 0xe0, 0xf2, 0xfa, 0xa9, 0xfb,
+ 0x44, 0x55, 0xa2, 0xe4, 0xb9, 0xe4, 0x60, 0x8d, 0x78,
+ 0x0e, 0xb2, 0xaf, 0xcd, 0xc6, 0xf0, 0x97, 0x0e, 0x00,
+ 0x1a, 0xe7, 0x1c, 0xb4, 0xfb, 0x74, 0xa5, 0xd2, 0x6c,
+ 0xb1, 0x22, 0xcf, 0x2b, 0x27, 0x92, 0x9e, 0x5d, 0xf2,
+ 0x50, 0x15, 0xee, 0xea, 0xc6, 0x06, 0xb0, 0x56, 0x32,
+ 0x6c, 0x25, 0x32, 0xb0, 0xf2, 0x77, 0x8e, 0x4f, 0x0d,
+ 0xab, 0x41, 0xf2, 0xda, 0x8a, 0x05, 0xae, 0xd8, 0xb2,
+ 0x16, 0xc3, 0xe2, 0x32, 0x56, 0xb2, 0xe3, 0x64, 0x97,
+ 0x4b, 0xa8, 0x69, 0x22, 0x2c, 0x11, 0x9d, 0xd7, 0x0e,
+ 0x17, 0xdd, 0x4d, 0x76, 0x7c, 0x5c, 0x68, 0x63, 0x77,
+ 0xaa, 0xcc, 0xd8, 0x01, 0x12, 0xd7, 0x6e, 0xa0, 0x62,
+ 0x8f, 0x67, 0xf3, 0x69, 0x3f, 0xd7, 0x2e, 0x28, 0x93,
+ 0x91, 0x27, 0x5f, 0x36, 0xf3, 0x76, 0x95, 0xf2, 0x14,
+ 0x78, 0x58, 0xef, 0x75, 0x55, 0x1d, 0x2a, 0xd7, 0x37,
+ 0x0c, 0x8c, 0xbc, 0x2a, 0x52),
+ /* labels too short for signer */
+ WIRE_INVALID(
+ 0x00, 0x06, 0x08, 0x00, 0x00, 0x01, 0x51, 0x80, 0x69,
+ 0xee, 0x44, 0x90, 0x69, 0xdd, 0x13, 0x00, 0xd4, 0x79,
+ 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x00,
+ 0xb4, 0x56, 0xdc, 0xa1, 0x53, 0xfc, 0x32, 0x7a, 0x5e,
+ 0x71, 0x4a, 0xae, 0x27, 0xf6, 0xa3, 0xf9, 0x77, 0x8d,
+ 0x81, 0x5e, 0xed, 0xb3, 0xd1, 0x92, 0x1d, 0x59, 0x17,
+ 0x69, 0x74, 0x49, 0xac, 0x9f, 0xd3, 0x12, 0x43, 0x99,
+ 0x02, 0x95, 0x46, 0xd0, 0x5e, 0x84, 0xf6, 0xc6, 0x29,
+ 0xc4, 0x69, 0x36, 0x22, 0xdc, 0x1e, 0x2c, 0xbe, 0x37,
+ 0xba, 0x38, 0xa8, 0x1a, 0x3d, 0x07, 0x5b, 0x06, 0x43,
+ 0x45, 0x1f, 0xaf, 0x6e, 0xea, 0xfe, 0x3b, 0xf7, 0x48,
+ 0x76, 0x17, 0xa2, 0x8a, 0x0d, 0x55, 0xbd, 0x4f, 0xbd,
+ 0x1d, 0x72, 0xe2, 0x40, 0xe5, 0x62, 0x63, 0xd2, 0x9c,
+ 0xeb, 0x88, 0xb1, 0x77, 0x22, 0x61, 0x60, 0x20, 0xb9,
+ 0x2b, 0xe6, 0xce, 0xd6, 0xab, 0x07, 0xc5, 0xdf, 0x8a,
+ 0x25, 0x73, 0x9a, 0xc7, 0x21, 0xdb, 0xa9, 0xc8, 0x58,
+ 0x90, 0x31, 0x72, 0xe0, 0xf2, 0xfa, 0xa9, 0xfb, 0x44,
+ 0x55, 0xa2, 0xe4, 0xb9, 0xe4, 0x60, 0x8d, 0x78, 0x0e,
+ 0xb2, 0xaf, 0xcd, 0xc6, 0xf0, 0x97, 0x0e, 0x00, 0x1a,
+ 0xe7, 0x1c, 0xb4, 0xfb, 0x74, 0xa5, 0xd2, 0x6c, 0xb1,
+ 0x22, 0xcf, 0x2b, 0x27, 0x92, 0x9e, 0x5d, 0xf2, 0x50,
+ 0x15, 0xee, 0xea, 0xc6, 0x06, 0xb0, 0x56, 0x32, 0x6c,
+ 0x25, 0x32, 0xb0, 0xf2, 0x77, 0x8e, 0x4f, 0x0d, 0xab,
+ 0x41, 0xf2, 0xda, 0x8a, 0x05, 0xae, 0xd8, 0xb2, 0x16,
+ 0xc3, 0xe2, 0x32, 0x56, 0xb2, 0xe3, 0x64, 0x97, 0x4b,
+ 0xa8, 0x69, 0x22, 0x2c, 0x11, 0x9d, 0xd7, 0x0e, 0x17,
+ 0xdd, 0x4d, 0x76, 0x7c, 0x5c, 0x68, 0x63, 0x77, 0xaa,
+ 0xcc, 0xd8, 0x01, 0x12, 0xd7, 0x6e, 0xa0, 0x62, 0x8f,
+ 0x67, 0xf3, 0x69, 0x3f, 0xd7, 0x2e, 0x28, 0x93, 0x91,
+ 0x27, 0x5f, 0x36, 0xf3, 0x76, 0x95, 0xf2, 0x14, 0x78,
+ 0x58, 0xef, 0x75, 0x55, 0x1d, 0x2a, 0xd7, 0x37, 0x0c,
+ 0x8c, 0xbc, 0x2a, 0x52),
+
+ WIRE_SENTINEL()
+ };
+
+ UNUSED(state);
+
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_rrsig, sizeof(dns_rdata_rrsig_t));
+}
+
/* SSHFP RDATA manipulations */
static void
sshfp(void **state) {
@@ -2867,6 +2980,7 @@ main(int argc, char **argv) {
cmocka_unit_test_setup_teardown(nsec, _setup, _teardown),
cmocka_unit_test_setup_teardown(nsec3, _setup, _teardown),
cmocka_unit_test_setup_teardown(nxt, _setup, _teardown),
+ cmocka_unit_test_setup_teardown(rrsig, _setup, _teardown),
cmocka_unit_test_setup_teardown(sshfp, _setup, _teardown),
cmocka_unit_test_setup_teardown(wks, _setup, _teardown),
cmocka_unit_test_setup_teardown(rkey, _setup, _teardown),
--
2.55.0

View File

@ -0,0 +1,263 @@
From 78ac82303bc3dd9d426d21fb268a5ed362d5535a Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Tue, 14 Apr 2026 15:14:06 +1000
Subject: [PATCH 1/2] Don't sign out of zone records in dnssec-signzone
dnssec-signzone was signing extraneous records that were not within
the namespace of the zone. This no longer occurs.
(cherry picked from commit e45c9af7051421fd370f20ba8325199c606223fd)
---
bin/dnssec/dnssec-signzone.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c
index 6567421..b7c8e55 100644
--- a/bin/dnssec/dnssec-signzone.c
+++ b/bin/dnssec/dnssec-signzone.c
@@ -1482,6 +1482,11 @@ assignwork(isc_task_t *task, isc_task_t *worker) {
dns_db_detachnode(gdb, &node);
goto next;
}
+ if (!dns_name_issubdomain(name, gorigin)) {
+ dumpnode(name, node);
+ dns_db_detachnode(gdb, &node);
+ goto next;
+ }
/*
* Sort the zone data from the glue and out-of-zone data.
* For NSEC zones nodes with zone data have NSEC records.
From 8c269a0269fdf12d5edd4698d08d663c6f64ddae Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Tue, 14 Apr 2026 12:24:33 +1000
Subject: [PATCH 2/2] Invalid signed wildcard records were being accepted
An RRSIG whose Labels field indicates fewer labels than its signer
name requires was being accepted. When such a record covers a
wildcard, the validator reconstructs a wildcard owner name above the
signer's zone and caches it as secure. RFC 8198 cache synthesis
(synth-from-dnssec) then serves that forged wildcard for unrelated
names, poisoning the cache.
These records are now rejected, both when an RRSIG is parsed and when
its signature is verified.
(cherry picked from commit 084ca5ee10515e461d46b63df9660b8394bc7de9)
---
lib/dns/dnssec.c | 45 +++++++++++++++++++++++---------
lib/dns/rdata/generic/rrsig_46.c | 37 +++++++++++++++++++-------
2 files changed, 60 insertions(+), 22 deletions(-)
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index b6b2405..2516a9a 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -142,11 +142,11 @@ dns_dnssec_keyfromrdata(dns_name_t *name, dns_rdata_t *rdata, isc_mem_t *mctx,
isc_buffer_t b;
isc_region_t r;
- INSIST(name != NULL);
- INSIST(rdata != NULL);
- INSIST(mctx != NULL);
- INSIST(key != NULL);
- INSIST(*key == NULL);
+ REQUIRE(name != NULL);
+ REQUIRE(rdata != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(key != NULL);
+ REQUIRE(*key == NULL);
REQUIRE(rdata->type == dns_rdatatype_key ||
rdata->type == dns_rdatatype_dnskey);
@@ -200,12 +200,14 @@ dns_dnssec_sign(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
isc_buffer_t *databuf = NULL;
char data[256 + 8];
uint32_t flags;
+ unsigned int labels;
unsigned int sigsize;
dns_fixedname_t fnewname;
dns_fixedname_t fsigner;
REQUIRE(name != NULL);
- REQUIRE(dns_name_countlabels(name) <= 255);
+ labels = dns_name_countlabels(name);
+ REQUIRE(labels <= 255 && labels > 0);
REQUIRE(set != NULL);
REQUIRE(key != NULL);
REQUIRE(inception != NULL);
@@ -243,7 +245,7 @@ dns_dnssec_sign(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
sig.covered = set->type;
sig.algorithm = dst_key_alg(key);
- sig.labels = dns_name_countlabels(name) - 1;
+ sig.labels = labels - 1;
if (dns_name_iswildcard(name))
sig.labels--;
sig.originalttl = set->ttl;
@@ -390,11 +392,14 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
isc_result_t ret;
unsigned char data[300];
dst_context_t *ctx = NULL;
- int labels = 0;
uint32_t flags;
+ unsigned int labels;
+ unsigned int siglabels;
bool downcase = false;
REQUIRE(name != NULL);
+ labels = dns_name_countlabels(name);
+ REQUIRE(labels > 0);
REQUIRE(set != NULL);
REQUIRE(key != NULL);
REQUIRE(mctx != NULL);
@@ -407,6 +412,21 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
if (set->type != sig.covered)
return (DNS_R_SIGINVALID);
+ /*
+ * The RRSIG labels field can't indicate fewer labels than the
+ * signer. Also the labels shouldn't be greater than that of
+ * the owner name.
+ *
+ * sig.labels doesn't include the root label, so add 1 to account
+ * for it.
+ */
+ siglabels = sig.labels + 1;
+ if (siglabels < dns_name_countlabels(&sig.signer) || siglabels > labels)
+ {
+ inc_stat(dns_dnssecstats_fail);
+ return DNS_R_SIGINVALID;
+ }
+
if (isc_serial_lt(sig.timeexpire, sig.timesigned)) {
inc_stat(dns_dnssecstats_fail);
return (DNS_R_SIGINVALID);
@@ -484,11 +504,10 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
* If the name is an expanded wildcard, use the wildcard name.
*/
dns_fixedname_init(&fnewname);
- labels = dns_name_countlabels(name) - 1;
RUNTIME_CHECK(dns_name_downcase(name, dns_fixedname_name(&fnewname),
NULL) == ISC_R_SUCCESS);
- if (labels - sig.labels > 0)
- dns_name_split(dns_fixedname_name(&fnewname), sig.labels + 1,
+ if (labels > siglabels)
+ dns_name_split(dns_fixedname_name(&fnewname), siglabels,
NULL, dns_fixedname_name(&fnewname));
dns_name_toregion(dns_fixedname_name(&fnewname), &r);
@@ -497,7 +516,7 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
* Create an envelope for each rdata: <name|type|class|ttl>.
*/
isc_buffer_init(&envbuf, data, sizeof(data));
- if (labels - sig.labels > 0) {
+ if (labels > siglabels) {
isc_buffer_putuint8(&envbuf, 1);
isc_buffer_putuint8(&envbuf, '*');
memmove(data + 2, r.base, r.length);
@@ -583,7 +602,7 @@ cleanup_struct:
if (ret != ISC_R_SUCCESS)
inc_stat(dns_dnssecstats_fail);
- if (ret == ISC_R_SUCCESS && labels - sig.labels > 0) {
+ if (ret == ISC_R_SUCCESS && labels > siglabels) {
if (wild != NULL)
RUNTIME_CHECK(dns_name_concatenate(dns_wildcardname,
dns_fixedname_name(&fnewname),
diff --git a/lib/dns/rdata/generic/rrsig_46.c b/lib/dns/rdata/generic/rrsig_46.c
index 0d4df67..35a1b4c 100644
--- a/lib/dns/rdata/generic/rrsig_46.c
+++ b/lib/dns/rdata/generic/rrsig_46.c
@@ -21,12 +21,12 @@
static inline isc_result_t
fromtext_rrsig(ARGS_FROMTEXT) {
isc_token_t token;
- unsigned char c;
+ unsigned char alg, labels;
long i;
dns_rdatatype_t covered;
- char *e;
+ char *e = NULL;
isc_result_t result;
- dns_name_t name;
+ dns_name_t signer;
isc_buffer_t buffer;
uint32_t time_signed, time_expire;
@@ -57,8 +57,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
*/
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
false));
- RETTOK(dns_secalg_fromtext(&c, &token.value.as_textregion));
- RETERR(mem_tobuffer(target, &c, 1));
+ RETTOK(dns_secalg_fromtext(&alg, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &alg, 1));
/*
* Labels.
@@ -67,8 +67,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
false));
if (token.value.as_ulong > 0xffU)
RETTOK(ISC_R_RANGE);
- c = (unsigned char)token.value.as_ulong;
- RETERR(mem_tobuffer(target, &c, 1));
+ labels = (unsigned char)token.value.as_ulong;
+ RETERR(mem_tobuffer(target, &labels, 1));
/*
* Original ttl.
@@ -131,11 +131,19 @@ fromtext_rrsig(ARGS_FROMTEXT) {
*/
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
false));
- dns_name_init(&name, NULL);
+ dns_name_init(&signer, NULL);
buffer_fromregion(&buffer, &token.value.as_region);
if (origin == NULL)
origin = dns_rootname;
- RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ RETTOK(dns_name_fromtext(&signer, &buffer, origin, options, target));
+
+ /*
+ * (RRSIG labels doesn't include the root label, so add one
+ * to normalize it before checking against the signer.)
+ */
+ if ((unsigned int)(labels + 1) < dns_name_countlabels(&signer)) {
+ RETTOK(ISC_R_RANGE);
+ }
/*
* Sig.
@@ -260,6 +268,7 @@ static inline isc_result_t
fromwire_rrsig(ARGS_FROMWIRE) {
isc_region_t sr;
dns_name_t name;
+ unsigned char labels;
REQUIRE(type == dns_rdatatype_rrsig);
@@ -281,6 +290,8 @@ fromwire_rrsig(ARGS_FROMWIRE) {
if (sr.length < 18)
return (ISC_R_UNEXPECTEDEND);
+ labels = sr.base[3];
+
isc_buffer_forward(source, 18);
RETERR(mem_tobuffer(target, sr.base, 18));
@@ -290,6 +301,14 @@ fromwire_rrsig(ARGS_FROMWIRE) {
dns_name_init(&name, NULL);
RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+ /*
+ * (RRSIG labels doesn't include the root label, so add one
+ * to normalize it before checking against the signer.)
+ */
+ if ((unsigned int)(labels + 1) < dns_name_countlabels(&name)) {
+ RETERR(DNS_R_FORMERR);
+ }
+
/*
* Sig.
*/

View File

@ -0,0 +1,154 @@
From 0be245b5c7641533fff2f808d19730e9766a9a36 Mon Sep 17 00:00:00 2001
From: Evan Hunt <each@isc.org>
Date: Wed, 13 May 2026 20:45:57 -0700
Subject: [PATCH] dns_rdataset_addnoqname() could find unsigned NSEC/NSEC3
The dns_rdatalist addnoqname() implementation searches for the first
NSEC or NSEC3 record in a message, then for the first RRSIG covering
that type in the same message. Previously, if no RRSIG for the type was
found, the function accepted the unsigned record. Now, it will instead
continue searching until an NSEC or NSEC3 that does have a matching
signature is found.
When this function is called from validated() in resolver.c, a
non-success return code is now treated as an error instead of triggering
an assertion failure.
Fixes: isc-projects/bind9#5985
(cherry picked from commit 57cba571ee31311e54d8a11cb38094d439f04e09)
---
bin/named/query.c | 4 +++-
lib/dns/rbtdb.c | 8 +++++---
lib/dns/rdatalist.c | 36 +++++++++++++++++++++---------------
lib/dns/resolver.c | 4 +++-
4 files changed, 32 insertions(+), 20 deletions(-)
diff --git a/bin/named/query.c b/bin/named/query.c
index e023d74..083ce83 100644
--- a/bin/named/query.c
+++ b/bin/named/query.c
@@ -6198,7 +6198,9 @@ query_addnoqnameproof(ns_client_t *client, dns_rdataset_t *rdataset) {
goto cleanup;
result = dns_rdataset_getnoqname(rdataset, fname, neg, negsig);
- RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
query_addrrset(client, &fname, &neg, &negsig, dbuf,
DNS_SECTION_AUTHORITY);
diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c
index 388ffdf..b7f21ac 100644
--- a/lib/dns/rbtdb.c
+++ b/lib/dns/rbtdb.c
@@ -6977,7 +6977,7 @@ static inline isc_result_t
addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
dns_rdataset_t *rdataset)
{
- struct noqname *noqname;
+ struct noqname *noqname = NULL;
isc_mem_t *mctx = rbtdb->common.mctx;
dns_name_t name;
dns_rdataset_t neg, negsig;
@@ -6989,7 +6989,9 @@ addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
dns_rdataset_init(&negsig);
result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig);
- RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
noqname = isc_mem_get(mctx, sizeof(*noqname));
if (noqname == NULL) {
@@ -7021,7 +7023,7 @@ cleanup:
dns_rdataset_disassociate(&negsig);
if (noqname != NULL)
free_noqname(mctx, &noqname);
- return(result);
+ return (result);
}
static inline isc_result_t
diff --git a/lib/dns/rdatalist.c b/lib/dns/rdatalist.c
index cc2619c..687953c 100644
--- a/lib/dns/rdatalist.c
+++ b/lib/dns/rdatalist.c
@@ -196,6 +196,7 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name) {
dns_rdataset_t *neg = NULL;
dns_rdataset_t *negsig = NULL;
dns_rdataset_t *rdset;
+ dns_rdataset_t *sigset;
dns_ttl_t ttl;
REQUIRE(rdataset != NULL);
@@ -204,26 +205,30 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name) {
rdset != NULL;
rdset = ISC_LIST_NEXT(rdset, link))
{
- if (rdset->rdclass != rdataset->rdclass)
+ if (rdset->rdclass != rdataset->rdclass ||
+ (rdset->type != dns_rdatatype_nsec &&
+ rdset->type != dns_rdatatype_nsec3))
+ {
continue;
- if (rdset->type == dns_rdatatype_nsec ||
- rdset->type == dns_rdatatype_nsec3)
- neg = rdset;
+ }
+
+ for (sigset = ISC_LIST_HEAD(name->list); sigset != NULL;
+ sigset = ISC_LIST_NEXT(sigset, link))
+ {
+ if (sigset->type == dns_rdatatype_rrsig &&
+ sigset->covers == rdset->type)
+ {
+ neg = rdset;
+ negsig = sigset;
+ break;
+ }
+ }
}
- if (neg == NULL)
- return (ISC_R_NOTFOUND);
- for (rdset = ISC_LIST_HEAD(name->list);
- rdset != NULL;
- rdset = ISC_LIST_NEXT(rdset, link))
- {
- if (rdset->type == dns_rdatatype_rrsig &&
- rdset->covers == neg->type)
- negsig = rdset;
+ if (neg == NULL || negsig == NULL) {
+ return (ISC_R_NOTFOUND);
}
- if (negsig == NULL)
- return (ISC_R_NOTFOUND);
/*
* Minimise ttl.
*/
@@ -235,6 +240,7 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name) {
rdataset->ttl = neg->ttl = negsig->ttl = ttl;
rdataset->attributes |= DNS_RDATASETATTR_NOQNAME;
rdataset->private6 = name;
+
return (ISC_R_SUCCESS);
}
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
index 5331ea9..78553f6 100644
--- a/lib/dns/resolver.c
+++ b/lib/dns/resolver.c
@@ -5153,7 +5153,9 @@ validated(isc_task_t *task, isc_event_t *event) {
if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) {
result = dns_rdataset_addnoqname(vevent->rdataset,
vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF]);
- RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (result != ISC_R_SUCCESS) {
+ goto noanswer_response;
+ }
INSIST(vevent->sigrdataset != NULL);
vevent->sigrdataset->ttl = vevent->rdataset->ttl;
if (vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER] != NULL) {

View File

@ -0,0 +1,270 @@
From 338661714d508a810a903ae6fdaf522c9abc2d4b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ayd=C4=B1n=20Mercan?= <aydin@isc.org>
Date: Thu, 7 May 2026 18:59:20 +0300
Subject: [PATCH 1/2] Reject out-of-zone NSEC next owner names
When verifying DNSSEC records, make sure that a next owner name of
an NSEC record is a subdomain of the signer field.
This follows the specification RFC 4034, section 4.1.1:
Owner names of RRsets for which the given zone is not authoritative
(such as glue records) MUST NOT be listed in the Next Domain Name
unless at least one authoritative RRset exists at the same owner
name.
While the above paragraph is intended for glue records, it also
applies to out-of-zone data.
(cherry picked from commit 4065512d25b71605b9502bb69dfb903776d35aa9)
---
lib/dns/dnssec.c | 18 ++++++++++++++++++
lib/dns/include/dns/dnssec.h | 6 ++++++
2 files changed, 24 insertions(+)
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index b6b2405fac..b9bd374ed6 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -380,8 +380,10 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
bool ignoretime, unsigned int maxbits,
isc_mem_t *mctx, dns_rdata_t *sigrdata, dns_name_t *wild)
{
+ dns_rdata_nsec_t nsec;
dns_rdata_rrsig_t sig;
dns_fixedname_t fnewname;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
isc_region_t r;
isc_buffer_t envbuf;
dns_rdata_t *rdatas;
@@ -454,6 +456,22 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
break;
}
+ /*
+ * Check for out of zone NSEC entries.
+ */
+ if (set->type == dns_rdatatype_nsec) {
+ if (dns_rdataset_first(set) != ISC_R_SUCCESS) {
+ return (DNS_R_NOVALIDNSEC);
+ }
+ dns_rdataset_current(set, &rdata);
+ if (dns_rdata_tostruct(&rdata, &nsec, NULL) != ISC_R_SUCCESS) {
+ return (DNS_R_NOVALIDNSEC);
+ }
+ if (!dns_name_issubdomain(&nsec.next, &sig.signer)) {
+ return (DNS_R_NOVALIDNSEC);
+ }
+ }
+
/*
* Is the key allowed to sign data?
*/
diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h
index 31e2586c9b..1f13a08181 100644
--- a/lib/dns/include/dns/dnssec.h
+++ b/lib/dns/include/dns/dnssec.h
@@ -138,6 +138,9 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
* this record, as this requires a resolver or database.
* If 'ignoretime' is true, temporal validity will not be checked.
*
+ * If 'set' is of type NSEC, this function also verifies that the
+ * Next Name is a subdomain of the Signer's Name from 'sigrdata'.
+ *
* 'maxbits' specifies the maximum number of rsa exponent bits accepted.
*
* Requires:
@@ -160,6 +163,9 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
*\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data (either
* it is not a zone key or its flags prevent
* authentication)
+ *
+ *\li #DNS_R_NOVALIDNSEC - the NSEC rdata is not valid
+ *\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data
*\li DST_R_*
*/
From dfb91ca4a45092c414a41cb5640cf4710f7ba0e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ayd=C4=B1n=20Mercan?= <aydin@isc.org>
Date: Tue, 12 May 2026 14:54:09 +0300
Subject: [PATCH 2/2] change dns_nsec_requiredtypespresent to dns_nsec_is_legal
Change `dns_nsec_requiredtypespresent` to `dns_nsec_is_legal` as a
function for checking multiple NSEC validity rules.
Currently we now additionally check for out-of-zone NSEC entries.
(cherry picked from commit be2a6a497312469890b552907d039d2de0b44ccc)
---
lib/dns/include/dns/nsec.h | 18 ++++++++++++++
lib/dns/nsec.c | 37 +++++++++++++++++++++++++++
lib/dns/resolver.c | 51 ++++++++++++++++++++++++++++++++++++++
3 files changed, 106 insertions(+)
diff --git a/lib/dns/include/dns/nsec.h b/lib/dns/include/dns/nsec.h
index 4e12cbea75..8caead6a1e 100644
--- a/lib/dns/include/dns/nsec.h
+++ b/lib/dns/include/dns/nsec.h
@@ -106,6 +106,24 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, dns_name_t *name,
* Return ISC_R_IGNORE when the NSEC is not the appropriate one.
*/
+bool
+dns_nsec_is_legal(dns_rdataset_t *rdataset, const dns_name_t *name);
+/**<
+ * \brief
+ * Validates a rdataset of type NSEC.
+ *
+ * This functions checks for the following in the given rdataset:
+ * \li All NSEC records have both NSEC and RRSIG present
+ * \li All NSEC entries are under the `name`
+ *
+ * \par Requires:
+ * \li rdataset to be a NSEC rdataset.
+ * \li `name` is a valid dns_name_t
+ *
+ * \retval true if all the checks pass
+ * \retval false otherwise
+ */
+
ISC_LANG_ENDDECLS
#endif /* DNS_NSEC_H */
diff --git a/lib/dns/nsec.c b/lib/dns/nsec.c
index d90c38589e..fd4c529da0 100644
--- a/lib/dns/nsec.c
+++ b/lib/dns/nsec.c
@@ -21,6 +21,7 @@
#include <isc/util.h>
#include <dns/db.h>
+#include <dns/name.h>
#include <dns/nsec.h>
#include <dns/rdata.h>
#include <dns/rdatalist.h>
@@ -445,3 +446,39 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, dns_name_t *name,
*exists = false;
return (ISC_R_SUCCESS);
}
+
+bool
+dns_nsec_is_legal(dns_rdataset_t *nsecset, const dns_name_t *name) {
+ dns_rdataset_t rdataset;
+ dns_rdata_nsec_t nsec;
+ isc_result_t result;
+ bool found = false;
+
+ REQUIRE(nsecset != NULL && nsecset->type == dns_rdatatype_nsec);
+
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_clone(nsecset, &rdataset);
+
+ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rdataset, &rdata);
+
+ /* must never fail */
+ result = dns_rdata_tostruct(&rdata, &nsec, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+
+ if (!dns_name_issubdomain(&nsec.next, name) ||
+ !dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig) ||
+ !dns_nsec_typepresent(&rdata, dns_rdatatype_nsec))
+ {
+ dns_rdataset_disassociate(&rdataset);
+ return (false);
+ }
+
+ found = true;
+ }
+ dns_rdataset_disassociate(&rdataset);
+ return (found);
+}
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
index 0f16eadcdd..8ef35bb641 100644
--- a/lib/dns/resolver.c
+++ b/lib/dns/resolver.c
@@ -62,8 +62,10 @@
#include <dns/rootns.h>
#include <dns/stats.h>
#include <dns/tsig.h>
+#include <dns/types.h>
#include <dns/validator.h>
+#include <dns/view.h>
#include <dns/zone.h>
#ifdef WANT_QUERYTRACE
#define RTRACE(m) isc_log_write(dns_lctx, \
@@ -4879,6 +4881,36 @@ maybe_destroy(fetchctx_t *fctx, bool locked) {
return (bucket_empty);
}
+static bool
+get_and_check_signer_name(dns_name_t *signer, dns_rdataset_t *sigrdataset) {
+ dns_rdata_rrsig_t rrsig;
+ isc_result_t result;
+ dns_rdata_t rdata;
+
+ if (dns_rdataset_first(sigrdataset) != ISC_R_SUCCESS) {
+ return false;
+ }
+
+ rdata = (dns_rdata_t)DNS_RDATA_INIT;
+ dns_rdataset_current(sigrdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_name_copy(&rrsig.signer, signer, NULL);
+
+ while (dns_rdataset_next(sigrdataset) == ISC_R_SUCCESS) {
+ rdata = (dns_rdata_t)DNS_RDATA_INIT;
+ dns_rdataset_current(sigrdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+
+ if (!dns_name_equal(signer, &rrsig.signer)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
/*
* The validator has finished.
*/
@@ -4907,6 +4939,8 @@ validated(isc_task_t *task, isc_event_t *event) {
unsigned options;
uint32_t bucketnum;
dns_message_t *rmessage = NULL;
+ dns_fixedname_t fsigner;
+ dns_name_t *signer = NULL;
UNUSED(task); /* for now */
@@ -5240,6 +5274,23 @@ validated(isc_task_t *task, isc_event_t *event) {
if (sigrdataset == NULL ||
sigrdataset->trust != dns_trust_secure)
continue;
+ /*
+ * Don't cache if all the RRSIGs don't have the same
+ * signer.
+ */
+ signer = dns_fixedname_initname(&fsigner);
+ if (!get_and_check_signer_name(signer, sigrdataset)) {
+ continue;
+ }
+ /*
+ * Don't cache NSEC if missing NSEC or RRSIG
+ * types.
+ */
+ if (rdataset->type == dns_rdatatype_nsec &&
+ !dns_nsec_is_legal(rdataset, signer))
+ {
+ continue;
+ }
result = dns_db_findnode(fctx->cache, name, true,
&nsnode);
if (result != ISC_R_SUCCESS)

View File

@ -68,7 +68,7 @@ Summary: The Berkeley Internet Name Domain (BIND) DNS (Domain Name System) serv
Name: bind
License: MPLv2.0
Version: 9.11.36
Release: 16%{?PATCHVER:.%{PATCHVER}}%{?PREVER:.%{PREVER}}%{?dist}.8
Release: 16%{?PATCHVER:.%{PATCHVER}}%{?PREVER:.%{PREVER}}%{?dist}.14
Epoch: 32
Url: https://www.isc.org/downloads/bind/
#
@ -212,6 +212,20 @@ Patch216: bind-9.11-CVE-2026-3039.patch
# https://gitlab.isc.org/isc-projects/bind9/commit/bba70f08b1818f697e8292b79270a464279adc23
# https://gitlab.isc.org/isc-projects/bind9/commit/befe1903d0aebfd69cb362c429afc1f9c1c89610
Patch217: bind-9.11-CVE-2026-5946.patch
# https://github.com/isc-projects/bind9/commit/0b72e907d10760c2a8b01fc1f5a1c1f0d1a4bfe0
Patch218: bind-9.11-CVE-2026-11622.patch
# https://github.com/isc-projects/bind9/commit/06778424f0f58375f0f3d6632813558c59d705c6
# https://github.com/isc-projects/bind9/commit/e5b16cfd1c0b7d7760108e1ca9e1fd9db30d9e6a
Patch219: bind-9.11-CVE-2026-11721.patch
# https://github.com/isc-projects/bind9/commit/058023c66f11d78590d4aa8c4f98946c4c965e21
# https://github.com/isc-projects/bind9/commit/f751e19a30d107f04c2f644aff9f8dab8fed03ab
Patch220: bind-9.11-CVE-2026-13321.patch
# https://github.com/isc-projects/bind9/commit/204fde85953d78475334694a7b9507dca47e73ba
Patch221: bind-9.11-CVE-2026-13204.patch
# https://github.com/isc-projects/bind9/commit/6965fa47edd3b45538db2b16488dbb6b4ad8066a
Patch223: bind-9.11-CVE-2026-10723.patch
# https://gitlab.isc.org/isc-projects/bind9/-/commit/c63f11976ffe6e0285e0a9a3524a53420786d50e
Patch224: bind-9.11-CVE-2026-11721-test.patch
# SDB patches
Patch11: bind-9.3.2b2-sdbsrc.patch
@ -644,6 +658,12 @@ are used for building ISC DHCP.
%patch -P 215 -p1 -b .CVE-2026-1519
%patch -P 216 -p1 -b .CVE-2026-3039
%patch -P 217 -p1 -b .CVE-2026-5946
%patch -P 218 -p1 -b .CVE-2026-11622
%patch -P 219 -p1 -b .CVE-2026-11721
%patch -P 220 -p1 -b .CVE-2026-13321
%patch -P 221 -p1 -b .CVE-2026-13204
%patch -P 223 -p1 -b .CVE-2026-10723
%patch -P 224 -p1 -b .CVE-2026-11721-test
mkdir lib/dns/tests/testdata/dstrandom
cp -a %{SOURCE50} lib/dns/tests/testdata/dstrandom/random.data
@ -1696,6 +1716,23 @@ rm -rf ${RPM_BUILD_ROOT}
%endif
%changelog
* Mon Jul 27 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.11.36-16.14
- Validate NSEC3 signer matches owning zone (CVE-2026-10723)
* Mon Jul 27 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.11.36-16.12
- Prevent accepting unsigned NSEC/NSEC3 records (CVE-2026-13204)
* Mon Jul 27 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.11.36-16.11
- Reject out-of-zone NSEC entries in DNSSEC validation
(CVE-2026-13321)
* Mon Jul 27 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.11.36-16.10
- Reject RRSIG records with invalid label counts (CVE-2026-11721)
- Add unittest check
* Mon Jul 27 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.11.36-16.9
- Add reference counting to cache dns_slabheaders (CVE-2026-11622)
* Wed May 27 2026 Petr Menšík <pemensik@redhat.com> - 32:9.11.36-16.8
- Fix GSS-API resource leak (CVE-2026-3039)
- Invalid handling of CLASS != IN (CVE-2026-5946)