Vulnerability: Heap-based buffer overflow in dns.cpp in InspIRCd 2.0.5 might allow remote attackers to execute arbitrary code via a crafted DNS query that uses compression.
Vulnerability ID: CVE-2012-1836
CWE: CWE-119
Severity score: 6.4
Project: https://github.com/inspircd/inspircd
Published: 2012-03-22
File: src/dns.cpp
Commit message: Merge pull request #1 from nenolod/insp20

DNS resolver hardening (insp20 branch)
Commit author: Sir Pogsalot

Vulnerable version (before fix):
 looks like this, walks like this or tastes like this. #include "configreader.h" #include "socket.h"  /** Masks to mask off the responses we get from the DNSRequest methods  */ enum QueryInfo class DNSRequest  	DNSRequest(DNS* dns, int id, const std::string &original); 	~DNSRequest(); 	DNSInfo ResultIsReady(DNSHeader &h, int length); 	int SendRequests(const DNSHeader *header, const int length, QueryType qt); };  int CachedQuery::CalcTTLRemaining() /* Allocate the processing buffer */ DNSRequest::DNSRequest(DNS* dns, int rid, const std::string &original) : dnsobj(dns) { 	res = new unsigned char[512]; 	*res = 0; 	orig = original; 	RequestTimeout* RT = new RequestTimeout(ServerInstance->Config->dns_timeout ? ServerInstance->Config->dns_timeout : 5, this, rid); DNSResult DNS::GetResult() }  /** A result is ready, process it */ DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, int length) { 	int i = 0; 	int q = 0; 	int curanswer, o; 	ResourceRecord rr;  	unsigned short ptr;  DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, int length)  	switch (rr.type) 	{ 		case DNS_QUERY_CNAME: 			/* CNAME and PTR have the same processing code */ 		case DNS_QUERY_PTR: 			o = 0; 			q = 0; 			while (q == 0 && i < length && o + 256 < 1023) 			{ 				if (header.payload[i] > 63) 				{ 					memcpy(&ptr,&header.payload[i],2); 					i = ntohs(ptr) - 0xC000 - 12; 				} 				else 				{ DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, int length) 						res[o] = 0; 						if (o != 0) 							res[o++] = '.'; 						memcpy(&res[o],&header.payload[i + 1],header.payload[i]); 						o += header.payload[i]; 						i += header.payload[i] + 1; 					} DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, int length) 			res[o] = 0; 		break; 		case DNS_QUERY_AAAA: 			memcpy(res,&header.payload[i],rr.rdlength); 			res[rr.rdlength] = 0; 		break; 		case DNS_QUERY_A: 			memcpy(res,&header.payload[i],rr.rdlength); 			res[rr.rdlength] = 0; 		break; 		default: 			memcpy(res,&header.payload[i],rr.rdlength); 			res[rr.rdlength] = 0; 		break; 	} 	return std::make_pair(res,"No error");

Fixed version (after fix):
 looks like this, walks like this or tastes like this. #include "configreader.h" #include "socket.h"  #define DN_COMP_BITMASK	0xC000		/* highest 6 bits in a DN label header */  /** Masks to mask off the responses we get from the DNSRequest methods  */ enum QueryInfo class DNSRequest  	DNSRequest(DNS* dns, int id, const std::string &original); 	~DNSRequest(); 	DNSInfo ResultIsReady(DNSHeader &h, unsigned length); 	int SendRequests(const DNSHeader *header, const int length, QueryType qt); };  int CachedQuery::CalcTTLRemaining() /* Allocate the processing buffer */ DNSRequest::DNSRequest(DNS* dns, int rid, const std::string &original) : dnsobj(dns) { 	/* hardening against overflow here:  make our work buffer twice the theoretical 	 * maximum size so that hostile input doesn't screw us over. 	 */ 	res = new unsigned char[sizeof(DNSHeader) * 2]; 	*res = 0; 	orig = original; 	RequestTimeout* RT = new RequestTimeout(ServerInstance->Config->dns_timeout ? ServerInstance->Config->dns_timeout : 5, this, rid); DNSResult DNS::GetResult() }  /** A result is ready, process it */ DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, unsigned length) { 	unsigned i = 0, o; 	int q = 0; 	int curanswer; 	ResourceRecord rr;  	unsigned short ptr;  DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, int length)  	switch (rr.type) 	{ 		/* 		 * CNAME and PTR are compressed.  We need to decompress them. 		 */ 		case DNS_QUERY_CNAME: 		case DNS_QUERY_PTR: 			o = 0; 			q = 0; 			while (q == 0 && i < length && o + 256 < 1023) 			{ 				/* DN label found (byte over 63) */ 				if (header.payload[i] > 63) 				{ 					memcpy(&ptr,&header.payload[i],2);  					i = ntohs(ptr);  					/* check that highest two bits are set. if not, we've been had */ 					if (!(i & DN_COMP_BITMASK)) 						return std::make_pair((unsigned char *) NULL, "DN label decompression header is bogus");  					/* mask away the two highest bits. */ 					i &= ~DN_COMP_BITMASK;  					/* and decrease length by 12 bytes. */ 					i =- 12; 				} 				else 				{ DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, int length) 						res[o] = 0; 						if (o != 0) 							res[o++] = '.';  						if (o + header.payload[i] > sizeof(DNSHeader)) 							return std::make_pair((unsigned char *) NULL, "DN label decompression is impossible -- malformed/hostile packet?");  						memcpy(&res[o], &header.payload[i + 1], header.payload[i]); 						o += header.payload[i]; 						i += header.payload[i] + 1; 					} DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, int length) 			res[o] = 0; 		break; 		case DNS_QUERY_AAAA: 			if (rr.rdlength != sizeof(struct in6_addr)) 				return std::make_pair((unsigned char *) NULL, "rr.rdlength is larger than 16 bytes for an ipv6 entry -- malformed/hostile packet?");  			memcpy(res,&header.payload[i],rr.rdlength); 			res[rr.rdlength] = 0; 		break; 		case DNS_QUERY_A: 			if (rr.rdlength != sizeof(struct in_addr)) 				return std::make_pair((unsigned char *) NULL, "rr.rdlength is larger than 4 bytes for an ipv4 entry -- malformed/hostile packet?");  			memcpy(res,&header.payload[i],rr.rdlength); 			res[rr.rdlength] = 0; 		break; 		default: 			return std::make_pair((unsigned char *) NULL, "don't know how to handle undefined type (" + ConvToStr(rr.type) + ") -- rejecting"); 		break; 	} 	return std::make_pair(res,"No error");

