import UBI bind-9.18.33-15.el10_2.10

This commit is contained in:
AlmaLinux RelEng Bot 2026-08-17 00:00:41 -04:00
parent 131c6587b0
commit c9ad8bab07
10 changed files with 1852 additions and 1 deletions

View File

@ -0,0 +1,63 @@
From 01bb483cbf162b79dde64f7005f2cbc76aaee96e Mon Sep 17 00:00:00 2001
From: Evan Hunt <each@isc.org>
Date: Thu, 21 May 2026 14:41:55 -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/isc/result.c | 2 +-
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index 662136db87..d6259f939e 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -424,10 +424,25 @@ dns_dnssec_verify(const 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 DNSSKEY 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/isc/result.c b/lib/isc/result.c
index 493c1a5309..5e242ac285 100644
--- a/lib/isc/result.c
+++ b/lib/isc/result.c
@@ -198,7 +198,7 @@ static const char *description[ISC_R_NRESULTS] = {
[DNS_R_COVERINGNSEC] = "covering NSEC record returned",
[DNS_R_MXISADDRESS] = "MX is an address",
[DNS_R_DUPLICATE] = "duplicate query",
- [DNS_R_INVALIDNSEC3] = "invalid NSEC3 owner name (wildcard)",
+ [DNS_R_INVALIDNSEC3] = "invalid NSEC3 owner name",
[DNS_R_NOTPRIMARY] = "not primary",
[DNS_R_BROKENCHAIN] = "broken trust chain",
[DNS_R_EXPIRED] = "expired",

View File

@ -0,0 +1,69 @@
From 8a54e87eee8ccb80f6b96c211c5d655b64feb28e Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Fri, 10 Apr 2026 10:24:06 +1000
Subject: [PATCH] Fix TTL extraction from A/AAAA record
(cherry picked from commit 89c86e338db2492b92e6618c586f146c6928dc6d)
(cherry picked from commit adc8285d23e2eac6ec463f5dbc5a9596fdd36c60)
Check rpz name too long wildcard CNAME expansion handling
(cherry picked from commit 9345394e2097031b55b3ef34ceaadf5a7ebbeef2)
(cherry picked from commit 095b11f20f911f5b8059bdc349b256d6c64ece30)
---
bin/tests/system/rpz/ns2/tld2.db | 2 ++
bin/tests/system/rpz/ns4/tld4.db | 2 ++
bin/tests/system/rpz/tests.sh | 7 +++++--
3 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/bin/tests/system/rpz/ns2/tld2.db b/bin/tests/system/rpz/ns2/tld2.db
index c6f2556db5..c091ee27b7 100644
--- a/bin/tests/system/rpz/ns2/tld2.db
+++ b/bin/tests/system/rpz/ns2/tld2.db
@@ -123,3 +123,5 @@ a7-1 A 192.168.7.1
a7-2 A 192.168.7.2
TXT "a7-2 tld2 text"
+
+*.wild A 192.168.9.1
diff --git a/bin/tests/system/rpz/ns4/tld4.db b/bin/tests/system/rpz/ns4/tld4.db
index fca419c6dd..8accd76baf 100644
--- a/bin/tests/system/rpz/ns4/tld4.db
+++ b/bin/tests/system/rpz/ns4/tld4.db
@@ -59,6 +59,8 @@ a3-6.tld2 A 56.56.56.56
a3-7.sub1.tld2 A 57.57.57.57
+*.wild.sub1.tld2 A 57.57.57.57
+
a3-8.tld2 A 58.58.58.58
a3-9.sub9.tld2 A 59.59.59.59
diff --git a/bin/tests/system/rpz/tests.sh b/bin/tests/system/rpz/tests.sh
index 2404c9f4ba..67b6710c16 100644
--- a/bin/tests/system/rpz/tests.sh
+++ b/bin/tests/system/rpz/tests.sh
@@ -391,7 +391,7 @@ addr() {
digcmd $2 >$DIGNM
#ckalive "$2" "server crashed by 'dig $2'" || return 1
ADDR_ESC=$(echo "$ADDR" | sed -e 's/\./\\./g')
- ADDR_TTL=$(sed -n -e "s/^[-.a-z0-9]\{1,\}[ ]*\([0-9]*\) IN AA* ${ADDR_ESC}\$/\1/p" $DIGNM)
+ ADDR_TTL=$(sed -n -e "s/^[-.a-z0-9]\{1,\}[ ]*\([0-9]*\)[ ]IN[ ]AA*[ ]${ADDR_ESC}\$/\1/p" $DIGNM)
if test -z "$ADDR_TTL"; then
setret "'dig $2' wrong; no address $ADDR record in $DIGNM"
return 0
@@ -516,7 +516,10 @@ nochange TCP a3-9.tld2 # 33 tcp-only
here x.servfail <<'EOF' # 34 qname-wait-recurse yes
;; status: SERVFAIL, x
EOF
-addr 35.35.35.35 "x.servfail @$ns5" # 35 qname-wait-recurse no
+addr 35.35.35.35 "x.servfail @$ns5" # 35 qname-wait-recurse no
+here aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.wild.sub1.tld2 <<'EOF' # 36 wildcard CNAME name to long
+ ;; status: YXDOMAIN, x
+EOF
end_group
ckstats $ns3 test1 ns3 22
ckstats $ns5 test1 ns5 1
--
2.55.0

View File

@ -0,0 +1,27 @@
From 713060f54dd7da6aa18fdd57e2cf5f6603ddd6d0 Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Fri, 10 Apr 2026 10:26:14 +1000
Subject: [PATCH] Properly handle rpz name to long wildcard expansion
Previously a self referential CNAME and the original address
record were returned. We now return a YXDOMAIN response.
(cherry picked from commit cfc4c4f69870ce492deaaa429453563d1621ded3)
---
lib/ns/query.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/ns/query.c b/lib/ns/query.c
index f7f0623713..609ef50739 100644
--- a/lib/ns/query.c
+++ b/lib/ns/query.c
@@ -7575,7 +7575,8 @@ query_rpzcname(query_ctx_t *qctx, dns_name_t *cname) {
qctx->fname, NULL);
if (result == DNS_R_NAMETOOLONG) {
client->message->rcode = dns_rcode_yxdomain;
- } else if (result != ISC_R_SUCCESS) {
+ }
+ if (result != ISC_R_SUCCESS) {
return result;
}
} else {

View File

@ -0,0 +1,279 @@
From be220a81691c7625a59aa8036188a1207bc07377 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)
---
bin/tests/system/reclimit/tests.sh | 4 +-
lib/dns/include/dns/rdataslab.h | 1 +
lib/dns/rbtdb.c | 77 +++++++++++++++++++++++++-----
3 files changed, 69 insertions(+), 13 deletions(-)
diff --git a/bin/tests/system/reclimit/tests.sh b/bin/tests/system/reclimit/tests.sh
index 8cc8fe1..726b5c8 100644
--- a/bin/tests/system/reclimit/tests.sh
+++ b/bin/tests/system/reclimit/tests.sh
@@ -337,13 +337,13 @@ echo_i "checking that NXDOMAIN names over the max-types-per-name limit don't get
# Query for 10 NXDOMAIN types
for ntype in $(seq 65270 65279); do
- check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR big SOA 0 || ret=1
+ check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR big SOA 60 || ret=1
done
# Wait at least 1 second
sleep 1
# Query for 10 NXDOMAIN types again - these should not be cached
for ntype in $(seq 65270 65279); do
- check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR big SOA 0 || ret=1
+ check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR big SOA 60 || ret=1
done
if [ $ret -ne 0 ]; then echo_i "failed"; fi
diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h
index 5729c00..6bd3b59 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 dadb273..d7f55cd 100644
--- a/lib/dns/rbtdb.c
+++ b/lib/dns/rbtdb.c
@@ -165,6 +165,7 @@ struct noqname {
};
typedef struct rdatasetheader {
+ isc_refcount_t references;
/*%
* Locked by the owning node's lock.
*/
@@ -1452,6 +1453,7 @@ init_rdataset(dns_rbtdb_t *rbtdb, rdatasetheader_t *h) {
h->heap_index = 0;
atomic_init(&h->attributes, 0);
atomic_init(&h->last_refresh_fail_ts, 0);
+ isc_refcount_init(&h->references, 1);
STATIC_ASSERT((sizeof(h->attributes) == 2),
"The .attributes field of rdatasetheader_t needs to be "
@@ -1564,6 +1566,9 @@ rollback_node(dns_rbtnode_t *node, rbtdb_serial_t serial) {
}
}
+static void
+clean_stale_headers(dns_rbtdb_t *rbtdb, isc_mem_t *mctx, rdatasetheader_t *top);
+
static void
mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
uint_least16_t attributes = atomic_load_acquire(&header->attributes);
@@ -1589,8 +1594,12 @@ mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
update_rrsetstats(rbtdb, header->type, attributes, false);
header->node->dirty = 1;
+ isc_refcount_decrement(&header->references);
+
/* Increment the stats counter for the ancient RRtype. */
update_rrsetstats(rbtdb, header->type, newattributes, true);
+
+ clean_stale_headers(rbtdb, rbtdb->common.mctx, header);
}
static void
@@ -1626,12 +1635,19 @@ static 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 void
@@ -1647,6 +1663,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, ancient, or stale and
* we are not keeping stale, we can clean it up.
@@ -3101,6 +3118,8 @@ bind_rdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, rdatasetheader_t *header,
return;
}
+ isc_refcount_increment(&header->references);
+
new_reference(rbtdb, node, locktype);
INSIST(rdataset->methods == NULL); /* We must be disassociated. */
@@ -6290,6 +6309,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, const dns_name_t *nodename,
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;
@@ -6837,6 +6857,7 @@ find_header:
}
if (IS_CACHE(rbtdb) && overmaxtype(rbtdb, ntypes)) {
+ do_expireheader = true;
if (expireheader == NULL) {
expireheader = newheader;
}
@@ -6850,15 +6871,6 @@ find_header:
*/
expireheader = newheader;
}
-
- set_ttl(rbtdb, expireheader, 0);
- mark_header_ancient(rbtdb, expireheader);
- /*
- * FIXME: In theory, we should mark the RRSIG
- * and the header at the same time, but there is
- * no direct link between those two header, so
- * we would have to check the whole list again.
- */
}
}
}
@@ -6882,6 +6894,15 @@ find_header:
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;
}
@@ -8677,6 +8698,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);
+ }
+
detachnode(db, &node);
}
@@ -8791,6 +8818,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);
+ }
INSIST(!ISC_LINK_LINKED(target, link));
*target = *source;
ISC_LINK_INIT(target, link);
@@ -8954,6 +8986,11 @@ rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) {
rbtiterator = (rbtdb_rdatasetiter_t *)(*iteratorp);
+ if (rbtiterator->current != NULL) {
+ isc_refcount_decrement(&rbtiterator->current->references);
+ rbtiterator->current = NULL;
+ }
+
if (rbtiterator->common.version != NULL) {
closeversion(rbtiterator->common.db,
&rbtiterator->common.version, false);
@@ -9031,9 +9068,18 @@ rdatasetiter_first(dns_rdatasetiter_t *iterator) {
}
}
+ if (header != NULL) {
+ isc_refcount_increment0(&header->references);
+ }
+
NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
isc_rwlocktype_read);
+ if (rbtiterator->current != NULL) {
+ isc_refcount_decrement(&rbtiterator->current->references);
+ rbtiterator->current = NULL;
+ }
+
rbtiterator->current = header;
if (header == NULL) {
@@ -9125,9 +9171,18 @@ rdatasetiter_next(dns_rdatasetiter_t *iterator) {
}
}
+ if (header != NULL) {
+ isc_refcount_increment0(&header->references);
+ }
+
NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
isc_rwlocktype_read);
+ if (rbtiterator->current != NULL) {
+ isc_refcount_decrement(&rbtiterator->current->references);
+ rbtiterator->current = NULL;
+ }
+
rbtiterator->current = header;
if (header == NULL) {
--
2.55.0

View File

@ -0,0 +1,40 @@
From 0532ea83b5e806e0d5b0b1fa85760c8d680ac173 Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Tue, 14 Apr 2026 15:14:06 +1000
Subject: [PATCH] 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)
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)
(cherry picked from commit 1a4986e2533f87e80eb21da3f06708d335aff1e2)
---
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 b2c77d497a..d968f95d5e 100644
--- a/bin/dnssec/dnssec-signzone.c
+++ b/bin/dnssec/dnssec-signzone.c
@@ -1643,6 +1643,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.
--
2.55.0

