CVE-2014-3597 (published 2014-08-23T01:55Z)

Multiple buffer overflows in the php_parserr function in ext/standard/dns.c in PHP before 5.4.32 and 5.5.x before 5.5.16 allow remote DNS servers to cause a denial of service (application crash) or possibly execute arbitrary code via a crafted DNS record, related to the dns_get_record function and the dn_expand function.  NOTE: this issue exists because of an incomplete fix for CVE-2014-4049.

Severity: MEDIUM
CVSS v2 base score: 6.8 (AV:N/AC:M/Au:N/C:P/I:P/A:P)

Weakness information:
cwe_id: CWE-119
cwe_name: Improper Restriction of Operations within the Bounds of a Memory Buffer
description: The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
extended_description: 
            Certain languages allow direct addressing of memory locations and do not automatically ensure that these locations are valid for the memory buffer that is being referenced. This can cause read or write operations to be performed on memory locations that may be associated with other variables, data structures, or internal program data.
         
         

Fix information:
repo_url: https://github.com/php/php-src
commit_details: repo_url: https://github.com/php/php-src
author: Remi Collet
author_date: 2014-08-19 08:33:49+02:00
msg: Fixed Sec Bug #67717 segfault in dns_get_record CVE-2014-3597

Incomplete fix for CVE-2014-4049

Check possible buffer overflow
- pass real buffer end to dn_expand calls
- check buffer len before each read
repository: repo_url: https://github.com/php/php-src
repo_name: php/php-src
description: The PHP Interpreter
repo_language: C
owner: php
file_changes: filename: dns.c
change_type: ModificationType.MODIFY
diff: @@ -412,8 +412,14 @@ PHP_FUNCTION(dns_check_record)
 
 #if HAVE_FULL_DNS_FUNCS
 
+#define CHECKCP(n) do { \
+	if (cp + n > end) { \
+		return NULL; \
+	} \
+} while (0)
+
 /* {{{ php_parserr */
-static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int store, int raw, zval **subarray)
+static u_char *php_parserr(u_char *cp, u_char *end, querybuf *answer, int type_to_fetch, int store, int raw, zval **subarray)
 {
 	u_short type, class, dlen;
 	u_long ttl;
@@ -425,16 +431,18 @@ static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int
 
 	*subarray = NULL;
 
-	n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, sizeof(name) - 2);
+	n = dn_expand(answer->qb2, end, cp, name, sizeof(name) - 2);
 	if (n < 0) {
 		return NULL;
 	}
 	cp += n;
 
+	CHECKCP(10);
 	GETSHORT(type, cp);
 	GETSHORT(class, cp);
 	GETLONG(ttl, cp);
 	GETSHORT(dlen, cp);
+	CHECKCP(dlen);
 	if (type_to_fetch != T_ANY && type != type_to_fetch) {
 		cp += dlen;
 		return cp;
@@ -461,12 +469,14 @@ static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int
 
 	switch (type) {
 		case DNS_T_A:
+			CHECKCP(4);
 			add_assoc_string(*subarray, "type", "A", 1);
 			snprintf(name, sizeof(name), "%d.%d.%d.%d", cp[0], cp[1], cp[2], cp[3]);
 			add_assoc_string(*subarray, "ip", name, 1);
 			cp += dlen;
 			break;
 		case DNS_T_MX:
+			CHECKCP(2);
 			add_assoc_string(*subarray, "type", "MX", 1);
 			GETSHORT(n, cp);
 			add_assoc_long(*subarray, "pri", n);
@@ -485,7 +495,7 @@ static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int
 			if (type == DNS_T_PTR) {
 				add_assoc_string(*subarray, "type", "PTR", 1);
 			}
-			n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, (sizeof name) - 2);
+			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
 			if (n < 0) {
 				return NULL;
 			}
@@ -495,18 +505,22 @@ static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int
 		case DNS_T_HINFO:
 			/* See RFC 1010 for values */
 			add_assoc_string(*subarray, "type", "HINFO", 1);
+			CHECKCP(1);
 			n = *cp & 0xFF;
 			cp++;
+			CHECKCP(n);
 			add_assoc_stringl(*subarray, "cpu", (char*)cp, n, 1);
 			cp += n;
+			CHECKCP(1);
 			n = *cp & 0xFF;
 			cp++;
+			CHECKCP(n);
 			add_assoc_stringl(*subarray, "os", (char*)cp, n, 1);
 			cp += n;
 			break;
 		case DNS_T_TXT:
 			{
-				int ll = 0;
+				int l1 = 0, l2 = 0;
 				zval *entries = NULL;
 
 				add_assoc_string(*subarray, "type", "TXT", 1);
@@ -515,37 +529,41 @@ static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int
 				MAKE_STD_ZVAL(entries);
 				array_init(entries);
 				
-				while (ll < dlen) {
-					n = cp[ll];
-					if ((ll + n) >= dlen) {
+				while (l1 < dlen) {
+					n = cp[l1];
+					if ((l1 + n) >= dlen) {
 						// Invalid chunk length, truncate
-						n = dlen - (ll + 1);
+						n = dlen - (l1 + 1);
+					}
+					if (n) {
+						memcpy(tp + l2 , cp + l1 + 1, n);
+						add_next_index_stringl(entries, cp + l1 + 1, n, 1);
 					}
-					memcpy(tp + ll , cp + ll + 1, n);
-					add_next_index_stringl(entries, cp + ll + 1, n, 1);
-					ll = ll + n + 1;
+					l1 = l1 + n + 1;
+					l2 = l2 + n;
 				}
-				tp[dlen] = '\0';
+				tp[l2] = '\0';
 				cp += dlen;
 
-				add_assoc_stringl(*subarray, "txt", tp, (dlen>0)?dlen - 1:0, 0);
+				add_assoc_stringl(*subarray, "txt", tp, l2, 0);
 				add_assoc_zval(*subarray, "entries", entries);
 			}
 			break;
 		case DNS_T_SOA:
 			add_assoc_string(*subarray, "type", "SOA", 1);
-			n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, (sizeof name) -2);
+			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) -2);
 			if (n < 0) {
 				return NULL;
 			}
 			cp += n;
 			add_assoc_string(*subarray, "mname", name, 1);
