Fix CVE-2026-11721: reject RRSIG records with invalid label counts

Backport fix for CVE-2026-11721 to bind 9.11 on c8s. The patch
combines two upstream commits (06778424f0 and e5b16cfd1c) that
prevent dnssec-signzone from signing out-of-zone records and
reject RRSIG records whose labels field indicates fewer labels
than the signer name requires, closing a cache poisoning vector
via forged wildcard records.

CVE: CVE-2026-11721
Upstream patches:
 - 06778424f0.patch
 - e5b16cfd1c.patch
Resolves: RHEL-213407

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:41:26 +00:00
parent 4f8f15daec
commit 3b30e917e8
2 changed files with 271 additions and 1 deletions

View File

@ -0,0 +1,263 @@
From 78ac82303bc3dd9d426d21fb268a5ed362d5535a Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Tue, 14 Apr 2026 15:14:06 +1000
Subject: [PATCH 1/2] Don't sign out of zone records in dnssec-signzone
dnssec-signzone was signing extraneous records that were not within
the namespace of the zone. This no longer occurs.
(cherry picked from commit e45c9af7051421fd370f20ba8325199c606223fd)
---
bin/dnssec/dnssec-signzone.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c
index 6567421..b7c8e55 100644
--- a/bin/dnssec/dnssec-signzone.c
+++ b/bin/dnssec/dnssec-signzone.c
@@ -1482,6 +1482,11 @@ assignwork(isc_task_t *task, isc_task_t *worker) {
dns_db_detachnode(gdb, &node);
goto next;
}
+ if (!dns_name_issubdomain(name, gorigin)) {
+ dumpnode(name, node);
+ dns_db_detachnode(gdb, &node);
+ goto next;
+ }
/*
* Sort the zone data from the glue and out-of-zone data.
* For NSEC zones nodes with zone data have NSEC records.
From 8c269a0269fdf12d5edd4698d08d663c6f64ddae Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Tue, 14 Apr 2026 12:24:33 +1000
Subject: [PATCH 2/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 | 45 +++++++++++++++++++++++---------
lib/dns/rdata/generic/rrsig_46.c | 37 +++++++++++++++++++-------
2 files changed, 60 insertions(+), 22 deletions(-)
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index b6b2405..2516a9a 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -142,11 +142,11 @@ dns_dnssec_keyfromrdata(dns_name_t *name, dns_rdata_t *rdata, isc_mem_t *mctx,
isc_buffer_t b;
isc_region_t r;
- INSIST(name != NULL);
- INSIST(rdata != NULL);
- INSIST(mctx != NULL);
- INSIST(key != NULL);
- INSIST(*key == NULL);
+ REQUIRE(name != NULL);
+ REQUIRE(rdata != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(key != NULL);
+ REQUIRE(*key == NULL);
REQUIRE(rdata->type == dns_rdatatype_key ||
rdata->type == dns_rdatatype_dnskey);
@@ -200,12 +200,14 @@ dns_dnssec_sign(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);
+ labels = dns_name_countlabels(name);
+ REQUIRE(labels <= 255 && labels > 0);
REQUIRE(set != NULL);
REQUIRE(key != NULL);
REQUIRE(inception != NULL);
@@ -243,7 +245,7 @@ dns_dnssec_sign(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--;
sig.originalttl = set->ttl;
@@ -390,11 +392,14 @@ dns_dnssec_verify3(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;
uint32_t flags;
+ unsigned int labels;
+ unsigned int siglabels;
bool downcase = false;
REQUIRE(name != NULL);
+ labels = dns_name_countlabels(name);
+ REQUIRE(labels > 0);
REQUIRE(set != NULL);
REQUIRE(key != NULL);
REQUIRE(mctx != NULL);
@@ -407,6 +412,21 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
if (set->type != sig.covered)
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);
@@ -484,11 +504,10 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
* 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)
- dns_name_split(dns_fixedname_name(&fnewname), sig.labels + 1,
+ if (labels > siglabels)
+ dns_name_split(dns_fixedname_name(&fnewname), siglabels,
NULL, dns_fixedname_name(&fnewname));
dns_name_toregion(dns_fixedname_name(&fnewname), &r);
@@ -497,7 +516,7 @@ dns_dnssec_verify3(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
* 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);
@@ -583,7 +602,7 @@ cleanup_struct:
if (ret != ISC_R_SUCCESS)
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,
dns_fixedname_name(&fnewname),
diff --git a/lib/dns/rdata/generic/rrsig_46.c b/lib/dns/rdata/generic/rrsig_46.c
index 0d4df67..35a1b4c 100644
--- a/lib/dns/rdata/generic/rrsig_46.c
+++ b/lib/dns/rdata/generic/rrsig_46.c
@@ -21,12 +21,12 @@
static inline isc_result_t
fromtext_rrsig(ARGS_FROMTEXT) {
isc_token_t token;
- unsigned char c;
+ unsigned char alg, 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;
@@ -57,8 +57,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
*/
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
false));
- RETTOK(dns_secalg_fromtext(&c, &token.value.as_textregion));
- RETERR(mem_tobuffer(target, &c, 1));
+ RETTOK(dns_secalg_fromtext(&alg, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &alg, 1));
/*
* Labels.
@@ -67,8 +67,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
false));
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.
@@ -131,11 +131,19 @@ 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.
@@ -260,6 +268,7 @@ static inline isc_result_t
fromwire_rrsig(ARGS_FROMWIRE) {
isc_region_t sr;
dns_name_t name;
+ unsigned char labels;
REQUIRE(type == dns_rdatatype_rrsig);
@@ -281,6 +290,8 @@ fromwire_rrsig(ARGS_FROMWIRE) {
if (sr.length < 18)
return (ISC_R_UNEXPECTEDEND);
+ labels = sr.base[3];
+
isc_buffer_forward(source, 18);
RETERR(mem_tobuffer(target, sr.base, 18));
@@ -290,6 +301,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.
*/

View File

@ -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}.9
Release: 16%{?PATCHVER:.%{PATCHVER}}%{?PREVER:.%{PREVER}}%{?dist}.10
Epoch: 32
Url: https://www.isc.org/downloads/bind/
#
@ -214,6 +214,9 @@ Patch216: bind-9.11-CVE-2026-3039.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
# https://github.com/isc-projects/bind9/commit/06778424f0f58375f0f3d6632813558c59d705c6
# https://github.com/isc-projects/bind9/commit/e5b16cfd1c0b7d7760108e1ca9e1fd9db30d9e6a
Patch219: bind-9.11-CVE-2026-11721.patch
# SDB patches
Patch11: bind-9.3.2b2-sdbsrc.patch
@ -647,6 +650,7 @@ are used for building ISC DHCP.
%patch -P 216 -p1 -b .CVE-2026-3039
%patch -P 217 -p1 -b .CVE-2026-5946
%patch -P 218 -p1 -b .CVE-2026-11622
%patch -P 219 -p1 -b .CVE-2026-11721
mkdir lib/dns/tests/testdata/dstrandom
cp -a %{SOURCE50} lib/dns/tests/testdata/dstrandom/random.data
@ -1699,6 +1703,9 @@ rm -rf ${RPM_BUILD_ROOT}
%endif
%changelog
* Mon Jul 27 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 32:9.11.36-16.10
- Reject RRSIG records with invalid label counts (CVE-2026-11721)
* 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)