View File

@ -0,0 +1,144 @@
From 247b1944f3e658f9cf412dc986d3de39aa81e67f 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 19e496ca260b6a756ae1378e8ebcbdb666b7d9ed)
---
tests/dns/rdata_test.c | 110 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 110 insertions(+)
diff --git a/tests/dns/rdata_test.c b/tests/dns/rdata_test.c
index 921f3f0591..26f1dd901f 100644
--- a/tests/dns/rdata_test.c
+++ b/tests/dns/rdata_test.c
@@ -2302,6 +2302,115 @@ ISC_RUN_TEST_IMPL(rkey) {
dns_rdatatype_rkey, sizeof(dns_rdata_rkey_t));
}
+ISC_RUN_TEST_IMPL(rrsig) {
+ 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()
+ };
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_rrsig, sizeof(dns_rdata_rrsig_t));
+}
+
ISC_RUN_TEST_IMPL(resinfo) {
text_ok_t text_ok[] = {
TEXT_VALID_CHANGED("qnamemin exterr=15,16,17 "
@@ -3152,6 +3261,7 @@ ISC_TEST_ENTRY(nsec3)
ISC_TEST_ENTRY(nxt)
ISC_TEST_ENTRY(rkey)
ISC_TEST_ENTRY(resinfo)
+ISC_TEST_ENTRY(rrsig)
ISC_TEST_ENTRY(sshfp)
ISC_TEST_ENTRY(wallet)
ISC_TEST_ENTRY(wks)
--
2.55.0

View File

@ -0,0 +1,236 @@
From 107fba6767a792bf9022dbfc8a75e3dc61c0d76d Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Tue, 14 Apr 2026 12:24:33 +1000
Subject: [PATCH] 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)
(cherry picked from commit 15089066b15f826d7487c3d160b5872820f84b83)
---
lib/dns/dnssec.c | 43 +++++++++++++++++++++++---------
lib/dns/rdata/generic/rrsig_46.c | 37 ++++++++++++++++++++-------
2 files changed, 59 insertions(+), 21 deletions(-)
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index 662136db87..86f03680e9 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -137,11 +137,11 @@ dns_dnssec_keyfromrdata(const dns_name_t *name, const dns_rdata_t *rdata,
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);
@@ -195,12 +195,14 @@ dns_dnssec_sign(const 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);
@@ -240,7 +242,7 @@ dns_dnssec_sign(const 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--;
}
@@ -384,11 +386,14 @@ dns_dnssec_verify(const 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;
+ unsigned int labels;
+ unsigned int siglabels;
uint32_t flags;
bool downcase = false;
REQUIRE(name != NULL);
+ labels = dns_name_countlabels(name);
+ REQUIRE(labels > 0);
REQUIRE(set != NULL);
REQUIRE(key != NULL);
REQUIRE(mctx != NULL);
@@ -403,6 +408,21 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
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;
@@ -482,10 +502,9 @@ again:
* 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) {
+ if (labels > siglabels) {
dns_name_split(dns_fixedname_name(&fnewname), sig.labels + 1,
NULL, dns_fixedname_name(&fnewname));
}
@@ -496,7 +515,7 @@ again:
* 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);
@@ -592,7 +611,7 @@ cleanup_struct:
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,
diff --git a/lib/dns/rdata/generic/rrsig_46.c b/lib/dns/rdata/generic/rrsig_46.c
index 2cc315bdea..53648f1bfa 100644
--- a/lib/dns/rdata/generic/rrsig_46.c
+++ b/lib/dns/rdata/generic/rrsig_46.c
@@ -23,12 +23,12 @@
static 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;
@@ -61,8 +61,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.
@@ -72,8 +72,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
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.
@@ -144,12 +144,20 @@ 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.
@@ -278,6 +286,7 @@ static isc_result_t
fromwire_rrsig(ARGS_FROMWIRE) {
isc_region_t sr;
dns_name_t name;
+ unsigned char labels;
REQUIRE(type == dns_rdatatype_rrsig);
@@ -300,6 +309,8 @@ fromwire_rrsig(ARGS_FROMWIRE) {
return ISC_R_UNEXPECTEDEND;
}
+ labels = sr.base[3];
+
isc_buffer_forward(source, 18);
RETERR(mem_tobuffer(target, sr.base, 18));
@@ -309,6 +320,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.
*/
--
2.55.0

View File

@ -0,0 +1,186 @@
From d190532c319cf4e3b84000f4e389596f2af98fd4 Mon Sep 17 00:00:00 2001
From: Evan Hunt <each@isc.org>
Date: Wed, 13 May 2026 20:45:57 -0700
Subject: [PATCH 1/2] 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)
---
lib/dns/rbtdb.c | 10 +++++++---
lib/dns/rdatalist.c | 33 ++++++++++++++++-----------------
lib/dns/resolver.c | 4 +++-
lib/ns/query.c | 3 +--
4 files changed, 27 insertions(+), 23 deletions(-)
diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c
index a341b007c7..dadb273bec 100644
--- a/lib/dns/rbtdb.c
+++ b/lib/dns/rbtdb.c
@@ -6906,7 +6906,7 @@ delegating_type(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
static isc_result_t
addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
uint32_t maxrrperset, 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;
@@ -6918,7 +6918,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));
dns_name_init(&noqname->name, NULL);
@@ -6944,7 +6946,9 @@ addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
cleanup:
dns_rdataset_disassociate(&neg);
dns_rdataset_disassociate(&negsig);
- free_noqname(mctx, &noqname);
+ if (noqname != NULL) {
+ free_noqname(mctx, &noqname);
+ }
return result;
}
diff --git a/lib/dns/rdatalist.c b/lib/dns/rdatalist.c
index 971e4e656c..c6d77c75dd 100644
--- a/lib/dns/rdatalist.c
+++ b/lib/dns/rdatalist.c
@@ -192,6 +192,7 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, const 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);
@@ -199,30 +200,27 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name) {
for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL;
rdset = ISC_LIST_NEXT(rdset, link))
{
- if (rdset->rdclass != rdataset->rdclass) {
- continue;
- }
- if (rdset->type == dns_rdatatype_nsec ||
- rdset->type == dns_rdatatype_nsec3)
+ if (rdset->rdclass != rdataset->rdclass ||
+ (rdset->type != dns_rdatatype_nsec &&
+ rdset->type != dns_rdatatype_nsec3))
{
- neg = rdset;
+ continue;
}
- }
- 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)
+ for (sigset = ISC_LIST_HEAD(name->list); sigset != NULL;
+ sigset = ISC_LIST_NEXT(sigset, link))
{
- negsig = rdset;
+ if (sigset->type == dns_rdatatype_rrsig &&
+ sigset->covers == rdset->type)
+ {
+ neg = rdset;
+ negsig = sigset;
+ break;
+ }
}
}
- if (negsig == NULL) {
+ if (neg == NULL || negsig == NULL) {
return ISC_R_NOTFOUND;
}
/*
@@ -238,6 +236,7 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, const 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 1326cb3428..a4efc9a847 100644
--- a/lib/dns/resolver.c
+++ b/lib/dns/resolver.c
@@ -5831,7 +5831,9 @@ validated(isc_task_t *task, isc_event_t *event) {
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) {
diff --git a/lib/ns/query.c b/lib/ns/query.c
index f7f0623713..f0239b20b9 100644
--- a/lib/ns/query.c
+++ b/lib/ns/query.c
@@ -7936,8 +7936,7 @@ query_addnoqnameproof(query_ctx_t *qctx) {
goto cleanup;
}
- result = dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig);
- RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ CHECK(dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig));
query_addrrset(qctx, &fname, &neg, &negsig, dbuf,
DNS_SECTION_AUTHORITY);
From 8051316fde01f4a4368e8a4cba24e6d2d483e5ce Mon Sep 17 00:00:00 2001
From: RHEL Packaging Agent <redhat-ymir-agent@redhat.com>
Date: Thu, 23 Jul 2026 10:26:08 +0000
Subject: [PATCH 2/2] Fix: replace CHECK macro with inline equivalent in
query.c
The CHECK macro is not defined in lib/ns/query.c (it is only defined
locally in some other source files). Replace with inline equivalent
that checks the result and goes to the cleanup label on failure.
This preserves the security fix semantics: instead of crashing with
RUNTIME_CHECK on error, gracefully jump to cleanup.
---
lib/ns/query.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/lib/ns/query.c b/lib/ns/query.c
index f0239b20b9..f265abfcef 100644
--- a/lib/ns/query.c
+++ b/lib/ns/query.c
@@ -7936,7 +7936,10 @@ query_addnoqnameproof(query_ctx_t *qctx) {
goto cleanup;
}
- CHECK(dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig));
+ result = dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
query_addrrset(qctx, &fname, &neg, &negsig, dbuf,
DNS_SECTION_AUTHORITY);

View File

@ -0,0 +1,766 @@
From 384331c4c560e658ce5349afc7e556e939f5301e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ayd=C4=B1n=20Mercan?= <aydin@isc.org>
Date: Wed, 6 May 2026 16:54:57 +0300
Subject: [PATCH 1/4] Add system test for out-of-zone nsec dnssec bypass
A malicious zone with out-of-zone NSEC entries can get a DNSSEC
validating resolver's cache to cover the victim zone for non-existence
and prevent nameserver queries without DNSSEC failure.
Test for this case with an `evil.test` zone that tries to cover the
`victim.test` zone.
(cherry picked from commit 654f9773c0af59965c343bdfeb096b3dffe9dd53)
---
.../system/dnssec_bypass/ns1/named.conf.j2 | 31 ++++
bin/tests/system/dnssec_bypass/ns1/root.db | 19 +++
bin/tests/system/dnssec_bypass/ns1/test.db | 23 +++
.../system/dnssec_bypass/ns2/named.conf.j2 | 26 +++
bin/tests/system/dnssec_bypass/ns2/victim.db | 18 +++
bin/tests/system/dnssec_bypass/ns3/evil.db | 23 +++
.../system/dnssec_bypass/ns3/named.conf.j2 | 26 +++
.../system/dnssec_bypass/ns4/named.conf.j2 | 35 ++++
.../system/dnssec_bypass/tests_bypass.py | 152 ++++++++++++++++++
9 files changed, 353 insertions(+)
create mode 100644 bin/tests/system/dnssec_bypass/ns1/named.conf.j2
create mode 100644 bin/tests/system/dnssec_bypass/ns1/root.db
create mode 100644 bin/tests/system/dnssec_bypass/ns1/test.db
create mode 100644 bin/tests/system/dnssec_bypass/ns2/named.conf.j2
create mode 100644 bin/tests/system/dnssec_bypass/ns2/victim.db
create mode 100644 bin/tests/system/dnssec_bypass/ns3/evil.db
create mode 100644 bin/tests/system/dnssec_bypass/ns3/named.conf.j2
create mode 100644 bin/tests/system/dnssec_bypass/ns4/named.conf.j2
create mode 100644 bin/tests/system/dnssec_bypass/tests_bypass.py
diff --git a/bin/tests/system/dnssec_bypass/ns1/named.conf.j2 b/bin/tests/system/dnssec_bypass/ns1/named.conf.j2
new file mode 100644
index 0000000..59ced18
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns1/named.conf.j2
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.1; };
+ listen-on-v6 { none; };
+ recursion no;
+ dnssec-validation no;
+};
+
+zone "test." {
+ type primary;
+ file "test.db.signed";
+};
+
+zone "." {
+ type primary;
+ file "root.db.signed";
+};
diff --git a/bin/tests/system/dnssec_bypass/ns1/root.db b/bin/tests/system/dnssec_bypass/ns1/root.db
new file mode 100644
index 0000000..8d98a04
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns1/root.db
@@ -0,0 +1,19 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 3600
+. IN SOA a.nil. a.nil. 1 3600 600 86400 300
+. IN NS a.root-servers.nil.
+
+a.root-servers.nil. IN A 10.53.0.1
+
+test. IN NS ns1.test.
+ns1.test. IN A 10.53.0.1
diff --git a/bin/tests/system/dnssec_bypass/ns1/test.db b/bin/tests/system/dnssec_bypass/ns1/test.db
new file mode 100644
index 0000000..6efcd95
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns1/test.db
@@ -0,0 +1,23 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$ORIGIN test.
+$TTL 3600
+
+@ IN SOA a a 1 3600 600 86400 300
+ IN NS ns1.test.
+ns1 IN A 10.53.0.1
+
+evil IN NS ns1.evil
+ns1.evil IN A 10.53.0.3
+
+victim IN NS ns1.victim
+ns1.victim IN A 10.53.0.2
diff --git a/bin/tests/system/dnssec_bypass/ns2/named.conf.j2 b/bin/tests/system/dnssec_bypass/ns2/named.conf.j2
new file mode 100644
index 0000000..e81cee7
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns2/named.conf.j2
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.2; };
+ listen-on-v6 { none; };
+ recursion no;
+ dnssec-validation no;
+};
+
+zone "victim.test." {
+ type primary;
+ file "victim.db.signed";
+};
diff --git a/bin/tests/system/dnssec_bypass/ns2/victim.db b/bin/tests/system/dnssec_bypass/ns2/victim.db
new file mode 100644
index 0000000..edcc234
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns2/victim.db
@@ -0,0 +1,18 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$ORIGIN victim.test.
+$TTL 3600
+
+@ IN SOA ns1 hostmaster 1 3600 600 86400 2147483647
+ IN NS ns1
+
+ns1 IN A 10.53.0.2
diff --git a/bin/tests/system/dnssec_bypass/ns3/evil.db b/bin/tests/system/dnssec_bypass/ns3/evil.db
new file mode 100644
index 0000000..618f9d3
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns3/evil.db
@@ -0,0 +1,23 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$ORIGIN evil.test.
+$TTL 300
+
+@ IN SOA ns1 hostmaster 1 3600 600 86400 300
+ IN NS ns1
+; Try to poison the victim zone in a resolver cache.
+; If admitted, the aggressive NSEC cache will accept a range such as
+; [evil.test, b.victim.test) and will cause the victim nameserver to
+; be never queried.
+ IN NSEC b.victim.test. NS SOA RRSIG NSEC DNSKEY
+
+ns1 IN A 10.53.0.3
diff --git a/bin/tests/system/dnssec_bypass/ns3/named.conf.j2 b/bin/tests/system/dnssec_bypass/ns3/named.conf.j2
new file mode 100644
index 0000000..17d3e18
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns3/named.conf.j2
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.3; };
+ listen-on-v6 { none; };
+ recursion no;
+ dnssec-validation no;
+};
+
+zone "evil.test." {
+ type primary;
+ file "evil.db.signed";
+};
diff --git a/bin/tests/system/dnssec_bypass/ns4/named.conf.j2 b/bin/tests/system/dnssec_bypass/ns4/named.conf.j2
new file mode 100644
index 0000000..039695d
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns4/named.conf.j2
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ query-source address 10.53.0.4;
+ notify-source 10.53.0.4;
+ transfer-source 10.53.0.4;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.4; };
+ listen-on-v6 { none; };
+ allow-transfer { any; };
+ recursion yes;
+ dnssec-validation yes;
+ synth-from-dnssec yes;
+};
+
+trust-anchors {
+ @root.domain@ @root.type@ @root.contents@;
+};
+
+zone "." {
+ type hint;
+ file "../../_common/root.hint";
+};
diff --git a/bin/tests/system/dnssec_bypass/tests_bypass.py b/bin/tests/system/dnssec_bypass/tests_bypass.py
new file mode 100644
index 0000000..c41bb7e
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/tests_bypass.py
@@ -0,0 +1,152 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+from datetime import datetime, timedelta, timezone
+
+import shutil
+
+from cryptography.hazmat.primitives.asymmetric import ec
+
+import dns.dnssec
+import dns.name
+import dns.rdataclass
+import dns.rdataset
+import dns.rdatatype
+import dns.rrset
+import dns.zone
+
+from isctest.run import EnvCmd
+
+import isctest
+
+TTL = 3600
+
+
+def bootstrap():
+ keygen = EnvCmd("KEYGEN", "-q -a ECDSA256")
+ signer = EnvCmd("SIGNER", "-S -g -O full")
+
+ def sign_regular_zone(ns: str, zone: str, database: str) -> isctest.kasp.Key:
+ isctest.log.info(f"{zone}: generate keys")
+ keygen(zone, cwd=ns).out.strip()
+ ksk = keygen(f"-f KSK {zone}", cwd=ns).out.strip()
+
+ isctest.log.info(f"{zone}: sign zone")
+ signer(f"-o {zone} {database}", cwd=ns)
+
+ if ns != "ns1":
+ shutil.copy(f"{ns}/dsset-{zone}", f"ns1/dsset-{zone}")
+ shutil.copy(f"{ns}/{ksk}.key", f"ns1/{ksk}.key")
+
+ return isctest.kasp.Key(ksk, keydir=ns)
+
+ # dnssec-signzone and `dns.dnssec.sign_zone` correctly disregard the invalid
+ # NSEC entries when signing the zone. However, for this test we actualy *want*
+ # to serve invalid yet signed zones. To accomplish this we sign the zone and then
+ # replace the correct entries with the faulty ones accompanied by its RRSIG.
+ #
+ # TODO(aydin): move this to `isctest` to sign broken zones
+ def sign_rogue_zone(ns: str, zone: str, database: str) -> None:
+ # Read zone.
+ origin = dns.name.from_text(zone)
+ data = dns.zone.from_file(f"{ns}/{database}", origin=origin, relativize=False)
+
+ # Get key for signing.
+ isctest.log.info(f"{zone}: generate keys")
+ private_key = ec.generate_private_key(ec.SECP256R1())
+ dnskey = dns.dnssec.make_dnskey(
+ public_key=private_key.public_key(),
+ algorithm=dns.dnssec.Algorithm.ECDSAP256SHA256,
+ flags=257,
+ )
+
+ # Sign zone.
+ isctest.log.info(f"{zone}: sign zone")
+ now = datetime.now(timezone.utc)
+ inception = now - timedelta(hours=1)
+ expiration = now + timedelta(days=30)
+
+ for name, node in data.nodes.items():
+ owner = name.derelativize(origin)
+ rdatasets = list(node.rdatasets)
+
+ for rdataset in rdatasets:
+ rrset = dns.rrset.RRset(owner, rdataset.rdclass, rdataset.rdtype)
+ rrset.update(rdataset)
+
+ rrsig = dns.dnssec.sign(
+ rrset=rrset,
+ private_key=private_key,
+ signer=origin,
+ dnskey=dnskey,
+ inception=inception,
+ expiration=expiration,
+ deterministic=False,
+ )
+
+ rdataset = dns.rdataset.Rdataset(rrset.rdclass, dns.rdatatype.RRSIG)
+ rdataset.add(rrsig, rrset.ttl)
+ node.replace_rdataset(rdataset)
+
+ # Sign DNSKEY RRset.
+ dnskey_rrset = dns.rrset.RRset(origin, dns.rdataclass.IN, dns.rdatatype.DNSKEY)
+ dnskey_rrset.add(dnskey, ttl=TTL)
+
+ apex_node = data.nodes[origin]
+ apex_node.replace_rdataset(dnskey_rrset)
+
+ rrsig = dns.dnssec.sign(
+ rrset=dnskey_rrset,
+ private_key=private_key,
+ signer=origin,
+ dnskey=dnskey,
+ inception=inception,
+ expiration=expiration,
+ deterministic=False,
+ )
+ rdataset = dns.rdataset.Rdataset(rrset.rdclass, dns.rdatatype.RRSIG)
+ rdataset.add(rrsig, dnskey_rrset.ttl)
+ apex_node.replace_rdataset(rdataset)
+
+ # Output zone.
+ data.to_file(f"{ns}/{database}.signed", relativize=False)
+
+ # Output DS.
+ ds = dns.dnssec.make_ds(name=origin, key=dnskey, algorithm="SHA256")
+ with open(f"ns1/dsset-{zone}", "w", encoding="utf-8") as f:
+ f.write(f"{zone} {TTL} IN DS {ds.to_text()}\n")
+
+ sign_rogue_zone("ns3", "evil.test.", "evil.db")
+ sign_regular_zone("ns2", "victim.test.", "victim.db")
+ sign_regular_zone("ns1", "test.", "test.db")
+ root_ksk = sign_regular_zone("ns1", ".", "root.db")
+
+ return {
+ "root": root_ksk.into_ta("static-key"),
+ }
+
+
+def test_out_of_zone_nsec(ns4):
+ isctest.log.info("trying to poison aggressive nsec cache")
+ msg = isctest.query.create("nx.evil.test", "A")
+ res = isctest.query.tcp(msg, ns4.ip)
+ isctest.check.noadflag(res)
+
+ isctest.log.info("query victim from recursive")
+ msg = isctest.query.create("victim.test", "SOA")
+ res = isctest.query.tcp(msg, ns4.ip, attempts=1)
+ isctest.check.noerror(res)
+ isctest.check.adflag(res)
+ isctest.check.rr_count_eq(res.answer, 2)
+
+ isctest.log.info("checking for query history on victim nameserver")
+ with open("ns2/named.run", "r", encoding="utf-8") as f:
+ assert "(victim.test): query 'victim.test/SOA/IN' approved" in f.read()
--
2.55.0
From a01a313653ad35eccf30e0001dfcdca6b66884fa 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 2/4] 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 | 13 +++++++++++++
lib/dns/include/dns/dnssec.h | 6 ++++++
2 files changed, 19 insertions(+)
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index 86f0368..9f2105e 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -376,8 +376,10 @@ isc_result_t
dns_dnssec_verify(const 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;
@@ -469,6 +471,17 @@ dns_dnssec_verify(const 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) {
+ RETERR(dns_rdataset_first(set));
+ dns_rdataset_current(set, &rdata);
+ RETERR(dns_rdata_tostruct(&rdata, &nsec, NULL));
+ 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 cb8fd9d..2be11b9 100644
--- a/lib/dns/include/dns/dnssec.h
+++ b/lib/dns/include/dns/dnssec.h
@@ -151,6 +151,9 @@ dns_dnssec_verify(const 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:
@@ -173,6 +176,9 @@ dns_dnssec_verify(const 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_*
*/
--
2.55.0
From 6d2f2273b904471f8e1713ef6946e6ce6abad577 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 3/4] 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 | 17 ++++++++++----
lib/dns/resolver.c | 48 ++++++++++++++++++++++++++++++++++++--
lib/ns/query.c | 6 ++---
4 files changed, 75 insertions(+), 14 deletions(-)
diff --git a/lib/dns/include/dns/nsec.h b/lib/dns/include/dns/nsec.h
index e68ea35..047e3c4 100644
--- a/lib/dns/include/dns/nsec.h
+++ b/lib/dns/include/dns/nsec.h
@@ -114,13 +114,21 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
*/
bool
-dns_nsec_requiredtypespresent(dns_rdataset_t *rdataset);
-/*
- * Return true if all the NSEC records in rdataset have both
- * NSEC and RRSIG present.
+dns_nsec_is_legal(dns_rdataset_t *rdataset, const dns_name_t *name);
+/**<
+ * \brief
+ * Validates a rdataset of type NSEC.
*
- * Requires:
+ * 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
diff --git a/lib/dns/nsec.c b/lib/dns/nsec.c
index fe53e2a..9fb79f8 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>
@@ -504,8 +505,9 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
}
bool
-dns_nsec_requiredtypespresent(dns_rdataset_t *nsecset) {
- dns_rdataset_t rdataset;
+dns_nsec_is_legal(dns_rdataset_t *nsecset, const dns_name_t *name) {
+ dns_rdataset_t rdataset = DNS_RDATASET_INIT;
+ dns_rdata_nsec_t nsec;
isc_result_t result;
bool found = false;
@@ -520,12 +522,19 @@ dns_nsec_requiredtypespresent(dns_rdataset_t *nsecset) {
{
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdataset_current(&rdataset, &rdata);
- if (!dns_nsec_typepresent(&rdata, dns_rdatatype_nsec) ||
- !dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig))
+
+ /* 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);
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
index a4efc9a..40480c5 100644
--- a/lib/dns/resolver.c
+++ b/lib/dns/resolver.c
@@ -63,7 +63,9 @@
#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>
/* Detailed logging of fctx attach/detach */
@@ -5540,6 +5542,36 @@ has_000_label(dns_rdataset_t *nsecset) {
return false;
}
+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);
+
+ 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.
*/
@@ -5570,6 +5602,8 @@ validated(isc_task_t *task, isc_event_t *event) {
dns_fixedname_t fwild;
dns_name_t *wild = NULL;
dns_message_t *message = NULL;
+ dns_fixedname_t fsigner;
+ dns_name_t *signer = NULL;
UNUSED(task); /* for now */
@@ -5959,10 +5993,20 @@ answer_response:
}
/*
- * Don't cache NSEC if missing NSEC or RRSIG types.
+ * 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_requiredtypespresent(rdataset))
+ !dns_nsec_is_legal(rdataset, signer))
{
continue;
}
diff --git a/lib/ns/query.c b/lib/ns/query.c
index 0f61fe1..2ef4111 100644
--- a/lib/ns/query.c
+++ b/lib/ns/query.c
@@ -10356,10 +10356,10 @@ query_coveringnsec(query_ctx_t *qctx) {
}
/*
- * If NSEC or RRSIG are missing from the type map
- * reject the NSEC RRset.
+ * Check that the NSEC entry is legal.
+ * (NSEC + RRSIG present and the entry isn't out-of-zone)
*/
- if (!dns_nsec_requiredtypespresent(qctx->rdataset)) {
+ if (!dns_nsec_is_legal(qctx->rdataset, signer)) {
goto cleanup;
}
--
2.55.0
From 88d9e228ca7496bb1f971163249eeae848f35ee7 Mon Sep 17 00:00:00 2001
From: RHEL Packaging Agent <redhat-ymir-agent@redhat.com>
Date: Thu, 23 Jul 2026 13:15:20 +0000
Subject: [PATCH 4/4] Fix RETERR usage in dns_dnssec_verify - use ret variable
instead of result
The dns_dnssec_verify function uses 'ret' as its local variable name and
has no 'failure:' label, but the RETERR macro assumes 'result' and
'goto failure'. Replace RETERR() calls with direct error checking using
the 'ret' variable and immediate returns.
---
lib/dns/dnssec.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index 9f2105e..546df26 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -475,9 +475,15 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
* Check for out of zone NSEC entries.
*/
if (set->type == dns_rdatatype_nsec) {
- RETERR(dns_rdataset_first(set));
+ ret = dns_rdataset_first(set);
+ if (ret != ISC_R_SUCCESS) {
+ return ret;
+ }
dns_rdataset_current(set, &rdata);
- RETERR(dns_rdata_tostruct(&rdata, &nsec, NULL));
+ ret = dns_rdata_tostruct(&rdata, &nsec, NULL);
+ if (ret != ISC_R_SUCCESS) {
+ return ret;
+ }
if (!dns_name_issubdomain(&nsec.next, &sig.signer)) {
return DNS_R_NOVALIDNSEC;
}
--
2.55.0

View File

@ -80,7 +80,7 @@ License: MPL-2.0 AND ISC AND MIT AND BSD-3-Clause AND BSD-2-Clause
# Before rebasing bind, ensure bind-dyndb-ldap is ready to be rebuild and use side-tag with it.
# Updating just bind will cause freeipa-dns-server package to be uninstallable.
Version: 9.18.33
Release: 15%{?dist}.2
Release: 15%{?dist}.10
Epoch: 32
Url: https://www.isc.org/downloads/bind/
#
@ -164,6 +164,25 @@ Patch229: bind-9.18-CVE-2026-1519-test.patch
Patch230: bind-9.18-CVE-2026-3039.patch
# https://gitlab.isc.org/isc-projects/bind9/-/commit/7ce6ce37b1b04af0953ed2d3211587465085600e
Patch231: bind-9.18-CVE-2026-5946.patch
# https://github.com/isc-projects/bind9/commit/48f5aa5fb3746d6194edcc57e8792a8b3cc3b454
Patch233: bind-9.18-CVE-2026-13204.patch
# https://github.com/isc-projects/bind9/commit/dc328a199f96222e0c30cc20b7b795bfc2c9b2e4
Patch234: bind-9.18-CVE-2026-11331.patch
# https://gitlab.isc.org/isc-projects/bind9/commit/15089066b15f826d7487c3d160b5872820f84b83
Patch235: bind-9.18-CVE-2026-11721.patch
# https://github.com/isc-projects/bind9/commit/231b1ca3edfb26389e1af39181aa6b4413e87ec4
Patch236: bind-9.18-CVE-2026-11622.patch
# https://github.com/isc-projects/bind9/commit/36f3d50f9c8ebc8d25ee033e707ca502e20b083f
Patch238: bind-9.18-CVE-2026-13321.patch
# https://github.com/isc-projects/bind9/commit/c9cb6a5e24e43489cf3fd4d4cc2193b6a74499cb
Patch239: bind-9.18-CVE-2026-10723.patch
# https://gitlab.isc.org/isc-projects/bind9/commit/adc8285d23e2eac6ec463f5dbc5a9596fdd36c60
# https://gitlab.isc.org/isc-projects/bind9/commit/095b11f20f911f5b8059bdc349b256d6c64ece30
Patch240: bind-9.18-CVE-2026-11331-test.patch
# https://gitlab.isc.org/isc-projects/bind9/commit/19e496ca260b6a756ae1378e8ebcbdb666b7d9ed
Patch241: bind-9.18-CVE-2026-11721-test.patch
# https://gitlab.isc.org/isc-projects/bind9/commit/1a4986e2533f87e80eb21da3f06708d335aff1e2
Patch242: bind-9.18-CVE-2026-11721-fix2.patch
%{?systemd_ordering}
# https://fedoraproject.org/wiki/Changes/RPMSuportForSystemdSysusers
@ -593,6 +612,7 @@ mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig
install -m 644 %{SOURCE1} ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig/named
install -m 644 %{SOURCE49} ${RPM_BUILD_ROOT}%{_sysconfdir}/named-chroot.files
%if "%{_bindir}" != "%{_sbindir}"
pushd ${RPM_BUILD_ROOT}%{_sbindir}
# Compatibility with previous major versions, only for selected binaries
for BIN in named-checkconf named-checkzone named-compilezone
@ -600,6 +620,7 @@ do
ln -s ../bin/$BIN $BIN
done
popd
%endif
# Remove libtool .la files:
find ${RPM_BUILD_ROOT}/%{_libdir} -name '*.la' -exec '/bin/rm' '-f' '{}' ';';
@ -965,6 +986,26 @@ fi;
%endif
%changelog
* Mon Jul 27 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.18.33-15.10
- Validate NSEC3 signer matches owning zone (CVE-2026-10723)
* Fri Jul 24 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.18.33-15.9
- Reject out-of-zone NSEC next owner names (CVE-2026-13321)
* Thu Jul 23 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.18.33-15.7
- Fix reference-counted dns_slabheaders in cache (CVE-2026-11622)
* Thu Jul 23 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.18.33-15.6
- Fix RRSIG label count validation for wildcard cache poisoning
(CVE-2026-11721)
* Thu Jul 23 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.18.33-15.5
- Fix RPZ name-too-long wildcard expansion (CVE-2026-11331)
* Thu Jul 23 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.18.33-15.4
- Fix assertion failure on malformed NSEC/NSEC3 responses
(CVE-2026-13204)
* Mon May 25 2026 Petr Menšík <pemensik@redhat.com> - 32:9.18.33-15.2
- Fix GSS-API resource leak (CVE-2026-3039)
- Invalid handling of CLASS != IN (CVE-2026-5946)