diff --git a/services/authzone.c b/services/authzone.c index 3fe2cec53..042e93611 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -98,6 +98,11 @@ #define NUM_TIMEOUTS_FALLBACK_IXFR 3 /** number of IXFRs before an AXFR is performed, to consolidate RPZ memory. */ #define NUM_IXFR_BEFORE_AXFR 5 +/** The max number of decompression pointers that are allowed in a received + * xfr packet. The parse fails if the limit is exceeded. + * For a lot of PTR records, with compression for a deep zone, that is about + * 20x pointers per RR, of about 16 bytes, more than 4000*20 = 80000. */ +#define AUTH_XFR_DECOMPRESS_PACKET_LIMIT 128000 /** pick up nextprobe task to start waiting to perform transfer actions */ static void xfr_set_timeout(struct auth_xfer* xfr, struct module_env* env, @@ -1363,7 +1368,8 @@ az_remove_rr(struct auth_zone* z, uint8_t* rr, size_t rr_len, static int decompress_rr_into_buffer(struct sldns_buffer* buf, uint8_t* pkt, size_t pktlen, uint8_t* dname, uint16_t rr_type, uint16_t rr_class, - uint32_t rr_ttl, uint8_t* rr_data, uint16_t rr_rdlen) + uint32_t rr_ttl, uint8_t* rr_data, uint16_t rr_rdlen, int* budget, + int limit) { sldns_buffer pktbuf; size_t dname_len = 0; @@ -1377,7 +1383,7 @@ decompress_rr_into_buffer(struct sldns_buffer* buf, uint8_t* pkt, /* decompress dname */ sldns_buffer_set_position(&pktbuf, (size_t)(dname - sldns_buffer_current(&pktbuf))); - dname_len = pkt_dname_len(&pktbuf); + dname_len = pkt_dname_len_budget(&pktbuf, budget, limit); if(dname_len == 0) return 0; /* parse fail on dname */ if(!sldns_buffer_available(buf, dname_len)) return 0; dname_pkt_copy(&pktbuf, sldns_buffer_current(buf), dname); @@ -1412,7 +1418,8 @@ decompress_rr_into_buffer(struct sldns_buffer* buf, uint8_t* pkt, /* moves pktbuf to right after the * compressed dname, and returns uncompressed * dname length */ - uncompressed_len = pkt_dname_len(&pktbuf); + uncompressed_len = pkt_dname_len_budget( + &pktbuf, budget, limit); if(!uncompressed_len) return 0; /* parse error in dname */ compressed_len = sldns_buffer_position( @@ -1471,13 +1478,13 @@ static int az_insert_rr_decompress(struct auth_zone* z, uint8_t* pkt, size_t pktlen, struct sldns_buffer* scratch_buffer, uint8_t* dname, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, uint8_t* rr_data, - uint16_t rr_rdlen, int* duplicate) + uint16_t rr_rdlen, int* duplicate, int* budget, int limit) { uint8_t* rr; size_t rr_len; size_t dname_len; if(!decompress_rr_into_buffer(scratch_buffer, pkt, pktlen, dname, - rr_type, rr_class, rr_ttl, rr_data, rr_rdlen)) { + rr_type, rr_class, rr_ttl, rr_data, rr_rdlen, budget, limit)) { log_err("could not decompress RR"); return 0; } @@ -1493,13 +1500,13 @@ static int az_remove_rr_decompress(struct auth_zone* z, uint8_t* pkt, size_t pktlen, struct sldns_buffer* scratch_buffer, uint8_t* dname, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, uint8_t* rr_data, - uint16_t rr_rdlen, int* nonexist) + uint16_t rr_rdlen, int* nonexist, int* budget, int limit) { uint8_t* rr; size_t rr_len; size_t dname_len; if(!decompress_rr_into_buffer(scratch_buffer, pkt, pktlen, dname, - rr_type, rr_class, rr_ttl, rr_data, rr_rdlen)) { + rr_type, rr_class, rr_ttl, rr_data, rr_rdlen, budget, limit)) { log_err("could not decompress RR"); return 0; } @@ -4873,7 +4880,7 @@ chunk_rrlist_end(struct auth_chunk* rr_chunk, int rr_num) /** RR list iterator, move to next RR */ static void chunk_rrlist_gonext(struct auth_chunk** rr_chunk, int* rr_num, - size_t* rr_pos, size_t rr_nextpos) + size_t* rr_pos, size_t rr_nextpos, int* budget) { /* already at end of chunks? */ if(!*rr_chunk) @@ -4887,6 +4894,7 @@ chunk_rrlist_gonext(struct auth_chunk** rr_chunk, int* rr_num, } /* no more RRs in this chunk */ /* continue with next chunk, see if it has RRs */ + *budget = 0; /* new budget for next packet */ if(*rr_chunk) *rr_chunk = (*rr_chunk)->next; while(*rr_chunk) { @@ -4970,11 +4978,12 @@ static int ixfr_start_serial(struct auth_chunk* rr_chunk, int rr_num, size_t rr_pos, uint8_t* rr_dname, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, uint16_t rr_rdlen, uint8_t* rr_rdata, - size_t rr_nextpos, uint32_t transfer_serial, uint32_t xfr_serial) + size_t rr_nextpos, uint32_t transfer_serial, uint32_t xfr_serial, + int* budget) { uint32_t startserial; /* move forward on RR */ - chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos); + chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos, budget); if(chunk_rrlist_end(rr_chunk, rr_num)) { /* no second SOA */ verbose(VERB_OPS, "IXFR has no second SOA record"); @@ -5031,6 +5040,7 @@ apply_ixfr(struct auth_xfer* xfr, struct auth_zone* z, size_t rr_counter = 0; int delmode = 0; int softfail = 0; + int budget = 0, limit = AUTH_XFR_DECOMPRESS_PACKET_LIMIT; xfr->num_ixfrs++; @@ -5066,7 +5076,8 @@ apply_ixfr(struct auth_xfer* xfr, struct auth_zone* z, if(!ixfr_start_serial(rr_chunk, rr_num, rr_pos, rr_dname, rr_type, rr_class, rr_ttl, rr_rdlen, rr_rdata, rr_nextpos, - transfer_serial, xfr->serial)) { + transfer_serial, xfr->serial, + &budget)) { return 0; } } else if(transfer_serial == serial) { @@ -5110,7 +5121,7 @@ apply_ixfr(struct auth_xfer* xfr, struct auth_zone* z, if(!az_remove_rr_decompress(z, rr_chunk->data, rr_chunk->len, scratch_buffer, rr_dname, rr_type, rr_class, rr_ttl, rr_rdata, rr_rdlen, - &nonexist)) { + &nonexist, &budget, limit)) { /* failed, malloc error or so */ return 0; } @@ -5130,7 +5141,7 @@ apply_ixfr(struct auth_xfer* xfr, struct auth_zone* z, if(!az_insert_rr_decompress(z, rr_chunk->data, rr_chunk->len, scratch_buffer, rr_dname, rr_type, rr_class, rr_ttl, rr_rdata, rr_rdlen, - &duplicate)) { + &duplicate, &budget, limit)) { /* failed, malloc error or so */ return 0; } @@ -5144,7 +5155,8 @@ apply_ixfr(struct auth_xfer* xfr, struct auth_zone* z, } rr_counter++; - chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos); + chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos, + &budget); } if(softfail) { verbose(VERB_ALGO, "IXFR did not apply cleanly, fetching full zone"); @@ -5168,6 +5180,7 @@ apply_axfr(struct auth_xfer* xfr, struct auth_zone* z, size_t rr_nextpos; size_t rr_counter = 0; int have_end_soa = 0; + int budget = 0, limit = AUTH_XFR_DECOMPRESS_PACKET_LIMIT; auth_zone_clear_data(z); xfr->have_zone = 0; @@ -5201,13 +5214,14 @@ apply_axfr(struct auth_xfer* xfr, struct auth_zone* z, /* add this RR */ if(!az_insert_rr_decompress(z, rr_chunk->data, rr_chunk->len, scratch_buffer, rr_dname, rr_type, rr_class, rr_ttl, - rr_rdata, rr_rdlen, NULL)) { + rr_rdata, rr_rdlen, NULL, &budget, limit)) { /* failed, malloc error or so */ return 0; } rr_counter++; - chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos); + chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos, + &budget); } if(!have_end_soa) { log_err("no end SOA record for AXFR"); @@ -6049,7 +6063,7 @@ check_xfer_packet(sldns_buffer* pkt, struct auth_xfer* xfr, int* gonextonfail, int* transferdone) { uint8_t* wire = sldns_buffer_begin(pkt); - int i; + int i, budget = 0, limit = AUTH_XFR_DECOMPRESS_PACKET_LIMIT; if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) { verbose(VERB_ALGO, "xfr to %s failed, packet too small", xfr->task_transfer->master->host); @@ -6111,7 +6125,7 @@ check_xfer_packet(sldns_buffer* pkt, struct auth_xfer* xfr, for(i=0; i<(int)LDNS_QDCOUNT(wire); i++) { size_t pos = sldns_buffer_position(pkt); uint16_t qtype, qclass; - if(pkt_dname_len(pkt) == 0) { + if(pkt_dname_len_budget(pkt, &budget, limit) == 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed dname", xfr->task_transfer->master->host); @@ -6161,7 +6175,7 @@ check_xfer_packet(sldns_buffer* pkt, struct auth_xfer* xfr, for(i=0; i<(int)LDNS_ANCOUNT(wire); i++) { size_t pos = sldns_buffer_position(pkt); uint16_t tp, rdlen; - if(pkt_dname_len(pkt) == 0) { + if(pkt_dname_len_budget(pkt, &budget, limit) == 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed dname in answer section", xfr->task_transfer->master->host); @@ -6277,7 +6291,7 @@ check_xfer_packet(sldns_buffer* pkt, struct auth_xfer* xfr, /* we skip over the RRs checking packet format */ for(i=0; i<(int)LDNS_NSCOUNT(wire); i++) { uint16_t rdlen; - if(pkt_dname_len(pkt) == 0) { + if(pkt_dname_len_budget(pkt, &budget, limit) == 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed dname in authority section", xfr->task_transfer->master->host); @@ -6306,7 +6320,7 @@ check_xfer_packet(sldns_buffer* pkt, struct auth_xfer* xfr, /* check additional section */ for(i=0; i<(int)LDNS_ARCOUNT(wire); i++) { uint16_t rdlen; - if(pkt_dname_len(pkt) == 0) { + if(pkt_dname_len_budget(pkt, &budget, limit) == 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed dname in additional section", xfr->task_transfer->master->host); diff --git a/services/mesh.c b/services/mesh.c index 04dedd329..136465d8b 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -506,8 +506,10 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, "incoming query."); if(rep->c->use_h2) http2_stream_remove_mesh_state(rep->c->h2_stream); +#ifdef HAVE_NGTCP2 else if(rep->c->type == comm_doq && rep->doq_stream) - doq_stream_remove_mesh_state(rep->doq_stream); + doq_find_stream_remove_mesh_state(rep); +#endif comm_point_drop_reply(rep); mesh->stats_dropped++; return; @@ -521,8 +523,10 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, "dropping incoming query."); if(rep->c->use_h2) http2_stream_remove_mesh_state(rep->c->h2_stream); +#ifdef HAVE_NGTCP2 else if(rep->c->type == comm_doq && rep->doq_stream) - doq_stream_remove_mesh_state(rep->doq_stream); + doq_find_stream_remove_mesh_state(rep); +#endif comm_point_drop_reply(rep); mesh->num_queries_replyaddr_limit++; return; @@ -598,6 +602,9 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, if(rep->c->use_h2) { http2_stream_add_meshstate(rep->c->h2_stream, mesh, s); } else if(rep->c->type == comm_doq && rep->doq_stream) { + /* The mesh_new_client call is in the worker callback, that + * is called from the doq handler that has the conn locked + * for this stream. It can add directly, without more locks. */ doq_stream_add_meshstate(rep->doq_stream, mesh, s); } /* add serve expired timer if required and not already there */ @@ -655,6 +662,9 @@ servfail_mem: if(rep->c->use_h2) http2_stream_remove_mesh_state(rep->c->h2_stream); else if(rep->c->type == comm_doq && rep->doq_stream) + /* The mesh_new_client call is in the worker callback, that + * is called from the doq handler that has the conn locked + * for this stream. It can remove without more locks. */ doq_stream_remove_mesh_state(rep->doq_stream); comm_point_send_reply(rep); if(added_reply_without_accounting) { @@ -1145,9 +1155,10 @@ mesh_state_cleanup(struct mesh_state* mstate) mstate); else if(rep->query_reply.c->use_h2) http2_stream_remove_mesh_state(rep->h2_stream); +#ifdef HAVE_NGTCP2 else if(rep->query_reply.doq_stream) - doq_stream_remove_mesh_state( - rep->query_reply.doq_stream); + doq_find_stream_remove_mesh_state(&rep->query_reply); +#endif comm_point_drop_reply(&rep->query_reply); log_assert(mesh->num_reply_addrs > 0); mesh->num_reply_addrs--; @@ -1538,7 +1549,7 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, r->h2_stream->mesh_state = NULL; #ifdef HAVE_NGTCP2 } else if(r->query_reply.doq_stream) { - r->query_reply.doq_stream->mesh_state = NULL; + doq_find_stream_remove_mesh_state(&r->query_reply); #endif } /* send the reply */ @@ -1837,8 +1848,10 @@ void mesh_query_done(struct mesh_state* mstate) mstate); else if(r->query_reply.c->use_h2) http2_stream_remove_mesh_state(r->h2_stream); +#ifdef HAVE_NGTCP2 else if(r->query_reply.doq_stream) - doq_stream_remove_mesh_state(r->query_reply.doq_stream); + doq_find_stream_remove_mesh_state(&r->query_reply); +#endif comm_point_drop_reply(&r->query_reply); log_assert(mstate->s.env->mesh->num_reply_addrs > 0); mstate->s.env->mesh->num_reply_addrs--; @@ -1871,8 +1884,10 @@ void mesh_query_done(struct mesh_state* mstate) r->query_reply.c->tcp_req_info, mstate); } else if(r->query_reply.c->use_h2) { http2_stream_remove_mesh_state(r->h2_stream); +#ifdef HAVE_NGTCP2 } else if(r->query_reply.doq_stream) { - doq_stream_remove_mesh_state(r->query_reply.doq_stream); + doq_find_stream_remove_mesh_state(&r->query_reply); +#endif } comm_point_drop_reply(&r->query_reply); log_assert(mstate->s.env->mesh->num_reply_addrs > 0); @@ -2481,8 +2496,11 @@ void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m, * don't point back. */ if(n->h2_stream) n->h2_stream->mesh_state = NULL; #ifdef HAVE_NGTCP2 - if(n->query_reply.doq_stream) - n->query_reply.doq_stream->mesh_state = NULL; + /* When doq calls with doq_stream nonNULL, it removes + * the mesh_reply itself, and also already has the conn + * locked, so no need to remove it here */ + if(!doq_stream && n->query_reply.doq_stream) + doq_find_stream_remove_mesh_state(&n->query_reply); #endif /* prev = prev; */ @@ -2663,8 +2681,10 @@ mesh_serve_expired_callback(void* arg) r->query_reply.c->tcp_req_info, mstate); else if(r->query_reply.c->use_h2) http2_stream_remove_mesh_state(r->h2_stream); +#ifdef HAVE_NGTCP2 else if(r->query_reply.doq_stream) - doq_stream_remove_mesh_state(r->query_reply.doq_stream); + doq_find_stream_remove_mesh_state(&r->query_reply); +#endif comm_point_drop_reply(&r->query_reply); log_assert(mstate->s.env->mesh->num_reply_addrs > 0); mstate->s.env->mesh->num_reply_addrs--; diff --git a/services/outside_network.c b/services/outside_network.c index 0f22c920f..f1571751d 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -1524,6 +1524,49 @@ outnet_send_wait_udp(struct outside_network* outnet) } } +/** Clear out all pending requests that are on the port. */ +static void +clear_pending_from_port(struct outside_network* outnet, struct comm_point* c) +{ + /* Otherwise, the clear the cache action of unwanted_threshold, can + * have revealed this communication port. */ + struct pending* p, *np, *list = NULL, *last = NULL; + /* First, remove the entries from the tree. */ + /* The rbtree rbnode.right is used to build the temporary list here. + * Otherwise the list could be malloced. */ + p = (struct pending*)rbtree_first(outnet->pending); + while((rbnode_type*)p != RBTREE_NULL) { + np = (struct pending*)rbtree_next((rbnode_type*)p); + if(p->pc->cp == c) { + comm_timer_disable(p->timer); + (void)rbtree_delete(outnet->pending, p->node.key); + if(last) + last->node.right = (rbnode_type*)p; + else list = p; + last = p; + p->node.right = NULL; + } + p = np; + } + + /* The entries are removed from the tree, call their callbacks. */ + p = list; + while(p) { + np = (struct pending*)p->node.right; + p->node.right = RBTREE_NULL; + /* callback error on pending */ + if(p->cb) { + fptr_ok(fptr_whitelist_pending_udp(p->cb)); + (void)(*p->cb)(p->pc->cp, p->cb_arg, + NETEVENT_CLOSED, NULL); + } + portcomm_loweruse(outnet, p->pc); + pending_delete(NULL, p); + outnet_send_wait_udp(outnet); + p = np; + } +} + int outnet_udp_cb(struct comm_point* c, void* arg, int error, struct comm_reply *reply_info) @@ -1568,6 +1611,7 @@ outnet_udp_cb(struct comm_point* c, void* arg, int error, outnet->unwanted_action)); (*outnet->unwanted_action)(outnet->unwanted_param); outnet->unwanted_total = 0; + clear_pending_from_port(outnet, c); } return 0; } @@ -1588,6 +1632,7 @@ outnet_udp_cb(struct comm_point* c, void* arg, int error, outnet->unwanted_action)); (*outnet->unwanted_action)(outnet->unwanted_param); outnet->unwanted_total = 0; + clear_pending_from_port(outnet, c); } return 0; } diff --git a/util/data/dname.c b/util/data/dname.c index 9fbbe1092..9c03b778b 100644 --- a/util/data/dname.c +++ b/util/data/dname.c @@ -236,6 +236,61 @@ fail: return 0; } +size_t +pkt_dname_len_budget(struct sldns_buffer* pkt, int* budget, int limit) +{ + size_t len = 0; + int ptrcount = 0; + uint8_t labellen; + size_t endpos = 0; + + /* read dname and determine length */ + /* check compression pointers, loops, out of bounds */ + while(1) { + /* read next label */ + if(sldns_buffer_remaining(pkt) < 1) + return 0; + labellen = sldns_buffer_read_u8(pkt); + if(LABEL_IS_PTR(labellen)) { + /* compression ptr */ + uint16_t ptr; + if(sldns_buffer_remaining(pkt) < 1) + return 0; + ptr = PTR_OFFSET(labellen, sldns_buffer_read_u8(pkt)); + if(++(*budget) > limit) { + verbose(VERB_ALGO, "The decompression pointer " + "limit of %d has been exceeded", limit); + return 0; /* too many decompression pointers */ + } + if(ptrcount++ > MAX_COMPRESS_PTRS) + return 0; /* loop! */ + if(sldns_buffer_limit(pkt) <= ptr) + return 0; /* out of bounds! */ + if(!endpos) + endpos = sldns_buffer_position(pkt); + sldns_buffer_set_position(pkt, ptr); + } else { + /* label contents */ + if(labellen > 0x3f) + return 0; /* label too long */ + len += 1 + labellen; + if(len > LDNS_MAX_DOMAINLEN) + return 0; + if(labellen == 0) { + /* end of dname */ + break; + } + if(sldns_buffer_remaining(pkt) < labellen) + return 0; + sldns_buffer_skip(pkt, (ssize_t)labellen); + } + } + if(endpos) + sldns_buffer_set_position(pkt, endpos); + + return len; +} + int dname_pkt_compare(sldns_buffer* pkt, uint8_t* d1, uint8_t* d2) { diff --git a/util/data/dname.h b/util/data/dname.h index f68c64a03..9be792ee1 100644 --- a/util/data/dname.h +++ b/util/data/dname.h @@ -109,6 +109,22 @@ int query_dname_compare(uint8_t* d1, uint8_t* d2); */ size_t pkt_dname_len(struct sldns_buffer* pkt); +/** + * Determine correct, compressed, dname present in packet. + * Checks for parse errors. Has a budget and limit for decompression, + * if the limit is exceeded, this is an error. + * @param pkt: packet to read from (from current start position). + * @param budget: the budget of decompression pointer follows. + * It is updated with the additional count (if any) of pointer follows, + * so that a running total can be kept over a number of dnames. + * @param limit: the limit of decompression pointer follows. + * @return: 0 on parse error. + * At exit the position is right after the (compressed) dname. + * Compression pointers are followed and checked for loops. + * The uncompressed wireformat length is returned. + */ +size_t pkt_dname_len_budget(struct sldns_buffer* pkt, int* budget, int limit); + /** * Compare dnames in packet (compressed). Dnames must be valid. * routine performs lowercasing, so the packet casing is preserved. diff --git a/util/data/msgparse.c b/util/data/msgparse.c index f9beb605c..8d878798c 100644 --- a/util/data/msgparse.c +++ b/util/data/msgparse.c @@ -54,6 +54,9 @@ #include "sldns/wire2str.h" #define MAX_PARSED_EDNS_OPTIONS 100 +/** The max number of decompression pointers that are allowed in a received + * packet. The parse fails if the limit is exceeded. */ +#define PARSE_DECOMPRESS_LIMIT 2048 /** smart comparison of (compressed, valid) dnames from packet */ static int @@ -114,7 +117,7 @@ new_rrset(struct msg_parse* msg, uint8_t* dname, size_t dnamelen, /** See if next rrset is nsec at zone apex */ static int -nsec_at_apex(sldns_buffer* pkt) +nsec_at_apex(sldns_buffer* pkt, int* budget, int limit) { /* we are at ttl position in packet. */ size_t pos = sldns_buffer_position(pkt); @@ -128,7 +131,7 @@ nsec_at_apex(sldns_buffer* pkt) return 0; /* parse error happens later */ } /* must validate the nsec next domain name format */ - if(pkt_dname_len(pkt) == 0) { + if(pkt_dname_len_budget(pkt, budget, limit) == 0) { sldns_buffer_set_position(pkt, pos); return 0; /* parse error */ } @@ -159,10 +162,11 @@ nsec_at_apex(sldns_buffer* pkt) /** Calculate rrset flags */ static uint32_t -pkt_rrset_flags(sldns_buffer* pkt, uint16_t type, sldns_pkt_section sec) +pkt_rrset_flags(sldns_buffer* pkt, uint16_t type, sldns_pkt_section sec, + int* budget, int limit) { uint32_t f = 0; - if(type == LDNS_RR_TYPE_NSEC && nsec_at_apex(pkt)) { + if(type == LDNS_RR_TYPE_NSEC && nsec_at_apex(pkt, budget, limit)) { f |= PACKED_RRSET_NSEC_AT_APEX; } else if(type == LDNS_RR_TYPE_SOA && sec == LDNS_SECTION_AUTHORITY) { f |= PACKED_RRSET_SOA_NEG; @@ -462,6 +466,8 @@ change_rrsig_rrset(struct rrset_parse* sigset, struct msg_parse* msg, * @param rrset_prev: last seen RRset. * @param section: the current section in the packet. * @param region: used to allocate temporary parsing data. + * @param budget: decompression budget. + * @param limit: decompression limit. * @return 0 on out of memory. */ static int @@ -471,7 +477,8 @@ find_rrset(struct msg_parse* msg, sldns_buffer* pkt, uint8_t* dname, uint8_t** prev_dname_first, uint8_t** prev_dname_last, size_t* prev_dnamelen, uint16_t* prev_type, uint16_t* prev_dclass, struct rrset_parse** rrset_prev, - sldns_pkt_section section, struct regional* region) + sldns_pkt_section section, struct regional* region, int* budget, + int limit) { hashvalue_type dname_h = pkt_hash_rrset_first(pkt, dname); uint16_t covtype; @@ -498,7 +505,7 @@ find_rrset(struct msg_parse* msg, sldns_buffer* pkt, uint8_t* dname, } } /* find by hashing and lookup in hashtable */ - *rrset_flags = pkt_rrset_flags(pkt, type, section); + *rrset_flags = pkt_rrset_flags(pkt, type, section, budget, limit); /* if rrsig - try to lookup matching data set first */ if(type == LDNS_RR_TYPE_RRSIG && pkt_rrsig_covered(pkt, @@ -580,10 +587,13 @@ find_rrset(struct msg_parse* msg, sldns_buffer* pkt, uint8_t* dname, * @param pkt: packet, position at call must be at start of query section. * at end position is after query section. * @param msg: store results here. + * @param budget: decompression budget. + * @param limit: decompression limit. * @return: 0 if OK, or rcode on error. */ static int -parse_query_section(sldns_buffer* pkt, struct msg_parse* msg) +parse_query_section(sldns_buffer* pkt, struct msg_parse* msg, int* budget, + int limit) { if(msg->qdcount == 0) return 0; @@ -593,7 +603,7 @@ parse_query_section(sldns_buffer* pkt, struct msg_parse* msg) if(sldns_buffer_remaining(pkt) <= 0) return LDNS_RCODE_FORMERR; msg->qname = sldns_buffer_current(pkt); - if((msg->qname_len = pkt_dname_len(pkt)) == 0) + if((msg->qname_len = pkt_dname_len_budget(pkt, budget, limit)) == 0) return LDNS_RCODE_FORMERR; if(sldns_buffer_remaining(pkt) < sizeof(uint16_t)*2) return LDNS_RCODE_FORMERR; @@ -638,7 +648,8 @@ get_rdf_size(sldns_rdf_type rdf) /** calculate the size of one rr */ static int -calc_size(sldns_buffer* pkt, uint16_t type, struct rr_parse* rr) +calc_size(sldns_buffer* pkt, uint16_t type, struct rr_parse* rr, int* budget, + int limit) { const sldns_rr_descriptor* desc; uint16_t pkt_len; /* length of rr inside the packet */ @@ -659,7 +670,8 @@ calc_size(sldns_buffer* pkt, uint16_t type, struct rr_parse* rr) case LDNS_RDF_TYPE_DNAME: /* decompress every domain name */ oldpos = sldns_buffer_position(pkt); - if((len = pkt_dname_len(pkt)) == 0) + if((len = pkt_dname_len_budget(pkt, budget, + limit)) == 0) return 0; /* malformed dname */ if(sldns_buffer_position(pkt)-oldpos > pkt_len) return 0; /* dname exceeds rdata */ @@ -758,7 +770,7 @@ sig_is_double(sldns_buffer* pkt, struct rrset_parse* rrset, uint8_t* ttldata) static int add_rr_to_rrset(struct rrset_parse* rrset, sldns_buffer* pkt, struct msg_parse* msg, struct regional* region, - sldns_pkt_section section, uint16_t type) + sldns_pkt_section section, uint16_t type, int* budget, int limit) { struct rr_parse* rr; /* check section of rrset. */ @@ -809,8 +821,10 @@ add_rr_to_rrset(struct rrset_parse* rrset, sldns_buffer* pkt, } /* calc decompressed size */ - if(!calc_size(pkt, type, rr)) + if(!calc_size(pkt, type, rr, budget, limit)) { + if(*budget > limit) return LDNS_RCODE_SERVFAIL; return LDNS_RCODE_FORMERR; + } rrset->size += rr->size; return 0; @@ -825,12 +839,14 @@ add_rr_to_rrset(struct rrset_parse* rrset, sldns_buffer* pkt, * @param section: section enum. * @param num_rrs: how many rrs are in the section. * @param num_rrsets: returns number of rrsets in the section. + * @param budget: decompression budget. + * @param limit: decompression limit. * @return: 0 if OK, or rcode on error. */ static int parse_section(sldns_buffer* pkt, struct msg_parse* msg, struct regional* region, sldns_pkt_section section, - uint16_t num_rrs, size_t* num_rrsets) + uint16_t num_rrs, size_t* num_rrsets, int* budget, int limit) { uint16_t i; uint8_t* dname, *prev_dname_f = NULL, *prev_dname_l = NULL; @@ -849,8 +865,10 @@ parse_section(sldns_buffer* pkt, struct msg_parse* msg, for(i=0; i limit) return LDNS_RCODE_SERVFAIL; return LDNS_RCODE_FORMERR; + } if(sldns_buffer_remaining(pkt) < 10) /* type, class, ttl, len */ return LDNS_RCODE_FORMERR; type = sldns_buffer_read_u16(pkt); @@ -886,7 +904,7 @@ parse_section(sldns_buffer* pkt, struct msg_parse* msg, if(!find_rrset(msg, pkt, dname, dnamelen, type, dclass, &hash, &rrset_flags, &prev_dname_f, &prev_dname_l, &prev_dnamelen, &prev_type, &prev_dclass, &rrset, - section, region)) + section, region, budget, limit)) return LDNS_RCODE_SERVFAIL; if(!rrset) { /* it is a new RR set. hash&flags already calculated.*/ @@ -906,7 +924,7 @@ parse_section(sldns_buffer* pkt, struct msg_parse* msg, } /* add to rrset. */ if((r=add_rr_to_rrset(rrset, pkt, msg, region, section, - type)) != 0) + type, budget, limit)) != 0) return r; } return 0; @@ -915,7 +933,7 @@ parse_section(sldns_buffer* pkt, struct msg_parse* msg, int parse_packet(sldns_buffer* pkt, struct msg_parse* msg, struct regional* region) { - int ret; + int ret, budget = 0, limit = PARSE_DECOMPRESS_LIMIT; if(sldns_buffer_remaining(pkt) < LDNS_HEADER_SIZE) return LDNS_RCODE_FORMERR; /* read the header */ @@ -927,19 +945,20 @@ parse_packet(sldns_buffer* pkt, struct msg_parse* msg, struct regional* region) msg->arcount = sldns_buffer_read_u16(pkt); if(msg->qdcount > 1) return LDNS_RCODE_FORMERR; - if((ret = parse_query_section(pkt, msg)) != 0) + if((ret = parse_query_section(pkt, msg, &budget, limit)) != 0) return ret; if((ret = parse_section(pkt, msg, region, LDNS_SECTION_ANSWER, - msg->ancount, &msg->an_rrsets)) != 0) + msg->ancount, &msg->an_rrsets, &budget, limit)) != 0) return ret; if((ret = parse_section(pkt, msg, region, LDNS_SECTION_AUTHORITY, - msg->nscount, &msg->ns_rrsets)) != 0) + msg->nscount, &msg->ns_rrsets, &budget, limit)) != 0) return ret; if(sldns_buffer_remaining(pkt) == 0 && msg->arcount == 1) { /* BIND accepts leniently that an EDNS record is missing. * so, we do too. */ } else if((ret = parse_section(pkt, msg, region, - LDNS_SECTION_ADDITIONAL, msg->arcount, &msg->ar_rrsets)) != 0) + LDNS_SECTION_ADDITIONAL, msg->arcount, &msg->ar_rrsets, + &budget, limit)) != 0) return ret; /* if(sldns_buffer_remaining(pkt) > 0) { */ /* there is spurious data at end of packet. ignore */ diff --git a/util/netevent.c b/util/netevent.c index 47e7c4b78..172e85826 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -2828,7 +2828,7 @@ doq_lookup_repinfo(struct doq_table* table, struct comm_reply* repinfo) /** doq find connection and stream. From inside callbacks from worker. */ static int doq_lookup_conn_stream(struct comm_reply* repinfo, struct comm_point* c, - struct doq_conn** conn, struct doq_stream** stream) + struct doq_conn** conn, struct doq_stream** stream, int checkclose) { log_assert(c->doq_socket); if(c->doq_socket->current_conn) { @@ -2856,7 +2856,7 @@ doq_lookup_conn_stream(struct comm_reply* repinfo, struct comm_point* c, } return 0; } - if((*stream)->is_closed) { + if(checkclose && (*stream)->is_closed) { /* stream is closed, ignore reply or drop */ if(!c->doq_socket->current_conn) { /* Not inside callbacks, we have our own lock on conn. @@ -2868,6 +2868,24 @@ doq_lookup_conn_stream(struct comm_reply* repinfo, struct comm_point* c, return 1; } +/** doq remove mesh state from stream */ +void doq_find_stream_remove_mesh_state(struct comm_reply* repinfo) +{ + struct doq_conn* conn; + struct doq_stream* stream; + log_assert(repinfo->c->type == comm_doq); + if(!doq_lookup_conn_stream(repinfo, repinfo->c, &conn, &stream, 0)) { + /* No stream, it may have been removed. */ + return; + } + doq_stream_remove_mesh_state(stream); + if(!repinfo->c->doq_socket->current_conn) { + /* Not inside callbacks, we have our own lock on conn. + * Release it. */ + lock_basic_unlock(&conn->lock); + } +} + /** doq send a reply from a comm reply */ static void doq_socket_send_reply(struct comm_reply* repinfo) @@ -2875,7 +2893,7 @@ doq_socket_send_reply(struct comm_reply* repinfo) struct doq_conn* conn; struct doq_stream* stream; log_assert(repinfo->c->type == comm_doq); - if(!doq_lookup_conn_stream(repinfo, repinfo->c, &conn, &stream)) { + if(!doq_lookup_conn_stream(repinfo, repinfo->c, &conn, &stream, 1)) { verbose(VERB_ALGO, "doq: send_reply but %s is gone", (conn?"stream":"connection")); /* No stream, it may have been closed. */ @@ -2906,7 +2924,7 @@ doq_socket_drop_reply(struct comm_reply* repinfo) struct doq_conn* conn; struct doq_stream* stream; log_assert(repinfo->c->type == comm_doq); - if(!doq_lookup_conn_stream(repinfo, repinfo->c, &conn, &stream)) { + if(!doq_lookup_conn_stream(repinfo, repinfo->c, &conn, &stream, 1)) { verbose(VERB_ALGO, "doq: drop_reply but %s is gone", (conn?"stream":"connection")); /* The connection or stream is already gone. */ diff --git a/util/netevent.h b/util/netevent.h index 7f53464b1..48a3bd02d 100644 --- a/util/netevent.h +++ b/util/netevent.h @@ -1136,6 +1136,10 @@ void doq_send_pkt(struct comm_point* c, struct doq_pkt_addr* paddr, /** doq timer callback function. */ void doq_timer_cb(void* arg); +/** Find and lock doq connection and remove mesh state from the stream + * (found by id).*/ +void doq_find_stream_remove_mesh_state(struct comm_reply* repinfo); + /** tcp read again callback function. For tcp req info listen. */ void tcp_read_again_cb(void* arg); diff --git a/validator/validator.c b/validator/validator.c index 4958a2242..2eecf5417 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -1963,14 +1963,7 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq, lookup_name = vq->qchase.qname; lookup_len = vq->qchase.qname_len; /* for type DS look at the parent side for keys/trustanchor */ - /* also for NSEC not at apex */ - if(vq->qchase.qtype == LDNS_RR_TYPE_DS || - (vq->qchase.qtype == LDNS_RR_TYPE_NSEC && - vq->orig_msg->rep->rrset_count > vq->rrset_skip && - ntohs(vq->orig_msg->rep->rrsets[vq->rrset_skip]->rk.type) == - LDNS_RR_TYPE_NSEC && - !(vq->orig_msg->rep->rrsets[vq->rrset_skip]-> - rk.flags&PACKED_RRSET_NSEC_AT_APEX))) { + if(vq->qchase.qtype == LDNS_RR_TYPE_DS) { dname_remove_label(&lookup_name, &lookup_len); } @@ -2007,13 +2000,15 @@ processInit(struct module_qstate* qstate, struct val_qstate* vq, anchor = anchors_lookup(qstate->env->anchors, lookup_name, lookup_len, vq->qchase.qclass); if(!anchor) { /* unsigned parent denies anchor*/ - verbose(VERB_QUERY, "unsigned parent zone denies" - " trust anchor, indeterminate"); - vq->chase_reply->security = sec_status_indeterminate; - update_reason_bogus(vq->chase_reply, LDNS_EDE_DNSSEC_INDETERMINATE); + verbose(VERB_ALGO, "unsigned parent zone denies" + " trust anchor, thus bogus"); + errinf_ede(qstate, "unsigned parent zone denies trust anchor, thus bogus", LDNS_EDE_DNSSEC_BOGUS); + vq->chase_reply->security = sec_status_bogus; + update_reason_bogus(vq->chase_reply, LDNS_EDE_DNSSEC_BOGUS); vq->state = VAL_FINISHED_STATE; return 1; } + vq->parent_denial_must_be_secure = 1; verbose(VERB_ALGO, "trust anchor NXDOMAIN by signed parent"); } else if(subtype == VAL_CLASS_POSITIVE && qstate->qinfo.qtype == LDNS_RR_TYPE_DNSKEY && @@ -2538,6 +2533,15 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq, enum val_classification subtype = val_classify_response( qstate->query_flags, &qstate->qinfo, &vq->qchase, vq->orig_msg->rep, vq->rrset_skip); + if(vq->parent_denial_must_be_secure) { + if(vq->chase_reply->security != sec_status_secure && + vq->chase_reply->security != sec_status_bogus) { + verbose(VERB_ALGO, "parent denial of trust island subdomain is not secure, thus bogus"); + vq->chase_reply->security = sec_status_bogus; + update_reason_bogus(vq->chase_reply, LDNS_EDE_DNSSEC_BOGUS); + errinf_ede(qstate, "parent denial of trust island subdomain is not secure, thus bogus", LDNS_EDE_DNSSEC_BOGUS); + } + } /* store overall validation result in orig_msg */ if(vq->rrset_skip == 0) { diff --git a/validator/validator.h b/validator/validator.h index f7bc3550a..ab88fe124 100644 --- a/validator/validator.h +++ b/validator/validator.h @@ -244,6 +244,10 @@ struct val_qstate { int num_nsec_attempts; /** The nsec attempts have been exceeded. */ int num_nsec_attempts_exceeded; + + /** if validation must be secure, (else bogus) for NXDOMAIN + * from signed parent to (removed) trust anchor subdomain. */ + int parent_denial_must_be_secure; }; /**