From 0f28e7dd6083257a3d936cef4e492d1f5562fdba Mon Sep 17 00:00:00 2001 From: RHEL Packaging Agent Date: Mon, 27 Jul 2026 15:22:12 +0000 Subject: [PATCH] Fix CVE-2026-13321: reject out-of-zone NSEC entries in DNSSEC validation Add patch for CVE-2026-13321 which rejects out-of-zone NSEC next owner names during DNSSEC validation. The patch is based on ISC's 9.11-specific backport commits (3bc7280158 and 27ceccfe77) which add a dns_nsec_is_legal() function and integrate NSEC validation checks into the resolver, using 9.11-compatible APIs and coding conventions. CVE: CVE-2026-13321 Upstream patches: - https://github.com/isc-projects/bind9/commit/058023c66f11d78590d4aa8c4f98946c4c965e21.patch - https://github.com/isc-projects/bind9/commit/f751e19a30d107f04c2f644aff9f8dab8fed03ab.patch Resolves: RHEL-213318 This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent. Assisted-by: Ymir --- bind-9.11-CVE-2026-13321.patch | 270 +++++++++++++++++++++++++++++++++ bind.spec | 10 +- 2 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 bind-9.11-CVE-2026-13321.patch diff --git a/bind-9.11-CVE-2026-13321.patch b/bind-9.11-CVE-2026-13321.patch new file mode 100644 index 0000000..a4b12d7 --- /dev/null +++ b/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/bind.spec b/bind.spec index dcd82fb..3211303 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}.10 +Release: 16%{?PATCHVER:.%{PATCHVER}}%{?PREVER:.%{PREVER}}%{?dist}.11 Epoch: 32 Url: https://www.isc.org/downloads/bind/ # @@ -217,6 +217,9 @@ 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 # SDB patches Patch11: bind-9.3.2b2-sdbsrc.patch @@ -651,6 +654,7 @@ are used for building ISC DHCP. %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 mkdir lib/dns/tests/testdata/dstrandom cp -a %{SOURCE50} lib/dns/tests/testdata/dstrandom/random.data @@ -1703,6 +1707,10 @@ rm -rf ${RPM_BUILD_ROOT} %endif %changelog +* 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)