CVE-2016-5094: Integer Overflow or Wraparound (CWE-190)
value: Integer overflow in the php_html_entities function in ext/standard/html.c in PHP before 5.5.36 and 5.6.x before 5.6.22 allows remote attackers to cause a denial of service or possibly have unspecified other impact by triggering a large output string from the htmlspecialchars function.
CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 8.6
Published: 2016-08-07T10:59Z
Repository: https://github.com/php/php-src
Language: C
Files changed: ext/standard/html.c
Commit date: 2016-05-15 23:26:51 -0700
Commit message: Fix bug #72135 - don't create strings with lengths outside int range

Diff: diff --git a/ext/standard/html.c b/ext/standard/html.c
index 72423b59f0e..81d8aff9e9a 100644
--- a/ext/standard/html.c
+++ b/ext/standard/html.c
@@ -161,11 +161,11 @@ static inline unsigned int get_next_char(
 					else if (avail < 4 || utf8_lead(str[pos + 3]))
 						MB_FAILURE(pos, 3);
 					else
 						MB_FAILURE(pos, 4);
 				}
-				
+
 				this_char = ((c & 0x07) << 18) | ((str[pos + 1] & 0x3f) << 12) | ((str[pos + 2] & 0x3f) << 6) | (str[pos + 3] & 0x3f);
 				if (this_char < 0x10000 || this_char > 0x10FFFF) { /* non-shortest form or outside range */
 					MB_FAILURE(pos, 4);
 				}
 				pos += 4;
