checked to be the same as the signer name. Also RRSIGs are
not considered valid when an NSEC3 is not b32.signerzone.
Thanks to Qifan Zhang, Palo Alto Networks, for the report.
+ - Fix that the aggressive negative cache does not insert NSEC
+ records with overreaching next owner name. Also the result
+ is not above the trust anchor's bailiwick. Also RRSIGS are
+ not considered valid when an NSEC next owner name is not
+ under the signer zone name. Thanks to Qifan Zhang, Palo
+ Alto Networks, for the report.
22 July 2026: Wouter
- Release tag for 1.25.2, with the security commits:
msg = val_neg_getmsg(qstate->env->neg_cache, &iq->qchase,
qstate->region, qstate->env->rrset_cache,
qstate->env->scratch_buffer,
- *qstate->env->now, 1/*add SOA*/, NULL,
+ *qstate->env->now, 1/*add SOA*/, dpname,
qstate->env->cfg);
}
/* item taken from cache does not match our query name, thus
--- /dev/null
+; config options
+; The island of trust is at test.
+server:
+ trust-anchor: "test. DS 1444 8 2 8a87d067fd09a5965244fe2e317dd26d182c468e0a7f26ecc4c7b479bf89db9b"
+ val-override-date: "20201020135527"
+ target-fetch-policy: "0 0 0 0 0"
+ qname-minimisation: "no"
+ fake-sha1: yes
+ trust-anchor-signaling: no
+ minimal-responses: no
+ iter-scrub-promiscuous: no
+ aggressive-nsec: yes
+ local-zone: test. nodefault
+ log-servfail: yes
+
+stub-zone:
+ name: "."
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test overreaching NSEC with aggressive cache
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+ ADDRESS 193.0.14.129
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET. IN A 193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+test. IN NS
+SECTION AUTHORITY
+test. IN NS ns.test.
+SECTION ADDITIONAL
+ns.test. IN A 1.2.3.5
+ENTRY_END
+RANGE_END
+
+; ns.test
+RANGE_BEGIN 0 100
+ ADDRESS 1.2.3.5
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+test. IN NS
+SECTION ANSWER
+test. IN NS ns.test
+test. 3600 IN RRSIG NS 8 1 3600 20201116135527 20201019135527 1444 test. RGCxIO32TbbLTk6xZmTr+fjYPH50hntBxeOQ2DIj2pDsmjALcHYtVkOfpfk2EhOhHZd+9PLuoJPbJh6a9NqLSFeBvr0XZoCZoQ2g0tCHUNHcH5EVjA2TuYBQem6DVYnPLJ3914aRx0uA1j42b8dC2xsam/XkOo7U+dLbUW2Os1s=
+SECTION ADDITIONAL
+ns.test. IN A 1.2.3.5
+ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw=
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+ns.test. IN A
+SECTION ANSWER
+ns.test. IN A 1.2.3.5
+ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw=
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+ns.test. IN AAAA
+SECTION AUTHORITY
+test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600
+test. 3600 IN RRSIG SOA 8 1 3600 20201116135527 20201019135527 1444 test. IZJIDmEgf0W7A5G7hvvZ2hUqJ9Trbv1/i7ySapDmPbYV9lVCmHHobySxO01yDhI2/Pvpsvxqrm1Tiv3BxH8uzZ4keKgiQjBsSy4htAsFct9I4E7ly2glPj/Fm3oun3PsjJDv5QYhx0KS7w4IQKU7Nc9pfJc92uoUI5bdoC1pRGw=
+ns.test. 3600 IN NSEC nz.test. A RRSIG
+ns.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 1444 test. PElArVB3KPg8KHAP7lzcNbhFuXNxTsHNTn1dZVncB5qmWRdIaeKpaXDjpH0JSXMaelGFS+/QhuQ6Hmw9+4VyZFRqMzGhw4agUR/2bxABHcDIG4ZpUwyeSP61ATTfHUkQVxaH2wjCWI/tfmesdP2xVE4GXyUvCIBxU914MkZbULU=
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+test. IN DNSKEY
+SECTION ANSWER
+test. 3600 IN DNSKEY 257 3 8 AwEAAbd9WqjzE2Pynz21OG5doSf9hFzMr5dhzz2waZ3vTa+0o5r7AjTAqmA1yH/B3+aAMihUm5ucZSfVqo7+kOaRE8yFj9aivOmA1n1+JLevJq/oyvQyjxQN2Qb89LyaNUT5oKZIiL+uyyhNW3KDR3SSbQ/GBwQNDHVcZi+JDR3RC0r7 ;{id = 1444 (ksk), size = 1024b}
+test. 3600 IN RRSIG DNSKEY 8 1 3600 20201116135527 20201019135527 1444 test. UmRMS4iG9NBBHZYOtpwFFcJgbEb5SfHSgHd9XRe/8pTWM31WSDayn5ViPOBMqI1T5TXg2amc13dDI574xIM2oKMus3b5cBW72jJLW13jprBtslO6P8BMWb4HNnvLrJtQjwf3ErRirtTxinLmywQtmyr1cdthyG3Gp4N7i90fHSc=
+SECTION ADDITIONAL
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qname qtype
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+example.test. IN DS
+SECTION ANSWER
+example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39
+example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I=
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+example.test. IN NS
+SECTION AUTHORITY
+example.test. IN NS ns.example.test.
+example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39
+example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I=
+SECTION ADDITIONAL
+ns.example.test. IN A 1.2.3.4
+ENTRY_END
+RANGE_END
+
+; ns.example.test.
+RANGE_BEGIN 0 100
+ ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.test. IN NS
+SECTION ANSWER
+example.test. IN NS ns.example.test.
+example.test. 3600 IN RRSIG NS 8 2 3600 20201116135527 20201019135527 55567 example.test. l1JT0wMlK0YI7/CWHzexf/k0iafUhCgN+BdgjBXIRXmSQNf4HDTiAkbcWL2/15qtnp12nQy9JeiTdSQ3vtPoHAJX4C5uTWaze4ms+Wrrf+n92sLCjacP9x50uuicH3URT6cKb1QCAPwlvlWxIlZjAMYFScSns7+C441NMJT8aE4=
+SECTION ADDITIONAL
+ns.example.test. IN A 1.2.3.4
+ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo=
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+ns.example.test. IN A
+SECTION ANSWER
+ns.example.test. IN A 1.2.3.4
+ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo=
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+ns.example.test. IN AAAA
+SECTION AUTHORITY
+example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600
+example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8=
+ns.example.test. 3600 IN NSEC nz.example.test. A RRSIG
+example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600
+ENTRY_END
+
+; response to DNSKEY priming query
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.test. IN DNSKEY
+SECTION ANSWER
+example.test. 3600 IN DNSKEY 257 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55567 (ksk), size = 1024b}
+example.test. 3600 IN RRSIG DNSKEY 8 2 3600 20201116135527 20201019135527 55567 example.test. IbWMC6quOuZFNPAVxQLqCJ9nLhindBo826rnLcg5yMgs9dGUSPOCXAfHTmbgJAUNs9HTFfrJWNvasnETs0UOpmEuifGwWdH1OlME7Gny4RL2QmITUFeMW81Jz1tiVQxFXl6yxT0jxOxvz+bqMHlrz+8IeWQXcO+GZTPu8ueq30g=
+ENTRY_END
+
+; response to query of interest
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+example.test. IN NSEC
+SECTION ANSWER
+; normal record has next owner in example.test.
+; example.test. IN NSEC b.example.test. SOA DNSKEY NS RRSIG NSEC
+example.test. IN NSEC b.foo.test. NS SOA RRSIG NSEC DNSKEY
+example.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 55567 example.test. xxepzzIxsJURk4/eZrwcDm5jhQNHtf1OmnPuu3T/w8y5NWwzlgn/hL17xoI71dIgTJg2GAq97wxEUhp951jtGMCeLH2Dz5lDZXxQI4wf2Wl43u2mTBQFRagDwfAauFc6Z4FYI/biDZyYcylZ3A5Q6j6ifFnsgMTL+cP0UIEZBTQ=
+ENTRY_END
+RANGE_END
+
+; CD=1 query for type NSEC
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD CD DO
+SECTION QUESTION
+example.test. IN NSEC
+ENTRY_END
+
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD CD RA DO NOERROR
+SECTION QUESTION
+example.test. IN NSEC
+SECTION ANSWER
+; The overreaching NSEC is removed by the scrubber.
+ENTRY_END
+
+SCENARIO_END
val-override-date: "20070916134226"
fake-sha1: yes
trust-anchor-signaling: no
+ domain-insecure: "anotherexample.local"
stub-zone:
name: "."
SECTION QUESTION
anotherexample.local. IN TXT
SECTION ANSWER
-anotherexample.local. 86400 IN TXT "should not resolve this"
+anotherexample.local. 86400 IN TXT "stub works"
ENTRY_END
RANGE_END
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
-anotherexample.local. IN TXT
+anotherexample2.local. IN TXT
ENTRY_END
; query should be answered using NXDOMAIN for local in cache
MATCH all
REPLY QR RD RA NXDOMAIN
SECTION QUESTION
-anotherexample.local. IN TXT
+anotherexample2.local. IN TXT
SECTION AUTHORITY
. 86400 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2010111601 1800 900 604800 86400
ENTRY_END
+STEP 40 QUERY
+ENTRY_BEGIN
+REPLY RD
+SECTION QUESTION
+anotherexample.local. IN TXT
+ENTRY_END
+
+; The stub stops going higher in the negative cache.
+STEP 50 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA NOERROR
+SECTION QUESTION
+anotherexample.local. IN TXT
+SECTION ANSWER
+anotherexample.local. 86400 IN TXT "stub works"
+ENTRY_END
+
SCENARIO_END
continue;
if(!dname_subdomain_c(rep->rrsets[i]->rk.dname,
zone->name)) continue;
+ if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC &&
+ !nsec_nextowner_subdomain(rep->rrsets[i], zone->name)) {
+ continue; /* nextowner not in zone */
+ }
/* insert NSEC into this zone's tree */
neg_insert_data(neg, zone, rep->rrsets[i]);
}
continue;
if(!dname_subdomain_c(rep->rrsets[i]->rk.dname,
zone->name)) continue;
+ if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC &&
+ !nsec_nextowner_subdomain(rep->rrsets[i], zone->name)) {
+ continue; /* nextowner not in zone */
+ }
/* insert NSEC into this zone's tree */
neg_insert_data(neg, zone, rep->rrsets[i]);
}
* @param rrset_cache: rrset cache
* @param now: to check ttl against
* @param region: where to alloc result
+ * @param topname: do not look higher than this name, so that the
+ * result cannot be taken from a zone above the current trust anchor.
* @return rrset or NULL
*/
static struct ub_packed_rrset_key*
neg_find_nsec(struct val_neg_cache* neg_cache, uint8_t* qname, size_t qname_len,
uint16_t qclass, struct rrset_cache* rrset_cache, time_t now,
- struct regional* region)
+ struct regional* region, uint8_t* topname)
{
int labs;
uint32_t flags;
lock_basic_unlock(&neg_cache->lock);
return NULL;
}
+ if(topname && !dname_subdomain_c(zone->name, topname)) {
+ /* Reject NSEC not within trust anchor's bailiwick */
+ lock_basic_unlock(&neg_cache->lock);
+ return NULL;
+ }
/* NSEC only for now */
if(zone->nsec3_hash) {
/* Get best available NSEC for qname */
nsec = neg_find_nsec(neg, qinfo->qname, qinfo->qname_len, qinfo->qclass,
- rrset_cache, now, region);
+ rrset_cache, now, region, topname);
/* Matching NSEC, use to generate No Data answer. Not creating answers
* yet for No Data proven using wildcard. */
* proof */
if(!(wcrr = neg_find_nsec(neg, wc_qinfo.qname,
wc_qinfo.qname_len, qinfo->qclass,
- rrset_cache, now, region)))
+ rrset_cache, now, region, topname)))
return NULL;
nodata_wc = NULL;
return sec_status_bogus; /* NSEC3 owner not b32.signer */
}
}
+ /* NSEC, a next owner that is not under the signer is not allowed.*/
+ if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_NSEC &&
+ !nsec_nextowner_subdomain(rrset, signer)) {
+ verbose(VERB_QUERY, "verify: NSEC next owner overreaches signer name");
+ *reason = "NSEC next owner overreaches signer name";
+ if(reason_bogus)
+ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS;
+ return sec_status_bogus; /* nextowner overreaching */
+ }
+
sigblock = (unsigned char*)signer+signer_len;
if(siglen < 2+18+signer_len+1) {
verbose(VERB_QUERY, "verify: too short, no signature data");
memmove(out+prefix_len, dname_target, dname_target_len);
return 1;
}
+
+int nsec_nextowner_subdomain(struct ub_packed_rrset_key* rrset, uint8_t* name)
+{
+ struct packed_rrset_data* d;
+ uint8_t* next;
+ size_t nextlen;
+ if(ntohs(rrset->rk.type) != LDNS_RR_TYPE_NSEC)
+ return 0;
+ d = (struct packed_rrset_data*)rrset->entry.data;
+ if(!d || d->count == 0)
+ return 0;
+ next = d->rr_data[0]+2;
+ nextlen = dname_valid(next, d->rr_len[0]-2);
+ if(nextlen == 0)
+ return 0; /* malformed */
+ return dname_subdomain_c(next, name);
+}
void rrsig_get_signer(uint8_t* data, size_t len, uint8_t** sname,
size_t* slen);
+/** See if the NSEC nextowner name is a subdomain of the name. */
+int nsec_nextowner_subdomain(struct ub_packed_rrset_key* rrset, uint8_t* name);
+
#endif /* VALIDATOR_VAL_UTILS_H */