From 704934c53c3cb9f85ef3a13b2f6fe4fd2163326d Mon Sep 17 00:00:00 2001 From: AlmaLinux RelEng Bot Date: Thu, 13 Aug 2026 14:37:08 -0400 Subject: [PATCH] import CS git bind-9.11.36-16.el8_10.14 --- SOURCES/bind-9.11-CVE-2026-10723.patch | 64 ++++ SOURCES/bind-9.11-CVE-2026-11622.patch | 318 ++++++++++++++++++++ SOURCES/bind-9.11-CVE-2026-11721-test.patch | 168 +++++++++++ SOURCES/bind-9.11-CVE-2026-11721.patch | 263 ++++++++++++++++ SOURCES/bind-9.11-CVE-2026-13204.patch | 154 ++++++++++ SOURCES/bind-9.11-CVE-2026-13321.patch | 270 +++++++++++++++++ SPECS/bind.spec | 39 ++- 7 files changed, 1275 insertions(+), 1 deletion(-) create mode 100644 SOURCES/bind-9.11-CVE-2026-10723.patch create mode 100644 SOURCES/bind-9.11-CVE-2026-11622.patch create mode 100644 SOURCES/bind-9.11-CVE-2026-11721-test.patch create mode 100644 SOURCES/bind-9.11-CVE-2026-11721.patch create mode 100644 SOURCES/bind-9.11-CVE-2026-13204.patch create mode 100644 SOURCES/bind-9.11-CVE-2026-13321.patch diff --git a/SOURCES/bind-9.11-CVE-2026-10723.patch b/SOURCES/bind-9.11-CVE-2026-10723.patch new file mode 100644 index 0000000..886b047 --- /dev/null +++ b/SOURCES/bind-9.11-CVE-2026-10723.patch @@ -0,0 +1,64 @@ +From 6e7215866cf1b9d92b7cdbb5e1990f0f7581336a Mon Sep 17 00:00:00 2001 +From: Evan Hunt +Date: Wed, 1 Jul 2026 23:24:09 -0700 +Subject: [PATCH] Check NSEC3 signer matches the owning zone + +When validating NSEC3 records, reject any signature whose signer field +does not match the zone owning the NSEC3. + +This ensures that a child zone cannot impersonate its parent and forge +NXDOMAIN responses for sibling domains. + +Fixes: isc-projects/bind9#5874 + +(cherry picked from commit 6e5066bb1f0f12d090e8707adb7d6ccf74f8012b) +--- + lib/dns/dnssec.c | 19 +++++++++++++++++-- + lib/dns/result.c | 2 +- + 2 files changed, 18 insertions(+), 3 deletions(-) + +diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c +index b6b2405..8dae801 100644 +--- a/lib/dns/dnssec.c ++++ b/lib/dns/dnssec.c +@@ -428,10 +428,25 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + } + + /* +- * NS, SOA and DNSSKEY records are signed by their owner. +- * DS records are signed by the parent. ++ * NS, SOA and DNSKEY records are signed by their owners. ++ * NSEC3 records are signed by the apex, exactly one level up ++ * from their owner names. ++ * DS records are signed by the parent zone. + */ + switch (set->type) { ++ case dns_rdatatype_nsec3: { ++ dns_name_t apex = DNS_NAME_INITEMPTY; ++ labels = dns_name_countlabels(name); ++ if (labels <= 1) { ++ inc_stat(dns_dnssecstats_fail); ++ return DNS_R_INVALIDNSEC3; ++ } ++ dns_name_split(name, labels - 1, NULL, &apex); ++ if (!dns_name_equal(&apex, &sig.signer)) { ++ inc_stat(dns_dnssecstats_fail); ++ return DNS_R_SIGINVALID; ++ } ++ } break; + case dns_rdatatype_ns: + case dns_rdatatype_soa: + case dns_rdatatype_dnskey: +diff --git a/lib/dns/result.c b/lib/dns/result.c +index 24aa01e..45ac114 100644 +--- a/lib/dns/result.c ++++ b/lib/dns/result.c +@@ -148,7 +148,7 @@ static const char *text[DNS_R_NRESULTS] = { + "covering NSEC record returned", /*%< 101 DNS_R_COVERINGNSEC */ + "MX is an address", /*%< 102 DNS_R_MXISADDRESS */ + "duplicate query", /*%< 103 DNS_R_DUPLICATE */ +- "invalid NSEC3 owner name (wildcard)", /*%< 104 DNS_R_INVALIDNSEC3 */ ++ "invalid NSEC3 owner name", /*%< 104 DNS_R_INVALIDNSEC3 */ + + "not master", /*%< 105 DNS_R_NOTMASTER */ + "broken trust chain", /*%< 106 DNS_R_BROKENCHAIN */ diff --git a/SOURCES/bind-9.11-CVE-2026-11622.patch b/SOURCES/bind-9.11-CVE-2026-11622.patch new file mode 100644 index 0000000..cde83b4 --- /dev/null +++ b/SOURCES/bind-9.11-CVE-2026-11622.patch @@ -0,0 +1,318 @@ +From a323885e23d763255a6abea0ae7752449c0c2cde 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) +--- + lib/dns/include/dns/rdataslab.h | 1 + + lib/dns/rbtdb.c | 105 +++++++++++++++++++++++++++----- + 2 files changed, 90 insertions(+), 16 deletions(-) + +diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h +index 40c40a8..f353f03 100644 +--- a/lib/dns/include/dns/rdataslab.h ++++ b/lib/dns/include/dns/rdataslab.h +@@ -44,6 +44,7 @@ + #include + + #include ++#include + + #include + +diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c +index 388ffdf..7ec553e 100644 +--- a/lib/dns/rbtdb.c ++++ b/lib/dns/rbtdb.c +@@ -441,6 +441,7 @@ struct noqname { + typedef struct acachectl acachectl_t; + + typedef struct rdatasetheader { ++ isc_refcount_t references; + /*% + * Locked by the owning node's lock. + */ +@@ -1760,6 +1761,8 @@ init_rdataset(dns_rbtdb_t *rbtdb, rdatasetheader_t *h) { + h->next_is_relative = 0; + h->node_is_relative = 0; + ++ isc_refcount_init(&h->references, 1); ++ + #if TRACE_HEADER + if (IS_CACHE(rbtdb) && rbtdb->common.rdclass == dns_rdataclass_in) + fprintf(stderr, "initialized header: %p\n", h); +@@ -1885,6 +1888,9 @@ rollback_node(dns_rbtnode_t *node, rbtdb_serial_t serial) { + node->dirty = 1; + } + ++static void ++clean_stale_headers(dns_rbtdb_t *rbtdb, isc_mem_t *mctx, rdatasetheader_t *top); ++ + static inline void + mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) { + +@@ -1897,26 +1903,37 @@ mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) { + header->attributes |= RDATASET_ATTR_ANCIENT; + header->node->dirty = 1; + ++ isc_refcount_decrement(&header->references, NULL); ++ + /* +- * If we have not been counted then there is nothing to do. ++ * If this header has been counted, move it to the stale stats bucket. + */ +- if ((header->attributes & RDATASET_ATTR_STATCOUNT) == 0) +- return; +- +- if (EXISTS(header)) ++ if ((header->attributes & RDATASET_ATTR_STATCOUNT) != 0 && ++ EXISTS(header)) ++ { + update_rrsetstats(rbtdb, header, true); ++ } ++ ++ clean_stale_headers(rbtdb, rbtdb->common.mctx, header); + } + + static inline void + clean_stale_headers(dns_rbtdb_t *rbtdb, isc_mem_t *mctx, rdatasetheader_t *top) + { + rdatasetheader_t *d, *down_next; ++ rdatasetheader_t *down_parent = top; + + for (d = top->down; d != NULL; d = down_next) { + down_next = d->down; +- free_rdataset(rbtdb, mctx, d); ++ d->next = down_parent; ++ ++ if (isc_refcount_current(&d->references) == 0) { ++ free_rdataset(rbtdb, mctx, d); ++ down_parent->down = down_next; ++ } else { ++ down_parent = d; ++ } + } +- top->down = NULL; + } + + static inline void +@@ -1932,6 +1949,7 @@ clean_cache_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) { + for (current = node->data; current != NULL; current = top_next) { + top_next = current->next; + clean_stale_headers(rbtdb, mctx, current); ++ INSIST(current->down == NULL); + /* + * If current is nonexistent or stale, we can clean it up. + */ +@@ -3469,6 +3487,8 @@ bind_rdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, rdatasetheader_t *header, + if (rdataset == NULL) + return; + ++ isc_refcount_increment(&header->references, NULL); ++ + new_reference(rbtdb, node, locktype); + + INSIST(rdataset->methods == NULL); /* We must be disassociated. */ +@@ -6386,6 +6406,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion, + bool header_nx; + bool newheader_nx; + bool merge; ++ bool do_expireheader = false; + dns_rdatatype_t rdtype, covers; + rbtdb_rdatatype_t negtype, sigtype; + dns_trust_t trust; +@@ -6918,6 +6939,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion, + } + + if (IS_CACHE(rbtdb) && overmaxtype(rbtdb, ntypes)) { ++ do_expireheader = true; + if (expireheader == NULL) { + expireheader = newheader; + } +@@ -6931,9 +6953,6 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion, + */ + expireheader = newheader; + } +- +- set_ttl(rbtdb, expireheader, 0); +- mark_header_ancient(rbtdb, expireheader); + } + } + } +@@ -6954,6 +6973,15 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion, + isc_rwlocktype_write, addedrdataset); + } + ++ /* ++ * We need to delay the expiration of the header until we are bound to ++ * it to prevent decrement-then-increment on the header references. ++ */ ++ if (do_expireheader) { ++ set_ttl(rbtdb, expireheader, 0); ++ mark_header_ancient(rbtdb, expireheader); ++ } ++ + return (ISC_R_SUCCESS); + } + +@@ -9228,6 +9256,12 @@ rdataset_disassociate(dns_rdataset_t *rdataset) { + dns_db_t *db = rdataset->private1; + dns_dbnode_t *node = rdataset->private2; + ++ if (rdataset->methods == &rdataset_methods) { ++ rdatasetheader_t *header = rdataset->private3; ++ header--; ++ isc_refcount_decrement(&header->references, NULL); ++ } ++ + detachnode(db, &node); + } + +@@ -9339,6 +9373,11 @@ rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) { + dns_dbnode_t *cloned_node = NULL; + + attachnode(db, node, &cloned_node); ++ if (source->methods == &rdataset_methods) { ++ rdatasetheader_t *header = source->private3; ++ header--; ++ isc_refcount_increment(&header->references, NULL); ++ } + INSIST(!ISC_LINK_LINKED(target, link)); + *target = *source; + ISC_LINK_INIT(target, link); +@@ -9504,6 +9543,11 @@ rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) { + + rbtiterator = (rbtdb_rdatasetiter_t *)(*iteratorp); + ++ if (rbtiterator->current != NULL) { ++ isc_refcount_decrement(&rbtiterator->current->references, NULL); ++ rbtiterator->current = NULL; ++ } ++ + if (rbtiterator->common.version != NULL) + closeversion(rbtiterator->common.db, + &rbtiterator->common.version, false); +@@ -9561,9 +9605,18 @@ rdatasetiter_first(dns_rdatasetiter_t *iterator) { + break; + } + ++ if (header != NULL) { ++ isc_refcount_increment0(&header->references, NULL); ++ } ++ + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); + ++ if (rbtiterator->current != NULL) { ++ isc_refcount_decrement(&rbtiterator->current->references, NULL); ++ rbtiterator->current = NULL; ++ } ++ + rbtiterator->current = header; + + if (header == NULL) +@@ -9637,9 +9690,18 @@ rdatasetiter_next(dns_rdatasetiter_t *iterator) { + } + } + ++ if (header != NULL) { ++ isc_refcount_increment0(&header->references, NULL); ++ } ++ + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, + isc_rwlocktype_read); + ++ if (rbtiterator->current != NULL) { ++ isc_refcount_decrement(&rbtiterator->current->references, NULL); ++ rbtiterator->current = NULL; ++ } ++ + rbtiterator->current = header; + + if (header == NULL) +@@ -10249,6 +10311,19 @@ rdataset_getadditional(dns_rdataset_t *rdataset, dns_rdatasetadditional_t type, + return (result); + } + ++static void ++free_acache_cbarg(isc_mem_t *mctx, acache_cbarg_t **cbargp) { ++ acache_cbarg_t *cbarg; ++ ++ REQUIRE(cbargp != NULL && *cbargp != NULL); ++ ++ cbarg = *cbargp; ++ isc_refcount_decrement(&cbarg->header->references, NULL); ++ isc_mem_put(mctx, cbarg, sizeof(*cbarg)); ++ ++ *cbargp = NULL; ++} ++ + static void + acache_callback(dns_acacheentry_t *entry, void **arg) { + dns_rbtdb_t *rbtdb; +@@ -10288,7 +10363,7 @@ acache_callback(dns_acacheentry_t *entry, void **arg) { + acarray[count].entry = NULL; + INSIST(acarray[count].cbarg == cbarg); + acarray[count].cbarg = NULL; +- isc_mem_put(rbtdb->common.mctx, cbarg, sizeof(acache_cbarg_t)); ++ free_acache_cbarg(rbtdb->common.mctx, &cbarg); + dns_acache_detachentry(&entry); + } + +@@ -10317,9 +10392,7 @@ acache_cancelentry(isc_mem_t *mctx, dns_acacheentry_t *entry, + dns_db_detach(&cbarg->db); + } + +- isc_mem_put(mctx, cbarg, sizeof(acache_cbarg_t)); +- +- *cbargp = NULL; ++ free_acache_cbarg(mctx, cbargp); + } + + static isc_result_t +@@ -10355,6 +10428,7 @@ rdataset_setadditional(dns_rdataset_t *rdataset, dns_rdatasetadditional_t type, + newcbarg = isc_mem_get(rbtdb->common.mctx, sizeof(*newcbarg)); + if (newcbarg == NULL) + return (ISC_R_NOMEMORY); ++ isc_refcount_increment(&header->references, NULL); + newcbarg->type = type; + newcbarg->count = count; + newcbarg->header = header; +@@ -10448,8 +10522,7 @@ rdataset_setadditional(dns_rdataset_t *rdataset, dns_rdatasetadditional_t type, + } else { + dns_db_detachnode((dns_db_t *)rbtdb, &newcbarg->node); + dns_db_detach(&newcbarg->db); +- isc_mem_put(rbtdb->common.mctx, newcbarg, +- sizeof(*newcbarg)); ++ free_acache_cbarg(rbtdb->common.mctx, &newcbarg); + } + } + diff --git a/SOURCES/bind-9.11-CVE-2026-11721-test.patch b/SOURCES/bind-9.11-CVE-2026-11721-test.patch new file mode 100644 index 0000000..939791d --- /dev/null +++ b/SOURCES/bind-9.11-CVE-2026-11721-test.patch @@ -0,0 +1,168 @@ +From 9c1f5b579a7a4c338be8319a40927c83a0b4f886 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 5f3b7aef42be50630ecc349a6dbc22acf96d7413) + +Fix failing dnssec-signzone -M sub-test + +Restore example.db to the default contents which does not result +in too many records causing the test to fail. + +(cherry picked from commit 8b11c48ed044844e26eaaca4315868231014dbb7) +--- + bin/tests/system/dnssec/tests.sh | 1 + + lib/dns/tests/rdata_test.c | 114 +++++++++++++++++++++++++++++++ + 2 files changed, 115 insertions(+) + +diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh +index 1eb97f42c5..e5cb97924e 100644 +--- a/bin/tests/system/dnssec/tests.sh ++++ b/bin/tests/system/dnssec/tests.sh +@@ -1793,6 +1793,7 @@ echo_i "checking TTLs are capped by dnssec-signzone -M ($n)" + ret=0 + ( + cd signer ++cp example.db.in example.db + $SIGNER -O full -f signer.out.8 -S -M 30 -o example example.db > /dev/null 2>&1 + ) || ret=1 + awk '/^;/ { next; } $2 > 30 { exit 1; }' signer/signer.out.8 || ret=1 +diff --git a/lib/dns/tests/rdata_test.c b/lib/dns/tests/rdata_test.c +index fe01e48fa3..1cde60b1d8 100644 +--- a/lib/dns/tests/rdata_test.c ++++ b/lib/dns/tests/rdata_test.c +@@ -2356,6 +2356,119 @@ rkey(void **state) { + dns_rdatatype_rkey, sizeof(dns_rdata_rkey_t)); + } + ++static void ++rrsig(void **state) { ++ text_ok_t text_ok[] = { ++ TEXT_VALID("SOA 8 0 86400 20260426170000 20260413160000 54393 " ++ ". " ++ "tFbcoVP8MnpecUquJ/aj+XeNgV7ts9GSHVkXaXRJrJ/" ++ "TEkOZApVG0F6E " ++ "9sYpxGk2ItweLL43ujioGj0HWwZDRR+vbur+O/" ++ "dIdheiig1VvU+9HXLi " ++ "QOViY9Kc64ixdyJhYCC5K+bO1qsHxd+" ++ "KJXOaxyHbqchYkDFy4PL6qftE " ++ "VaLkueRgjXgOsq/" ++ "NxvCXDgAa5xy0+3Sl0myxIs8rJ5KeXfJQFe7qxgaw " ++ "VjJsJTKw8neOTw2rQfLaigWu2LIWw+" ++ "IyVrLjZJdLqGkiLBGd1w4X3U12 " ++ "fFxoY3eqzNgBEtduoGKPZ/" ++ "NpP9cuKJORJ18283aV8hR4WO91VR0q1zcM jLwqUg=="), ++ /* labels too short for signer */ ++ TEXT_INVALID("SOA 8 0 86400 20260426170000 20260413160000 " ++ "54393 example. " ++ "tFbcoVP8MnpecUquJ/aj+XeNgV7ts9GSHVkXaXRJrJ/" ++ "TEkOZApVG0F6E " ++ "9sYpxGk2ItweLL43ujioGj0HWwZDRR+vbur+O/" ++ "dIdheiig1VvU+9HXLi " ++ "QOViY9Kc64ixdyJhYCC5K+bO1qsHxd+" ++ "KJXOaxyHbqchYkDFy4PL6qftE " ++ "VaLkueRgjXgOsq/" ++ "NxvCXDgAa5xy0+3Sl0myxIs8rJ5KeXfJQFe7qxgaw " ++ "VjJsJTKw8neOTw2rQfLaigWu2LIWw+" ++ "IyVrLjZJdLqGkiLBGd1w4X3U12 " ++ "fFxoY3eqzNgBEtduoGKPZ/" ++ "NpP9cuKJORJ18283aV8hR4WO91VR0q1zcM jLwqUg=="), ++ /* ++ * Sentinel. ++ */ ++ TEXT_SENTINEL() ++ }; ++ wire_ok_t wire_ok[] = { ++ WIRE_VALID(0x00, 0x06, 0x08, 0x00, 0x00, 0x01, 0x51, 0x80, 0x69, ++ 0xee, 0x44, 0x90, 0x69, 0xdd, 0x13, 0x00, 0xd4, 0x79, ++ 0x00, 0xb4, 0x56, 0xdc, 0xa1, 0x53, 0xfc, 0x32, 0x7a, ++ 0x5e, 0x71, 0x4a, 0xae, 0x27, 0xf6, 0xa3, 0xf9, 0x77, ++ 0x8d, 0x81, 0x5e, 0xed, 0xb3, 0xd1, 0x92, 0x1d, 0x59, ++ 0x17, 0x69, 0x74, 0x49, 0xac, 0x9f, 0xd3, 0x12, 0x43, ++ 0x99, 0x02, 0x95, 0x46, 0xd0, 0x5e, 0x84, 0xf6, 0xc6, ++ 0x29, 0xc4, 0x69, 0x36, 0x22, 0xdc, 0x1e, 0x2c, 0xbe, ++ 0x37, 0xba, 0x38, 0xa8, 0x1a, 0x3d, 0x07, 0x5b, 0x06, ++ 0x43, 0x45, 0x1f, 0xaf, 0x6e, 0xea, 0xfe, 0x3b, 0xf7, ++ 0x48, 0x76, 0x17, 0xa2, 0x8a, 0x0d, 0x55, 0xbd, 0x4f, ++ 0xbd, 0x1d, 0x72, 0xe2, 0x40, 0xe5, 0x62, 0x63, 0xd2, ++ 0x9c, 0xeb, 0x88, 0xb1, 0x77, 0x22, 0x61, 0x60, 0x20, ++ 0xb9, 0x2b, 0xe6, 0xce, 0xd6, 0xab, 0x07, 0xc5, 0xdf, ++ 0x8a, 0x25, 0x73, 0x9a, 0xc7, 0x21, 0xdb, 0xa9, 0xc8, ++ 0x58, 0x90, 0x31, 0x72, 0xe0, 0xf2, 0xfa, 0xa9, 0xfb, ++ 0x44, 0x55, 0xa2, 0xe4, 0xb9, 0xe4, 0x60, 0x8d, 0x78, ++ 0x0e, 0xb2, 0xaf, 0xcd, 0xc6, 0xf0, 0x97, 0x0e, 0x00, ++ 0x1a, 0xe7, 0x1c, 0xb4, 0xfb, 0x74, 0xa5, 0xd2, 0x6c, ++ 0xb1, 0x22, 0xcf, 0x2b, 0x27, 0x92, 0x9e, 0x5d, 0xf2, ++ 0x50, 0x15, 0xee, 0xea, 0xc6, 0x06, 0xb0, 0x56, 0x32, ++ 0x6c, 0x25, 0x32, 0xb0, 0xf2, 0x77, 0x8e, 0x4f, 0x0d, ++ 0xab, 0x41, 0xf2, 0xda, 0x8a, 0x05, 0xae, 0xd8, 0xb2, ++ 0x16, 0xc3, 0xe2, 0x32, 0x56, 0xb2, 0xe3, 0x64, 0x97, ++ 0x4b, 0xa8, 0x69, 0x22, 0x2c, 0x11, 0x9d, 0xd7, 0x0e, ++ 0x17, 0xdd, 0x4d, 0x76, 0x7c, 0x5c, 0x68, 0x63, 0x77, ++ 0xaa, 0xcc, 0xd8, 0x01, 0x12, 0xd7, 0x6e, 0xa0, 0x62, ++ 0x8f, 0x67, 0xf3, 0x69, 0x3f, 0xd7, 0x2e, 0x28, 0x93, ++ 0x91, 0x27, 0x5f, 0x36, 0xf3, 0x76, 0x95, 0xf2, 0x14, ++ 0x78, 0x58, 0xef, 0x75, 0x55, 0x1d, 0x2a, 0xd7, 0x37, ++ 0x0c, 0x8c, 0xbc, 0x2a, 0x52), ++ /* labels too short for signer */ ++ WIRE_INVALID( ++ 0x00, 0x06, 0x08, 0x00, 0x00, 0x01, 0x51, 0x80, 0x69, ++ 0xee, 0x44, 0x90, 0x69, 0xdd, 0x13, 0x00, 0xd4, 0x79, ++ 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x00, ++ 0xb4, 0x56, 0xdc, 0xa1, 0x53, 0xfc, 0x32, 0x7a, 0x5e, ++ 0x71, 0x4a, 0xae, 0x27, 0xf6, 0xa3, 0xf9, 0x77, 0x8d, ++ 0x81, 0x5e, 0xed, 0xb3, 0xd1, 0x92, 0x1d, 0x59, 0x17, ++ 0x69, 0x74, 0x49, 0xac, 0x9f, 0xd3, 0x12, 0x43, 0x99, ++ 0x02, 0x95, 0x46, 0xd0, 0x5e, 0x84, 0xf6, 0xc6, 0x29, ++ 0xc4, 0x69, 0x36, 0x22, 0xdc, 0x1e, 0x2c, 0xbe, 0x37, ++ 0xba, 0x38, 0xa8, 0x1a, 0x3d, 0x07, 0x5b, 0x06, 0x43, ++ 0x45, 0x1f, 0xaf, 0x6e, 0xea, 0xfe, 0x3b, 0xf7, 0x48, ++ 0x76, 0x17, 0xa2, 0x8a, 0x0d, 0x55, 0xbd, 0x4f, 0xbd, ++ 0x1d, 0x72, 0xe2, 0x40, 0xe5, 0x62, 0x63, 0xd2, 0x9c, ++ 0xeb, 0x88, 0xb1, 0x77, 0x22, 0x61, 0x60, 0x20, 0xb9, ++ 0x2b, 0xe6, 0xce, 0xd6, 0xab, 0x07, 0xc5, 0xdf, 0x8a, ++ 0x25, 0x73, 0x9a, 0xc7, 0x21, 0xdb, 0xa9, 0xc8, 0x58, ++ 0x90, 0x31, 0x72, 0xe0, 0xf2, 0xfa, 0xa9, 0xfb, 0x44, ++ 0x55, 0xa2, 0xe4, 0xb9, 0xe4, 0x60, 0x8d, 0x78, 0x0e, ++ 0xb2, 0xaf, 0xcd, 0xc6, 0xf0, 0x97, 0x0e, 0x00, 0x1a, ++ 0xe7, 0x1c, 0xb4, 0xfb, 0x74, 0xa5, 0xd2, 0x6c, 0xb1, ++ 0x22, 0xcf, 0x2b, 0x27, 0x92, 0x9e, 0x5d, 0xf2, 0x50, ++ 0x15, 0xee, 0xea, 0xc6, 0x06, 0xb0, 0x56, 0x32, 0x6c, ++ 0x25, 0x32, 0xb0, 0xf2, 0x77, 0x8e, 0x4f, 0x0d, 0xab, ++ 0x41, 0xf2, 0xda, 0x8a, 0x05, 0xae, 0xd8, 0xb2, 0x16, ++ 0xc3, 0xe2, 0x32, 0x56, 0xb2, 0xe3, 0x64, 0x97, 0x4b, ++ 0xa8, 0x69, 0x22, 0x2c, 0x11, 0x9d, 0xd7, 0x0e, 0x17, ++ 0xdd, 0x4d, 0x76, 0x7c, 0x5c, 0x68, 0x63, 0x77, 0xaa, ++ 0xcc, 0xd8, 0x01, 0x12, 0xd7, 0x6e, 0xa0, 0x62, 0x8f, ++ 0x67, 0xf3, 0x69, 0x3f, 0xd7, 0x2e, 0x28, 0x93, 0x91, ++ 0x27, 0x5f, 0x36, 0xf3, 0x76, 0x95, 0xf2, 0x14, 0x78, ++ 0x58, 0xef, 0x75, 0x55, 0x1d, 0x2a, 0xd7, 0x37, 0x0c, ++ 0x8c, 0xbc, 0x2a, 0x52), ++ ++ WIRE_SENTINEL() ++ }; ++ ++ UNUSED(state); ++ ++ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, ++ dns_rdatatype_rrsig, sizeof(dns_rdata_rrsig_t)); ++} ++ + /* SSHFP RDATA manipulations */ + static void + sshfp(void **state) { +@@ -2867,6 +2980,7 @@ main(int argc, char **argv) { + cmocka_unit_test_setup_teardown(nsec, _setup, _teardown), + cmocka_unit_test_setup_teardown(nsec3, _setup, _teardown), + cmocka_unit_test_setup_teardown(nxt, _setup, _teardown), ++ cmocka_unit_test_setup_teardown(rrsig, _setup, _teardown), + cmocka_unit_test_setup_teardown(sshfp, _setup, _teardown), + cmocka_unit_test_setup_teardown(wks, _setup, _teardown), + cmocka_unit_test_setup_teardown(rkey, _setup, _teardown), +-- +2.55.0 + diff --git a/SOURCES/bind-9.11-CVE-2026-11721.patch b/SOURCES/bind-9.11-CVE-2026-11721.patch new file mode 100644 index 0000000..b85195d --- /dev/null +++ b/SOURCES/bind-9.11-CVE-2026-11721.patch @@ -0,0 +1,263 @@ +From 78ac82303bc3dd9d426d21fb268a5ed362d5535a Mon Sep 17 00:00:00 2001 +From: Mark Andrews +Date: Tue, 14 Apr 2026 15:14:06 +1000 +Subject: [PATCH 1/2] Don't sign out of zone records in dnssec-signzone + +dnssec-signzone was signing extraneous records that were not within +the namespace of the zone. This no longer occurs. + +(cherry picked from commit e45c9af7051421fd370f20ba8325199c606223fd) +--- + bin/dnssec/dnssec-signzone.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c +index 6567421..b7c8e55 100644 +--- a/bin/dnssec/dnssec-signzone.c ++++ b/bin/dnssec/dnssec-signzone.c +@@ -1482,6 +1482,11 @@ assignwork(isc_task_t *task, isc_task_t *worker) { + dns_db_detachnode(gdb, &node); + goto next; + } ++ if (!dns_name_issubdomain(name, gorigin)) { ++ dumpnode(name, node); ++ dns_db_detachnode(gdb, &node); ++ goto next; ++ } + /* + * Sort the zone data from the glue and out-of-zone data. + * For NSEC zones nodes with zone data have NSEC records. + +From 8c269a0269fdf12d5edd4698d08d663c6f64ddae Mon Sep 17 00:00:00 2001 +From: Mark Andrews +Date: Tue, 14 Apr 2026 12:24:33 +1000 +Subject: [PATCH 2/2] Invalid signed wildcard records were being accepted + +An RRSIG whose Labels field indicates fewer labels than its signer +name requires was being accepted. When such a record covers a +wildcard, the validator reconstructs a wildcard owner name above the +signer's zone and caches it as secure. RFC 8198 cache synthesis +(synth-from-dnssec) then serves that forged wildcard for unrelated +names, poisoning the cache. + +These records are now rejected, both when an RRSIG is parsed and when +its signature is verified. + +(cherry picked from commit 084ca5ee10515e461d46b63df9660b8394bc7de9) +--- + lib/dns/dnssec.c | 45 +++++++++++++++++++++++--------- + lib/dns/rdata/generic/rrsig_46.c | 37 +++++++++++++++++++------- + 2 files changed, 60 insertions(+), 22 deletions(-) + +diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c +index b6b2405..2516a9a 100644 +--- a/lib/dns/dnssec.c ++++ b/lib/dns/dnssec.c +@@ -142,11 +142,11 @@ dns_dnssec_keyfromrdata(dns_name_t *name, dns_rdata_t *rdata, isc_mem_t *mctx, + isc_buffer_t b; + isc_region_t r; + +- INSIST(name != NULL); +- INSIST(rdata != NULL); +- INSIST(mctx != NULL); +- INSIST(key != NULL); +- INSIST(*key == NULL); ++ REQUIRE(name != NULL); ++ REQUIRE(rdata != NULL); ++ REQUIRE(mctx != NULL); ++ REQUIRE(key != NULL); ++ REQUIRE(*key == NULL); + REQUIRE(rdata->type == dns_rdatatype_key || + rdata->type == dns_rdatatype_dnskey); + +@@ -200,12 +200,14 @@ dns_dnssec_sign(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + isc_buffer_t *databuf = NULL; + char data[256 + 8]; + uint32_t flags; ++ unsigned int labels; + unsigned int sigsize; + dns_fixedname_t fnewname; + dns_fixedname_t fsigner; + + REQUIRE(name != NULL); +- REQUIRE(dns_name_countlabels(name) <= 255); ++ labels = dns_name_countlabels(name); ++ REQUIRE(labels <= 255 && labels > 0); + REQUIRE(set != NULL); + REQUIRE(key != NULL); + REQUIRE(inception != NULL); +@@ -243,7 +245,7 @@ dns_dnssec_sign(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + + sig.covered = set->type; + sig.algorithm = dst_key_alg(key); +- sig.labels = dns_name_countlabels(name) - 1; ++ sig.labels = labels - 1; + if (dns_name_iswildcard(name)) + sig.labels--; + sig.originalttl = set->ttl; +@@ -390,11 +392,14 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + isc_result_t ret; + unsigned char data[300]; + dst_context_t *ctx = NULL; +- int labels = 0; + uint32_t flags; ++ unsigned int labels; ++ unsigned int siglabels; + bool downcase = false; + + REQUIRE(name != NULL); ++ labels = dns_name_countlabels(name); ++ REQUIRE(labels > 0); + REQUIRE(set != NULL); + REQUIRE(key != NULL); + REQUIRE(mctx != NULL); +@@ -407,6 +412,21 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + if (set->type != sig.covered) + return (DNS_R_SIGINVALID); + ++ /* ++ * The RRSIG labels field can't indicate fewer labels than the ++ * signer. Also the labels shouldn't be greater than that of ++ * the owner name. ++ * ++ * sig.labels doesn't include the root label, so add 1 to account ++ * for it. ++ */ ++ siglabels = sig.labels + 1; ++ if (siglabels < dns_name_countlabels(&sig.signer) || siglabels > labels) ++ { ++ inc_stat(dns_dnssecstats_fail); ++ return DNS_R_SIGINVALID; ++ } ++ + if (isc_serial_lt(sig.timeexpire, sig.timesigned)) { + inc_stat(dns_dnssecstats_fail); + return (DNS_R_SIGINVALID); +@@ -484,11 +504,10 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + * If the name is an expanded wildcard, use the wildcard name. + */ + dns_fixedname_init(&fnewname); +- labels = dns_name_countlabels(name) - 1; + RUNTIME_CHECK(dns_name_downcase(name, dns_fixedname_name(&fnewname), + NULL) == ISC_R_SUCCESS); +- if (labels - sig.labels > 0) +- dns_name_split(dns_fixedname_name(&fnewname), sig.labels + 1, ++ if (labels > siglabels) ++ dns_name_split(dns_fixedname_name(&fnewname), siglabels, + NULL, dns_fixedname_name(&fnewname)); + + dns_name_toregion(dns_fixedname_name(&fnewname), &r); +@@ -497,7 +516,7 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + * Create an envelope for each rdata: . + */ + isc_buffer_init(&envbuf, data, sizeof(data)); +- if (labels - sig.labels > 0) { ++ if (labels > siglabels) { + isc_buffer_putuint8(&envbuf, 1); + isc_buffer_putuint8(&envbuf, '*'); + memmove(data + 2, r.base, r.length); +@@ -583,7 +602,7 @@ cleanup_struct: + if (ret != ISC_R_SUCCESS) + inc_stat(dns_dnssecstats_fail); + +- if (ret == ISC_R_SUCCESS && labels - sig.labels > 0) { ++ if (ret == ISC_R_SUCCESS && labels > siglabels) { + if (wild != NULL) + RUNTIME_CHECK(dns_name_concatenate(dns_wildcardname, + dns_fixedname_name(&fnewname), +diff --git a/lib/dns/rdata/generic/rrsig_46.c b/lib/dns/rdata/generic/rrsig_46.c +index 0d4df67..35a1b4c 100644 +--- a/lib/dns/rdata/generic/rrsig_46.c ++++ b/lib/dns/rdata/generic/rrsig_46.c +@@ -21,12 +21,12 @@ + static inline isc_result_t + fromtext_rrsig(ARGS_FROMTEXT) { + isc_token_t token; +- unsigned char c; ++ unsigned char alg, labels; + long i; + dns_rdatatype_t covered; +- char *e; ++ char *e = NULL; + isc_result_t result; +- dns_name_t name; ++ dns_name_t signer; + isc_buffer_t buffer; + uint32_t time_signed, time_expire; + +@@ -57,8 +57,8 @@ fromtext_rrsig(ARGS_FROMTEXT) { + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); +- RETTOK(dns_secalg_fromtext(&c, &token.value.as_textregion)); +- RETERR(mem_tobuffer(target, &c, 1)); ++ RETTOK(dns_secalg_fromtext(&alg, &token.value.as_textregion)); ++ RETERR(mem_tobuffer(target, &alg, 1)); + + /* + * Labels. +@@ -67,8 +67,8 @@ fromtext_rrsig(ARGS_FROMTEXT) { + false)); + if (token.value.as_ulong > 0xffU) + RETTOK(ISC_R_RANGE); +- c = (unsigned char)token.value.as_ulong; +- RETERR(mem_tobuffer(target, &c, 1)); ++ labels = (unsigned char)token.value.as_ulong; ++ RETERR(mem_tobuffer(target, &labels, 1)); + + /* + * Original ttl. +@@ -131,11 +131,19 @@ fromtext_rrsig(ARGS_FROMTEXT) { + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); +- dns_name_init(&name, NULL); ++ dns_name_init(&signer, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) + origin = dns_rootname; +- RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); ++ RETTOK(dns_name_fromtext(&signer, &buffer, origin, options, target)); ++ ++ /* ++ * (RRSIG labels doesn't include the root label, so add one ++ * to normalize it before checking against the signer.) ++ */ ++ if ((unsigned int)(labels + 1) < dns_name_countlabels(&signer)) { ++ RETTOK(ISC_R_RANGE); ++ } + + /* + * Sig. +@@ -260,6 +268,7 @@ static inline isc_result_t + fromwire_rrsig(ARGS_FROMWIRE) { + isc_region_t sr; + dns_name_t name; ++ unsigned char labels; + + REQUIRE(type == dns_rdatatype_rrsig); + +@@ -281,6 +290,8 @@ fromwire_rrsig(ARGS_FROMWIRE) { + if (sr.length < 18) + return (ISC_R_UNEXPECTEDEND); + ++ labels = sr.base[3]; ++ + isc_buffer_forward(source, 18); + RETERR(mem_tobuffer(target, sr.base, 18)); + +@@ -290,6 +301,14 @@ fromwire_rrsig(ARGS_FROMWIRE) { + dns_name_init(&name, NULL); + RETERR(dns_name_fromwire(&name, source, dctx, options, target)); + ++ /* ++ * (RRSIG labels doesn't include the root label, so add one ++ * to normalize it before checking against the signer.) ++ */ ++ if ((unsigned int)(labels + 1) < dns_name_countlabels(&name)) { ++ RETERR(DNS_R_FORMERR); ++ } ++ + /* + * Sig. + */ diff --git a/SOURCES/bind-9.11-CVE-2026-13204.patch b/SOURCES/bind-9.11-CVE-2026-13204.patch new file mode 100644 index 0000000..1b5e425 --- /dev/null +++ b/SOURCES/bind-9.11-CVE-2026-13204.patch @@ -0,0 +1,154 @@ +From 0be245b5c7641533fff2f808d19730e9766a9a36 Mon Sep 17 00:00:00 2001 +From: Evan Hunt +Date: Wed, 13 May 2026 20:45:57 -0700 +Subject: [PATCH] dns_rdataset_addnoqname() could find unsigned NSEC/NSEC3 + +The dns_rdatalist addnoqname() implementation searches for the first +NSEC or NSEC3 record in a message, then for the first RRSIG covering +that type in the same message. Previously, if no RRSIG for the type was +found, the function accepted the unsigned record. Now, it will instead +continue searching until an NSEC or NSEC3 that does have a matching +signature is found. + +When this function is called from validated() in resolver.c, a +non-success return code is now treated as an error instead of triggering +an assertion failure. + +Fixes: isc-projects/bind9#5985 +(cherry picked from commit 57cba571ee31311e54d8a11cb38094d439f04e09) +--- + bin/named/query.c | 4 +++- + lib/dns/rbtdb.c | 8 +++++--- + lib/dns/rdatalist.c | 36 +++++++++++++++++++++--------------- + lib/dns/resolver.c | 4 +++- + 4 files changed, 32 insertions(+), 20 deletions(-) + +diff --git a/bin/named/query.c b/bin/named/query.c +index e023d74..083ce83 100644 +--- a/bin/named/query.c ++++ b/bin/named/query.c +@@ -6198,7 +6198,9 @@ query_addnoqnameproof(ns_client_t *client, dns_rdataset_t *rdataset) { + goto cleanup; + + result = dns_rdataset_getnoqname(rdataset, fname, neg, negsig); +- RUNTIME_CHECK(result == ISC_R_SUCCESS); ++ if (result != ISC_R_SUCCESS) { ++ goto cleanup; ++ } + + query_addrrset(client, &fname, &neg, &negsig, dbuf, + DNS_SECTION_AUTHORITY); +diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c +index 388ffdf..b7f21ac 100644 +--- a/lib/dns/rbtdb.c ++++ b/lib/dns/rbtdb.c +@@ -6977,7 +6977,7 @@ static inline isc_result_t + addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader, + dns_rdataset_t *rdataset) + { +- struct noqname *noqname; ++ struct noqname *noqname = NULL; + isc_mem_t *mctx = rbtdb->common.mctx; + dns_name_t name; + dns_rdataset_t neg, negsig; +@@ -6989,7 +6989,9 @@ addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader, + dns_rdataset_init(&negsig); + + result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig); +- RUNTIME_CHECK(result == ISC_R_SUCCESS); ++ if (result != ISC_R_SUCCESS) { ++ goto cleanup; ++ } + + noqname = isc_mem_get(mctx, sizeof(*noqname)); + if (noqname == NULL) { +@@ -7021,7 +7023,7 @@ cleanup: + dns_rdataset_disassociate(&negsig); + if (noqname != NULL) + free_noqname(mctx, &noqname); +- return(result); ++ return (result); + } + + static inline isc_result_t +diff --git a/lib/dns/rdatalist.c b/lib/dns/rdatalist.c +index cc2619c..687953c 100644 +--- a/lib/dns/rdatalist.c ++++ b/lib/dns/rdatalist.c +@@ -196,6 +196,7 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name) { + dns_rdataset_t *neg = NULL; + dns_rdataset_t *negsig = NULL; + dns_rdataset_t *rdset; ++ dns_rdataset_t *sigset; + dns_ttl_t ttl; + + REQUIRE(rdataset != NULL); +@@ -204,26 +205,30 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name) { + rdset != NULL; + rdset = ISC_LIST_NEXT(rdset, link)) + { +- if (rdset->rdclass != rdataset->rdclass) ++ if (rdset->rdclass != rdataset->rdclass || ++ (rdset->type != dns_rdatatype_nsec && ++ rdset->type != dns_rdatatype_nsec3)) ++ { + continue; +- if (rdset->type == dns_rdatatype_nsec || +- rdset->type == dns_rdatatype_nsec3) +- neg = rdset; ++ } ++ ++ for (sigset = ISC_LIST_HEAD(name->list); sigset != NULL; ++ sigset = ISC_LIST_NEXT(sigset, link)) ++ { ++ if (sigset->type == dns_rdatatype_rrsig && ++ sigset->covers == rdset->type) ++ { ++ neg = rdset; ++ negsig = sigset; ++ break; ++ } ++ } + } +- if (neg == NULL) +- return (ISC_R_NOTFOUND); + +- for (rdset = ISC_LIST_HEAD(name->list); +- rdset != NULL; +- rdset = ISC_LIST_NEXT(rdset, link)) +- { +- if (rdset->type == dns_rdatatype_rrsig && +- rdset->covers == neg->type) +- negsig = rdset; ++ if (neg == NULL || negsig == NULL) { ++ return (ISC_R_NOTFOUND); + } + +- if (negsig == NULL) +- return (ISC_R_NOTFOUND); + /* + * Minimise ttl. + */ +@@ -235,6 +240,7 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name) { + rdataset->ttl = neg->ttl = negsig->ttl = ttl; + rdataset->attributes |= DNS_RDATASETATTR_NOQNAME; + rdataset->private6 = name; ++ + return (ISC_R_SUCCESS); + } + +diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c +index 5331ea9..78553f6 100644 +--- a/lib/dns/resolver.c ++++ b/lib/dns/resolver.c +@@ -5153,7 +5153,9 @@ validated(isc_task_t *task, isc_event_t *event) { + if (vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) { + result = dns_rdataset_addnoqname(vevent->rdataset, + vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF]); +- RUNTIME_CHECK(result == ISC_R_SUCCESS); ++ if (result != ISC_R_SUCCESS) { ++ goto noanswer_response; ++ } + INSIST(vevent->sigrdataset != NULL); + vevent->sigrdataset->ttl = vevent->rdataset->ttl; + if (vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER] != NULL) { diff --git a/SOURCES/bind-9.11-CVE-2026-13321.patch b/SOURCES/bind-9.11-CVE-2026-13321.patch new file mode 100644 index 0000000..a4b12d7 --- /dev/null +++ b/SOURCES/bind-9.11-CVE-2026-13321.patch @@ -0,0 +1,270 @@ +From 338661714d508a810a903ae6fdaf522c9abc2d4b 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 1/2] Reject out-of-zone NSEC next owner names + +When verifying DNSSEC records, make sure that a next owner name of +an NSEC record is a subdomain of the signer field. + +This follows the specification RFC 4034, section 4.1.1: + + Owner names of RRsets for which the given zone is not authoritative + (such as glue records) MUST NOT be listed in the Next Domain Name + unless at least one authoritative RRset exists at the same owner + name. + +While the above paragraph is intended for glue records, it also +applies to out-of-zone data. + +(cherry picked from commit 4065512d25b71605b9502bb69dfb903776d35aa9) +--- + lib/dns/dnssec.c | 18 ++++++++++++++++++ + lib/dns/include/dns/dnssec.h | 6 ++++++ + 2 files changed, 24 insertions(+) + +diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c +index b6b2405fac..b9bd374ed6 100644 +--- a/lib/dns/dnssec.c ++++ b/lib/dns/dnssec.c +@@ -380,8 +380,10 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + bool ignoretime, unsigned int maxbits, + isc_mem_t *mctx, dns_rdata_t *sigrdata, dns_name_t *wild) + { ++ dns_rdata_nsec_t nsec; + dns_rdata_rrsig_t sig; + dns_fixedname_t fnewname; ++ dns_rdata_t rdata = DNS_RDATA_INIT; + isc_region_t r; + isc_buffer_t envbuf; + dns_rdata_t *rdatas; +@@ -454,6 +456,22 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + break; + } + ++ /* ++ * Check for out of zone NSEC entries. ++ */ ++ if (set->type == dns_rdatatype_nsec) { ++ if (dns_rdataset_first(set) != ISC_R_SUCCESS) { ++ return (DNS_R_NOVALIDNSEC); ++ } ++ dns_rdataset_current(set, &rdata); ++ if (dns_rdata_tostruct(&rdata, &nsec, NULL) != ISC_R_SUCCESS) { ++ return (DNS_R_NOVALIDNSEC); ++ } ++ if (!dns_name_issubdomain(&nsec.next, &sig.signer)) { ++ return (DNS_R_NOVALIDNSEC); ++ } ++ } ++ + /* + * Is the key allowed to sign data? + */ +diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h +index 31e2586c9b..1f13a08181 100644 +--- a/lib/dns/include/dns/dnssec.h ++++ b/lib/dns/include/dns/dnssec.h +@@ -138,6 +138,9 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + * this record, as this requires a resolver or database. + * If 'ignoretime' is true, temporal validity will not be checked. + * ++ * If 'set' is of type NSEC, this function also verifies that the ++ * Next Name is a subdomain of the Signer's Name from 'sigrdata'. ++ * + * 'maxbits' specifies the maximum number of rsa exponent bits accepted. + * + * Requires: +@@ -160,6 +163,9 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + *\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data (either + * it is not a zone key or its flags prevent + * authentication) ++ * ++ *\li #DNS_R_NOVALIDNSEC - the NSEC rdata is not valid ++ *\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data + *\li DST_R_* + */ + + +From dfb91ca4a45092c414a41cb5640cf4710f7ba0e9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ayd=C4=B1n=20Mercan?= +Date: Tue, 12 May 2026 14:54:09 +0300 +Subject: [PATCH 2/2] change dns_nsec_requiredtypespresent to dns_nsec_is_legal + +Change `dns_nsec_requiredtypespresent` to `dns_nsec_is_legal` as a +function for checking multiple NSEC validity rules. + +Currently we now additionally check for out-of-zone NSEC entries. + +(cherry picked from commit be2a6a497312469890b552907d039d2de0b44ccc) +--- + lib/dns/include/dns/nsec.h | 18 ++++++++++++++ + lib/dns/nsec.c | 37 +++++++++++++++++++++++++++ + lib/dns/resolver.c | 51 ++++++++++++++++++++++++++++++++++++++ + 3 files changed, 106 insertions(+) + +diff --git a/lib/dns/include/dns/nsec.h b/lib/dns/include/dns/nsec.h +index 4e12cbea75..8caead6a1e 100644 +--- a/lib/dns/include/dns/nsec.h ++++ b/lib/dns/include/dns/nsec.h +@@ -106,6 +106,24 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, dns_name_t *name, + * Return ISC_R_IGNORE when the NSEC is not the appropriate one. + */ + ++bool ++dns_nsec_is_legal(dns_rdataset_t *rdataset, const dns_name_t *name); ++/**< ++ * \brief ++ * Validates a rdataset of type NSEC. ++ * ++ * This functions checks for the following in the given rdataset: ++ * \li All NSEC records have both NSEC and RRSIG present ++ * \li All NSEC entries are under the `name` ++ * ++ * \par Requires: ++ * \li rdataset to be a NSEC rdataset. ++ * \li `name` is a valid dns_name_t ++ * ++ * \retval true if all the checks pass ++ * \retval false otherwise ++ */ ++ + ISC_LANG_ENDDECLS + + #endif /* DNS_NSEC_H */ +diff --git a/lib/dns/nsec.c b/lib/dns/nsec.c +index d90c38589e..fd4c529da0 100644 +--- a/lib/dns/nsec.c ++++ b/lib/dns/nsec.c +@@ -21,6 +21,7 @@ + #include + + #include ++#include + #include + #include + #include +@@ -445,3 +446,39 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, dns_name_t *name, + *exists = false; + return (ISC_R_SUCCESS); + } ++ ++bool ++dns_nsec_is_legal(dns_rdataset_t *nsecset, const dns_name_t *name) { ++ dns_rdataset_t rdataset; ++ dns_rdata_nsec_t nsec; ++ isc_result_t result; ++ bool found = false; ++ ++ REQUIRE(nsecset != NULL && nsecset->type == dns_rdatatype_nsec); ++ ++ dns_rdataset_init(&rdataset); ++ dns_rdataset_clone(nsecset, &rdataset); ++ ++ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; ++ result = dns_rdataset_next(&rdataset)) ++ { ++ dns_rdata_t rdata = DNS_RDATA_INIT; ++ dns_rdataset_current(&rdataset, &rdata); ++ ++ /* must never fail */ ++ result = dns_rdata_tostruct(&rdata, &nsec, NULL); ++ INSIST(result == ISC_R_SUCCESS); ++ ++ if (!dns_name_issubdomain(&nsec.next, name) || ++ !dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig) || ++ !dns_nsec_typepresent(&rdata, dns_rdatatype_nsec)) ++ { ++ dns_rdataset_disassociate(&rdataset); ++ return (false); ++ } ++ ++ found = true; ++ } ++ dns_rdataset_disassociate(&rdataset); ++ return (found); ++} +diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c +index 0f16eadcdd..8ef35bb641 100644 +--- a/lib/dns/resolver.c ++++ b/lib/dns/resolver.c +@@ -62,8 +62,10 @@ + #include + #include + #include ++#include + #include ++#include + #include + + #ifdef WANT_QUERYTRACE + #define RTRACE(m) isc_log_write(dns_lctx, \ +@@ -4879,6 +4881,36 @@ maybe_destroy(fetchctx_t *fctx, bool locked) { + return (bucket_empty); + } + ++static bool ++get_and_check_signer_name(dns_name_t *signer, dns_rdataset_t *sigrdataset) { ++ dns_rdata_rrsig_t rrsig; ++ isc_result_t result; ++ dns_rdata_t rdata; ++ ++ if (dns_rdataset_first(sigrdataset) != ISC_R_SUCCESS) { ++ return false; ++ } ++ ++ rdata = (dns_rdata_t)DNS_RDATA_INIT; ++ dns_rdataset_current(sigrdataset, &rdata); ++ result = dns_rdata_tostruct(&rdata, &rrsig, NULL); ++ INSIST(result == ISC_R_SUCCESS); ++ dns_name_copy(&rrsig.signer, signer, NULL); ++ ++ while (dns_rdataset_next(sigrdataset) == ISC_R_SUCCESS) { ++ rdata = (dns_rdata_t)DNS_RDATA_INIT; ++ dns_rdataset_current(sigrdataset, &rdata); ++ result = dns_rdata_tostruct(&rdata, &rrsig, NULL); ++ INSIST(result == ISC_R_SUCCESS); ++ ++ if (!dns_name_equal(signer, &rrsig.signer)) { ++ return false; ++ } ++ } ++ ++ return true; ++} ++ + /* + * The validator has finished. + */ +@@ -4907,6 +4939,8 @@ validated(isc_task_t *task, isc_event_t *event) { + unsigned options; + uint32_t bucketnum; + dns_message_t *rmessage = NULL; ++ dns_fixedname_t fsigner; ++ dns_name_t *signer = NULL; + + UNUSED(task); /* for now */ + +@@ -5240,6 +5274,23 @@ validated(isc_task_t *task, isc_event_t *event) { + if (sigrdataset == NULL || + sigrdataset->trust != dns_trust_secure) + continue; ++ /* ++ * Don't cache if all the RRSIGs don't have the same ++ * signer. ++ */ ++ signer = dns_fixedname_initname(&fsigner); ++ if (!get_and_check_signer_name(signer, sigrdataset)) { ++ continue; ++ } ++ /* ++ * Don't cache NSEC if missing NSEC or RRSIG ++ * types. ++ */ ++ if (rdataset->type == dns_rdatatype_nsec && ++ !dns_nsec_is_legal(rdataset, signer)) ++ { ++ continue; ++ } + result = dns_db_findnode(fctx->cache, name, true, + &nsnode); + if (result != ISC_R_SUCCESS) diff --git a/SPECS/bind.spec b/SPECS/bind.spec index 41efe2b..097b9f2 100644 --- a/SPECS/bind.spec +++ b/SPECS/bind.spec @@ -68,7 +68,7 @@ Summary: The Berkeley Internet Name Domain (BIND) DNS (Domain Name System) serv Name: bind License: MPLv2.0 Version: 9.11.36 -Release: 16%{?PATCHVER:.%{PATCHVER}}%{?PREVER:.%{PREVER}}%{?dist}.8 +Release: 16%{?PATCHVER:.%{PATCHVER}}%{?PREVER:.%{PREVER}}%{?dist}.14 Epoch: 32 Url: https://www.isc.org/downloads/bind/ # @@ -212,6 +212,20 @@ Patch216: bind-9.11-CVE-2026-3039.patch # https://gitlab.isc.org/isc-projects/bind9/commit/bba70f08b1818f697e8292b79270a464279adc23 # https://gitlab.isc.org/isc-projects/bind9/commit/befe1903d0aebfd69cb362c429afc1f9c1c89610 Patch217: bind-9.11-CVE-2026-5946.patch +# https://github.com/isc-projects/bind9/commit/0b72e907d10760c2a8b01fc1f5a1c1f0d1a4bfe0 +Patch218: bind-9.11-CVE-2026-11622.patch +# https://github.com/isc-projects/bind9/commit/06778424f0f58375f0f3d6632813558c59d705c6 +# https://github.com/isc-projects/bind9/commit/e5b16cfd1c0b7d7760108e1ca9e1fd9db30d9e6a +Patch219: bind-9.11-CVE-2026-11721.patch +# https://github.com/isc-projects/bind9/commit/058023c66f11d78590d4aa8c4f98946c4c965e21 +# https://github.com/isc-projects/bind9/commit/f751e19a30d107f04c2f644aff9f8dab8fed03ab +Patch220: bind-9.11-CVE-2026-13321.patch +# https://github.com/isc-projects/bind9/commit/204fde85953d78475334694a7b9507dca47e73ba +Patch221: bind-9.11-CVE-2026-13204.patch +# https://github.com/isc-projects/bind9/commit/6965fa47edd3b45538db2b16488dbb6b4ad8066a +Patch223: bind-9.11-CVE-2026-10723.patch +# https://gitlab.isc.org/isc-projects/bind9/-/commit/c63f11976ffe6e0285e0a9a3524a53420786d50e +Patch224: bind-9.11-CVE-2026-11721-test.patch # SDB patches Patch11: bind-9.3.2b2-sdbsrc.patch @@ -644,6 +658,12 @@ are used for building ISC DHCP. %patch -P 215 -p1 -b .CVE-2026-1519 %patch -P 216 -p1 -b .CVE-2026-3039 %patch -P 217 -p1 -b .CVE-2026-5946 +%patch -P 218 -p1 -b .CVE-2026-11622 +%patch -P 219 -p1 -b .CVE-2026-11721 +%patch -P 220 -p1 -b .CVE-2026-13321 +%patch -P 221 -p1 -b .CVE-2026-13204 +%patch -P 223 -p1 -b .CVE-2026-10723 +%patch -P 224 -p1 -b .CVE-2026-11721-test mkdir lib/dns/tests/testdata/dstrandom cp -a %{SOURCE50} lib/dns/tests/testdata/dstrandom/random.data @@ -1696,6 +1716,23 @@ rm -rf ${RPM_BUILD_ROOT} %endif %changelog +* Mon Jul 27 2026 RHEL Packaging Agent - 32:9.11.36-16.14 +- Validate NSEC3 signer matches owning zone (CVE-2026-10723) + +* Mon Jul 27 2026 RHEL Packaging Agent - 32:9.11.36-16.12 +- Prevent accepting unsigned NSEC/NSEC3 records (CVE-2026-13204) + +* Mon Jul 27 2026 RHEL Packaging Agent - 32:9.11.36-16.11 +- Reject out-of-zone NSEC entries in DNSSEC validation + (CVE-2026-13321) + +* Mon Jul 27 2026 RHEL Packaging Agent - 32:9.11.36-16.10 +- Reject RRSIG records with invalid label counts (CVE-2026-11721) +- Add unittest check + +* Mon Jul 27 2026 RHEL Packaging Agent - 32:9.11.36-16.9 +- Add reference counting to cache dns_slabheaders (CVE-2026-11622) + * Wed May 27 2026 Petr Menšík - 32:9.11.36-16.8 - Fix GSS-API resource leak (CVE-2026-3039) - Invalid handling of CLASS != IN (CVE-2026-5946)