Patch:
patch: @@ -38,6 +38,8 @@ looks like this, walks like this or tastes like this.
 #include "configreader.h"
 #include "socket.h"
 
+#define DN_COMP_BITMASK	0xC000		/* highest 6 bits in a DN label header */
+
 /** Masks to mask off the responses we get from the DNSRequest methods
  */
 enum QueryInfo
@@ -98,7 +100,7 @@ class DNSRequest
 
 	DNSRequest(DNS* dns, int id, const std::string &original);
 	~DNSRequest();
-	DNSInfo ResultIsReady(DNSHeader &h, int length);
+	DNSInfo ResultIsReady(DNSHeader &h, unsigned length);
 	int SendRequests(const DNSHeader *header, const int length, QueryType qt);
 };
 
@@ -161,7 +163,10 @@ int CachedQuery::CalcTTLRemaining()
 /* Allocate the processing buffer */
 DNSRequest::DNSRequest(DNS* dns, int rid, const std::string &original) : dnsobj(dns)
 {
-	res = new unsigned char[512];
+	/* hardening against overflow here:  make our work buffer twice the theoretical
+	 * maximum size so that hostile input doesn't screw us over.
+	 */
+	res = new unsigned char[sizeof(DNSHeader) * 2];
 	*res = 0;
 	orig = original;
 	RequestTimeout* RT = new RequestTimeout(ServerInstance->Config->dns_timeout ? ServerInstance->Config->dns_timeout : 5, this, rid);
@@ -688,11 +693,11 @@ DNSResult DNS::GetResult()
 }
 
 /** A result is ready, process it */
-DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, int length)
+DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, unsigned length)
 {
-	int i = 0;
+	unsigned i = 0, o;
 	int q = 0;
-	int curanswer, o;
+	int curanswer;
 	ResourceRecord rr;
  	unsigned short ptr;
 
@@ -790,17 +795,31 @@ DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, int length)
 
 	switch (rr.type)
 	{
+		/*
+		 * CNAME and PTR are compressed.  We need to decompress them.
+		 */
 		case DNS_QUERY_CNAME:
-			/* CNAME and PTR have the same processing code */
 		case DNS_QUERY_PTR:
 			o = 0;
 			q = 0;
 			while (q == 0 && i < length && o + 256 < 1023)
 			{
+				/* DN label found (byte over 63) */
 				if (header.payload[i] > 63)
 				{
 					memcpy(&ptr,&header.payload[i],2);
-					i = ntohs(ptr) - 0xC000 - 12;
+
+					i = ntohs(ptr);
+
+					/* check that highest two bits are set. if not, we've been had */
+					if (!(i & DN_COMP_BITMASK))
+						return std::make_pair((unsigned char *) NULL, "DN label decompression header is bogus");
+
+					/* mask away the two highest bits. */
+					i &= ~DN_COMP_BITMASK;
+
+					/* and decrease length by 12 bytes. */
+					i =- 12;
 				}
 				else
 				{
@@ -813,7 +832,11 @@ DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, int length)
 						res[o] = 0;
 						if (o != 0)
 							res[o++] = '.';
-						memcpy(&res[o],&header.payload[i + 1],header.payload[i]);
+
+						if (o + header.payload[i] > sizeof(DNSHeader))
+							return std::make_pair((unsigned char *) NULL, "DN label decompression is impossible -- malformed/hostile packet?");
+
+						memcpy(&res[o], &header.payload[i + 1], header.payload[i]);
 						o += header.payload[i];
 						i += header.payload[i] + 1;
 					}
@@ -822,16 +845,21 @@ DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, int length)
 			res[o] = 0;
 		break;
 		case DNS_QUERY_AAAA:
+			if (rr.rdlength != sizeof(struct in6_addr))
+				return std::make_pair((unsigned char *) NULL, "rr.rdlength is larger than 16 bytes for an ipv6 entry -- malformed/hostile packet?");
+
 			memcpy(res,&header.payload[i],rr.rdlength);
 			res[rr.rdlength] = 0;
 		break;
 		case DNS_QUERY_A:
+			if (rr.rdlength != sizeof(struct in_addr))
+				return std::make_pair((unsigned char *) NULL, "rr.rdlength is larger than 4 bytes for an ipv4 entry -- malformed/hostile packet?");
+
 			memcpy(res,&header.payload[i],rr.rdlength);
 			res[rr.rdlength] = 0;
 		break;
 		default:
-			memcpy(res,&header.payload[i],rr.rdlength);
-			res[rr.rdlength] = 0;
+			return std::make_pair((unsigned char *) NULL, "don't know how to handle undefined type (" + ConvToStr(rr.type) + ") -- rejecting");
 		break;
 	}
 	return std::make_pair(res,"No error");
path: src/dns.cpp