ize; struct edns_data es = *edns; es.edns_version = EDNS_ADVERTISED_VERSION; es.udp_size = EDNS_ADVERTISED_SIZE; es.ext_rcode = (uint8_t)(rcode >> 4); es.bits &= EDNS_DO; - if(sldns_buffer_limit(buf) + calc_edns_field_size(&es) > - edns->udp_size) { + /* EDEs are optional. If space is a concern try in order: + * - removing any EXTRA-TEXT fields from explicit EDEs, or + * - removing all EDEs, + * to see if EDNS can fit. */ + edns_field_size = calc_edns_field_size(&es); + ede_size = calc_ede_option_size(&es, &ede_txt_size); + if((size_t)edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size) + attach_edns_record_max_msg_sz(buf, &es, edns->udp_size); + else if((size_t)edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size - ede_txt_size) { + ede_trim_text(&es.opt_list_inplace_cb_out); + ede_trim_text(&es.opt_list_out); + attach_edns_record_max_msg_sz(buf, &es, edns->udp_size); + } else if((size_t)edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size - ede_size) { edns_opt_list_remove(&es.opt_list_inplace_cb_out, LDNS_EDNS_EDE); edns_opt_list_remove(&es.opt_list_out, LDNS_EDNS_EDE); - if(sldns_buffer_limit(buf) + calc_edns_field_size(&es) > - edns->udp_size) { - return; - } + attach_edns_record_max_msg_sz(buf, &es, edns->udp_size); } - attach_edns_record(buf, &es); } } diff --git a/contrib/unbound/util/data/msgencode.h b/contrib/unbound/util/data/msgencode.h index 6aff06099ee9..f6297b78e419 100644 --- a/contrib/unbound/util/data/msgencode.h +++ b/contrib/unbound/util/data/msgencode.h @@ -106,7 +106,7 @@ void qinfo_query_encode(struct sldns_buffer* pkt, struct query_info* qinfo); * @param edns: edns data or NULL. * @return octets to reserve for EDNS. */ -uint16_t calc_edns_field_size(struct edns_data* edns); +size_t calc_edns_field_size(struct edns_data* edns); /** * Calculate the size of a specific EDNS option in packet. @@ -127,7 +127,7 @@ uint16_t calc_edns_option_size(struct edns_data* edns, uint16_t code); * extra text. * @return octets the option will take up. */ -uint16_t calc_ede_option_size(struct edns_data* edns, uint16_t* txt_size); +uint16_t calc_ede_option_size(struct edns_data* edns, size_t* txt_size); /** * Attach EDNS record to buffer. Buffer has complete packet. There must diff --git a/contrib/unbound/util/data/msgparse.c b/contrib/unbound/util/data/msgparse.c index 6963d850171e..169709b7e3c0 100644 --- a/contrib/unbound/util/data/msgparse.c +++ b/contrib/unbound/util/data/msgparse.c @@ -53,6 +53,8 @@ #include "sldns/parseutil.h" #include "sldns/wire2str.h" +#define MAX_PARSED_EDNS_OPTIONS 100 + /** smart comparison of (compressed, valid) dnames from packet */ static int smart_compare(sldns_buffer* pkt, uint8_t* dnow, @@ -950,6 +952,7 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, struct comm_reply* repinfo, uint32_t now, struct regional* region, struct cookie_secrets* cookie_secrets) { + int i = 0, nsid_seen = 0, cookie_seen = 0, padding_seen = 0; /* To respond with a Keepalive option, the client connection must have * received one message with a TCP Keepalive EDNS option, and that * option must have 0 length data. Subsequent messages sent on that @@ -969,7 +972,7 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, /* while still more options, and have code+len to read */ /* ignores partial content (i.e. rdata len 3) */ - while(rdata_len >= 4) { + while(rdata_len >= 4 && i < MAX_PARSED_EDNS_OPTIONS) { uint16_t opt_code = sldns_read_uint16(rdata_ptr); uint16_t opt_len = sldns_read_uint16(rdata_ptr+2); uint8_t server_cookie[40]; @@ -984,8 +987,9 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, /* handle parse time edns options here */ switch(opt_code) { case LDNS_EDNS_NSID: - if (!cfg || !cfg->nsid) + if (!cfg || !cfg->nsid || nsid_seen) break; + nsid_seen = 1; if(!edns_opt_list_append(&edns->opt_list_out, LDNS_EDNS_NSID, cfg->nsid_len, cfg->nsid, region)) { @@ -1027,8 +1031,9 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, case LDNS_EDNS_PADDING: if(!cfg || !cfg->pad_responses || - !c || c->type != comm_tcp ||!c->ssl) + !c || c->type != comm_tcp ||!c->ssl || padding_seen) break; + padding_seen = 1; if(!edns_opt_list_append(&edns->opt_list_out, LDNS_EDNS_PADDING, 0, NULL, region)) { @@ -1039,8 +1044,9 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, break; case LDNS_EDNS_COOKIE: - if(!cfg || !cfg->do_answer_cookie || !repinfo) + if(!cfg || !cfg->do_answer_cookie || !repinfo || cookie_seen) break; + cookie_seen = 1; if(opt_len != 8 && (opt_len < 16 || opt_len > 40)) { verbose(VERB_ALGO, "worker request: " "badly formatted cookie"); @@ -1146,6 +1152,7 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, } rdata_ptr += opt_len; rdata_len -= opt_len; + i++; } return LDNS_RCODE_NOERROR; } @@ -1160,6 +1167,7 @@ parse_extract_edns_from_response_msg(struct msg_parse* msg, struct rrset_parse* found_prev = 0; size_t rdata_len; uint8_t* rdata_ptr; + int i = 0; /* since the class encodes the UDP size, we cannot use hash table to * find the EDNS OPT record. Scan the packet. */ while(rrset) { @@ -1219,7 +1227,7 @@ parse_extract_edns_from_response_msg(struct msg_parse* msg, /* while still more options, and have code+len to read */ /* ignores partial content (i.e. rdata len 3) */ - while(rdata_len >= 4) { + while(rdata_len >= 4 && i < MAX_PARSED_EDNS_OPTIONS) { uint16_t opt_code = sldns_read_uint16(rdata_ptr); uint16_t opt_len = sldns_read_uint16(rdata_ptr+2); rdata_ptr += 4; @@ -1234,6 +1242,7 @@ parse_extract_edns_from_response_msg(struct msg_parse* msg, } rdata_ptr += opt_len; rdata_len -= opt_len; + i++; } /* ignore rrsigs */ return LDNS_RCODE_NOERROR; diff --git a/contrib/unbound/util/data/msgparse.h b/contrib/unbound/util/data/msgparse.h index 62f0d5aacd80..55d8229b8d40 100644 --- a/contrib/unbound/util/data/msgparse.h +++ b/contrib/unbound/util/data/msgparse.h @@ -98,6 +98,10 @@ extern time_t SERVE_EXPIRED_REPLY_TTL; /** If we serve the original TTL or decrementing TTLs */ extern int SERVE_ORIGINAL_TTL; +/** Check if TTL is expired. 0 TTL is considered expired. + * Used mainly to identify parts of the code that do this comparison. */ +#define TTL_IS_EXPIRED(ttl, now) ((ttl) <= (now)) + /** * Data stored in scratch pad memory during parsing. * Stores the data that will enter into the msgreply and packet result. diff --git a/contrib/unbound/validator/val_neg.c b/contrib/unbound/validator/val_neg.c index b5b678fdea68..3998b12010fe 100644 --- a/contrib/unbound/validator/val_neg.c +++ b/contrib/unbound/validator/val_neg.c @@ -62,6 +62,13 @@ #include "sldns/rrdef.h" #include "sldns/sbuffer.h" +/** + * The maximum salt length that the negative cache is willing to use. + * Larger salt increases the computation time, while recommendations are + * for zero salt length for zones. + */ +#define MAX_SALT_LENGTH 64 + int val_neg_data_compare(const void* a, const void* b) { struct val_neg_data* x = (struct val_neg_data*)a; @@ -826,7 +833,11 @@ void neg_insert_data(struct val_neg_cache* neg, (slen != 0 && zone->nsec3_salt && s && memcmp(zone->nsec3_salt, s, slen) != 0))) { - if(slen > 0) { + if(slen > MAX_SALT_LENGTH) { + /* RFC 9276 s3.1: operators SHOULD NOT use a salt; large + * salts inflate per-hash block count. Decline to cache. */ + return; + } else if(slen > 0) { uint8_t* sa = memdup(s, slen); if(sa) { free(zone->nsec3_salt); @@ -1169,6 +1180,15 @@ neg_find_nsec3_ce(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len, uint8_t hashce[NSEC3_SHA_LEN]; uint8_t b32[257]; size_t celen, b32len; + int hashmax = MAX_NSEC3_CALCULATIONS; + if(qlabs > hashmax) { + /* strip leading labels so the walk costs at most + * MAX_NSEC3_CALCULATIONS hashes, mirroring val_nsec3.c */ + while(qlabs > hashmax) { + dname_remove_label(&qname, &qname_len); + qlabs--; + } + } *nclen = 0; while(qlabs > 0) { @@ -1269,6 +1289,12 @@ neg_nsec3_proof_ds(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len, if(!zone->nsec3_hash) return NULL; /* not nsec3 zone */ + if(!topname && qlabs > zone->labs + 1) + return NULL; /* iterator caller; opt-out proof would be discarded + * at the !topname check below anyway. + * The qlabs check allows the exact-match for + * the one-label-below-zone case. */ + if(!(data=neg_find_nsec3_ce(zone, qname, qname_len, qlabs, buf, hashnc, &nclen))) { return NULL; diff --git a/contrib/unbound/validator/val_nsec3.c b/contrib/unbound/validator/val_nsec3.c index 998fcc4e38ee..62effde2093f 100644 --- a/contrib/unbound/validator/val_nsec3.c +++ b/contrib/unbound/validator/val_nsec3.c @@ -59,11 +59,6 @@ #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 @@ -456,6 +451,67 @@ filter_init(struct nsec3_filter* filter, struct ub_packed_rrset_key** list, } } +/** Check if the NSEC3s have the same parameter set. */ +static int +param_set_same(struct nsec3_filter* flt, char** reason) +{ + size_t rrsetnum; + int rrnum; + struct ub_packed_rrset_key* rrset; + int have_params = 0; + int first_algo = 0; + size_t first_iter = 0; + uint8_t* first_salt = NULL; + size_t first_saltlen = 0; + + /* If the NSEC3 parameter sets have distinct values, then they are + * from different NSEC3 chains, and we do not want that. */ + for(rrset=filter_first(flt, &rrsetnum, &rrnum); rrset; + rrset=filter_next(flt, &rrsetnum, &rrnum)) { + if(!have_params) { + first_algo = nsec3_get_algo(rrset, rrnum); + first_iter = nsec3_get_iter(rrset, rrnum); + if(!nsec3_get_salt(rrset, rrnum, &first_salt, + &first_saltlen)) { + verbose(VERB_ALGO, "NSEC3 salt malformed"); + if(reason) + *reason = "NSEC3 salt malformed"; + return 0; + } + have_params = 1; + } else { + uint8_t* salt = NULL; + size_t saltlen = 0; + if(nsec3_get_algo(rrset, rrnum) != first_algo) { + verbose(VERB_ALGO, "NSEC3 algorithm mismatch"); + if(reason) + *reason = "NSEC3 algorithm mismatch"; + return 0; + } + if(nsec3_get_iter(rrset, rrnum) != first_iter) { + verbose(VERB_ALGO, "NSEC3 iterations mismatch"); + if(reason) + *reason = "NSEC3 iterations mismatch"; + return 0; + } + if(!nsec3_get_salt(rrset, rrnum, &salt, &saltlen)) { + verbose(VERB_ALGO, "NSEC3 salt malformed"); + if(reason) + *reason = "NSEC3 salt malformed"; + return 0; + } + if(saltlen != first_saltlen || + memcmp(salt, first_salt, saltlen) != 0) { + verbose(VERB_ALGO, "NSEC3 salt mismatch"); + if(reason) + *reason = "NSEC3 salt mismatch"; + return 0; + } + } + } + return 1; +} + /** * Find max iteration count using config settings and key size * @param ve: validator environment with iteration count config settings. @@ -1192,6 +1248,8 @@ nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) return sec_status_bogus; /* no RRs */ + if(!param_set_same(&flt, NULL)) + return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ log_nametypeclass(VERB_ALGO, "start nsec3 nameerror proof, zone", @@ -1378,6 +1436,8 @@ nsec3_prove_nodata(struct module_env* env, struct val_env* ve, filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) return sec_status_bogus; /* no RRs */ + if(!param_set_same(&flt, NULL)) + return sec_status_bogus; /* nsec3 params from distinct chains*/ 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, calc); @@ -1401,6 +1461,8 @@ nsec3_prove_wildcard(struct module_env* env, struct val_env* ve, filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) return sec_status_bogus; /* no RRs */ + if(!param_set_same(&flt, NULL)) + return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ @@ -1503,6 +1565,8 @@ nsec3_prove_nods(struct module_env* env, struct val_env* ve, *reason = "no NSEC3 records"; return sec_status_bogus; /* no RRs */ } + if(!param_set_same(&flt, reason)) + return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ @@ -1596,6 +1660,8 @@ nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve, filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) return sec_status_bogus; /* no RRs */ + if(!param_set_same(&flt, NULL)) + return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ diff --git a/contrib/unbound/validator/val_nsec3.h b/contrib/unbound/validator/val_nsec3.h index f668a270ff12..a13e92991106 100644 --- a/contrib/unbound/validator/val_nsec3.h +++ b/contrib/unbound/validator/val_nsec3.h @@ -98,6 +98,12 @@ struct sldns_buffer; /** The SHA1 hash algorithm for NSEC3 */ #define NSEC3_HASH_SHA1 0x01 +/** + * 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 + /** * Cache table for NSEC3 hashes. * It keeps a *pointer* to the region its items are allocated. diff --git a/contrib/unbound/validator/val_utils.c b/contrib/unbound/validator/val_utils.c index 549264d76a1f..4495695ac853 100644 --- a/contrib/unbound/validator/val_utils.c +++ b/contrib/unbound/validator/val_utils.c @@ -1066,10 +1066,10 @@ val_fill_reply(struct reply_info* chase, struct reply_info* orig, if(query_dname_compare(name, orig->rrsets[i]->rk.dname) == 0) chase->rrsets[chase->an_numrrsets - +orig->ns_numrrsets+chase->ar_numrrsets++] + +chase->ns_numrrsets+chase->ar_numrrsets++] = orig->rrsets[i]; } else if(rrset_has_signer(orig->rrsets[i], name, len)) { - chase->rrsets[chase->an_numrrsets+orig->ns_numrrsets+ + chase->rrsets[chase->an_numrrsets+chase->ns_numrrsets+ chase->ar_numrrsets++] = orig->rrsets[i]; } }