From 78ac82303bc3dd9d426d21fb268a5ed362d5535a Mon Sep 17 00:00:00 2001 From: Mark Andrews 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 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: . */ 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. */