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:
parent
ef4cc02326
commit
5747971c87
260
bind-9.18-CVE-2026-11721.patch
Normal file
260
bind-9.18-CVE-2026-11721.patch
Normal 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);
|
||||
@ -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)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user