From b7c61d7cc256d6a174e6179622c7fa968272c259 Mon Sep 17 00:00:00 2001
From: Yorgos Thessalonikefs <yorgos@nlnetlabs.nl>
Date: Thu, 3 Oct 2024 14:46:57 +0200
Subject: [PATCH] - Fix CVE-2024-8508, unbounded name compression could lead to
 denial of   service.

---
 util/data/msgencode.c | 77 ++++++++++++++++++++++++++-----------------
 1 file changed, 46 insertions(+), 31 deletions(-)

diff --git a/util/data/msgencode.c b/util/data/msgencode.c
index 898ff8412..6d116fb52 100644
--- a/util/data/msgencode.c
+++ b/util/data/msgencode.c
@@ -62,6 +62,10 @@
 #define RETVAL_TRUNC	-4
 /** return code that means all is peachy keen. Equal to DNS rcode NOERROR */
 #define RETVAL_OK	0
+/** Max compressions we are willing to perform; more than that will result
+ *  in semi-compressed messages, or truncated even on TCP for huge messages, to
+ *  avoid locking the CPU for long */
+#define MAX_COMPRESSION_PER_MESSAGE 120
 
 /**
  * Data structure to help domain name compression in outgoing messages.
@@ -284,15 +288,17 @@ write_compressed_dname(sldns_buffer* pkt, uint8_t* dname, int labs,
 
 /** compress owner name of RR, return RETVAL_OUTMEM RETVAL_TRUNC */
 static int
