CVE-2016-7412: Improper Restriction of Operations within the Bounds of a Memory Buffer (CWE-119)
value: ext/mysqlnd/mysqlnd_wireprotocol.c in PHP before 5.6.26 and 7.x before 7.0.11 does not verify that a BIT field has the UNSIGNED_FLAG flag, which allows remote MySQL servers to cause a denial of service (heap-based buffer overflow) or possibly have unspecified other impact via crafted field metadata.
CWE 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.
CVSS v3 base score: 8.1
Published: 2016-09-17T21:59Z
Repository: https://github.com/php/php-src
Language: C
Files changed: ext/mysqlnd/mysqlnd_wireprotocol.c
Commit date: 2016-09-12 21:04:23 -0700
Commit message: Fix bug #72293 - Heap overflow in mysqlnd related to BIT fields

Diff: diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c
index 74c97249912..855a25cea1c 100644
--- a/ext/mysqlnd/mysqlnd_wireprotocol.c
+++ b/ext/mysqlnd/mysqlnd_wireprotocol.c
@@ -1583,10 +1583,11 @@ php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_MEMORY_POOL_CHUNK * row_buffer,
 	zend_bool last_field_was_string = FALSE;
 	zval **current_field, **end_field, **start_field;
 	zend_uchar * p = row_buffer->ptr;
 	size_t data_size = row_buffer->app;
 	zend_uchar * bit_area = (zend_uchar*) row_buffer->ptr + data_size + 1; /* we allocate from here */
+	const zend_uchar * const packet_end = (zend_uchar*) row_buffer->ptr + data_size;
 
 	DBG_ENTER("php_mysqlnd_rowp_read_text_protocol_aux");
 
 	if (!fields) {
 		DBG_RETURN(FAIL);
@@ -1604,12 +1605,17 @@ php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_MEMORY_POOL_CHUNK * row_buffer,
 
 	for (i = 0, current_field = start_field; current_field < end_field; current_field++, i++) {
 		/* Don't reverse the order. It is significant!*/
 		zend_uchar *this_field_len_pos = p;
 		/* php_mysqlnd_net_field_length() call should be after *this_field_len_pos = p; */
-		unsigned long len = php_mysqlnd_net_field_length(&p);
+		const unsigned long len = php_mysqlnd_net_field_length(&p);
 
+		if (len != MYSQLND_NULL_LENGTH && ((p + len) > packet_end)) {
+			php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing "MYSQLND_SZ_T_SPEC
+											  " bytes after end of packet", (p + len) - packet_end - 1);
+			DBG_RETURN(FAIL);
+		}
 		if (copy_data == FALSE && current_field > start_field && last_field_was_string) {
 			/*
 			  Normal queries:
 			  We have to put \0 now to the end of the previous field, if it was
 			  a string. IS_NULL doesn't matter. Because we have already read our

Vulnerable code: 		unsigned long len = php_mysqlnd_net_field_length(&p);
Fixed code: 	const zend_uchar * const packet_end = (zend_uchar*) row_buffer->ptr + data_size;
		const unsigned long len = php_mysqlnd_net_field_length(&p);
		if (len != MYSQLND_NULL_LENGTH && ((p + len) > packet_end)) {
			php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing "MYSQLND_SZ_T_SPEC
											  " bytes after end of packet", (p + len) - packet_end - 1);
			DBG_RETURN(FAIL);
		}