@@ -435,11 +435,11 @@ static enum entity_charset determine_charset(char *charset_hint TSRMLS_DC)
 
 det_charset:
 
 	if (charset_hint) {
 		int found = 0;
-		
+
 		/* now walk the charset map and look for the codeset */
 		for (i = 0; charset_map[i].codeset; i++) {
 			if (len == strlen(charset_map[i].codeset) && strncasecmp(charset_hint, charset_map[i].codeset, len) == 0) {
 				charset = charset_map[i].charset;
 				found = 1;
@@ -543,11 +543,11 @@ static inline unsigned char unimap_bsearch(const uni_to_enc *table, unsigned cod
 	/* we have no mappings outside the BMP */
 	if (code_key_a > 0xFFFFU)
 		return 0;
 
 	code_key = (unsigned short) code_key_a;
-	
+
 	while (l <= h) {
 		m = l + (h - l) / 2;
 		if (code_key < m->un_code_point)
 			h = m - 1;
 		else if (code_key > m->un_code_point)
@@ -569,11 +569,11 @@ static inline int map_from_unicode(unsigned code, enum entity_charset charset, u
 	switch (charset) {
 	case cs_8859_1:
 		/* identity mapping of code points to unicode */
 		if (code > 0xFF) {
 			return FAILURE;
-		} 
+		}
 		*res = code;
 		break;
 
 	case cs_8859_5:
 		if (code <= 0xA0 || code == 0xAD /* soft hyphen */) {
@@ -588,11 +588,11 @@ static inline int map_from_unicode(unsigned code, enum entity_charset charset, u
 			*res = code - 0x360;
 		} else {
 			return FAILURE;
 		}
 		break;
-		
+
 	case cs_8859_15:
 		if (code < 0xA4 || (code > 0xBE && code <= 0xFF)) {
 			*res = code;
 		} else { /* between A4 and 0xBE */
 			found = unimap_bsearch(unimap_iso885915,
@@ -632,11 +632,11 @@ static inline int map_from_unicode(unsigned code, enum entity_charset charset, u
 		table_size = sizeof(unimap_koi8r) / sizeof(*unimap_koi8r);
 		goto table_over_7F;
 	case cs_cp866:
 		table = unimap_cp866;
 		table_size = sizeof(unimap_cp866) / sizeof(*unimap_cp866);
-		
+
 table_over_7F:
 		if (code <= 0x7F) {
 			*res = code;
 		} else {
 			found = unimap_bsearch(table, code, table_size);
@@ -708,11 +708,11 @@ static inline int unicode_cp_is_allowed(unsigned uni_cp, int document_type)
 	 * HTML 5:    <http://dev.w3.org/html5/spec/Overview.html#preprocessing-the-input-stream>
 	 *
 	 * Not sure this is the relevant part for HTML 5, though. I opted to
 	 * disallow the characters that would result in a parse error when
 	 * preprocessing of the input stream. See also section 8.1.3.
-	 * 
+	 *
 	 * It's unclear if XHTML 1.0 allows C1 characters. I'll opt to apply to
 	 * XHTML 1.0 the same rules as for XML 1.0.
 	 * See <http://cmsmcq.com/2007/C1.xml>.
 	 */
 
@@ -772,21 +772,21 @@ static inline int numeric_entity_is_allowed(unsigned uni_cp, int document_type)
 /* }}} */
 
 /* {{{ process_numeric_entity
  * Auxiliary function to traverse_for_entities.
  * On input, *buf should point to the first character after # and on output, it's the last
- * byte read, no matter if there was success or insuccess. 
+ * byte read, no matter if there was success or insuccess.
  */
 static inline int process_numeric_entity(const char **buf, unsigned *code_point)
 {
 	long code_l;
 	int hexadecimal = (**buf == 'x' || **buf == 'X'); /* TODO: XML apparently disallows "X" */
 	char *endptr;
 
 	if (hexadecimal && (**buf != '\0'))
 		(*buf)++;
-			
+
 	/* strtol allows whitespace and other stuff in the beginning
 		* we're not interested */
 	if ((hexadecimal && !isxdigit(**buf)) ||
 			(!hexadecimal && !isdigit(**buf))) {
 		return FAILURE;
@@ -967,11 +967,11 @@ static void traverse_for_entities(
 			if (!all && (code > 63U ||
 					stage3_table_be_apos_00000[code].data.ent.entity == NULL))
 				goto invalid_code;
 
 			/* are we allowed to decode this entity in this document type?
-			 * HTML 5 is the only that has a character that cannot be used in 
+			 * HTML 5 is the only that has a character that cannot be used in
 			 * a numeric entity but is allowed literally (U+000D). The
 			 * unoptimized version would be ... || !numeric_entity_is_allowed(code) */
 			if (!unicode_cp_is_allowed(code, doctype) ||
 					(doctype == ENT_HTML_DOC_HTML5 && code == 0x0D))
 				goto invalid_code;
@@ -994,13 +994,13 @@ static void traverse_for_entities(
 				} else {
 					goto invalid_code;
 				}
 			}
 		}
-		
+
 		assert(*next == ';');
-		
+
 		if (((code == '\'' && !(flags & ENT_HTML_QUOTE_SINGLE)) ||
 				(code == '"' && !(flags & ENT_HTML_QUOTE_DOUBLE)))
 				/* && code2 == '\0' always true for current maps */)
 			goto invalid_code;
 
@@ -1024,11 +1024,11 @@ static void traverse_for_entities(
 invalid_code:
 		for (; p < next; p++) {
 			*(q++) = *p;
 		}
 	}
-	
+
 	*q = '\0';
 	*retlen = (size_t)(q - ret);
 }
 /* }}} */
 
@@ -1064,11 +1064,11 @@ static const entity_ht *unescape_inverse_map(int all, int flags)
 static entity_table_opt determine_entity_table(int all, int doctype)
 {
 	entity_table_opt retval = {NULL};
 
 	assert(!(doctype == ENT_HTML_DOC_XML1 && all));
-	
+
 	if (all) {
 		retval.ms_table = (doctype == ENT_HTML_DOC_HTML5) ?
 			entity_ms_table_html5 : entity_ms_table_html4;
 	} else {
 		retval.table = (doctype == ENT_HTML_DOC_HTML401) ?
@@ -1109,17 +1109,17 @@ PHPAPI char *php_unescape_html_entities(unsigned char *old, size_t oldlen, size_
 	*ret = '\0';
 	retlen = oldlen;
 	if (retlen == 0) {
 		goto empty_source;
 	}
-	
+
 	inverse_map = unescape_inverse_map(all, flags);
-	
+
 	/* replace numeric entities */
 	traverse_for_entities(old, oldlen, ret, &retlen, all, flags, inverse_map, charset);
 
-empty_source:	
+empty_source:
 	*newlen = retlen;
 	return ret;
 }
 /* }}} */
 
@@ -1139,11 +1139,11 @@ static inline void find_entity_for_char(
 	size_t oldlen,
 	size_t *cursor)
 {
 	unsigned stage1_idx = ENT_STAGE1_INDEX(k);
 	const entity_stage3_row *c;
-	
+
 	if (stage1_idx > 0x1D) {
 		*entity     = NULL;
 		*entity_len = 0;
 		return;
 	}
@@ -1160,11 +1160,11 @@ static inline void find_entity_for_char(
 		unsigned next_char;
 
 		if (!(*cursor < oldlen))
 			goto no_suitable_2nd;
 
-		next_char = get_next_char(charset, old, oldlen, cursor, &status); 
+		next_char = get_next_char(charset, old, oldlen, cursor, &status);
 
 		if (status == FAILURE)
 			goto no_suitable_2nd;
 
 		{
@@ -1185,11 +1185,11 @@ static inline void find_entity_for_char(
 no_suitable_2nd:
 		*cursor = cursor_before;
 		*entity = (const unsigned char *)
 			c->data.multicodepoint_table[0].leading_entry.default_entity;
 		*entity_len = c->data.multicodepoint_table[0].leading_entry.default_entity_len;
-	}	
+	}
 }
 /* }}} */
 
 /* {{{ find_entity_for_char_basic */
 static inline void find_entity_for_char_basic(
@@ -1253,11 +1253,11 @@ PHPAPI char *php_escape_html_entities_ex(unsigned char *old, size_t oldlen, size
 		}
 	}
 
 	/* initial estimate */
 	if (oldlen < 64) {
-		maxlen = 128;	
+		maxlen = 128;
 	} else {
 		maxlen = 2 * oldlen;
 		if (maxlen < oldlen) {
 			zend_error_noreturn(E_ERROR, "Input string is too long");
 			return NULL;
@@ -1442,10 +1442,14 @@ static void php_html_entities(INTERNAL_FUNCTION_PARAMETERS, int all)
 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ls!b", &str, &str_len, &flags, &hint_charset, &hint_charset_len, &double_encode) == FAILURE) {
 		return;
 	}
 
 	replaced = php_escape_html_entities_ex(str, str_len, &new_len, all, (int) flags, hint_charset, double_encode TSRMLS_CC);
+	if (new_len > INT_MAX) {
+		efree(replaced);
+		RETURN_FALSE;
+	}
 	RETVAL_STRINGL(replaced, (int)new_len, 0);
 }
 /* }}} */
 
 #define HTML_SPECIALCHARS 	0
@@ -1575,11 +1579,11 @@ static inline void write_s3row_data(
 				if (map_from_unicode(uni_cp, charset, &spe_cp) == FAILURE)
 					continue; /* non representable in this charset */
 			} else {
 				spe_cp = uni_cp;
 			}
-			
+
 			written_k2 = write_octet_sequence(&key[written_k1], charset, spe_cp);
 			memcpy(&entity[1], mcpr[i].normal_entry.entity, l);
 			entity[l + 1] = ';';
 			entity[l + 1] = '\0';
 			add_assoc_stringl_ex(arr, key, written_k1 + written_k2 + 1, entity, l + 1, 1);
@@ -1613,11 +1617,11 @@ PHP_FUNCTION(get_html_translation_table)
 	charset = determine_charset(charset_hint TSRMLS_CC);
 	doctype = flags & ENT_HTML_DOC_TYPE_MASK;
 	LIMIT_ALL(all, doctype, charset);
 
 	array_init(return_value);
-	
+
 	entity_table = determine_entity_table(all, doctype);
 	if (all && !CHARSET_UNICODE_COMPAT(charset)) {
 		to_uni_table = enc_to_uni_index[charset];
 	}
 

Vulnerable code: 				
		
	
		} 
		
		
	 * 
 * byte read, no matter if there was success or insuccess. 
			
			 * HTML 5 is the only that has a character that cannot be used in 
		
		
	
	
	
	
empty_source:	
	
		next_char = get_next_char(charset, old, oldlen, cursor, &status); 
	}	
		maxlen = 128;	
			
	
Fixed code: 


		}


	 *
 * byte read, no matter if there was success or insuccess.

			 * HTML 5 is the only that has a character that cannot be used in






empty_source:

		next_char = get_next_char(charset, old, oldlen, cursor, &status);
	}
		maxlen = 128;
	if (new_len > INT_MAX) {
		efree(replaced);
		RETURN_FALSE;
	}