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/testcode/unitdname.c b/testcode/unitdname.c index 95c6e1fda..9e8cae0e3 100644 --- a/testcode/unitdname.c +++ b/testcode/unitdname.c @@ -269,6 +269,55 @@ dname_test_pkt_dname_len(sldns_buffer* buff) unit_assert( pkt_dname_len(buff) == 0 ); } +/** test pkt_dname_len_budget */ +static void +dname_test_pkt_dname_len_budget(sldns_buffer* buff) +{ + int budget = 0, limit = 20; + unit_show_func("util/data/dname.c", "pkt_dname_len_budget"); + + sldns_buffer_clear(buff); + sldns_buffer_write(buff, "\000", 1); + sldns_buffer_flip(buff); + unit_assert( pkt_dname_len_budget(buff, &budget, limit) == 1 ); + unit_assert( sldns_buffer_position(buff) == 1); + unit_assert( budget == 0); + + sldns_buffer_clear(buff); + sldns_buffer_write(buff, "\007example\003com\000", 13); + sldns_buffer_flip(buff); + unit_assert( pkt_dname_len_budget(buff, &budget, limit) == 13 ); + unit_assert( sldns_buffer_position(buff) == 13); + unit_assert( budget == 0); + + sldns_buffer_clear(buff); + sldns_buffer_skip(buff, 12); /* skip header */ + sldns_buffer_write(buff, "\007example\003com\000", 13); + sldns_buffer_skip(buff, 10); /* more packet content */ + sldns_buffer_write(buff, "\003www", 4); + sldns_buffer_write_u8(buff, 0xc0); + sldns_buffer_write_u8(buff, 0x0c); + sldns_buffer_flip(buff); + sldns_buffer_set_position(buff, 35); + unit_assert( pkt_dname_len_budget(buff, &budget, limit) == 17 ); + unit_assert( sldns_buffer_position(buff) == 41); + unit_assert( budget == 1); + + sldns_buffer_clear(buff); + sldns_buffer_skip(buff, 12); /* skip header */ + sldns_buffer_write(buff, "\001a", 2); + sldns_buffer_write_u8(buff, 0xc0); + sldns_buffer_write_u8(buff, 0x0c); + sldns_buffer_skip(buff, 10); /* more packet content */ + sldns_buffer_write(buff, "\003www", 4); + sldns_buffer_write_u8(buff, 0xc0); + sldns_buffer_write_u8(buff, 0x0c); + sldns_buffer_flip(buff); + sldns_buffer_set_position(buff, 26); + unit_assert( pkt_dname_len_budget(buff, &budget, limit) == 0 ); + unit_assert( budget == 21); +} + /** test dname_lab_cmp */ static void dname_test_dname_lab_cmp(void) @@ -1296,6 +1345,7 @@ void dname_test(void) dname_test_count_size_labels(); dname_test_dname_lab_cmp(); dname_test_pkt_dname_len(buff); + dname_test_pkt_dname_len_budget(buff); dname_test_strict_subdomain(); dname_test_subdomain(); dname_test_isroot(); 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 */