Fix CVE-2026-11721: RRSIG label count validation for wildcard cache poisoning

Add patch for CVE-2026-11721: an RRSIG whose Labels field
indicates fewer labels than its signer name requires was being
accepted, allowing cache poisoning via forged wildcard records
through RFC 8198 synthesis. The patch is a two-commit mbox: the
original upstream fix plus an adaptation replacing the
DNS_NAME_VALID() macro (BIND 9.20+) with the equivalent
ISC_MAGIC_VALID() expression available in BIND 9.18.

CVE: CVE-2026-11721
Upstream patches:
 - 8a46533cbe.patch
Resolves: RHEL-213411

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-23 12:33:45 +00:00 committed by Petr Menšík
parent ef4cc02326
commit 5747971c87
2 changed files with 267 additions and 1 deletions

View File

@ -0,0 +1,260 @@
From fad5a6422c34fee9f159b81b76a3926bcb8d1487 Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Tue, 14 Apr 2026 12:24:33 +1000
Subject: [PATCH 1/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 | 39 ++++++++++++++++++++++++--------
lib/dns/rdata/generic/rrsig_46.c | 33 +++++++++++++++++++++------
2 files changed, 55 insertions(+), 17 deletions(-)
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index 662136db87..ea8feedb18 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -137,7 +137,7 @@ dns_dnssec_keyfromrdata(const dns_name_t *name, const dns_rdata_t *rdata,
isc_buffer_t b;
isc_region_t r;
- INSIST(name != NULL);
+ INSIST(DNS_NAME_VALID(name));
INSIST(rdata != NULL);
INSIST(mctx != NULL);
INSIST(key != NULL);
@@ -195,12 +195,14 @@ dns_dnssec_sign(const 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);
+ REQUIRE(DNS_NAME_VALID(name));
+ labels = dns_name_countlabels(name);
+ REQUIRE(labels <= 255 && labels > 0);
REQUIRE(set != NULL);
REQUIRE(key != NULL);
REQUIRE(inception != NULL);
@@ -240,7 +242,7 @@ dns_dnssec_sign(const 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--;
}
@@ -384,11 +386,14 @@ dns_dnssec_verify(const 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;
+ unsigned int labels;
+ unsigned int siglabels;
uint32_t flags;
bool downcase = false;
- REQUIRE(name != NULL);
+ REQUIRE(DNS_NAME_VALID(name));
+ labels = dns_name_countlabels(name);
+ REQUIRE(labels > 0);
REQUIRE(set != NULL);
REQUIRE(key != NULL);
REQUIRE(mctx != NULL);
@@ -403,6 +408,21 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
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;
@@ -482,10 +502,9 @@ again:
* 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) {
+ if (labels > siglabels) {
dns_name_split(dns_fixedname_name(&fnewname), sig.labels + 1,
NULL, dns_fixedname_name(&fnewname));
}
@@ -496,7 +515,7 @@ again:
* Create an envelope for each rdata: <name|type|class|ttl>.
*/
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);
@@ -592,7 +611,7 @@ cleanup_struct:
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,
diff --git a/lib/dns/rdata/generic/rrsig_46.c b/lib/dns/rdata/generic/rrsig_46.c
index 2cc315bdea..3230eca78b 100644
--- a/lib/dns/rdata/generic/rrsig_46.c
+++ b/lib/dns/rdata/generic/rrsig_46.c
@@ -23,12 +23,12 @@
static isc_result_t
fromtext_rrsig(ARGS_FROMTEXT) {
isc_token_t token;
- unsigned char c;
+ unsigned char c, 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;
@@ -72,8 +72,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
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.
@@ -144,12 +144,20 @@ 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.
@@ -278,6 +286,7 @@ static isc_result_t
fromwire_rrsig(ARGS_FROMWIRE) {
isc_region_t sr;
dns_name_t name;
+ unsigned char labels;
REQUIRE(type == dns_rdatatype_rrsig);
@@ -300,6 +309,8 @@ fromwire_rrsig(ARGS_FROMWIRE) {
return ISC_R_UNEXPECTEDEND;
}
+ labels = sr.base[3];
+
isc_buffer_forward(source, 18);
RETERR(mem_tobuffer(target, sr.base, 18));
@@ -309,6 +320,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.
*/
From 25fcb6ca775a2f3f047842fa89b185c582e19d30 Mon Sep 17 00:00:00 2001
From: RHEL Packaging Agent <redhat-ymir-agent@redhat.com>
Date: Thu, 23 Jul 2026 12:17:07 +0000
Subject: [PATCH 2/2] Replace DNS_NAME_VALID with ISC_MAGIC_VALID equivalent
for 9.18 compatibility
DNS_NAME_VALID macro was introduced in BIND 9.20 and is not available in 9.18.
Replace with the equivalent ISC_MAGIC_VALID(name, DNS_NAME_MAGIC) expression
which is available in 9.18.
---
lib/dns/dnssec.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index ea8feedb18..0b3b7fb2dc 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -137,7 +137,7 @@ dns_dnssec_keyfromrdata(const dns_name_t *name, const dns_rdata_t *rdata,
isc_buffer_t b;
isc_region_t r;
- INSIST(DNS_NAME_VALID(name));
+ INSIST(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
INSIST(rdata != NULL);
INSIST(mctx != NULL);
INSIST(key != NULL);
@@ -200,7 +200,7 @@ dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
dns_fixedname_t fnewname;
dns_fixedname_t fsigner;
- REQUIRE(DNS_NAME_VALID(name));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
labels = dns_name_countlabels(name);
REQUIRE(labels <= 255 && labels > 0);
REQUIRE(set != NULL);
@@ -391,7 +391,7 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
uint32_t flags;
bool downcase = false;
- REQUIRE(DNS_NAME_VALID(name));
+ REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
labels = dns_name_countlabels(name);
REQUIRE(labels > 0);
REQUIRE(set != NULL);

View File

@ -80,7 +80,7 @@ License: MPL-2.0 AND ISC AND MIT AND BSD-3-Clause AND BSD-2-Clause
# Before rebasing bind, ensure bind-dyndb-ldap is ready to be rebuild and use side-tag with it.
# Updating just bind will cause freeipa-dns-server package to be uninstallable.
Version: 9.18.33
Release: 21%{?dist}
Release: 22%{?dist}
Epoch: 32
Url: https://www.isc.org/downloads/bind/
#
@ -174,6 +174,8 @@ Patch235: bind-9.18-CVE-2026-5946.patch
Patch236: bind-9.18-CVE-2026-13204.patch
# https://github.com/isc-projects/bind9/commit/dc328a199f96222e0c30cc20b7b795bfc2c9b2e4
Patch237: bind-9.18-CVE-2026-11331.patch
# https://github.com/isc-projects/bind9/commit/8a46533cbeab78c215ee035d73967a3a59925375
Patch238: bind-9.18-CVE-2026-11721.patch
%{?systemd_ordering}
# https://fedoraproject.org/wiki/Changes/RPMSuportForSystemdSysusers
@ -975,6 +977,10 @@ fi;
%endif
%changelog
* Thu Jul 23 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.18.33-22
- Fix RRSIG label count validation for wildcard cache poisoning
(CVE-2026-11721)
* Thu Jul 23 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.18.33-21
- Fix RPZ name-too-long wildcard expansion (CVE-2026-11331)