-			n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, (sizeof name) -2);
+			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) -2);
 			if (n < 0) {
 				return NULL;
 			}
 			cp += n;
 			add_assoc_string(*subarray, "rname", name, 1);
+			CHECKCP(5*4);
 			GETLONG(n, cp);
 			add_assoc_long(*subarray, "serial", n);
 			GETLONG(n, cp);
@@ -559,6 +577,7 @@ static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int
 			break;
 		case DNS_T_AAAA:
 			tp = (u_char*)name;
+			CHECKCP(8*2);
 			for(i=0; i < 8; i++) {
 				GETSHORT(s, cp);
 				if (s != 0) {
@@ -593,6 +612,7 @@ static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int
 		case DNS_T_A6:
 			p = cp;
 			add_assoc_string(*subarray, "type", "A6", 1);
+			CHECKCP(1);
 			n = ((int)cp[0]) & 0xFF;
 			cp++;
 			add_assoc_long(*subarray, "masklen", n);
@@ -628,6 +648,7 @@ static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int
 				cp++;
 			}
 			for (i = (n + 8) / 16; i < 8; i++) {
+				CHECKCP(2);
 				GETSHORT(s, cp);
 				if (s != 0) {
 					if (tp > (u_char *)name) {
@@ -657,7 +678,7 @@ static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int
 			tp[0] = '\0';
 			add_assoc_string(*subarray, "ipv6", name, 1);
 			if (cp < p + dlen) {
-				n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, (sizeof name) - 2);
+				n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
 				if (n < 0) {
 					return NULL;
 				}
@@ -666,6 +687,7 @@ static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int
 			}
 			break;
 		case DNS_T_SRV:
+			CHECKCP(3*2);
 			add_assoc_string(*subarray, "type", "SRV", 1);
 			GETSHORT(n, cp);
 			add_assoc_long(*subarray, "pri", n);
@@ -673,7 +695,7 @@ static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int
 			add_assoc_long(*subarray, "weight", n);
 			GETSHORT(n, cp);
 			add_assoc_long(*subarray, "port", n);
-			n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, (sizeof name) - 2);
+			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
 			if (n < 0) {
 				return NULL;
 			}
@@ -681,21 +703,35 @@ static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int
 			add_assoc_string(*subarray, "target", name, 1);
 			break;
 		case DNS_T_NAPTR:
+			CHECKCP(2*2);
 			add_assoc_string(*subarray, "type", "NAPTR", 1);
 			GETSHORT(n, cp);
 			add_assoc_long(*subarray, "order", n);
 			GETSHORT(n, cp);
 			add_assoc_long(*subarray, "pref", n);
+
+			CHECKCP(1);
 			n = (cp[0] & 0xFF);
-			add_assoc_stringl(*subarray, "flags", (char*)++cp, n, 1);
+			cp++;
+			CHECKCP(n);
+			add_assoc_stringl(*subarray, "flags", (char*)cp, n, 1);
 			cp += n;
+
+			CHECKCP(1);
 			n = (cp[0] & 0xFF);
-			add_assoc_stringl(*subarray, "services", (char*)++cp, n, 1);
+			cp++;
+			CHECKCP(n);
+			add_assoc_stringl(*subarray, "services", (char*)cp, n, 1);
 			cp += n;
+
+			CHECKCP(1);
 			n = (cp[0] & 0xFF);
-			add_assoc_stringl(*subarray, "regex", (char*)++cp, n, 1);
+			cp++;
+			CHECKCP(n);
+			add_assoc_stringl(*subarray, "regex", (char*)cp, n, 1);
 			cp += n;
-			n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, (sizeof name) - 2);
+
+			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
 			if (n < 0) {
 				return NULL;
 			}
@@ -888,7 +924,7 @@ PHP_FUNCTION(dns_get_record)
 			while (an-- && cp && cp < end) {
 				zval *retval;
 
-				cp = php_parserr(cp, &answer, type_to_fetch, store_results, raw, &retval);
+				cp = php_parserr(cp, end, &answer, type_to_fetch, store_results, raw, &retval);
 				if (retval != NULL && store_results) {
 					add_next_index_zval(return_value, retval);
 				}
@@ -901,7 +937,7 @@ PHP_FUNCTION(dns_get_record)
 				while (ns-- > 0 && cp && cp < end) {
 					zval *retval = NULL;
 
-					cp = php_parserr(cp, &answer, DNS_T_ANY, authns != NULL, raw, &retval);
+					cp = php_parserr(cp, end, &answer, DNS_T_ANY, authns != NULL, raw, &retval);
 					if (retval != NULL) {
 						add_next_index_zval(authns, retval);
 					}
@@ -913,7 +949,7 @@ PHP_FUNCTION(dns_get_record)
 				while (ar-- > 0 && cp && cp < end) {
 					zval *retval = NULL;
 
-					cp = php_parserr(cp, &answer, DNS_T_ANY, 1, raw, &retval);
+					cp = php_parserr(cp, end, &answer, DNS_T_ANY, 1, raw, &retval);
 					if (retval != NULL) {
 						add_next_index_zval(addtl, retval);
 					}

programming_language: C