Fix CVE-2026-11622: reference-count dns_slabheaders in cache

Backport upstream commit 0b72e907d1 to bind-9.11.36 to fix
CVE-2026-11622. The patch adds reference counting to
dns_slabheaders in the cache (rbtdb.c) so that stale headers
can be reclaimed as soon as their own reference count reaches
zero, and adds the required isc/refcount.h include to
rdataslab.h. Three hunks required manual adaptation for the
RHEL 8 codebase which uses mark_header_ancient instead of
upstream's mark_stale_header.

CVE: CVE-2026-11622
Upstream patches:
 - 0b72e907d1.patch
Resolves: RHEL-213379

This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent.

Assisted-by: Ymir
This commit is contained in:
RHEL Packaging Agent 2026-07-27 14:17:50 +00:00
parent 92d501d5d9
commit 4f8f15daec
2 changed files with 325 additions and 1 deletions

View File

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

View File

@ -68,7 +68,7 @@ Summary: The Berkeley Internet Name Domain (BIND) DNS (Domain Name System) serv
Name: bind Name: bind
License: MPLv2.0 License: MPLv2.0
Version: 9.11.36 Version: 9.11.36
Release: 16%{?PATCHVER:.%{PATCHVER}}%{?PREVER:.%{PREVER}}%{?dist}.8 Release: 16%{?PATCHVER:.%{PATCHVER}}%{?PREVER:.%{PREVER}}%{?dist}.9
Epoch: 32 Epoch: 32
Url: https://www.isc.org/downloads/bind/ 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/bba70f08b1818f697e8292b79270a464279adc23
# https://gitlab.isc.org/isc-projects/bind9/commit/befe1903d0aebfd69cb362c429afc1f9c1c89610 # https://gitlab.isc.org/isc-projects/bind9/commit/befe1903d0aebfd69cb362c429afc1f9c1c89610
Patch217: bind-9.11-CVE-2026-5946.patch 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 # SDB patches
Patch11: bind-9.3.2b2-sdbsrc.patch 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 215 -p1 -b .CVE-2026-1519
%patch -P 216 -p1 -b .CVE-2026-3039 %patch -P 216 -p1 -b .CVE-2026-3039
%patch -P 217 -p1 -b .CVE-2026-5946 %patch -P 217 -p1 -b .CVE-2026-5946
%patch -P 218 -p1 -b .CVE-2026-11622
mkdir lib/dns/tests/testdata/dstrandom mkdir lib/dns/tests/testdata/dstrandom
cp -a %{SOURCE50} lib/dns/tests/testdata/dstrandom/random.data cp -a %{SOURCE50} lib/dns/tests/testdata/dstrandom/random.data
@ -1696,6 +1699,9 @@ rm -rf ${RPM_BUILD_ROOT}
%endif %endif
%changelog %changelog
* Mon Jul 27 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.11.36-16.9
- Add reference counting to cache dns_slabheaders (CVE-2026-11622)
* Wed May 27 2026 Petr Menšík <pemensik@redhat.com> - 32:9.11.36-16.8 * Wed May 27 2026 Petr Menšík <pemensik@redhat.com> - 32:9.11.36-16.8
- Fix GSS-API resource leak (CVE-2026-3039) - Fix GSS-API resource leak (CVE-2026-3039)
- Invalid handling of CLASS != IN (CVE-2026-5946) - Invalid handling of CLASS != IN (CVE-2026-5946)