return msg;
}
+struct dns_msg*
+dns_msg_deepcopy_region(struct dns_msg* origin, struct regional* region)
+{
+ size_t i;
+ struct dns_msg* res = NULL;
+ res = gen_dns_msg(region, &origin->qinfo, origin->rep->rrset_count);
+ if(!res) return NULL;
+ *res->rep = *origin->rep;
+ if(origin->rep->reason_bogus_str) {
+ res->rep->reason_bogus_str = regional_strdup(region,
+ origin->rep->reason_bogus_str);
+ }
+ for(i=0; i<res->rep->rrset_count; i++) {
+ res->rep->rrsets[i] = packed_rrset_copy_region(
+ origin->rep->rrsets[i], region, 0);
+ if(!res->rep->rrsets[i]) {
+ return NULL;
+ }
+ }
+ return res;
+}
+
/** synthesize RRset-only response from cached RRset item */
static struct dns_msg*
rrset_msg(struct ub_packed_rrset_key* rrset, struct regional* region,
struct reply_info* r, struct regional* region, time_t now,
int allow_expired, struct regional* scratch);
+/**
+ * Deep copy a dns_msg to a region.
+ * @param origin: the dns_msg to copy.
+ * @param region: the region to copy all the data to.
+ * @return the new dns_msg or NULL on malloc error.
+ */
+struct dns_msg* dns_msg_deepcopy_region(struct dns_msg* origin,
+ struct regional* region);
+
/**
* Find cached message
* @param env: module environment with the DNS cache.
ret = nsec3_hash_name(ct, region, buf, nsec3, 0, qname,
qinfo.qname_len, &hash);
- if(ret != 1) {
+ if(ret < 1) {
printf("Bad nsec3_hash_name retcode %d\n", ret);
- unit_assert(ret == 1);
+ unit_assert(ret == 1 || ret == 2);
}
unit_assert(hash->dname && hash->hash && hash->hash_len &&
hash->b32 && hash->b32_len);
www.example.com. IN A
ENTRY_END
+; Allow validation resuming for NSEC3 hash calculations
+STEP 2 TIME_PASSES ELAPSE 0.05
+
; recursion happens here.
STEP 10 CHECK_ANSWER
ENTRY_BEGIN
else if(fptr == &pending_udp_timer_delay_cb) return 1;
else if(fptr == &worker_stat_timer_cb) return 1;
else if(fptr == &worker_probe_timer_cb) return 1;
- else if(fptr == &validate_msg_signatures_timer_cb) return 1;
+ else if(fptr == &validate_suspend_timer_cb) return 1;
#ifdef UB_ON_WINDOWS
else if(fptr == &wsvc_cron_cb) return 1;
#endif
/* we include nsec.h for the bitmap_has_type function */
#include "validator/val_nsec.h"
#include "sldns/sbuffer.h"
+#include "util/config_file.h"
+
+/**
+ * Max number of NSEC3 calculations at once, suspend query for later.
+ * 8 is low enough and allows for cases where multiple proofs are needed.
+ */
+#define MAX_NSEC3_CALCULATIONS 8
+/**
+ * When all allowed NSEC3 calculations at once resulted in error treat as
+ * bogus. NSEC3 hash errors are not cached and this helps breaks loops with
+ * erroneous data.
+ */
+#define MAX_NSEC3_ERRORS -1
/**
* This function we get from ldns-compat or from base system
return memcmp(s1, s2, s1len);
}
+int
+nsec3_cache_table_init(struct nsec3_cache_table* ct, struct regional* region)
+{
+ if(ct->ct) return 1;
+ ct->ct = (rbtree_type*)regional_alloc(region, sizeof(*ct->ct));
+ if(!ct->ct) return 0;
+ ct->region = region;
+ rbtree_init(ct->ct, &nsec3_hash_cmp);
+ return 1;
+}
+
size_t
nsec3_get_hashed(sldns_buffer* buf, uint8_t* nm, size_t nmlen, int algo,
size_t iter, uint8_t* salt, size_t saltlen, uint8_t* res, size_t max)
c = (struct nsec3_cached_hash*)rbtree_search(table, &looki);
if(c) {
*hash = c;
- return 1;
+ return 2;
}
/* create a new entry */
c = (struct nsec3_cached_hash*)regional_alloc(region, sizeof(*c));
c->dname_len = dname_len;
r = nsec3_calc_hash(region, buf, c);
if(r != 1)
- return r;
+ return r; /* returns -1 or 0 */
r = nsec3_calc_b32(region, buf, c);
if(r != 1)
- return r;
+ return r; /* returns 0 */
#ifdef UNBOUND_DEBUG
n =
#else
struct nsec3_cached_hash* hash, struct ub_packed_rrset_key* s)
{
uint8_t* nm = s->rk.dname;
+ if(!hash) return 0; /* please clang */
/* compare, does hash of name based on params in this NSEC3
* match the owner name of this NSEC3?
* name must be: <hashlength>base32 . zone name
* @param nmlen: length of name.
* @param rrset: nsec3 that matches is returned here.
* @param rr: rr number in nsec3 rrset that matches.
+ * @param calculations: current hash calculations.
* @return true if a matching NSEC3 is found, false if not.
*/
static int
find_matching_nsec3(struct module_env* env, struct nsec3_filter* flt,
- rbtree_type* ct, uint8_t* nm, size_t nmlen,
- struct ub_packed_rrset_key** rrset, int* rr)
+ struct nsec3_cache_table* ct, uint8_t* nm, size_t nmlen,
+ struct ub_packed_rrset_key** rrset, int* rr,
+ int* calculations)
{
size_t i_rs;
int i_rr;
struct ub_packed_rrset_key* s;
struct nsec3_cached_hash* hash = NULL;
int r;
+ int calc_errors = 0;
/* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */
for(s=filter_first(flt, &i_rs, &i_rr); s;
s=filter_next(flt, &i_rs, &i_rr)) {
+ /* check if we are allowed more calculations */
+ if(*calculations >= MAX_NSEC3_CALCULATIONS) {
+ if(calc_errors == *calculations) {
+ *calculations = MAX_NSEC3_ERRORS;
+ }
+ break;
+ }
/* get name hashed for this NSEC3 RR */
- r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer,
+ r = nsec3_hash_name(ct->ct, ct->region, env->scratch_buffer,
s, i_rr, nm, nmlen, &hash);
if(r == 0) {
log_err("nsec3: malloc failure");
break; /* alloc failure */
- } else if(r != 1)
- continue; /* malformed NSEC3 */
- else if(nsec3_hash_matches_owner(flt, hash, s)) {
- *rrset = s; /* rrset with this name */
- *rr = i_rr; /* matches hash with these parameters */
- return 1;
+ } else if(r < 0) {
+ /* malformed NSEC3 */
+ calc_errors++;
+ (*calculations)++;
+ continue;
+ } else {
+ if(r == 1) (*calculations)++;
+ if(nsec3_hash_matches_owner(flt, hash, s)) {
+ *rrset = s; /* rrset with this name */
+ *rr = i_rr; /* matches hash with these parameters */
+ return 1;
+ }
}
}
*rrset = NULL;
if(!nsec3_get_nextowner(rrset, rr, &next, &nextlen))
return 0; /* malformed RR proves nothing */
+ if(!hash) return 0; /* please clang */
/* check the owner name is a hashed value . apex
* base32 encoded values must have equal length.
* hash_value and next hash value must have equal length. */
* @param nmlen: length of name.
* @param rrset: covering NSEC3 rrset is returned here.
* @param rr: rr of cover is returned here.
+ * @param calculations: current hash calculations.
* @return true if a covering NSEC3 is found, false if not.
*/
static int
find_covering_nsec3(struct module_env* env, struct nsec3_filter* flt,
- rbtree_type* ct, uint8_t* nm, size_t nmlen,
- struct ub_packed_rrset_key** rrset, int* rr)
+ struct nsec3_cache_table* ct, uint8_t* nm, size_t nmlen,
+ struct ub_packed_rrset_key** rrset, int* rr,
+ int* calculations)
{
size_t i_rs;
int i_rr;
struct ub_packed_rrset_key* s;
struct nsec3_cached_hash* hash = NULL;
int r;
+ int calc_errors = 0;
/* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */
for(s=filter_first(flt, &i_rs, &i_rr); s;
s=filter_next(flt, &i_rs, &i_rr)) {
+ /* check if we are allowed more calculations */
+ if(*calculations >= MAX_NSEC3_CALCULATIONS) {
+ if(calc_errors == *calculations) {
+ *calculations = MAX_NSEC3_ERRORS;
+ }
+ break;
+ }
/* get name hashed for this NSEC3 RR */
- r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer,
+ r = nsec3_hash_name(ct->ct, ct->region, env->scratch_buffer,
s, i_rr, nm, nmlen, &hash);
if(r == 0) {
log_err("nsec3: malloc failure");
break; /* alloc failure */
- } else if(r != 1)
- continue; /* malformed NSEC3 */
- else if(nsec3_covers(flt->zone, hash, s, i_rr,
- env->scratch_buffer)) {
- *rrset = s; /* rrset with this name */
- *rr = i_rr; /* covers hash with these parameters */
- return 1;
+ } else if(r < 0) {
+ /* malformed NSEC3 */
+ calc_errors++;
+ (*calculations)++;
+ continue;
+ } else {
+ if(r == 1) (*calculations)++;
+ if(nsec3_covers(flt->zone, hash, s, i_rr,
+ env->scratch_buffer)) {
+ *rrset = s; /* rrset with this name */
+ *rr = i_rr; /* covers hash with these parameters */
+ return 1;
+ }
}
}
*rrset = NULL;
* @param ct: cached hashes table.
* @param qinfo: query that is verified for.
* @param ce: closest encloser information is returned in here.
+ * @param calculations: current hash calculations.
* @return true if a closest encloser candidate is found, false if not.
*/
static int
-nsec3_find_closest_encloser(struct module_env* env, struct nsec3_filter* flt,
- rbtree_type* ct, struct query_info* qinfo, struct ce_response* ce)
+nsec3_find_closest_encloser(struct module_env* env, struct nsec3_filter* flt,
+ struct nsec3_cache_table* ct, struct query_info* qinfo,
+ struct ce_response* ce, int* calculations)
{
uint8_t* nm = qinfo->qname;
size_t nmlen = qinfo->qname_len;
* may be the case. */
while(dname_subdomain_c(nm, flt->zone)) {
+ if(*calculations >= MAX_NSEC3_CALCULATIONS ||
+ *calculations == MAX_NSEC3_ERRORS) {
+ return 0;
+ }
if(find_matching_nsec3(env, flt, ct, nm, nmlen,
- &ce->ce_rrset, &ce->ce_rr)) {
+ &ce->ce_rrset, &ce->ce_rr, calculations)) {
ce->ce = nm;
ce->ce_len = nmlen;
return 1;
* If set true, and the return value is true, then you can be
* certain that the ce.nc_rrset and ce.nc_rr are set properly.
* @param ce: closest encloser information is returned in here.
+ * @param calculations: pointer to the current NSEC3 hash calculations.
* @return bogus if no closest encloser could be proven.
* secure if a closest encloser could be proven, ce is set.
* insecure if the closest-encloser candidate turns out to prove
* that an insecure delegation exists above the qname.
+ * unchecked if no more hash calculations are allowed at this point.
*/
static enum sec_status
-nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt,
- rbtree_type* ct, struct query_info* qinfo, int prove_does_not_exist,
- struct ce_response* ce)
+nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt,
+ struct nsec3_cache_table* ct, struct query_info* qinfo,
+ int prove_does_not_exist, struct ce_response* ce, int* calculations)
{
uint8_t* nc;
size_t nc_len;
/* robust: clean out ce, in case it gets abused later */
memset(ce, 0, sizeof(*ce));
- if(!nsec3_find_closest_encloser(env, flt, ct, qinfo, ce)) {
+ if(!nsec3_find_closest_encloser(env, flt, ct, qinfo, ce, calculations)) {
+ if(*calculations == MAX_NSEC3_ERRORS) {
+ verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could "
+ "not find a candidate for the closest "
+ "encloser; all attempted hash calculations "
+ "were erroneous; bogus");
+ return sec_status_bogus;
+ } else if(*calculations >= MAX_NSEC3_CALCULATIONS) {
+ verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could "
+ "not find a candidate for the closest "
+ "encloser; reached MAX_NSEC3_CALCULATIONS "
+ "(%d); unchecked still",
+ MAX_NSEC3_CALCULATIONS);
+ return sec_status_unchecked;
+ }
verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could "
"not find a candidate for the closest encloser.");
return sec_status_bogus;
/* Otherwise, we need to show that the next closer name is covered. */
next_closer(qinfo->qname, qinfo->qname_len, ce->ce, &nc, &nc_len);
if(!find_covering_nsec3(env, flt, ct, nc, nc_len,
- &ce->nc_rrset, &ce->nc_rr)) {
+ &ce->nc_rrset, &ce->nc_rr, calculations)) {
+ if(*calculations == MAX_NSEC3_ERRORS) {
+ verbose(VERB_ALGO, "nsec3: Could not find proof that the "
+ "candidate encloser was the closest encloser; "
+ "all attempted hash calculations were "
+ "erroneous; bogus");
+ return sec_status_bogus;
+ } else if(*calculations >= MAX_NSEC3_CALCULATIONS) {
+ verbose(VERB_ALGO, "nsec3: Could not find proof that the "
+ "candidate encloser was the closest encloser; "
+ "reached MAX_NSEC3_CALCULATIONS (%d); "
+ "unchecked still",
+ MAX_NSEC3_CALCULATIONS);
+ return sec_status_unchecked;
+ }
verbose(VERB_ALGO, "nsec3: Could not find proof that the "
- "candidate encloser was the closest encloser");
+ "candidate encloser was the closest encloser");
return sec_status_bogus;
}
return sec_status_secure;
/** Do the name error proof */
static enum sec_status
-nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt,
- rbtree_type* ct, struct query_info* qinfo)
+nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt,
+ struct nsec3_cache_table* ct, struct query_info* qinfo, int* calc)
{
struct ce_response ce;
uint8_t* wc;
/* First locate and prove the closest encloser to qname. We will
* use the variant that fails if the closest encloser turns out
* to be qname. */
- sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce);
+ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce, calc);
if(sec != sec_status_secure) {
if(sec == sec_status_bogus)
verbose(VERB_ALGO, "nsec3 nameerror proof: failed "
"to prove a closest encloser");
+ else if(sec == sec_status_unchecked)
+ verbose(VERB_ALGO, "nsec3 nameerror proof: will "
+ "continue proving closest encloser after "
+ "suspend");
else verbose(VERB_ALGO, "nsec3 nameerror proof: closest "
"nsec3 is an insecure delegation");
return sec;
/* At this point, we know that qname does not exist. Now we need
* to prove that the wildcard does not exist. */
log_assert(ce.ce);
- wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen);
- if(!wc || !find_covering_nsec3(env, flt, ct, wc, wclen,
- &wc_rrset, &wc_rr)) {
+ wc = nsec3_ce_wildcard(ct->region, ce.ce, ce.ce_len, &wclen);
+ if(!wc) {
+ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove "
+ "that the applicable wildcard did not exist.");
+ return sec_status_bogus;
+ }
+ if(!find_covering_nsec3(env, flt, ct, wc, wclen, &wc_rrset, &wc_rr, calc)) {
+ if(*calc == MAX_NSEC3_ERRORS) {
+ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove "
+ "that the applicable wildcard did not exist; "
+ "all attempted hash calculations were "
+ "erroneous; bogus");
+ return sec_status_bogus;
+ } else if(*calc >= MAX_NSEC3_CALCULATIONS) {
+ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove "
+ "that the applicable wildcard did not exist; "
+ "reached MAX_NSEC3_CALCULATIONS (%d); "
+ "unchecked still",
+ MAX_NSEC3_CALCULATIONS);
+ return sec_status_unchecked;
+ }
verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove "
"that the applicable wildcard did not exist.");
return sec_status_bogus;
enum sec_status
nsec3_prove_nameerror(struct module_env* env, struct val_env* ve,
struct ub_packed_rrset_key** list, size_t num,
- struct query_info* qinfo, struct key_entry_key* kkey)
+ struct query_info* qinfo, struct key_entry_key* kkey,
+ struct nsec3_cache_table* ct, int* calc)
{
- rbtree_type ct;
struct nsec3_filter flt;
if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
return sec_status_bogus; /* no valid NSEC3s, bogus */
- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
filter_init(&flt, list, num, qinfo); /* init RR iterator */
if(!flt.zone)
return sec_status_bogus; /* no RRs */
return sec_status_insecure; /* iteration count too high */
log_nametypeclass(VERB_ALGO, "start nsec3 nameerror proof, zone",
flt.zone, 0, 0);
- return nsec3_do_prove_nameerror(env, &flt, &ct, qinfo);
+ return nsec3_do_prove_nameerror(env, &flt, ct, qinfo, calc);
}
/*
/** Do the nodata proof */
static enum sec_status
-nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt,
- rbtree_type* ct, struct query_info* qinfo)
+nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt,
+ struct nsec3_cache_table* ct, struct query_info* qinfo,
+ int* calc)
{
struct ce_response ce;
uint8_t* wc;
enum sec_status sec;
if(find_matching_nsec3(env, flt, ct, qinfo->qname, qinfo->qname_len,
- &rrset, &rr)) {
+ &rrset, &rr, calc)) {
/* cases 1 and 2 */
if(nsec3_has_type(rrset, rr, qinfo->qtype)) {
verbose(VERB_ALGO, "proveNodata: Matching NSEC3 "
}
return sec_status_secure;
}
+ if(*calc == MAX_NSEC3_ERRORS) {
+ verbose(VERB_ALGO, "proveNodata: all attempted hash "
+ "calculations were erroneous while finding a matching "
+ "NSEC3, bogus");
+ return sec_status_bogus;
+ } else if(*calc >= MAX_NSEC3_CALCULATIONS) {
+ verbose(VERB_ALGO, "proveNodata: reached "
+ "MAX_NSEC3_CALCULATIONS (%d) while finding a "
+ "matching NSEC3; unchecked still",
+ MAX_NSEC3_CALCULATIONS);
+ return sec_status_unchecked;
+ }
/* For cases 3 - 5, we need the proven closest encloser, and it
* can't match qname. Although, at this point, we know that it
* won't since we just checked that. */
- sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce);
+ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce, calc);
if(sec == sec_status_bogus) {
verbose(VERB_ALGO, "proveNodata: did not match qname, "
"nor found a proven closest encloser.");
verbose(VERB_ALGO, "proveNodata: closest nsec3 is insecure "
"delegation.");
return sec_status_insecure;
+ } else if(sec==sec_status_unchecked) {
+ return sec_status_unchecked;
}
/* Case 3: removed */
/* Case 4: */
log_assert(ce.ce);
- wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen);
- if(wc && find_matching_nsec3(env, flt, ct, wc, wclen, &rrset, &rr)) {
+ wc = nsec3_ce_wildcard(ct->region, ce.ce, ce.ce_len, &wclen);
+ if(wc && find_matching_nsec3(env, flt, ct, wc, wclen, &rrset, &rr,
+ calc)) {
/* found wildcard */
if(nsec3_has_type(rrset, rr, qinfo->qtype)) {
verbose(VERB_ALGO, "nsec3 nodata proof: matching "
}
return sec_status_secure;
}
+ if(*calc == MAX_NSEC3_ERRORS) {
+ verbose(VERB_ALGO, "nsec3 nodata proof: all attempted hash "
+ "calculations were erroneous while matching "
+ "wildcard, bogus");
+ return sec_status_bogus;
+ } else if(*calc >= MAX_NSEC3_CALCULATIONS) {
+ verbose(VERB_ALGO, "nsec3 nodata proof: reached "
+ "MAX_NSEC3_CALCULATIONS (%d) while matching "
+ "wildcard, unchecked still",
+ MAX_NSEC3_CALCULATIONS);
+ return sec_status_unchecked;
+ }
/* Case 5: */
/* Due to forwarders, cnames, and other collating effects, we
enum sec_status
nsec3_prove_nodata(struct module_env* env, struct val_env* ve,
struct ub_packed_rrset_key** list, size_t num,
- struct query_info* qinfo, struct key_entry_key* kkey)
+ struct query_info* qinfo, struct key_entry_key* kkey,
+ struct nsec3_cache_table* ct, int* calc)
{
- rbtree_type ct;
struct nsec3_filter flt;
if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
return sec_status_bogus; /* no valid NSEC3s, bogus */
- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
filter_init(&flt, list, num, qinfo); /* init RR iterator */
if(!flt.zone)
return sec_status_bogus; /* no RRs */
if(nsec3_iteration_count_high(ve, &flt, kkey))
return sec_status_insecure; /* iteration count too high */
- return nsec3_do_prove_nodata(env, &flt, &ct, qinfo);
+ return nsec3_do_prove_nodata(env, &flt, ct, qinfo, calc);
}
enum sec_status
nsec3_prove_wildcard(struct module_env* env, struct val_env* ve,
struct ub_packed_rrset_key** list, size_t num,
- struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc)
+ struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc,
+ struct nsec3_cache_table* ct, int* calc)
{
- rbtree_type ct;
struct nsec3_filter flt;
struct ce_response ce;
uint8_t* nc;
if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
return sec_status_bogus; /* no valid NSEC3s, bogus */
- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
filter_init(&flt, list, num, qinfo); /* init RR iterator */
if(!flt.zone)
return sec_status_bogus; /* no RRs */
/* Now we still need to prove that the original data did not exist.
* Otherwise, we need to show that the next closer name is covered. */
next_closer(qinfo->qname, qinfo->qname_len, ce.ce, &nc, &nc_len);
- if(!find_covering_nsec3(env, &flt, &ct, nc, nc_len,
- &ce.nc_rrset, &ce.nc_rr)) {
+ if(!find_covering_nsec3(env, &flt, ct, nc, nc_len,
+ &ce.nc_rrset, &ce.nc_rr, calc)) {
+ if(*calc == MAX_NSEC3_ERRORS) {
+ verbose(VERB_ALGO, "proveWildcard: did not find a "
+ "covering NSEC3 that covered the next closer "
+ "name; all attempted hash calculations were "
+ "erroneous; bogus");
+ return sec_status_bogus;
+ } else if(*calc >= MAX_NSEC3_CALCULATIONS) {
+ verbose(VERB_ALGO, "proveWildcard: did not find a "
+ "covering NSEC3 that covered the next closer "
+ "name; reached MAX_NSEC3_CALCULATIONS "
+ "(%d); unchecked still",
+ MAX_NSEC3_CALCULATIONS);
+ return sec_status_unchecked;
+ }
verbose(VERB_ALGO, "proveWildcard: did not find a covering "
"NSEC3 that covered the next closer name.");
return sec_status_bogus;
nsec3_prove_nods(struct module_env* env, struct val_env* ve,
struct ub_packed_rrset_key** list, size_t num,
struct query_info* qinfo, struct key_entry_key* kkey, char** reason,
- sldns_ede_code* reason_bogus, struct module_qstate* qstate)
+ sldns_ede_code* reason_bogus, struct module_qstate* qstate,
+ struct nsec3_cache_table* ct)
{
- rbtree_type ct;
struct nsec3_filter flt;
struct ce_response ce;
struct ub_packed_rrset_key* rrset;
int rr;
+ int calc = 0;
+ enum sec_status sec;
+
log_assert(qinfo->qtype == LDNS_RR_TYPE_DS);
if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) {
*reason = "not all NSEC3 records secure";
return sec_status_bogus; /* not all NSEC3 records secure */
}
- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
filter_init(&flt, list, num, qinfo); /* init RR iterator */
if(!flt.zone) {
*reason = "no NSEC3 records";
/* Look for a matching NSEC3 to qname -- this is the normal
* NODATA case. */
- if(find_matching_nsec3(env, &flt, &ct, qinfo->qname, qinfo->qname_len,
- &rrset, &rr)) {
+ if(find_matching_nsec3(env, &flt, ct, qinfo->qname, qinfo->qname_len,
+ &rrset, &rr, &calc)) {
/* If the matching NSEC3 has the SOA bit set, it is from
* the wrong zone (the child instead of the parent). If
* it has the DS bit set, then we were lied to. */
/* Otherwise, this proves no DS. */
return sec_status_secure;
}
+ if(calc == MAX_NSEC3_ERRORS) {
+ verbose(VERB_ALGO, "nsec3 provenods: all attempted hash "
+ "calculations were erroneous while finding a matching "
+ "NSEC3, bogus");
+ return sec_status_bogus;
+ } else if(calc >= MAX_NSEC3_CALCULATIONS) {
+ verbose(VERB_ALGO, "nsec3 provenods: reached "
+ "MAX_NSEC3_CALCULATIONS (%d) while finding a "
+ "matching NSEC3, unchecked still",
+ MAX_NSEC3_CALCULATIONS);
+ return sec_status_unchecked;
+ }
/* Otherwise, we are probably in the opt-out case. */
- if(nsec3_prove_closest_encloser(env, &flt, &ct, qinfo, 1, &ce)
- != sec_status_secure) {
+ sec = nsec3_prove_closest_encloser(env, &flt, ct, qinfo, 1, &ce, &calc);
+ if(sec == sec_status_unchecked) {
+ return sec_status_unchecked;
+ } else if(sec != sec_status_secure) {
/* an insecure delegation *above* the qname does not prove
* anything about this qname exactly, and bogus is bogus */
verbose(VERB_ALGO, "nsec3 provenods: did not match qname, "
enum sec_status
nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve,
- struct ub_packed_rrset_key** list, size_t num,
- struct query_info* qinfo, struct key_entry_key* kkey, int* nodata)
+ struct ub_packed_rrset_key** list, size_t num,
+ struct query_info* qinfo, struct key_entry_key* kkey, int* nodata,
+ struct nsec3_cache_table* ct, int* calc)
{
enum sec_status sec, secnx;
- rbtree_type ct;
struct nsec3_filter flt;
*nodata = 0;
if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
return sec_status_bogus; /* no valid NSEC3s, bogus */
- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
filter_init(&flt, list, num, qinfo); /* init RR iterator */
if(!flt.zone)
return sec_status_bogus; /* no RRs */
/* try nxdomain and nodata after another, while keeping the
* hash cache intact */
- secnx = nsec3_do_prove_nameerror(env, &flt, &ct, qinfo);
+ secnx = nsec3_do_prove_nameerror(env, &flt, ct, qinfo, calc);
if(secnx==sec_status_secure)
return sec_status_secure;
- sec = nsec3_do_prove_nodata(env, &flt, &ct, qinfo);
+ else if(secnx == sec_status_unchecked)
+ return sec_status_unchecked;
+ sec = nsec3_do_prove_nodata(env, &flt, ct, qinfo, calc);
if(sec==sec_status_secure) {
*nodata = 1;
} else if(sec == sec_status_insecure) {
*nodata = 1;
} else if(secnx == sec_status_insecure) {
sec = sec_status_insecure;
+ } else if(sec == sec_status_unchecked) {
+ return sec_status_unchecked;
}
return sec;
}
/** The SHA1 hash algorithm for NSEC3 */
#define NSEC3_HASH_SHA1 0x01
+/**
+* Cache table for NSEC3 hashes.
+* It keeps a *pointer* to the region its items are allocated.
+*/
+struct nsec3_cache_table {
+ rbtree_type* ct;
+ struct regional* region;
+};
+
/**
* Determine if the set of NSEC3 records provided with a response prove NAME
* ERROR. This means that the NSEC3s prove a) the closest encloser exists,
* @param num: number of RRsets in the array to examine.
* @param qinfo: query that is verified for.
* @param kkey: key entry that signed the NSEC3s.
+ * @param ct: cached hashes table.
+ * @param calc: current hash calculations.
* @return:
* sec_status SECURE of the Name Error is proven by the NSEC3 RRs,
- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
+ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored,
+ * UNCHECKED if no more hash calculations are allowed at this point.
*/
enum sec_status
nsec3_prove_nameerror(struct module_env* env, struct val_env* ve,
struct ub_packed_rrset_key** list, size_t num,
- struct query_info* qinfo, struct key_entry_key* kkey);
+ struct query_info* qinfo, struct key_entry_key* kkey,
+ struct nsec3_cache_table* ct, int* calc);
/**
* Determine if the NSEC3s provided in a response prove the NOERROR/NODATA
* @param num: number of RRsets in the array to examine.
* @param qinfo: query that is verified for.
* @param kkey: key entry that signed the NSEC3s.
+ * @param ct: cached hashes table.
+ * @param calc: current hash calculations.
* @return:
* sec_status SECURE of the proposition is proven by the NSEC3 RRs,
- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
+ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored,
+ * UNCHECKED if no more hash calculations are allowed at this point.
*/
enum sec_status
nsec3_prove_nodata(struct module_env* env, struct val_env* ve,
struct ub_packed_rrset_key** list, size_t num,
- struct query_info* qinfo, struct key_entry_key* kkey);
-
+ struct query_info* qinfo, struct key_entry_key* kkey,
+ struct nsec3_cache_table* ct, int* calc);
/**
* Prove that a positive wildcard match was appropriate (no direct match
* @param kkey: key entry that signed the NSEC3s.
* @param wc: The purported wildcard that matched. This is the wildcard name
* as *.wildcard.name., with the *. label already removed.
+ * @param ct: cached hashes table.
+ * @param calc: current hash calculations.
* @return:
* sec_status SECURE of the proposition is proven by the NSEC3 RRs,
- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
+ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored,
+ * UNCHECKED if no more hash calculations are allowed at this point.
*/
enum sec_status
nsec3_prove_wildcard(struct module_env* env, struct val_env* ve,
struct ub_packed_rrset_key** list, size_t num,
- struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc);
+ struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc,
+ struct nsec3_cache_table* ct, int* calc);
/**
* Prove that a DS response either had no DS, or wasn't a delegation point.
* @param reason: string for bogus result.
* @param reason_bogus: EDE (RFC8914) code paired with the reason of failure.
* @param qstate: qstate with region.
+ * @param ct: cached hashes table.
* @return:
* sec_status SECURE of the proposition is proven by the NSEC3 RRs,
* BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
* or if there was no DS in an insecure (i.e., opt-in) way,
- * INDETERMINATE if it was clear that this wasn't a delegation point.
+ * INDETERMINATE if it was clear that this wasn't a delegation point,
+ * UNCHECKED if no more hash calculations are allowed at this point.
*/
enum sec_status
nsec3_prove_nods(struct module_env* env, struct val_env* ve,
struct ub_packed_rrset_key** list, size_t num,
struct query_info* qinfo, struct key_entry_key* kkey, char** reason,
- sldns_ede_code* reason_bogus, struct module_qstate* qstate);
+ sldns_ede_code* reason_bogus, struct module_qstate* qstate,
+ struct nsec3_cache_table* ct);
/**
* Prove NXDOMAIN or NODATA.
* @param kkey: key entry that signed the NSEC3s.
* @param nodata: if return value is secure, this indicates if nodata or
* nxdomain was proven.
+ * @param ct: cached hashes table.
+ * @param calc: current hash calculations.
* @return:
* sec_status SECURE of the proposition is proven by the NSEC3 RRs,
- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored.
+ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored,
+ * UNCHECKED if no more hash calculations are allowed at this point.
*/
enum sec_status
nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve,
struct ub_packed_rrset_key** list, size_t num,
- struct query_info* qinfo, struct key_entry_key* kkey, int* nodata);
+ struct query_info* qinfo, struct key_entry_key* kkey, int* nodata,
+ struct nsec3_cache_table* ct, int* calc);
/**
* The NSEC3 hash result storage.
*/
int nsec3_hash_cmp(const void* c1, const void* c2);
+/**
+ * Initialise the NSEC3 cache table.
+ * @param ct: the nsec3 cache table.
+ * @param region: the region where allocations for the table will happen.
+ * @return true on success, false on malloc error.
+ */
+int nsec3_cache_table_init(struct nsec3_cache_table* ct, struct regional* region);
+
/**
* Obtain the hash of an owner name.
* Used internally by the nsec3 proof functions in this file.
* @param dname_len: the length of the name.
* @param hash: the hash node is returned on success.
* @return:
- * 1 on success, either from cache or newly hashed hash is returned.
+ * 2 on success, hash from cache is returned.
+ * 1 on success, newly computed hash is returned.
* 0 on a malloc failure.
* -1 if the NSEC3 rr was badly formatted (i.e. formerr).
*/
/* forward decl for cache response and normal super inform calls of a DS */
static void process_ds_response(struct module_qstate* qstate,
struct val_qstate* vq, int id, int rcode, struct dns_msg* msg,
- struct query_info* qinfo, struct sock_list* origin);
+ struct query_info* qinfo, struct sock_list* origin, int* suspend);
/* Updates the suplied EDE (RFC8914) code selectively so we don't lose
struct comm_timer* temp_timer;
int restart_count;
if(!vq) return;
- temp_timer = vq->msg_signatures_timer;
+ temp_timer = vq->suspend_timer;
restart_count = vq->restart_count+1;
memset(vq, 0, sizeof(*vq));
- vq->msg_signatures_timer = temp_timer;
+ vq->suspend_timer = temp_timer;
vq->restart_count = restart_count;
vq->state = VAL_INIT_STATE;
}
* @param chase_reply: answer to validate.
* @param key_entry: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
- * @param suspend: returned true if the task takes to long and needs to
+ * @param suspend: returned true if the task takes too long and needs to
* suspend to continue the effort later.
* @return false if any of the rrsets in the an or ns sections of the message
* fail to verify. The message is then set to bogus.
}
void
-validate_msg_signatures_timer_cb(void* arg)
+validate_suspend_timer_cb(void* arg)
{
struct module_qstate* qstate = (struct module_qstate*)arg;
- verbose(VERB_ALGO, "validate_msg_signatures timer, continue");
+ verbose(VERB_ALGO, "validate_suspend timer, continue");
mesh_run(qstate->env->mesh, qstate->mesh_info, module_event_pass,
NULL);
}
/** Setup timer to continue validation of msg signatures later */
static int
-validate_msg_signatures_setup_timer(struct module_qstate* qstate,
- struct val_qstate* vq, int id)
+validate_suspend_setup_timer(struct module_qstate* qstate,
+ struct val_qstate* vq, int id, enum val_state resume_state)
{
struct timeval tv;
int usec, slack, base;
if(vq->suspend_count >= MAX_VALIDATION_SUSPENDS) {
- verbose(VERB_ALGO, "validate_msg_signatures_setup_timer: "
+ verbose(VERB_ALGO, "validate_suspend timer: "
"reached MAX_VALIDATION_SUSPENDS (%d); error out",
MAX_VALIDATION_SUSPENDS);
errinf(qstate, "max validation suspends reached, "
"too many RRSIG validations");
return 0;
}
- vq->state = VAL_VALIDATE_STATE;
+ verbose(VERB_ALGO, "validate_suspend timer, set for suspend");
+ vq->state = resume_state;
qstate->ext_state[id] = module_wait_reply;
- if(!vq->msg_signatures_timer) {
- vq->msg_signatures_timer = comm_timer_create(
+ if(!vq->suspend_timer) {
+ vq->suspend_timer = comm_timer_create(
qstate->env->worker_base,
- validate_msg_signatures_timer_cb, qstate);
- if(!vq->msg_signatures_timer) {
- log_err("validate_msg_signatures_setup_timer: "
+ validate_suspend_timer_cb, qstate);
+ if(!vq->suspend_timer) {
+ log_err("validate_suspend_setup_timer: "
"out of memory for comm_timer_create");
return 0;
}
tv.tv_usec = (usec % 1000000);
tv.tv_sec = (usec / 1000000);
vq->suspend_count ++;
- comm_timer_set(vq->msg_signatures_timer, &tv);
+ comm_timer_set(vq->suspend_timer, &tv);
return 1;
}
* @param chase_reply: answer to that query to validate.
* @param kkey: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
+ * @param qstate: query state for the region.
+ * @param vq: validator state for the nsec3 cache table.
+ * @param nsec3_calculations: current nsec3 hash calculations.
+ * @param suspend: returned true if the task takes too long and needs to
+ * suspend to continue the effort later.
*/
static void
validate_positive_response(struct module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
- struct key_entry_key* kkey)
+ struct key_entry_key* kkey, struct module_qstate* qstate,
+ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
{
uint8_t* wc = NULL;
size_t wl;
int nsec3s_seen = 0;
size_t i;
struct ub_packed_rrset_key* s;
+ *suspend = 0;
/* validate the ANSWER section - this will be the answer itself */
for(i=0; i<chase_reply->an_numrrsets; i++) {
/* If this was a positive wildcard response that we haven't already
* proven, and we have NSEC3 records, try to prove it using the NSEC3
* records. */
- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
- enum sec_status sec = nsec3_prove_wildcard(env, ve,
+ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen &&
+ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ enum sec_status sec = nsec3_prove_wildcard(env, ve,
chase_reply->rrsets+chase_reply->an_numrrsets,
- chase_reply->ns_numrrsets, qchase, kkey, wc);
+ chase_reply->ns_numrrsets, qchase, kkey, wc,
+ &vq->nsec3_cache_table, nsec3_calculations);
if(sec == sec_status_insecure) {
verbose(VERB_ALGO, "Positive wildcard response is "
"insecure");
chase_reply->security = sec_status_insecure;
return;
- } else if(sec == sec_status_secure)
+ } else if(sec == sec_status_secure) {
wc_NSEC_ok = 1;
+ } else if(sec == sec_status_unchecked) {
+ *suspend = 1;
+ return;
+ }
}
/* If after all this, we still haven't proven the positive wildcard
* @param chase_reply: answer to that query to validate.
* @param kkey: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
+ * @param qstate: query state for the region.
+ * @param vq: validator state for the nsec3 cache table.
+ * @param nsec3_calculations: current nsec3 hash calculations.
+ * @param suspend: returned true if the task takes too long and needs to
+ * suspend to continue the effort later.
*/
static void
validate_nodata_response(struct module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
- struct key_entry_key* kkey)
+ struct key_entry_key* kkey, struct module_qstate* qstate,
+ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
{
/* Since we are here, there must be nothing in the ANSWER section to
* validate. */
int nsec3s_seen = 0; /* nsec3s seen */
struct ub_packed_rrset_key* s;
size_t i;
+ *suspend = 0;
for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
chase_reply->ns_numrrsets; i++) {
}
}
- if(!has_valid_nsec && nsec3s_seen) {
+ if(!has_valid_nsec && nsec3s_seen &&
+ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
enum sec_status sec = nsec3_prove_nodata(env, ve,
chase_reply->rrsets+chase_reply->an_numrrsets,
- chase_reply->ns_numrrsets, qchase, kkey);
+ chase_reply->ns_numrrsets, qchase, kkey,
+ &vq->nsec3_cache_table, nsec3_calculations);
if(sec == sec_status_insecure) {
verbose(VERB_ALGO, "NODATA response is insecure");
chase_reply->security = sec_status_insecure;
return;
- } else if(sec == sec_status_secure)
+ } else if(sec == sec_status_secure) {
has_valid_nsec = 1;
+ } else if(sec == sec_status_unchecked) {
+ /* check is incomplete; suspend */
+ *suspend = 1;
+ return;
+ }
}
if(!has_valid_nsec) {
* @param kkey: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
* @param rcode: adjusted RCODE, in case of RCODE/proof mismatch leniency.
+ * @param qstate: query state for the region.
+ * @param vq: validator state for the nsec3 cache table.
+ * @param nsec3_calculations: current nsec3 hash calculations.
+ * @param suspend: returned true if the task takes too long and needs to
+ * suspend to continue the effort later.
*/
static void
validate_nameerror_response(struct module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
- struct key_entry_key* kkey, int* rcode)
+ struct key_entry_key* kkey, int* rcode,
+ struct module_qstate* qstate, struct val_qstate* vq,
+ int* nsec3_calculations, int* suspend)
{
int has_valid_nsec = 0;
int has_valid_wnsec = 0;
uint8_t* ce;
int ce_labs = 0;
int prev_ce_labs = 0;
+ *suspend = 0;
for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
chase_reply->ns_numrrsets; i++) {
nsec3s_seen = 1;
}
- if((!has_valid_nsec || !has_valid_wnsec) && nsec3s_seen) {
+ if((!has_valid_nsec || !has_valid_wnsec) && nsec3s_seen &&
+ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
/* use NSEC3 proof, both answer and auth rrsets, in case
* NSEC3s end up in the answer (due to qtype=NSEC3 or so) */
chase_reply->security = nsec3_prove_nameerror(env, ve,
chase_reply->rrsets, chase_reply->an_numrrsets+
- chase_reply->ns_numrrsets, qchase, kkey);
- if(chase_reply->security != sec_status_secure) {
+ chase_reply->ns_numrrsets, qchase, kkey,
+ &vq->nsec3_cache_table, nsec3_calculations);
+ if(chase_reply->security == sec_status_unchecked) {
+ *suspend = 1;
+ return;
+ } else if(chase_reply->security != sec_status_secure) {
verbose(VERB_QUERY, "NameError response failed nsec, "
"nsec3 proof was %s", sec_status_to_string(
chase_reply->security));
/* If the message fails to prove either condition, it is bogus. */
if(!has_valid_nsec) {
+ validate_nodata_response(env, ve, qchase, chase_reply, kkey,
+ qstate, vq, nsec3_calculations, suspend);
+ if(*suspend) return;
verbose(VERB_QUERY, "NameError response has failed to prove: "
"qname does not exist");
- chase_reply->security = sec_status_bogus;
- update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
/* Be lenient with RCODE in NSEC NameError responses */
- validate_nodata_response(env, ve, qchase, chase_reply, kkey);
- if (chase_reply->security == sec_status_secure)
+ if(chase_reply->security == sec_status_secure) {
*rcode = LDNS_RCODE_NOERROR;
+ } else {
+ chase_reply->security = sec_status_bogus;
+ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+ }
return;
}
if(!has_valid_wnsec) {
+ validate_nodata_response(env, ve, qchase, chase_reply, kkey,
+ qstate, vq, nsec3_calculations, suspend);
+ if(*suspend) return;
verbose(VERB_QUERY, "NameError response has failed to prove: "
"covering wildcard does not exist");
- chase_reply->security = sec_status_bogus;
- update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
/* Be lenient with RCODE in NSEC NameError responses */
- validate_nodata_response(env, ve, qchase, chase_reply, kkey);
- if (chase_reply->security == sec_status_secure)
+ if (chase_reply->security == sec_status_secure) {
*rcode = LDNS_RCODE_NOERROR;
+ } else {
+ chase_reply->security = sec_status_bogus;
+ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+ }
return;
}
* @param chase_reply: answer to that query to validate.
* @param kkey: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
+ * @param qstate: query state for the region.
+ * @param vq: validator state for the nsec3 cache table.
+ * @param nsec3_calculations: current nsec3 hash calculations.
+ * @param suspend: returned true if the task takes too long and needs to
+ * suspend to continue the effort later.
*/
static void
validate_any_response(struct module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
- struct key_entry_key* kkey)
+ struct key_entry_key* kkey, struct module_qstate* qstate,
+ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
{
/* all answer and auth rrsets already verified */
/* but check if a wildcard response is given, then check NSEC/NSEC3
int nsec3s_seen = 0;
size_t i;
struct ub_packed_rrset_key* s;
+ *suspend = 0;
if(qchase->qtype != LDNS_RR_TYPE_ANY) {
log_err("internal error: ANY validation called for non-ANY");
/* If this was a positive wildcard response that we haven't already
* proven, and we have NSEC3 records, try to prove it using the NSEC3
* records. */
- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
+ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen &&
+ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
/* look both in answer and auth section for NSEC3s */
- enum sec_status sec = nsec3_prove_wildcard(env, ve,
+ enum sec_status sec = nsec3_prove_wildcard(env, ve,
chase_reply->rrsets,
- chase_reply->an_numrrsets+chase_reply->ns_numrrsets,
- qchase, kkey, wc);
+ chase_reply->an_numrrsets+chase_reply->ns_numrrsets,
+ qchase, kkey, wc, &vq->nsec3_cache_table,
+ nsec3_calculations);
if(sec == sec_status_insecure) {
verbose(VERB_ALGO, "Positive ANY wildcard response is "
"insecure");
chase_reply->security = sec_status_insecure;
return;
- } else if(sec == sec_status_secure)
+ } else if(sec == sec_status_secure) {
wc_NSEC_ok = 1;
+ } else if(sec == sec_status_unchecked) {
+ *suspend = 1;
+ return;
+ }
}
/* If after all this, we still haven't proven the positive wildcard
* @param chase_reply: answer to that query to validate.
* @param kkey: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
+ * @param qstate: query state for the region.
+ * @param vq: validator state for the nsec3 cache table.
+ * @param nsec3_calculations: current nsec3 hash calculations.
+ * @param suspend: returned true if the task takes too long and needs to
+ * suspend to continue the effort later.
*/
static void
validate_cname_response(struct module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
- struct key_entry_key* kkey)
+ struct key_entry_key* kkey, struct module_qstate* qstate,
+ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
{
uint8_t* wc = NULL;
size_t wl;
int nsec3s_seen = 0;
size_t i;
struct ub_packed_rrset_key* s;
+ *suspend = 0;
/* validate the ANSWER section - this will be the CNAME (+DNAME) */
for(i=0; i<chase_reply->an_numrrsets; i++) {
/* If this was a positive wildcard response that we haven't already
* proven, and we have NSEC3 records, try to prove it using the NSEC3
* records. */
- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
- enum sec_status sec = nsec3_prove_wildcard(env, ve,
+ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen &&
+ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ enum sec_status sec = nsec3_prove_wildcard(env, ve,
chase_reply->rrsets+chase_reply->an_numrrsets,
- chase_reply->ns_numrrsets, qchase, kkey, wc);
+ chase_reply->ns_numrrsets, qchase, kkey, wc,
+ &vq->nsec3_cache_table, nsec3_calculations);
if(sec == sec_status_insecure) {
verbose(VERB_ALGO, "wildcard CNAME response is "
"insecure");
chase_reply->security = sec_status_insecure;
return;
- } else if(sec == sec_status_secure)
+ } else if(sec == sec_status_secure) {
wc_NSEC_ok = 1;
+ } else if(sec == sec_status_unchecked) {
+ *suspend = 1;
+ return;
+ }
}
/* If after all this, we still haven't proven the positive wildcard
* @param chase_reply: answer to that query to validate.
* @param kkey: the key entry, which is trusted, and which matches
* the signer of the answer. The key entry isgood().
+ * @param qstate: query state for the region.
+ * @param vq: validator state for the nsec3 cache table.
+ * @param nsec3_calculations: current nsec3 hash calculations.
+ * @param suspend: returned true if the task takes too long and needs to
+ * suspend to continue the effort later.
*/
static void
validate_cname_noanswer_response(struct module_env* env, struct val_env* ve,
struct query_info* qchase, struct reply_info* chase_reply,
- struct key_entry_key* kkey)
+ struct key_entry_key* kkey, struct module_qstate* qstate,
+ struct val_qstate* vq, int* nsec3_calculations, int* suspend)
{
int nodata_valid_nsec = 0; /* If true, then NODATA has been proven.*/
uint8_t* ce = NULL; /* for wildcard nodata responses. This is the
uint8_t* nsec_ce; /* Used to find the NSEC with the longest ce */
int ce_labs = 0;
int prev_ce_labs = 0;
+ *suspend = 0;
/* the AUTHORITY section */
for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS);
return;
}
- if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen) {
+ if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen &&
+ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
int nodata;
enum sec_status sec = nsec3_prove_nxornodata(env, ve,
chase_reply->rrsets+chase_reply->an_numrrsets,
- chase_reply->ns_numrrsets, qchase, kkey, &nodata);
+ chase_reply->ns_numrrsets, qchase, kkey, &nodata,
+ &vq->nsec3_cache_table, nsec3_calculations);
if(sec == sec_status_insecure) {
verbose(VERB_ALGO, "CNAMEchain to noanswer response "
"is insecure");
if(nodata)
nodata_valid_nsec = 1;
else nxdomain_valid_nsec = 1;
+ } else if(sec == sec_status_unchecked) {
+ *suspend = 1;
+ return;
}
}
* Uses negative cache for NSEC3 lookup of DS responses. */
/* only if cache not blacklisted, of course */
struct dns_msg* msg;
- if(!qstate->blacklist && !vq->chain_blacklist &&
+ int suspend;
+ if(vq->sub_ds_msg) {
+ /* We have a suspended DS reply from a sub-query;
+ * process it. */
+ verbose(VERB_ALGO, "Process suspended sub DS response");
+ msg = vq->sub_ds_msg;
+ process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR,
+ msg, &msg->qinfo, NULL, &suspend);
+ if(suspend) {
+ /* we'll come back here later to continue */
+ if(!validate_suspend_setup_timer(qstate, vq,
+ id, VAL_FINDKEY_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
+ vq->sub_ds_msg = NULL;
+ return 1; /* continue processing ds-response results */
+ } else if(!qstate->blacklist && !vq->chain_blacklist &&
(msg=val_find_DS(qstate->env, target_key_name,
target_key_len, vq->qchase.qclass, qstate->region,
vq->key_entry->name)) ) {
verbose(VERB_ALGO, "Process cached DS response");
process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR,
- msg, &msg->qinfo, NULL);
+ msg, &msg->qinfo, NULL, &suspend);
+ if(suspend) {
+ /* we'll come back here later to continue */
+ if(!validate_suspend_setup_timer(qstate, vq,
+ id, VAL_FINDKEY_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
return 1; /* continue processing ds-response results */
}
if(!generate_request(qstate, id, target_key_name,
struct val_env* ve, int id)
{
enum val_classification subtype;
- int rcode, suspend;
+ int rcode, suspend, nsec3_calculations = 0;
if(!vq->key_entry) {
verbose(VERB_ALGO, "validate: no key entry, failed");
if(!validate_msg_signatures(qstate, vq, qstate->env, ve, &vq->qchase,
vq->chase_reply, vq->key_entry, &suspend)) {
if(suspend) {
- if(!validate_msg_signatures_setup_timer(qstate, vq,
- id))
+ if(!validate_suspend_setup_timer(qstate, vq,
+ id, VAL_VALIDATE_STATE))
return val_error(qstate, id);
return 0;
}
case VAL_CLASS_POSITIVE:
verbose(VERB_ALGO, "Validating a positive response");
validate_positive_response(qstate->env, ve,
- &vq->qchase, vq->chase_reply, vq->key_entry);
+ &vq->qchase, vq->chase_reply, vq->key_entry,
+ qstate, vq, &nsec3_calculations, &suspend);
+ if(suspend) {
+ if(!validate_suspend_setup_timer(qstate,
+ vq, id, VAL_VALIDATE_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
verbose(VERB_DETAIL, "validate(positive): %s",
sec_status_to_string(
vq->chase_reply->security));
case VAL_CLASS_NODATA:
verbose(VERB_ALGO, "Validating a nodata response");
validate_nodata_response(qstate->env, ve,
- &vq->qchase, vq->chase_reply, vq->key_entry);
+ &vq->qchase, vq->chase_reply, vq->key_entry,
+ qstate, vq, &nsec3_calculations, &suspend);
+ if(suspend) {
+ if(!validate_suspend_setup_timer(qstate,
+ vq, id, VAL_VALIDATE_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
verbose(VERB_DETAIL, "validate(nodata): %s",
sec_status_to_string(
vq->chase_reply->security));
rcode = (int)FLAGS_GET_RCODE(vq->orig_msg->rep->flags);
verbose(VERB_ALGO, "Validating a nxdomain response");
validate_nameerror_response(qstate->env, ve,
- &vq->qchase, vq->chase_reply, vq->key_entry, &rcode);
+ &vq->qchase, vq->chase_reply, vq->key_entry, &rcode,
+ qstate, vq, &nsec3_calculations, &suspend);
+ if(suspend) {
+ if(!validate_suspend_setup_timer(qstate,
+ vq, id, VAL_VALIDATE_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
verbose(VERB_DETAIL, "validate(nxdomain): %s",
sec_status_to_string(
vq->chase_reply->security));
case VAL_CLASS_CNAME:
verbose(VERB_ALGO, "Validating a cname response");
validate_cname_response(qstate->env, ve,
- &vq->qchase, vq->chase_reply, vq->key_entry);
+ &vq->qchase, vq->chase_reply, vq->key_entry,
+ qstate, vq, &nsec3_calculations, &suspend);
+ if(suspend) {
+ if(!validate_suspend_setup_timer(qstate,
+ vq, id, VAL_VALIDATE_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
verbose(VERB_DETAIL, "validate(cname): %s",
sec_status_to_string(
vq->chase_reply->security));
verbose(VERB_ALGO, "Validating a cname noanswer "
"response");
validate_cname_noanswer_response(qstate->env, ve,
- &vq->qchase, vq->chase_reply, vq->key_entry);
+ &vq->qchase, vq->chase_reply, vq->key_entry,
+ qstate, vq, &nsec3_calculations, &suspend);
+ if(suspend) {
+ if(!validate_suspend_setup_timer(qstate,
+ vq, id, VAL_VALIDATE_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
verbose(VERB_DETAIL, "validate(cname_noanswer): %s",
sec_status_to_string(
vq->chase_reply->security));
case VAL_CLASS_ANY:
verbose(VERB_ALGO, "Validating a positive ANY "
"response");
- validate_any_response(qstate->env, ve, &vq->qchase,
- vq->chase_reply, vq->key_entry);
+ validate_any_response(qstate->env, ve, &vq->qchase,
+ vq->chase_reply, vq->key_entry, qstate, vq,
+ &nsec3_calculations, &suspend);
+ if(suspend) {
+ if(!validate_suspend_setup_timer(qstate,
+ vq, id, VAL_VALIDATE_STATE))
+ return val_error(qstate, id);
+ return 0;
+ }
verbose(VERB_DETAIL, "validate(positive_any): %s",
sec_status_to_string(
vq->chase_reply->security));
* DS response indicated an end to secure space, is_good if the DS
* validated. It returns ke=NULL if the DS response indicated that the
* request wasn't a delegation point.
- * @return 0 on servfail error (malloc failure).
+ * @return
+ * 0 on success,
+ * 1 on servfail error (malloc failure),
+ * 2 on NSEC3 suspend.
*/
static int
ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq,
ub_packed_rrset_ttl(ds),
LDNS_EDE_UNSUPPORTED_DS_DIGEST, NULL,
*qstate->env->now);
- return (*ke) != NULL;
+ return (*ke) == NULL;
}
/* Otherwise, we return the positive response. */
*ke = key_entry_create_rrset(qstate->region,
qinfo->qname, qinfo->qname_len, qinfo->qclass, ds,
NULL, LDNS_EDE_NONE, NULL, *qstate->env->now);
- return (*ke) != NULL;
+ return (*ke) == NULL;
} else if(subtype == VAL_CLASS_NODATA ||
subtype == VAL_CLASS_NAMEERROR) {
/* NODATA means that the qname exists, but that there was
qinfo->qclass, proof_ttl,
LDNS_EDE_NONE, NULL,
*qstate->env->now);
- return (*ke) != NULL;
+ return (*ke) == NULL;
case sec_status_insecure:
verbose(VERB_DETAIL, "NSEC RRset for the "
"referral proved not a delegation point");
*ke = NULL;
- return 1;
+ return 0;
case sec_status_bogus:
verbose(VERB_DETAIL, "NSEC RRset for the "
"referral did not prove no DS.");
break;
}
+ if(!nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) {
+ log_err("malloc failure in ds_response_to_ke for "
+ "NSEC3 cache");
+ reason = "malloc failure";
+ errinf_ede(qstate, reason, 0);
+ goto return_bogus;
+ }
sec = nsec3_prove_nods(qstate->env, ve,
msg->rep->rrsets + msg->rep->an_numrrsets,
msg->rep->ns_numrrsets, qinfo, vq->key_entry, &reason,
- &reason_bogus, qstate);
+ &reason_bogus, qstate, &vq->nsec3_cache_table);
switch(sec) {
case sec_status_insecure:
/* case insecure also continues to unsigned
qinfo->qclass, proof_ttl,
LDNS_EDE_NONE, NULL,
*qstate->env->now);
- return (*ke) != NULL;
+ return (*ke) == NULL;
case sec_status_indeterminate:
verbose(VERB_DETAIL, "NSEC3s for the "
"referral proved no delegation");
*ke = NULL;
- return 1;
+ return 0;
case sec_status_bogus:
verbose(VERB_DETAIL, "NSEC3s for the "
"referral did not prove no DS.");
errinf_ede(qstate, reason, reason_bogus);
goto return_bogus;
case sec_status_unchecked:
+ return 2;
default:
/* NSEC3 proof did not work */
break;
"proof that DS does not exist");
/* and that it is not a referral point */
*ke = NULL;
- return 1;
+ return 0;
}
errinf(qstate, "CNAME in DS response was not secure.");
errinf_ede(qstate, reason, reason_bogus);
*ke = key_entry_create_bad(qstate->region, qinfo->qname,
qinfo->qname_len, qinfo->qclass, BOGUS_KEY_TTL,
reason_bogus, reason, *qstate->env->now);
- return (*ke) != NULL;
+ return (*ke) == NULL;
}
/**
static void
process_ds_response(struct module_qstate* qstate, struct val_qstate* vq,
int id, int rcode, struct dns_msg* msg, struct query_info* qinfo,
- struct sock_list* origin)
+ struct sock_list* origin, int* suspend)
{
struct val_env* ve = (struct val_env*)qstate->env->modinfo[id];
struct key_entry_key* dske = NULL;
uint8_t* olds = vq->empty_DS_name;
+ int ret;
+ *suspend = 0;
vq->empty_DS_name = NULL;
- if(!ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske)) {
+ ret = ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske);
+ if(ret != 0) {
+ switch(ret) {
+ case 1:
log_err("malloc failure in process_ds_response");
vq->key_entry = NULL; /* make it error */
vq->state = VAL_VALIDATE_STATE;
return;
+ case 2:
+ *suspend = 1;
+ return;
+ default:
+ log_err("unhandled error value for ds_response_to_ke");
+ vq->key_entry = NULL; /* make it error */
+ vq->state = VAL_VALIDATE_STATE;
+ return;
+ }
}
if(dske == NULL) {
vq->empty_DS_name = regional_alloc_init(qstate->region,
return;
}
if(qstate->qinfo.qtype == LDNS_RR_TYPE_DS) {
+ int suspend;
process_ds_response(super, vq, id, qstate->return_rcode,
- qstate->return_msg, &qstate->qinfo,
- qstate->reply_origin);
+ qstate->return_msg, &qstate->qinfo,
+ qstate->reply_origin, &suspend);
+ /* If NSEC3 was needed during validation, NULL the NSEC3 cache;
+ * it will be re-initiated if needed later on.
+ * Validation (and the cache table) are happening/allocated in
+ * the super qstate whilst the RRs are allocated (and pointed
+ * to) in this sub qstate. */
+ if(vq->nsec3_cache_table.ct) {
+ vq->nsec3_cache_table.ct = NULL;
+ }
+ if(suspend) {
+ /* deep copy the return_msg to vq->sub_ds_msg; it will
+ * be resumed later in the super state with the caveat
+ * that the initial calculations will be re-caclulated
+ * and re-suspended there before continuing. */
+ vq->sub_ds_msg = dns_msg_deepcopy_region(
+ qstate->return_msg, super->region);
+ }
return;
} else if(qstate->qinfo.qtype == LDNS_RR_TYPE_DNSKEY) {
process_dnskey_response(super, vq, id, qstate->return_rcode,
return;
vq = (struct val_qstate*)qstate->minfo[id];
if(vq) {
- if(vq->msg_signatures_timer) {
- comm_timer_delete(vq->msg_signatures_timer);
+ if(vq->suspend_timer) {
+ comm_timer_delete(vq->suspend_timer);
}
}
/* everything is allocated in the region, so assign NULL */
#include "util/module.h"
#include "util/data/msgreply.h"
#include "validator/val_utils.h"
+#include "validator/val_nsec3.h"
struct val_anchors;
struct key_cache;
struct key_entry_key;
int msg_signatures_state;
/** The rrset index for the msg signatures to continue from */
size_t msg_signatures_index;
+ /** Cache table for NSEC3 hashes */
+ struct nsec3_cache_table nsec3_cache_table;
+ /** DS message from sub if it got suspended from NSEC3 calculations */
+ struct dns_msg* sub_ds_msg;
/** The timer to resume processing msg signatures */
- struct comm_timer* msg_signatures_timer;
- /** number of suspends */
- int suspend_count;
+ struct comm_timer* suspend_timer;
+ /** Number of suspends */
+ int suspend_count;
};
/**
size_t val_get_mem(struct module_env* env, int id);
/** Timer callback for msg signatures continue timer */
-void validate_msg_signatures_timer_cb(void* arg);
+void validate_suspend_timer_cb(void* arg);
#endif /* VALIDATOR_VALIDATOR_H */