diff --git a/bind-9.11-CVE-2026-11622.patch b/bind-9.11-CVE-2026-11622.patch new file mode 100644 index 0000000..cde83b4 --- /dev/null +++ b/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/bind.spec b/bind.spec index 41efe2b..c962827 100644 --- a/bind.spec +++ b/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}.9 Epoch: 32 Url: https://www.isc.org/downloads/bind/ # @@ -212,6 +212,8 @@ 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 # SDB patches Patch11: bind-9.3.2b2-sdbsrc.patch @@ -644,6 +646,7 @@ 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 mkdir lib/dns/tests/testdata/dstrandom cp -a %{SOURCE50} lib/dns/tests/testdata/dstrandom/random.data @@ -1696,6 +1699,9 @@ rm -rf ${RPM_BUILD_ROOT} %endif %changelog +* 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)