diff --git a/bind-9.18-CVE-2026-10723.patch b/bind-9.18-CVE-2026-10723.patch new file mode 100644 index 0000000..192fdca --- /dev/null +++ b/bind-9.18-CVE-2026-10723.patch @@ -0,0 +1,63 @@ +From 01bb483cbf162b79dde64f7005f2cbc76aaee96e Mon Sep 17 00:00:00 2001 +From: Evan Hunt +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", diff --git a/bind-9.18-CVE-2026-11331-test.patch b/bind-9.18-CVE-2026-11331-test.patch new file mode 100644 index 0000000..5a84063 --- /dev/null +++ b/bind-9.18-CVE-2026-11331-test.patch @@ -0,0 +1,69 @@ +From 8a54e87eee8ccb80f6b96c211c5d655b64feb28e Mon Sep 17 00:00:00 2001 +From: Mark Andrews +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 + diff --git a/bind-9.18-CVE-2026-11331.patch b/bind-9.18-CVE-2026-11331.patch new file mode 100644 index 0000000..5d891e8 --- /dev/null +++ b/bind-9.18-CVE-2026-11331.patch @@ -0,0 +1,27 @@ +From 713060f54dd7da6aa18fdd57e2cf5f6603ddd6d0 Mon Sep 17 00:00:00 2001 +From: Mark Andrews +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 { diff --git a/bind-9.18-CVE-2026-11622.patch b/bind-9.18-CVE-2026-11622.patch new file mode 100644 index 0000000..d1ccd2d --- /dev/null +++ b/bind-9.18-CVE-2026-11622.patch @@ -0,0 +1,279 @@ +From be220a81691c7625a59aa8036188a1207bc07377 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= +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 + + #include ++#include + + #include + +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 + diff --git a/bind-9.18-CVE-2026-11721-fix2.patch b/bind-9.18-CVE-2026-11721-fix2.patch new file mode 100644 index 0000000..0e2ae16 --- /dev/null +++ b/bind-9.18-CVE-2026-11721-fix2.patch @@ -0,0 +1,40 @@ +From 0532ea83b5e806e0d5b0b1fa85760c8d680ac173 Mon Sep 17 00:00:00 2001 +From: Mark Andrews +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 + diff --git a/bind-9.18-CVE-2026-11721-test.patch b/bind-9.18-CVE-2026-11721-test.patch new file mode 100644 index 0000000..644a672 --- /dev/null +++ b/bind-9.18-CVE-2026-11721-test.patch @@ -0,0 +1,144 @@ +From 247b1944f3e658f9cf412dc986d3de39aa81e67f Mon Sep 17 00:00:00 2001 +From: Mark Andrews +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 + diff --git a/bind-9.18-CVE-2026-11721.patch b/bind-9.18-CVE-2026-11721.patch new file mode 100644 index 0000000..5e524e6 --- /dev/null +++ b/bind-9.18-CVE-2026-11721.patch @@ -0,0 +1,236 @@ +From 107fba6767a792bf9022dbfc8a75e3dc61c0d76d Mon Sep 17 00:00:00 2001 +From: Mark Andrews +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: . + */ + 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 + diff --git a/bind-9.18-CVE-2026-13204.patch b/bind-9.18-CVE-2026-13204.patch new file mode 100644 index 0000000..d7dede3 --- /dev/null +++ b/bind-9.18-CVE-2026-13204.patch @@ -0,0 +1,186 @@ +From d190532c319cf4e3b84000f4e389596f2af98fd4 Mon Sep 17 00:00:00 2001 +From: Evan Hunt +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 +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); diff --git a/bind-9.18-CVE-2026-13321.patch b/bind-9.18-CVE-2026-13321.patch new file mode 100644 index 0000000..eae56b6 --- /dev/null +++ b/bind-9.18-CVE-2026-13321.patch @@ -0,0 +1,766 @@ +From 384331c4c560e658ce5349afc7e556e939f5301e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ayd=C4=B1n=20Mercan?= +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?= +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?= +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 + + #include ++#include + #include + #include + #include +@@ -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 + #include + #include ++#include + #include ++#include + #include + + /* 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 +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 + diff --git a/bind.spec b/bind.spec index d5492d1..e3a111b 100644 --- a/bind.spec +++ b/bind.spec @@ -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 - 32:9.18.33-15.10 +- Validate NSEC3 signer matches owning zone (CVE-2026-10723) + +* Fri Jul 24 2026 RHEL Packaging Agent - 32:9.18.33-15.9 +- Reject out-of-zone NSEC next owner names (CVE-2026-13321) + +* Thu Jul 23 2026 RHEL Packaging Agent - 32:9.18.33-15.7 +- Fix reference-counted dns_slabheaders in cache (CVE-2026-11622) + +* Thu Jul 23 2026 RHEL Packaging Agent - 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 - 32:9.18.33-15.5 +- Fix RPZ name-too-long wildcard expansion (CVE-2026-11331) + +* Thu Jul 23 2026 RHEL Packaging Agent - 32:9.18.33-15.4 +- Fix assertion failure on malformed NSEC/NSEC3 responses + (CVE-2026-13204) + * Mon May 25 2026 Petr Menšík - 32:9.18.33-15.2 - Fix GSS-API resource leak (CVE-2026-3039) - Invalid handling of CLASS != IN (CVE-2026-5946)