diff --git a/bind-9.18-CVE-2026-12617-test.patch b/bind-9.18-CVE-2026-12617-test.patch new file mode 100644 index 0000000..b2add78 --- /dev/null +++ b/bind-9.18-CVE-2026-12617-test.patch @@ -0,0 +1,435 @@ +From a31296b120efc985fb1fc3c932882e965156473b Mon Sep 17 00:00:00 2001 +From: Colin Vidal +Date: Mon, 15 Jun 2026 11:34:08 +0200 +Subject: [PATCH] Reproducer for #5946 (assertion in some CNAME/DNAME queries) + +Add a system test reproducing the issue reported by #5946, which +is also CVE-2026-12617. There are two scenarios: + +- A client send queries for a DNAME and A record to the resolver (ns3), + and the authoritative server (ans2) responds positively to the A query + but delay the DNAME response and respond later negatively; + +- A client send queries for a CNAME and A record to the resolver (ns3), + and the authoritative server (ans2) responds positively to the A query + but delay the CNAME response and respond later with a self-referential + CNAME. + +The test does not check the results of the queries, however, it expects +the resolver to correctly handle those and do not assert. + +(cherry picked from commit e88271f2e584010157b068cc998dd76451273562) +(cherry picked from commit bb92832fb6ae899bee7206c2d8966258461c2f71) + +Stabilize timing in the cname_dname_negcache test + +The #5946 reproducer relied on ans2 answering the negative DNAME/CNAME +query a fixed second after receiving it, racing that delay against the +resolver's per-query timeout. When the timeout fired first -- which +happens under load, most notably under ThreadSanitizer, where named is +slowed but ans2 (wall-clock) is not -- the resolver dropped the late +answer, never processed the negative response, and the watched SOA never +appeared, so the test timed out. This made it flaky on the +security-bind-9.20 CI. Merely shortening the fixed delay would trade +that for the opposite, worse failure: the negative answer arriving +before the positive one is cached, silently not exercising the bug. + +Release the negative answer based on the resolver's progress instead of +a wall-clock deadline: hold it until ans2 has sent the positive answer +(a shared event), then wait a short settle for the resolver to cache it. +Both queries traverse the same delegation, so any latency reaching ans2 +shifts the positive send and the negative fetch's deadline together and +cancels out; only the settle, kept well under MINIMUM_QUERY_TIMEOUT +(301 ms), has to fit inside the per-query timeout. + +Verified that the stabilized test still triggers the +INSIST(namereln == dns_namereln_subdomain) assertion when the resolver +fix is reverted. + +Assisted-by: Claude:claude-opus-4-8 +(cherry picked from commit 738456d91564526e6f15c3858b4c809cd6749e1e) +(cherry picked from commit 0c20ee4e8e68999ca617434cde65dd3808f57d8c) + +Split cname_dname_negcache into per-scenario modules + +The DNAME and CNAME scenarios shared a single module, hence a single +module-scoped ns3 (the framework sets servers up per module, not per +test function). test_dname_negcache cached foo.test. DNAME bar.test.; +when test_cname_negcache ran next against the same resolver, +cname.foo.test. was DNAME-mapped to cname.bar.test., so the resolver +never queried ans2 for the self-referential CNAME and that half of the +bug was never exercised. The hardcoded, unanchored "foo.test." watcher +still matched test_dname's leftover SOA, so test_cname passed without +testing anything -- the CNAME assertion had no coverage. + +Give each scenario its own module so each gets a fresh server set, and +anchor the watcher to the queried name so a test cannot pass on an +unrelated record. + +With the resolver fix reverted, each module now independently triggers +its own assertion: + + DNAME query.c INSIST(namereln == dns_namereln_subdomain) + CNAME query.c INSIST(qctx->rdataset == NULL || qctx->qtype == dname) + +Assisted-by: Claude:claude-opus-4-8 +(cherry picked from commit 3ef0b8d04a1653407cfb9ee88772ae18689b1318) +(cherry picked from commit 887124315f03a006c4dc76e48ae3d0d8aac3c407) +--- + .../system/cname_dname_negcache/ans2/ans.py | 148 ++++++++++++++++++ + .../system/cname_dname_negcache/common.py | 46 ++++++ + .../cname_dname_negcache/ns1/bar.test.db | 5 + + .../cname_dname_negcache/ns1/named.conf.j2 | 24 +++ + .../system/cname_dname_negcache/ns1/root.db | 6 + + .../system/cname_dname_negcache/ns1/test.db | 8 + + .../cname_dname_negcache/ns3/named.conf.j2 | 11 ++ + .../tests_cname_negcache.py | 16 ++ + .../tests_dname_negcache.py | 16 ++ + 9 files changed, 280 insertions(+) + create mode 100644 bin/tests/system/cname_dname_negcache/ans2/ans.py + create mode 100644 bin/tests/system/cname_dname_negcache/common.py + create mode 100644 bin/tests/system/cname_dname_negcache/ns1/bar.test.db + create mode 100644 bin/tests/system/cname_dname_negcache/ns1/named.conf.j2 + create mode 100644 bin/tests/system/cname_dname_negcache/ns1/root.db + create mode 100644 bin/tests/system/cname_dname_negcache/ns1/test.db + create mode 100644 bin/tests/system/cname_dname_negcache/ns3/named.conf.j2 + create mode 100644 bin/tests/system/cname_dname_negcache/tests_cname_negcache.py + create mode 100644 bin/tests/system/cname_dname_negcache/tests_dname_negcache.py + +diff --git a/bin/tests/system/cname_dname_negcache/ans2/ans.py b/bin/tests/system/cname_dname_negcache/ans2/ans.py +new file mode 100644 +index 0000000000..392fe1e088 +--- /dev/null ++++ b/bin/tests/system/cname_dname_negcache/ans2/ans.py +@@ -0,0 +1,148 @@ ++""" ++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 collections.abc import AsyncGenerator ++ ++import asyncio ++ ++from dns import name, rcode, rdataclass, rdatatype, rrset ++ ++from isctest.asyncserver import ( ++ AsyncDnsServer, ++ DnsResponseSend, ++ QnameQtypeHandler, ++ QueryContext, ++ StaticResponseHandler, ++) ++ ++# The attack relies on the resolver caching the positive CNAME/DNAME answer ++# *before* it processes the negative answer for the same name. The negative ++# answer must therefore be held back until the positive one has been sent, but ++# released again while the negative fetch is still waiting for it. ++# ++# Releasing it at a fixed wall-clock delay (the original approach) is racy: the ++# delay must be larger than the time it takes the resolver to cache the ++# positive answer, yet smaller than the resolver's per-query timeout. Under ++# load -- most notably ThreadSanitizer, which slows down `named` but not this ++# (wall-clock) server -- those bounds can be violated in either direction, ++# making the test either time out (#5946 CI failures) or, worse, silently stop ++# exercising the bug. ++# ++# Instead, gate the negative answer on an event set right after the positive ++# answer is sent. Both queries traverse the same delegation, so any latency in ++# reaching this server shifts the positive send and the negative fetch's ++# deadline together and cancels out; only the small settle below has to fit ++# inside the per-query timeout. ++# ++# _SETTLE must be longer than the few milliseconds the resolver needs to cache ++# the positive answer, and shorter than MINIMUM_QUERY_TIMEOUT (301 ms in ++# lib/dns/resolver.c) so the in-flight negative fetch has not given up yet. ++_SETTLE = 0.1 ++ ++_dname_positive_sent = asyncio.Event() ++_cname_positive_sent = asyncio.Event() ++ ++ ++async def _hold_until_positive_cached(positive_sent: asyncio.Event) -> None: ++ await positive_sent.wait() ++ await asyncio.sleep(_SETTLE) ++ ++ ++def build_rrset( ++ qname: name.Name | str, ++ rtype: rdatatype.RdataType, ++ rdata: str, ++ ttl: int = 300, ++) -> rrset.RRset: ++ return rrset.from_text(qname, ttl, rdataclass.IN, rtype, rdata) ++ ++ ++class FooTestNsHandler(QnameQtypeHandler, StaticResponseHandler): ++ qnames = ["foo.test."] ++ qtypes = [rdatatype.NS] ++ answer = [build_rrset("foo.test.", rdatatype.NS, "ns.foo.test.")] ++ additional = [build_rrset("ns.foo.test.", rdatatype.A, "10.53.0.2")] ++ ++ ++class DelayedDnameNegHandler(QnameQtypeHandler, StaticResponseHandler): ++ qnames = ["foo.test."] ++ qtypes = [rdatatype.DNAME] ++ authority = [ ++ build_rrset( ++ "foo.test.", ++ rdatatype.SOA, ++ "ns.test. op.ns.test. 2081509183 86400 3600 3600000 300", ++ ) ++ ] ++ ++ async def get_responses( ++ self, qctx: QueryContext ++ ) -> AsyncGenerator[DnsResponseSend, None]: ++ await _hold_until_positive_cached(_dname_positive_sent) ++ async for response in super().get_responses(qctx): ++ yield response ++ ++ ++class DnamePosHandler(QnameQtypeHandler, StaticResponseHandler): ++ qnames = ["a.foo.test."] ++ qtypes = [rdatatype.A] ++ answer = [ ++ build_rrset("foo.test.", rdatatype.DNAME, "bar.test."), ++ build_rrset("a.foo.test.", rdatatype.CNAME, "a.bar.test."), ++ ] ++ ++ async def get_responses( ++ self, qctx: QueryContext ++ ) -> AsyncGenerator[DnsResponseSend, None]: ++ async for response in super().get_responses(qctx): ++ yield response ++ _dname_positive_sent.set() ++ ++ ++class CnameHandler(QnameQtypeHandler): ++ qnames = ["cname.foo.test."] ++ qtypes = [rdatatype.CNAME, rdatatype.A] ++ answer = [build_rrset("cname.foo.test.", rdatatype.CNAME, "cname.foo.test.")] ++ authority = [ ++ build_rrset( ++ "cname.foo.test.", ++ rdatatype.SOA, ++ "ns.test. op.ns.test. 2081509183 86400 3600 3600000 300", ++ ) ++ ] ++ ++ async def get_responses( ++ self, qctx: QueryContext ++ ) -> AsyncGenerator[DnsResponseSend, None]: ++ if qctx.qtype == rdatatype.CNAME: ++ await _hold_until_positive_cached(_cname_positive_sent) ++ qctx.prepare_new_response(with_zone_data=False) ++ qctx.response.authority.extend(self.authority) ++ yield DnsResponseSend(qctx.response, authoritative=True) ++ else: ++ qctx.prepare_new_response(with_zone_data=False) ++ qctx.response.answer.extend(self.answer) ++ yield DnsResponseSend(qctx.response, authoritative=True) ++ _cname_positive_sent.set() ++ ++ ++def main() -> None: ++ server = AsyncDnsServer(default_aa=True, default_rcode=rcode.NOERROR) ++ server.install_response_handlers( ++ FooTestNsHandler(), DelayedDnameNegHandler(), DnamePosHandler(), CnameHandler() ++ ) ++ server.run() ++ ++ ++if __name__ == "__main__": ++ main() +diff --git a/bin/tests/system/cname_dname_negcache/common.py b/bin/tests/system/cname_dname_negcache/common.py +new file mode 100644 +index 0000000000..397cfdfa3d +--- /dev/null ++++ b/bin/tests/system/cname_dname_negcache/common.py +@@ -0,0 +1,46 @@ ++# 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 os import environ ++from re import compile as Re ++from re import escape ++from socket import AF_INET, SOCK_DGRAM, socket ++ ++import isctest ++ ++ ++def run_attack(ns, name1, type1, name2, type2): ++ msg1 = isctest.query.create(name1, type1, cd=True) ++ msg2 = isctest.query.create(name2, type2, cd=True) ++ port = int(environ["PORT"]) ++ ++ with socket(AF_INET, SOCK_DGRAM) as sock: ++ # The order the requests go out doesn't matter. What is important is ++ # that the first query starts recursion before the second query returns ++ # the answer, and the second query returns the answer before the first ++ # query returns the answer. (So, when the NOERROR/NODATA comes back from ++ # the first query, the cache is queried and we get the positive response ++ # cached from the second query attached to the fresp rdataset of the ++ # response of the first query.) ++ # That ordering is enforced by ans2, which holds back the negative ++ # answer to the first query until it has answered the second one (see ++ # ans2/ans.py); the resolver must not crash while reconciling them. ++ sock.sendto(msg1.to_wire(), (ns.ip, port)) ++ sock.sendto(msg2.to_wire(), (ns.ip, port)) ++ ++ # The second query comes back immediately, the resolver caches the DNAME. ++ # The first query comes back shortly after, once ans2 has released the ++ # negative answer, and should not crash the server. Wait for the negative ++ # SOA for this specific name (not just any foo.test. one) so the test cannot ++ # pass on an unrelated record. ++ soa = Re(rf"(? +Date: Thu, 18 Jun 2026 18:17:05 +0200 +Subject: [PATCH] Do not assert in some CNAME/DNAME queries + +Fix a `named` crash because of a fail assertion for certains types of +CNAME and DNAME queries: + +- If a client queries for a DNAME and A record to the resolver, and the + authoritative server responds positively to the A query but delay the + DNAME response and respond later negatively; + +- If a client queries for a CNAME and A record to the resolver, and the + authoritative server responds positively to the A query but delay the + CNAME response and respond later with a self-referential CNAME. + +The first scenario consists of sending two queries: `foo.test./DNAME` +and `a.foo.test./A`. The authoritative server delays the answer for +`foo.test./DNAME` but immediately answers the DNAME record for the +second query: `foo.test. DNAME bar.test.`. The resolver caches it, +follows the DNAME, and resolves `a.bar.test./A`. The authoritative +server eventually answers negatively for `foo.test./DNAME` +(NOERROR/NODATA, with only an SOA in the authority section). The +resolver pulls out the previously cached rdataset (because it has a +higher trust level than the received negative answer), and wrongly (this +is the first bug) sets the result to `DNS_R_DNAME` instead of +`ISC_R_SUCCESS`. The code in `ns/query.c` that handles the resolver +result interprets this as "this is a non-DNAME query and we got a DNAME +rdataset, so follow the chain". It goes into the `query_dname()` +function, which asserts that the qname is a subdomain of the owner name +in the rdataset. That assertion fails because the qname (`foo.test.`) is +exactly equal to the owner name of the DNAME (`foo.test.`), rather than +being a subdomain of it. `DNS_R_DNAME` must only be set when the qtype +is something other than DNAME and the resolver has obtained a DNAME that +needs to be followed. + +The second scenario consists of sending two queries: +`cname.foo.test./CNAME` and `cname.foo.test./A`. The authoritative +server delays the answer for `cname.foo.test./CNAME` but immediately +answers the CNAME record for the second query: `cname.foo.test. CNAME +cname.foo.test.`. Note that the CNAME is self-referential. The resolver +caches it and sets the result code to `DNS_R_CNAME`. Then `ns/query.c` +interprets this as "this is a non-CNAME query and we got a CNAME +rdataset, so follow the chain" (which is correct in this case; however, +because the CNAME rdataset is self-referential, the resolver responds +with SERVFAIL, which is expected). The authoritative server eventually +answers negatively for `cname.foo.test./CNAME`. The resolver then pulls +out the previously cached CNAME rdataset (obtained from the A answer, +even though it was self-referential, the resolver cached it) and wrongly +sets the result to `DNS_R_CNAME` (this is the second bug). As noted +above, `ns/query.c` interprets this as "this is a non-CNAME query and we +got a CNAME rdataset, so follow the chain". The internals here are +slightly more subtle: it first goes into `query_cname()` and sets the +CNAME rdataset in the message answer section, then restarts the query to +follow the CNAME. The restart retrieves the CNAME rdataset from the +cache directly (without going to the resolver), and this time the query +context result is `ISC_R_SUCCESS` (since it was found) and +`qctx->rdataset` points to the same CNAME again (as it is +self-referential), so it goes directly into the +`query_prepresponse()/query_respond()` flow, which attempts to add the +rdataset to the message answer again. However, this fails because the +rdataset is already in the message, and the assertion which expects that +operation to succeed fails (due to `qctx->rdataset` being set to `NULL` +when ownership of the rdataset was transferred). `DNS_R_CNAME` must only +be set when the qtype is something other than CNAME and the resolver has +obtained a CNAME that needs to be followed. + +In both cases, the correct answer from the resolver should have been +`ISC_R_SUCCESS` (instead of respectively `DNS_R_DNAME` and +`DNS_R_CNAME`) becuase the rdataset that has been looked up was found. + +(cherry picked from commit 773d46d58c693047a5945c8fe40512edd0ac214e) +(cherry picked from commit c740c37689f234e21a9b0ef760471ef2cf1133f5) +--- + lib/dns/resolver.c | 137 ++++++++++++++++++++------------------------- + 1 file changed, 60 insertions(+), 77 deletions(-) + +diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c +index edc9c207e1..1f8b5058d1 100644 +--- a/lib/dns/resolver.c ++++ b/lib/dns/resolver.c +@@ -692,10 +692,10 @@ fctx_destroy(fetchctx_t *fctx, bool exiting); + static void + send_shutdown_events(dns_resolver_t *res); + static isc_result_t +-ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, +- dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, +- dns_ttl_t maxttl, bool optout, bool secure, +- dns_rdataset_t *ardataset, isc_result_t *eresultp); ++ncache_adderesult(fetchctx_t *fctx, dns_message_t *message, dns_dbnode_t *node, ++ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl, ++ bool optout, bool secure, dns_rdataset_t *ardataset, ++ isc_result_t *eresultp); + static void + validated(isc_task_t *task, isc_event_t *event); + static void +@@ -5563,6 +5563,46 @@ has_000_label(dns_rdataset_t *nsecset) { + return false; + } + ++/* ++ * After a (non-error) negative-cache add, 'rdataset' is bound to whatever ++ * rdataset the cache authoritatively holds for the queried name and type. ++ * Map that to the result code the fetch should report: ++ * ++ * - A negative cache entry (the one we just added, or a pre-existing one): ++ * DNS_R_NCACHENXDOMAIN or DNS_R_NCACHENXRRSET, depending on NXDOMAIN vs ++ * NODATA. ++ * ++ * - A positive rdataset that was already cached at higher trust, which ++ * caused our negative entry to be discarded (e.g. a CNAME or DNAME cached ++ * by a concurrent query): ISC_R_SUCCESS, because that cached positive ++ * answer is what gets returned. Note the specific case for CNAME and ++ * DNAME *if* the query type is not the same as the rdataset type. There ++ * is a chain to follow *only* if the query type doesn't ask for the CNAME ++ * or the DNAME. ++ */ ++static isc_result_t ++fctx_setresult(fetchctx_t *fctx, dns_rdataset_t *rdataset) { ++ isc_result_t result = ISC_R_SUCCESS; ++ ++ if (NEGATIVE(rdataset)) { ++ result = NXDOMAIN(rdataset) ? DNS_R_NCACHENXDOMAIN ++ : DNS_R_NCACHENXRRSET; ++ } else if (result == ISC_R_SUCCESS && rdataset->type != fctx->type) { ++ switch (rdataset->type) { ++ case dns_rdatatype_cname: ++ result = DNS_R_CNAME; ++ break; ++ case dns_rdatatype_dname: ++ result = DNS_R_DNAME; ++ break; ++ default: ++ break; ++ } ++ } ++ ++ return result; ++} ++ + /* + * The validator has finished. + */ +@@ -5836,8 +5876,7 @@ validated(isc_task_t *task, isc_event_t *event) { + ttl = 0; + } + +- result = ncache_adderesult(message, fctx->cache, node, covers, +- now, fctx->res->view->minncachettl, ++ result = ncache_adderesult(fctx, message, node, covers, now, + ttl, vevent->optout, vevent->secure, + ardataset, &eresult); + if (result != ISC_R_SUCCESS) { +@@ -6081,23 +6120,7 @@ answer_response: + */ + INSIST(hevent->rdataset != NULL); + if (dns_rdataset_isassociated(hevent->rdataset)) { +- if (NEGATIVE(hevent->rdataset)) { +- INSIST(eresult == DNS_R_NCACHENXDOMAIN || +- eresult == DNS_R_NCACHENXRRSET); +- } else if (eresult == ISC_R_SUCCESS && +- hevent->rdataset->type != fctx->type) +- { +- switch (hevent->rdataset->type) { +- case dns_rdatatype_cname: +- eresult = DNS_R_CNAME; +- break; +- case dns_rdatatype_dname: +- eresult = DNS_R_DNAME; +- break; +- default: +- break; +- } +- } ++ eresult = fctx_setresult(fctx, hevent->rdataset); + } + + hevent->result = eresult; +@@ -6747,24 +6770,7 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message, + * event->result. + */ + if (dns_rdataset_isassociated(event->rdataset)) { +- if (NEGATIVE(event->rdataset)) { +- INSIST(eresult == +- DNS_R_NCACHENXDOMAIN || +- eresult == DNS_R_NCACHENXRRSET); +- } else if (eresult == ISC_R_SUCCESS && +- event->rdataset->type != fctx->type) +- { +- switch (event->rdataset->type) { +- case dns_rdatatype_cname: +- eresult = DNS_R_CNAME; +- break; +- case dns_rdatatype_dname: +- eresult = DNS_R_DNAME; +- break; +- default: +- break; +- } +- } ++ eresult = fctx_setresult(fctx, event->rdataset); + } + event->result = eresult; + if (adbp != NULL && *adbp != NULL) { +@@ -6833,12 +6839,14 @@ cache_message(fetchctx_t *fctx, dns_message_t *message, + * eresult. + */ + static isc_result_t +-ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, +- dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, +- dns_ttl_t maxttl, bool optout, bool secure, +- dns_rdataset_t *ardataset, isc_result_t *eresultp) { ++ncache_adderesult(fetchctx_t *fctx, dns_message_t *message, dns_dbnode_t *node, ++ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl, ++ bool optout, bool secure, dns_rdataset_t *ardataset, ++ isc_result_t *eresultp) { + isc_result_t result; + dns_rdataset_t rdataset; ++ dns_db_t *cache = fctx->cache; ++ dns_ttl_t minttl = fctx->res->view->minncachettl; + + if (ardataset == NULL) { + dns_rdataset_init(&rdataset); +@@ -6854,37 +6862,13 @@ ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, + } + if (result == DNS_R_UNCHANGED || result == ISC_R_SUCCESS) { + /* +- * If the cache now contains a negative entry and we +- * care about whether it is DNS_R_NCACHENXDOMAIN or +- * DNS_R_NCACHENXRRSET then extract it. ++ * The cache settled successfully (DNS_R_UNCHANGED means our ++ * negative entry was discarded in favour of existing ++ * higher-trust data). Either way 'ardataset' is now bound to ++ * the rdataset the cache holds for this name and type; derive ++ * the result code from it. + */ +- if (NEGATIVE(ardataset)) { +- /* +- * The cache data is a negative cache entry. +- */ +- if (NXDOMAIN(ardataset)) { +- *eresultp = DNS_R_NCACHENXDOMAIN; +- } else { +- *eresultp = DNS_R_NCACHENXRRSET; +- } +- } else { +- /* +- * The attempt to add a negative cache entry +- * was rejected. Set *eresultp to reflect +- * the type of the dataset being returned. +- */ +- switch (ardataset->type) { +- case dns_rdatatype_cname: +- *eresultp = DNS_R_CNAME; +- break; +- case dns_rdatatype_dname: +- *eresultp = DNS_R_DNAME; +- break; +- default: +- *eresultp = ISC_R_SUCCESS; +- break; +- } +- } ++ *eresultp = fctx_setresult(fctx, ardataset); + result = ISC_R_SUCCESS; + } + if (ardataset == &rdataset && dns_rdataset_isassociated(ardataset)) { +@@ -7029,8 +7013,7 @@ ncache_message(fetchctx_t *fctx, dns_message_t *message, + ttl = 0; + } + +- result = ncache_adderesult(message, fctx->cache, node, covers, now, +- fctx->res->view->minncachettl, ttl, false, ++ result = ncache_adderesult(fctx, message, node, covers, now, ttl, false, + false, ardataset, &eresult); + if (result != ISC_R_SUCCESS) { + goto unlock; +-- +2.55.0 + diff --git a/bind.spec b/bind.spec index 4dd562a..bd46400 100644 --- a/bind.spec +++ b/bind.spec @@ -151,6 +151,10 @@ Patch37: bind-9.18-CVE-2026-11331-test.patch # https://gitlab.isc.org/isc-projects/bind9/commit/c5d2fc706ca3635d9928c1cc68db73bffb35d772 Patch38: bind-9.18-CVE-2026-10822.patch Patch40: bind-9.18-CVE-2026-10822-test.patch +# https://gitlab.isc.org/isc-projects/bind9/commit/5a0cef5bdb077df73fa59504efb8ca4c8b0a84c5 +Patch41: bind-9.18-CVE-2026-12617.patch +# https://gitlab.isc.org/isc-projects/bind9/commit/26437be900c68bbe0974ec9ab804c2894c19c419 +Patch42: bind-9.18-CVE-2026-12617-test.patch %{?systemd_ordering} # https://fedoraproject.org/wiki/Changes/RPMSuportForSystemdSysusers @@ -954,6 +958,7 @@ fi; * Tue Aug 25 2026 Petr Menšík - 32:9.18.50-2 - Potential wildcard CNAME RPZ policy bypass (CVE-2026-11331) - Key Record using PRIVATEDNS algorithm may lead to exit (CVE-2026-10822) +- Record ordering based unexpected exit with CNAME or DNAME (CVE-2026-12617) * Wed Jun 17 2026 Petr Menšík - 32:9.18.50-1 - Update to 9.18.50 (rhbz#2489833)