-compress_owner(struct ub_packed_rrset_key* key, sldns_buffer* pkt, 
-	struct regional* region, struct compress_tree_node** tree, 
-	size_t owner_pos, uint16_t* owner_ptr, int owner_labs)
+compress_owner(struct ub_packed_rrset_key* key, sldns_buffer* pkt,
+	struct regional* region, struct compress_tree_node** tree,
+	size_t owner_pos, uint16_t* owner_ptr, int owner_labs,
+	size_t* compress_count)
 {
 	struct compress_tree_node* p;
 	struct compress_tree_node** insertpt = NULL;
 	if(!*owner_ptr) {
 		/* compress first time dname */
-		if((p = compress_tree_lookup(tree, key->rk.dname, 
+		if(*compress_count < MAX_COMPRESSION_PER_MESSAGE &&
+			(p = compress_tree_lookup(tree, key->rk.dname,
 			owner_labs, &insertpt))) {
 			if(p->labs == owner_labs) 
 				/* avoid ptr chains, since some software is
@@ -301,6 +307,7 @@ compress_owner(struct ub_packed_rrset_key* key, sldns_buffer* pkt,
 			if(!write_compressed_dname(pkt, key->rk.dname, 
 				owner_labs, p))
 				return RETVAL_TRUNC;
+			(*compress_count)++;
 			/* check if typeclass+4 ttl + rdatalen is available */
 			if(sldns_buffer_remaining(pkt) < 4+4+2)
 				return RETVAL_TRUNC;
@@ -313,7 +320,8 @@ compress_owner(struct ub_packed_rrset_key* key, sldns_buffer* pkt,
 			if(owner_pos <= PTR_MAX_OFFSET)
 				*owner_ptr = htons(PTR_CREATE(owner_pos));
 		}
-		if(!compress_tree_store(key->rk.dname, owner_labs, 
+		if(*compress_count < MAX_COMPRESSION_PER_MESSAGE &&
+			!compress_tree_store(key->rk.dname, owner_labs,
 			owner_pos, region, p, insertpt))
 			return RETVAL_OUTMEM;
 	} else {
@@ -333,20 +341,24 @@ compress_owner(struct ub_packed_rrset_key* key, sldns_buffer* pkt,
 
 /** compress any domain name to the packet, return RETVAL_* */
 static int
-compress_any_dname(uint8_t* dname, sldns_buffer* pkt, int labs, 
-	struct regional* region, struct compress_tree_node** tree)
+compress_any_dname(uint8_t* dname, sldns_buffer* pkt, int labs,
+	struct regional* region, struct compress_tree_node** tree,
+	size_t* compress_count)
 {
 	struct compress_tree_node* p;
 	struct compress_tree_node** insertpt = NULL;
 	size_t pos = sldns_buffer_position(pkt);
-	if((p = compress_tree_lookup(tree, dname, labs, &insertpt))) {
+	if(*compress_count < MAX_COMPRESSION_PER_MESSAGE &&
+		(p = compress_tree_lookup(tree, dname, labs, &insertpt))) {
 		if(!write_compressed_dname(pkt, dname, labs, p))
 			return RETVAL_TRUNC;
+		(*compress_count)++;
 	} else {
 		if(!dname_buffer_write(pkt, dname))
 			return RETVAL_TRUNC;
 	}
-	if(!compress_tree_store(dname, labs, pos, region, p, insertpt))
+	if(*compress_count < MAX_COMPRESSION_PER_MESSAGE &&
+		!compress_tree_store(dname, labs, pos, region, p, insertpt))
 		return RETVAL_OUTMEM;
 	return RETVAL_OK;
 }
@@ -364,9 +376,9 @@ type_rdata_compressable(struct ub_packed_rrset_key* key)
 
 /** compress domain names in rdata, return RETVAL_* */
 static int
-compress_rdata(sldns_buffer* pkt, uint8_t* rdata, size_t todolen, 
-	struct regional* region, struct compress_tree_node** tree, 
-	const sldns_rr_descriptor* desc)
+compress_rdata(sldns_buffer* pkt, uint8_t* rdata, size_t todolen,
+	struct regional* region, struct compress_tree_node** tree,
+	const sldns_rr_descriptor* desc, size_t* compress_count)
 {
 	int labs, r, rdf = 0;
 	size_t dname_len, len, pos = sldns_buffer_position(pkt);
@@ -380,8 +392,8 @@ compress_rdata(sldns_buffer* pkt, uint8_t* rdata, size_t todolen,
 		switch(desc->_wireformat[rdf]) {
 		case LDNS_RDF_TYPE_DNAME:
 			labs = dname_count_size_labels(rdata, &dname_len);
-			if((r=compress_any_dname(rdata, pkt, labs, region, 
-				tree)) != RETVAL_OK)
+			if((r=compress_any_dname(rdata, pkt, labs, region,
+				tree, compress_count)) != RETVAL_OK)
 				return r;
 			rdata += dname_len;
 			todolen -= dname_len;
@@ -449,7 +461,8 @@ static int
 packed_rrset_encode(struct ub_packed_rrset_key* key, sldns_buffer* pkt, 
 	uint16_t* num_rrs, time_t timenow, struct regional* region,
 	int do_data, int do_sig, struct compress_tree_node** tree,
-	sldns_pkt_section s, uint16_t qtype, int dnssec, size_t rr_offset)
+	sldns_pkt_section s, uint16_t qtype, int dnssec, size_t rr_offset,
+	size_t* compress_count)
 {
 	size_t i, j, owner_pos;
 	int r, owner_labs;
@@ -477,9 +490,9 @@ packed_rrset_encode(struct ub_packed_rrset_key* key, sldns_buffer* pkt,
 		for(i=0; i<data->count; i++) {
 			/* rrset roundrobin */
 			j = (i + rr_offset) % data->count;
-			if((r=compress_owner(key, pkt, region, tree, 
-				owner_pos, &owner_ptr, owner_labs))
-				!= RETVAL_OK)
+			if((r=compress_owner(key, pkt, region, tree,
+				owner_pos, &owner_ptr, owner_labs,
+				compress_count)) != RETVAL_OK)
 				return r;
 			sldns_buffer_write(pkt, &key->rk.type, 2);
 			sldns_buffer_write(pkt, &key->rk.rrset_class, 2);
@@ -489,8 +502,8 @@ packed_rrset_encode(struct ub_packed_rrset_key* key, sldns_buffer* pkt,
 					data->rr_ttl[j]-timenow);
 			if(c) {
 				if((r=compress_rdata(pkt, data->rr_data[j],
-					data->rr_len[j], region, tree, c))
-					!= RETVAL_OK)
+					data->rr_len[j], region, tree, c,
+					compress_count)) != RETVAL_OK)
 					return r;
 			} else {
 				if(sldns_buffer_remaining(pkt) < data->rr_len[j])
@@ -510,9 +523,9 @@ packed_rrset_encode(struct ub_packed_rrset_key* key, sldns_buffer* pkt,
 					return RETVAL_TRUNC;
 				sldns_buffer_write(pkt, &owner_ptr, 2);
 			} else {
-				if((r=compress_any_dname(key->rk.dname, 
-					pkt, owner_labs, region, tree))
-					!= RETVAL_OK)
+				if((r=compress_any_dname(key->rk.dname,
+					pkt, owner_labs, region, tree,
+					compress_count)) != RETVAL_OK)
 					return r;
 				if(sldns_buffer_remaining(pkt) < 
 					4+4+data->rr_len[i])
@@ -544,7 +557,8 @@ static int
 insert_section(struct reply_info* rep, size_t num_rrsets, uint16_t* num_rrs,
 	sldns_buffer* pkt, size_t rrsets_before, time_t timenow, 
 	struct regional* region, struct compress_tree_node** tree,
-	sldns_pkt_section s, uint16_t qtype, int dnssec, size_t rr_offset)
+	sldns_pkt_section s, uint16_t qtype, int dnssec, size_t rr_offset,
+	size_t* compress_count)
 {
 	int r;
 	size_t i, setstart;
@@ -560,7 +574,7 @@ insert_section(struct reply_info* rep, size_t num_rrsets, uint16_t* num_rrs,
 			setstart = sldns_buffer_position(pkt);
 			if((r=packed_rrset_encode(rep->rrsets[rrsets_before+i], 
 				pkt, num_rrs, timenow, region, 1, 1, tree,
-				s, qtype, dnssec, rr_offset))
+				s, qtype, dnssec, rr_offset, compress_count))
 				!= RETVAL_OK) {
 				/* Bad, but if due to size must set TC bit */
 				/* trim off the rrset neatly. */
@@ -573,7 +587,7 @@ insert_section(struct reply_info* rep, size_t num_rrsets, uint16_t* num_rrs,
 			setstart = sldns_buffer_position(pkt);
 			if((r=packed_rrset_encode(rep->rrsets[rrsets_before+i], 
 				pkt, num_rrs, timenow, region, 1, 0, tree,
-				s, qtype, dnssec, rr_offset))
+				s, qtype, dnssec, rr_offset, compress_count))
 				!= RETVAL_OK) {
 				sldns_buffer_set_position(pkt, setstart);
 				return r;
@@ -584,7 +598,7 @@ insert_section(struct reply_info* rep, size_t num_rrsets, uint16_t* num_rrs,
 			setstart = sldns_buffer_position(pkt);
 			if((r=packed_rrset_encode(rep->rrsets[rrsets_before+i], 
 				pkt, num_rrs, timenow, region, 0, 1, tree,
-				s, qtype, dnssec, rr_offset))
+				s, qtype, dnssec, rr_offset, compress_count))
 				!= RETVAL_OK) {
 				sldns_buffer_set_position(pkt, setstart);
 				return r;
@@ -677,6 +691,7 @@ reply_info_encode(struct query_info* qinfo, struct reply_info* rep,
 	struct compress_tree_node* tree = 0;
 	int r;
 	size_t rr_offset;
+	size_t compress_count=0;
 
 	sldns_buffer_clear(buffer);
 	if(udpsize < sldns_buffer_limit(buffer))
@@ -723,7 +738,7 @@ reply_info_encode(struct query_info* qinfo, struct reply_info* rep,
 		arep.rrsets = &qinfo->local_alias->rrset;
 		if((r=insert_section(&arep, 1, &ancount, buffer, 0,
 			timezero, region, &tree, LDNS_SECTION_ANSWER,
-			qinfo->qtype, dnssec, rr_offset)) != RETVAL_OK) {
+			qinfo->qtype, dnssec, rr_offset, &compress_count)) != RETVAL_OK) {
 			if(r == RETVAL_TRUNC) {
 				/* create truncated message */
 				sldns_buffer_write_u16_at(buffer, 6, ancount);
@@ -738,7 +753,7 @@ reply_info_encode(struct query_info* qinfo, struct reply_info* rep,
 	/* insert answer section */
 	if((r=insert_section(rep, rep->an_numrrsets, &ancount, buffer,
 		0, timenow, region, &tree, LDNS_SECTION_ANSWER, qinfo->qtype,
-		dnssec, rr_offset)) != RETVAL_OK) {
+		dnssec, rr_offset, &compress_count)) != RETVAL_OK) {
 		if(r == RETVAL_TRUNC) {
 			/* create truncated message */
 			sldns_buffer_write_u16_at(buffer, 6, ancount);
@@ -756,7 +771,7 @@ reply_info_encode(struct query_info* qinfo, struct reply_info* rep,
 		if((r=insert_section(rep, rep->ns_numrrsets, &nscount, buffer,
 			rep->an_numrrsets, timenow, region, &tree,
 			LDNS_SECTION_AUTHORITY, qinfo->qtype,
-			dnssec, rr_offset)) != RETVAL_OK) {
+			dnssec, rr_offset, &compress_count)) != RETVAL_OK) {
 			if(r == RETVAL_TRUNC) {
 				/* create truncated message */
 				sldns_buffer_write_u16_at(buffer, 8, nscount);
@@ -773,7 +788,7 @@ reply_info_encode(struct query_info* qinfo, struct reply_info* rep,
 			if((r=insert_section(rep, rep->ar_numrrsets, &arcount, buffer,
 				rep->an_numrrsets + rep->ns_numrrsets, timenow, region,
 				&tree, LDNS_SECTION_ADDITIONAL, qinfo->qtype,
-				dnssec, rr_offset)) != RETVAL_OK) {
+				dnssec, rr_offset, &compress_count)) != RETVAL_OK) {
 				if(r == RETVAL_TRUNC) {
 					/* no need to set TC bit, this is the additional */
 					sldns_buffer_write_u16_at(buffer, 10, arcount);
