From: Wouter Wijngaards Date: Thu, 23 Aug 2007 15:23:45 +0000 (+0000) Subject: CNAME validation. X-Git-Tag: release-0.5~99 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b54a0400ab381bcc60c1dd54d400dd7b5d25845c;p=thirdparty%2Funbound.git CNAME validation. git-svn-id: file:///svn/unbound/trunk@542 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/doc/Changelog b/doc/Changelog index 7eda8301a..418f3104d 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,12 @@ +23 August 2007: Wouter + - CNAME handling - move needs_validation to before val_new(). + val_new() setups the chase-reply to be an edited copy of the msg. + new classification, and find signer can find for it. + removal of unsigned crap from additional, and query restart for + cname. + - refuse to follow wildcarded DNAMEs when validating. + But you can query for qtype ANY, or qtype DNAME and validate that. + 22 August 2007: Wouter - bogus TTL. - review - use val_error(). diff --git a/validator/val_utils.c b/validator/val_utils.c index 40e025e36..c4a35b194 100644 --- a/validator/val_utils.c +++ b/validator/val_utils.c @@ -49,7 +49,8 @@ #include "util/net_help.h" enum val_classification -val_classify_response(struct query_info* qinf, struct reply_info* rep) +val_classify_response(struct query_info* qinf, struct reply_info* rep, + size_t skip) { int rcode = (int)FLAGS_GET_RCODE(rep->flags); size_t i; @@ -60,6 +61,10 @@ val_classify_response(struct query_info* qinf, struct reply_info* rep) return VAL_CLASS_NAMEERROR; log_assert(rcode == LDNS_RCODE_NOERROR); + /* next check if the skip into the answer section shows no answer */ + if(skip>0 && rep->an_numrrsets <= skip) + return VAL_CLASS_CNAMENOANSWER; + /* Next is NODATA */ if(rep->an_numrrsets == 0) return VAL_CLASS_NODATA; @@ -74,7 +79,7 @@ val_classify_response(struct query_info* qinf, struct reply_info* rep) /* Note that DNAMEs will be ignored here, unless qtype=DNAME. Unless * qtype=CNAME, this will yield a CNAME response. */ - for(i=0; ian_numrrsets; i++) { + for(i=skip; ian_numrrsets; i++) { if(ntohs(rep->rrsets[i]->rk.type) == qinf->qtype) return VAL_CLASS_POSITIVE; if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME) @@ -135,17 +140,57 @@ val_find_rrset_signer(struct ub_packed_rrset_key* rrset, uint8_t** sname, sname, slen); } +/** + * Find best signer name in this set of rrsigs. + * @param rrset: which rrsigs to look through. + * @param qinf: the query name that needs validation. + * @param signer_name: the best signer_name. Updated if a better one is found. + * @param signer_len: length of signer name. + * @param matchcount: count of current best name (starts at 0 for no match). + * Updated if match is improved. + */ +static void +val_find_best_signer(struct ub_packed_rrset_key* rrset, + struct query_info* qinf, uint8_t** signer_name, size_t* signer_len, + int* matchcount) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + uint8_t* sign; + size_t i; + int m; + for(i=d->count; icount+d->rrsig_count; i++) { + sign = d->rr_data[i]+2+18; + /* look at signatures that are valid (long enough), + * and have a signer name that is a superdomain of qname, + * and then check the number of labels in the shared topdomain + * improve the match if possible */ + if(d->rr_len[i] > 2+19 && /* rdata, sig + root label*/ + dname_subdomain_c(qinf->qname, sign)) { + (void)dname_lab_cmp(qinf->qname, + dname_count_labels(qinf->qname), + sign, dname_count_labels(sign), &m); + if(m > *matchcount) { + *matchcount = m; + *signer_name = sign; + (void)dname_count_size_labels(*signer_name, + signer_len); + } + } + } +} + void -val_find_signer(struct query_info* qinf, struct reply_info* rep, - uint8_t** signer_name, size_t* signer_len) +val_find_signer(enum val_classification subtype, struct query_info* qinf, + struct reply_info* rep, size_t cname_skip, uint8_t** signer_name, + size_t* signer_len) { - enum val_classification subtype = val_classify_response(qinf, rep); size_t i; if(subtype == VAL_CLASS_POSITIVE || subtype == VAL_CLASS_CNAME || subtype == VAL_CLASS_ANY) { /* check for the answer rrset */ - for(i=0; ian_numrrsets; i++) { + for(i=cname_skip; ian_numrrsets; i++) { if(query_dname_compare(qinf->qname, rep->rrsets[i]->rk.dname) == 0) { val_find_rrset_signer(rep->rrsets[i], @@ -168,6 +213,21 @@ val_find_signer(struct query_info* qinf, struct reply_info* rep, return; } } + } else if(subtype == VAL_CLASS_CNAMENOANSWER) { + /* find closest superdomain signer name in authority section + * NSEC and NSEC3s */ + int matchcount = 0; + *signer_name = NULL; + *signer_len = 0; + for(i=rep->an_numrrsets; ian_numrrsets+rep-> + ns_numrrsets; i++) { + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC + || ntohs(rep->rrsets[i]->rk.type) == + LDNS_RR_TYPE_NSEC3) { + val_find_best_signer(rep->rrsets[i], qinf, + signer_name, signer_len, &matchcount); + } + } } else { verbose(VERB_ALGO, "find_signer: could not find signer name" " for unknown type response"); @@ -408,3 +468,120 @@ val_rrset_wildcard(struct ub_packed_rrset_key* rrset, uint8_t** wc) *wc = NULL; return 1; } + +int +val_chase_cname(struct query_info* qchase, struct reply_info* rep, + size_t* cname_skip) { + size_t i; + /* skip any DNAMEs, go to the CNAME for next part */ + for(i = *cname_skip; i < rep->an_numrrsets; i++) { + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME && + query_dname_compare(qchase->qname, rep->rrsets[i]-> + rk.dname) == 0) { + qchase->qname = NULL; + get_cname_target(rep->rrsets[i], &qchase->qname, + &qchase->qname_len); + if(!qchase->qname) + return 0; /* bad CNAME rdata */ + (*cname_skip) = i; + return 1; + } + } + return 0; /* CNAME classified but no matching CNAME ?! */ +} + +/** see if rrset has signer name as one of the rrsig signers */ +static int +rrset_has_signer(struct ub_packed_rrset_key* rrset, uint8_t* name, size_t len) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*)rrset-> + entry.data; + size_t i; + for(i = d->count; i< d->count+d->rrsig_count; i++) { + if(d->rr_len[i] > 2+18+len) { + /* at least rdatalen + signature + signame (+1 sig)*/ + if(query_dname_compare(name, d->rr_data[i]+2+18) == 0) + { + return 1; + } + } + } + return 0; +} + +void +val_fill_reply(struct reply_info* chase, struct reply_info* orig, + size_t cname_skip, uint8_t* name, size_t len) +{ + /* unsigned RRsets are never copied, but should not happen in + * secure answers anyway. Except for the synthesized CNAME after + * a DNAME. */ + size_t i; + int seen_dname = 0; + chase->rrset_count = 0; + chase->an_numrrsets = 0; + chase->ns_numrrsets = 0; + chase->ar_numrrsets = 0; + /* ANSWER section */ + for(i=cname_skip; ian_numrrsets; i++) { + if(seen_dname && ntohs(orig->rrsets[i]->rk.type) == + LDNS_RR_TYPE_CNAME) { + chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i]; + seen_dname = 0; + } else if(rrset_has_signer(orig->rrsets[i], name, len)) { + chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i]; + if(ntohs(orig->rrsets[i]->rk.type) == + LDNS_RR_TYPE_DNAME) { + seen_dname = 1; + } + } + } + /* AUTHORITY section */ + for(i=orig->an_numrrsets; ian_numrrsets+orig->ns_numrrsets; + i++) { + if(rrset_has_signer(orig->rrsets[i], name, len)) { + chase->rrsets[chase->an_numrrsets+ + chase->ns_numrrsets++] = orig->rrsets[i]; + } + } + /* ADDITIONAL section */ + for(i=orig->an_numrrsets+orig->ns_numrrsets; irrset_count; + i++) { + if(rrset_has_signer(orig->rrsets[i], name, len)) { + chase->rrsets[chase->an_numrrsets+orig->ns_numrrsets+ + chase->ar_numrrsets++] = orig->rrsets[i]; + } + } + chase->rrset_count = chase->an_numrrsets + chase->ns_numrrsets + + chase->ar_numrrsets; +} + +void +val_dump_nonsecure(struct reply_info* rep) +{ + size_t i; + /* authority */ + for(i=rep->an_numrrsets; ian_numrrsets+rep->ns_numrrsets; i++) { + if(((struct packed_rrset_data*)rep->rrsets[i]->entry.data) + ->security != sec_status_secure) { + /* remove this unsigned/bogus/unneeded rrset */ + memmove(rep->rrsets+i, rep->rrsets+i+1, + sizeof(struct ub_packed_rrset_key*)* + (rep->rrset_count - i - 1)); + rep->ns_numrrsets--; + rep->rrset_count--; + } + } + /* additional */ + for(i=rep->an_numrrsets+rep->ns_numrrsets; irrset_count; i++) { + if(((struct packed_rrset_data*)rep->rrsets[i]->entry.data) + ->security != sec_status_secure) { + /* remove this unsigned/bogus/unneeded rrset */ + memmove(rep->rrsets+i, rep->rrsets+i+1, + sizeof(struct ub_packed_rrset_key*)* + (rep->rrset_count - i - 1)); + rep->ar_numrrsets--; + rep->rrset_count--; + } + } +} diff --git a/validator/val_utils.h b/validator/val_utils.h index 2819c6469..83c60862f 100644 --- a/validator/val_utils.h +++ b/validator/val_utils.h @@ -66,32 +66,42 @@ enum val_classification { VAL_CLASS_NODATA, /** A NXDOMAIN response. */ VAL_CLASS_NAMEERROR, + /** A CNAME/DNAME chain, and the offset is at the end of it, + * but there is no answer here, it can be NAMERROR or NODATA. */ + VAL_CLASS_CNAMENOANSWER, /** A response to a qtype=ANY query. */ VAL_CLASS_ANY }; /** * Given a response, classify ANSWER responses into a subtype. - * @param qinf: query info - * @param rep: response - * @return A subtype ranging from UNKNOWN to NAMEERROR. + * @param qinf: query info. The chased query name. + * @param rep: response. The original response. + * @param skip: offset into the original response answer section. + * @return A subtype, all values possible except UNTYPED . + * Once CNAME type is returned you can increase skip. + * Then, another CNAME type, CNAME_NOANSWER or POSITIVE are possible. */ enum val_classification val_classify_response(struct query_info* qinf, - struct reply_info* rep); + struct reply_info* rep, size_t skip); /** * Given a response, determine the name of the "signer". This is primarily * to determine if the response is, in fact, signed at all, and, if so, what * is the name of the most pertinent keyset. * - * @param qinf: query - * @param rep: response to that + * @param subtype: the type from classify. + * @param qinf: query, the chased query name. + * @param rep: response to that, original response. + * @param cname_skip: how many answer rrsets have been skipped due to CNAME + * chains being chased around. * @param signer_name: signer name, if the response is signed * (even partially), or null if the response isn't signed. * @param signer_len: length of signer_name of 0 if signer_name is NULL. */ -void val_find_signer(struct query_info* qinf, struct reply_info* rep, - uint8_t** signer_name, size_t* signer_len); +void val_find_signer(enum val_classification subtype, + struct query_info* qinf, struct reply_info* rep, + size_t cname_skip, uint8_t** signer_name, size_t* signer_len); /** * Verify RRset with keys @@ -169,4 +179,39 @@ int val_dsset_isusable(struct ub_packed_rrset_key* ds_rrset); */ int val_rrset_wildcard(struct ub_packed_rrset_key* rrset, uint8_t** wc); +/** + * Chase the cname to the next query name. + * @param qchase: the current query name, updated to next target. + * @param rep: original message reply to look at CNAMEs. + * @param cname_skip: the skip into the answer section. Updated to skip + * DNAME and CNAME to the next part of the answer. + * @return false on error (bad rdata). + */ +int val_chase_cname(struct query_info* qchase, struct reply_info* rep, + size_t* cname_skip); + +/** + * Fill up the chased reply with the content from the original reply; + * as pointers to those rrsets. Select the part after the cname_skip into + * the answer section, NS and AR sections that are signed with same signer. + * + * @param chase: chased reply, filled up. + * @param orig: original reply. + * @param cname_skip: which part of the answer section to skip. + * The skipped part contains CNAME(and DNAME)s that have been chased. + * @param name: the signer name to look for. + * @param len: length of name. + */ +void val_fill_reply(struct reply_info* chase, struct reply_info* orig, + size_t cname_skip, uint8_t* name, size_t len); + +/** + * Remove all unsigned or non-secure status rrsets from NS and AR sections. + * So that unsigned data does not get let through to clients, when we have + * found the data to be secure. + * + * @param rep: reply to dump all nonsecure stuff out of. + */ +void val_dump_nonsecure(struct reply_info* rep); + #endif /* VALIDATOR_VAL_UTILS_H */ diff --git a/validator/validator.c b/validator/validator.c index e24324962..b41fffa9d 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -143,7 +143,17 @@ val_new(struct module_qstate* qstate, int id) vq->orig_msg = qstate->return_msg; } vq->qchase = qstate->qinfo; - vq->chase_reply = vq->orig_msg->rep; + /* chase reply will be an edited (sub)set of the orig msg rrset ptrs */ + vq->chase_reply = region_alloc_init(qstate->region, vq->orig_msg->rep, + sizeof(struct reply_info) - sizeof(struct rrset_ref)); + if(!vq->chase_reply) + return NULL; + vq->chase_reply->rrsets = region_alloc_init(qstate->region, + vq->orig_msg->rep->rrsets, sizeof(struct ub_packed_rrset_key*) + * vq->orig_msg->rep->rrset_count); + if(!vq->chase_reply->rrsets) + return NULL; + vq->cname_skip = 0; return vq; } @@ -170,12 +180,14 @@ val_error(struct module_qstate* qstate, int id) * REFUSED, etc.) * * @param qstate: query state. - * @param vq: validator query state. + * @param ret_rc: rcode for this message (if noerror - examine ret_msg). + * @param ret_msg: return msg, can be NULL; look at rcode instead. * @return true if the response could use validation (although this does not * mean we can actually validate this response). */ static int -needs_validation(struct module_qstate* qstate, struct val_qstate* vq) +needs_validation(struct module_qstate* qstate, int ret_rc, + struct dns_msg* ret_msg) { int rcode; @@ -186,20 +198,23 @@ needs_validation(struct module_qstate* qstate, struct val_qstate* vq) return 0; } - /* validate unchecked, and re-validate bogus messages */ - if (vq->orig_msg->rep->security > sec_status_bogus) - { - verbose(VERB_ALGO, "response has already been validated"); - return 0; - } + if(ret_rc != LDNS_RCODE_NOERROR || !ret_msg) + rcode = ret_rc; + else rcode = (int)FLAGS_GET_RCODE(ret_msg->rep->flags); - rcode = (int)FLAGS_GET_RCODE(vq->orig_msg->rep->flags); if(rcode != LDNS_RCODE_NOERROR && rcode != LDNS_RCODE_NXDOMAIN) { verbose(VERB_ALGO, "cannot validate non-answer, rcode %s", ldns_lookup_by_id(ldns_rcodes, rcode)? ldns_lookup_by_id(ldns_rcodes, rcode)->name:"??"); return 0; } + + /* validate unchecked, and re-validate bogus messages */ + if (ret_msg && ret_msg->rep->security > sec_status_bogus) + { + verbose(VERB_ALGO, "response has already been validated"); + return 0; + } return 1; } @@ -422,8 +437,9 @@ validate_nodata_response(struct module_env* env, struct val_env* ve, /* Since we are here, there must be nothing in the ANSWER section to * validate. */ /* (Note: CNAME/DNAME responses will not directly get here -- - * instead they are broken down into individual CNAME/DNAME/final answer - * responses. - TODO this will change though) */ + * instead, they are chased down into indiviual CNAME validations, + * and at the end of the cname chain a POSITIVE, or CNAME_NOANSWER + * validation.) */ /* validate the AUTHORITY section */ int has_valid_nsec = 0; /* If true, then the NODATA has been proven.*/ @@ -648,6 +664,265 @@ validate_any_response(struct module_env* env, struct val_env* ve, chase_reply->security = sec_status_secure; } +/** + * Validate CNAME response, or DNAME+CNAME. + * This is just like a positive proof, except that this is about a + * DNAME+CNAME. Possible wildcard proof. + * Also refuses wildcarded DNAMEs. + * + * Note that by the time this method is called, the process of finding the + * trusted DNSKEY rrset that signs this response must already have been + * completed. + * + * @param env: module env for verify. + * @param ve: validator env for verify. + * @param qchase: query that was made. + * @param chase_reply: answer to that query to validate. + * @param key_entry: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). + */ +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* key_entry) +{ + uint8_t* wc = NULL; + int wc_NSEC_ok = 0; + int dname_seen = 0; + int nsec3s_seen = 0; + size_t i; + struct ub_packed_rrset_key* s; + enum sec_status sec; + + /* validate the ANSWER section - this will be the CNAME (+DNAME) */ + for(i=0; ian_numrrsets; i++) { + s = chase_reply->rrsets[i]; + /* Skip the CNAME following a (validated) DNAME. + * Because of the normalization routines in the iterator, + * there will always be an unsigned CNAME following a DNAME + * (unless qtype=DNAME). */ + if(dname_seen && ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME) { + dname_seen = 0; + /* CNAME was synthesized by our own iterator */ + /* since the DNAME verified, mark the CNAME as secure */ + ((struct packed_rrset_data*)s->entry.data)->security = + sec_status_secure; + ((struct packed_rrset_data*)s->entry.data)->trust = + rrset_trust_validated; + continue; + } + + /* Verify the answer rrset */ + sec = val_verify_rrset_entry(env, ve, s, key_entry); + /* If the (answer) rrset failed to validate, then this + * message is BAD. */ + if(sec != sec_status_secure) { + log_nametypeclass(VERB_ALGO, "Cname response has " + "failed ANSWER rrset: ", s->rk.dname, + ntohs(s->rk.type), ntohs(s->rk.rrset_class)); + chase_reply->security = sec_status_bogus; + return; + } + /* Check to see if the rrset is the result of a wildcard + * expansion. If so, an additional check will need to be + * made in the authority section. */ + if(!val_rrset_wildcard(s, &wc)) { + log_nametypeclass(VERB_ALGO, "Cname response has " + "inconsistent wildcard sigs: ", s->rk.dname, + ntohs(s->rk.type), ntohs(s->rk.rrset_class)); + chase_reply->security = sec_status_bogus; + return; + } + + /* Notice a DNAME that should be followed by an unsigned + * CNAME. */ + if(qchase->qtype != LDNS_RR_TYPE_DNAME && + ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME) { + dname_seen = 1; + /* we will not follow a wildcarded DNAME because + * its synthesized expansion is underdefined */ + if(dname_is_wild(s->rk.dname)) { + log_nametypeclass(VERB_ALGO, "cannot follow " + "wildcard DNAME: ", s->rk.dname, + ntohs(s->rk.type), ntohs(s->rk.rrset_class)); + chase_reply->security = sec_status_bogus; + return; + } + } + } + + /* validate the AUTHORITY section as well - this will generally be + * the NS rrset (which could be missing, no problem) */ + for(i=chase_reply->an_numrrsets; ian_numrrsets+ + chase_reply->ns_numrrsets; i++) { + s = chase_reply->rrsets[i]; + sec = val_verify_rrset_entry(env, ve, s, key_entry); + /* If anything in the authority section fails to be secure, + * we have a bad message. */ + if(sec != sec_status_secure) { + log_nametypeclass(VERB_ALGO, "Cname response has " + "failed AUTHORITY rrset: ", s->rk.dname, + ntohs(s->rk.type), ntohs(s->rk.rrset_class)); + chase_reply->security = sec_status_bogus; + return; + } + + /* If this is a positive wildcard response, and we have a + * (just verified) NSEC record, try to use it to 1) prove + * that qname doesn't exist and 2) that the correct wildcard + * was used. */ + if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { + if(val_nsec_proves_positive_wildcard(s, qchase, wc)) { + wc_NSEC_ok = 1; + } + /* if not, continue looking for proof */ + } + + /* Otherwise, if this is a positive wildcard response and + * we have NSEC3 records */ + if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { + nsec3s_seen = 1; + } + } + + /* 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) { + /* TODO NSEC3 positive wildcard proof */ + /* possibly: wc_NSEC_ok = 1; */ + } + + /* If after all this, we still haven't proven the positive wildcard + * response, fail. */ + if(wc != NULL && !wc_NSEC_ok) { + verbose(VERB_ALGO, "CNAME response was wildcard " + "expansion and did not prove original data " + "did not exist"); + chase_reply->security = sec_status_bogus; + return; + } + + verbose(VERB_ALGO, "Successfully validated CNAME response"); + chase_reply->security = sec_status_secure; +} + +/** + * Validate CNAME NOANSWER response, no more data after a CNAME chain. + * This can be a NODATA or a NAME ERROR case, but not both at the same time. + * We don't know because the rcode has been set to NOERROR by the CNAME. + * + * Note that by the time this method is called, the process of finding the + * trusted DNSKEY rrset that signs this response must already have been + * completed. + * + * @param env: module env for verify. + * @param ve: validator env for verify. + * @param qchase: query that was made. + * @param chase_reply: answer to that query to validate. + * @param key_entry: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). + */ +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* key_entry) +{ + /* Since we are here, there must be nothing in the ANSWER section to + * validate. */ + + /* validate the AUTHORITY section */ + int nodata_valid_nsec = 0; /* If true, then NODATA has been proven.*/ + uint8_t* ce = NULL; /* for wildcard nodata responses. This is the + proven closest encloser. */ + uint8_t* wc = NULL; /* for wildcard nodata responses. wildcard nsec */ + int nxdomain_valid_nsec = 0; /* if true, namerror has been proven */ + int nxdomain_valid_wnsec = 0; + int nsec3s_seen = 0; /* nsec3s seen */ + struct ub_packed_rrset_key* s; + enum sec_status sec; + size_t i; + + for(i=chase_reply->an_numrrsets; ian_numrrsets+ + chase_reply->ns_numrrsets; i++) { + s = chase_reply->rrsets[i]; + sec = val_verify_rrset_entry(env, ve, s, key_entry); + if(sec != sec_status_secure) { + log_nametypeclass(VERB_ALGO, "CNAMEnoanswer response has " + "failed AUTHORITY rrset: ", s->rk.dname, + ntohs(s->rk.type), ntohs(s->rk.rrset_class)); + chase_reply->security = sec_status_bogus; + return; + } + + /* If we encounter an NSEC record, try to use it to prove + * NODATA. + * This needs to handle the ENT NODATA case. */ + if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { + if(nsec_proves_nodata(s, qchase)) { + nodata_valid_nsec = 1; + if(dname_is_wild(s->rk.dname)) + wc = s->rk.dname; + } + if(val_nsec_proves_name_error(s, qchase->qname)) { + ce = nsec_closest_encloser(qchase->qname, s); + nxdomain_valid_nsec = 1; + } + if(val_nsec_proves_no_wc(s, qchase->qname, + qchase->qname_len)) + nxdomain_valid_wnsec = 1; + } else if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { + nsec3s_seen = 1; + } + } + + /* check to see if we have a wildcard NODATA proof. */ + + /* The wildcard NODATA is 1 NSEC proving that qname does not exists + * (and also proving what the closest encloser is), and 1 NSEC + * showing the matching wildcard, which must be *.closest_encloser. */ + if(wc && !ce) + nodata_valid_nsec = 0; + else if(wc && ce) { + log_assert(dname_is_wild(wc)); + /* first label wc is \001*, so remove and compare to ce */ + if(query_dname_compare(wc+2, ce) != 0) { + nodata_valid_nsec = 0; + } + } + if(nxdomain_valid_nsec && !nxdomain_valid_wnsec) { + /* name error is missing wildcard denial proof */ + nxdomain_valid_nsec = 0; + } + + if(nodata_valid_nsec && nxdomain_valid_nsec) { + verbose(VERB_ALGO, "CNAMEnoanswer proves that name exists " + "and not exists, bogus"); + chase_reply->security = sec_status_bogus; + return; + } + if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen) { + /* TODO handle NSEC3 proof here */ + /* and set nodata_valid_nsec=1; if so */ + } + + if(!nodata_valid_nsec && !nxdomain_valid_nsec) { + verbose(VERB_ALGO, "CNAMEnoanswer response failed to prove " + "status with NSEC/NSEC3"); + if(verbosity >= VERB_ALGO) + log_dns_msg("Failed CNAMEnoanswer", qchase, chase_reply); + chase_reply->security = sec_status_bogus; + return; + } + + if(nodata_valid_nsec) + verbose(VERB_ALGO, "successfully validated CNAME to a " + "NODATA response."); + else verbose(VERB_ALGO, "successfully validated CNAME to a " + "NAMEERROR response."); + chase_reply->security = sec_status_secure; +} + /** * Process init state for validator. * Process the INIT state. First tier responses start in the INIT state. @@ -672,12 +947,9 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq, { uint8_t* lookup_name; size_t lookup_len; - if(!needs_validation(qstate, vq)) { - qstate->return_rcode = LDNS_RCODE_NOERROR; - qstate->return_msg = vq->orig_msg; - qstate->ext_state[id] = module_finished; - return 0; - } + enum val_classification subtype = val_classify_response(&vq->qchase, + vq->orig_msg->rep, vq->cname_skip); + vq->trust_anchor = anchors_lookup(ve->anchors, vq->qchase.qname, vq->qchase.qname_len, vq->qchase.qclass); if(vq->trust_anchor == NULL) { @@ -689,8 +961,8 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq, } /* Determine the signer/lookup name */ - val_find_signer(&vq->qchase, vq->chase_reply, - &vq->signer_name, &vq->signer_len); + val_find_signer(subtype, &vq->qchase, vq->orig_msg->rep, + vq->cname_skip, &vq->signer_name, &vq->signer_len); if(vq->signer_name == NULL) { lookup_name = vq->qchase.qname; lookup_len = vq->qchase.qname_len; @@ -699,6 +971,14 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq, lookup_len = vq->signer_len; } + if(vq->cname_skip > 0 || subtype == VAL_CLASS_CNAME) { + /* extract this part of orig_msg into chase_reply for + * the eventual VALIDATE stage */ + val_fill_reply(vq->chase_reply, vq->orig_msg->rep, + vq->cname_skip, lookup_name, lookup_len); + log_dns_msg("chased extract", &vq->qchase, vq->chase_reply); + } + vq->key_entry = key_cache_obtain(ve->kcache, lookup_name, lookup_len, vq->qchase.qclass, qstate->region); @@ -877,7 +1157,8 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, return 1; } - subtype = val_classify_response(&vq->qchase, vq->chase_reply); + subtype = val_classify_response(&vq->qchase, vq->orig_msg->rep, + vq->cname_skip); switch(subtype) { case VAL_CLASS_POSITIVE: verbose(VERB_ALGO, "Validating a positive response"); @@ -899,13 +1180,31 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, 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); /* * TODO special CNAME state or routines + * validate CNAME + wildcard NSEC or DNAME. + * reject wildcarded DNAMEs. validate_cname_response(vq->qchase, vq->chase_reply, vq->key_entry); */ break; + case VAL_CLASS_CNAMENOANSWER: + verbose(VERB_ALGO, "Validating a cname noanswer " + "response"); + validate_cname_noanswer_response(qstate->env, ve, + &vq->qchase, vq->chase_reply, vq->key_entry); + /* + * TODO special CNAME state or routines + * see if nodata or noerror can be proven. + * but not both. + validate_cnamenoanswer_response(vq->qchase, + vq->chase_reply, vq->key_entry); + */ + break; + case VAL_CLASS_ANY: verbose(VERB_ALGO, "Validating a positive ANY " "response"); @@ -935,21 +1234,56 @@ static int processFinished(struct module_qstate* qstate, struct val_qstate* vq, struct val_env* ve, int id) { - /* TODO CNAME query restarts */ + enum val_classification subtype = val_classify_response(&vq->qchase, + vq->orig_msg->rep, vq->cname_skip); + + /* store overall validation result in orig_msg */ + if(vq->cname_skip == 0) + vq->orig_msg->rep->security = vq->chase_reply->security; + else { + /* use the lowest security status as end result. */ + if(vq->chase_reply->security < vq->orig_msg->rep->security) + vq->orig_msg->rep->security = + vq->chase_reply->security; + } + + if(vq->chase_reply->security != sec_status_bogus && + subtype == VAL_CLASS_CNAME) { + /* chase the CNAME; process next part of the message */ + if(!val_chase_cname(&vq->qchase, vq->orig_msg->rep, + &vq->cname_skip)) { + verbose(VERB_ALGO, "validator: failed to chase CNAME"); + vq->orig_msg->rep->security = sec_status_bogus; + } else { + /* restart process for new qchase at cname_skip */ + log_query_info(VERB_DETAIL, "validator: chased to", + &vq->qchase); + vq->chase_reply->security = sec_status_unchecked; + vq->state = VAL_INIT_STATE; + return 1; + } + } + + if(vq->orig_msg->rep->security == sec_status_secure) { + /* Do not store the validated status of the dropped RRsets. + * (only secure is reused). These rrsets are apparantly + * added on maliciously, or are unsigned additional data */ + val_dump_nonsecure(vq->orig_msg->rep); + } /* if the result is bogus - set message ttl to bogus ttl to avoid * endless bogus revalidation */ - if(vq->chase_reply->security == sec_status_bogus) { - vq->chase_reply->ttl = time(0) + ve->bogus_ttl; + if(vq->orig_msg->rep->security == sec_status_bogus) { + vq->orig_msg->rep->ttl = time(0) + ve->bogus_ttl; } /* store results in cache */ - if(!dns_cache_store(qstate->env, &vq->qchase, vq->chase_reply, 0)) { + if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo, + vq->orig_msg->rep, 0)) { log_err("out of memory caching validator results"); } qstate->return_rcode = LDNS_RCODE_NOERROR; qstate->return_msg = vq->orig_msg; - qstate->return_msg->rep = vq->chase_reply; qstate->ext_state[id] = module_finished; return 0; } @@ -1019,16 +1353,9 @@ val_operate(struct module_qstate* qstate, enum module_ev event, int id, if(event == module_event_moddone) { /* check if validation is needed */ verbose(VERB_ALGO, "validator: nextmodule returned"); - if(qstate->return_rcode != LDNS_RCODE_NOERROR && - qstate->return_rcode != LDNS_RCODE_NXDOMAIN && - qstate->return_rcode != LDNS_RCODE_YXDOMAIN) { - verbose(VERB_ALGO, "rcode for error not validated"); - qstate->ext_state[id] = module_finished; - return; - } - if(qstate->query_flags & BIT_CD) { + if(!needs_validation(qstate, qstate->return_rcode, + qstate->return_msg)) { /* no need to validate this */ - verbose(VERB_ALGO, "CD bit set: query not validated"); qstate->ext_state[id] = module_finished; return; } @@ -1175,7 +1502,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, goto return_bogus; } - subtype = val_classify_response(qinfo, msg->rep); + subtype = val_classify_response(qinfo, msg->rep, 0); if(subtype == VAL_CLASS_POSITIVE) { struct ub_packed_rrset_key* ds; enum sec_status sec; diff --git a/validator/validator.h b/validator/validator.h index 1d0989bfd..ea75e5ef8 100644 --- a/validator/validator.h +++ b/validator/validator.h @@ -113,10 +113,21 @@ struct val_qstate { * The chased reply, extract from original message. Can be: * o CNAME * o DNAME + CNAME - * o answer plus authority, additional (nsecs). + * o answer + * plus authority, additional (nsecs) that have same signature. */ struct reply_info* chase_reply; + /** + * The cname skip value; the number of rrsets that have been skipped + * due to chasing cnames. This is the offset into the + * orig_msg->rep->rrsets array, into the answer section. + * starts at 0 - for the full original message. + * if it is >0 - qchase followed the cname, chase_reply setup to be + * that message and relevant authority rrsets. + */ + size_t cname_skip; + /** the trust anchor rrset */ struct trust_anchor* trust_anchor;