Vulnerability: 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.
Vulnerability ID: CVE-2016-7412
CWE: CWE-119
Severity score: 6.4
Project: https://github.com/php/php-src
Published: 2016-09-17
File: ext/mysqlnd/mysqlnd_wireprotocol.c
Commit message: Fix bug #72293 - Heap overflow in mysqlnd related to BIT fields
Commit author: Stanislav Malyshev

Vulnerable version (before fix):
 php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, 	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 */  	DBG_ENTER("php_mysqlnd_rowp_read_text_protocol_aux");  php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, 		/* 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);  		if (copy_data == FALSE && current_field > start_field && last_field_was_string) { 			/* 			  Normal queries:

Fixed version (after fix):
 php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, 	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");  php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, 		/* 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; */ 		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:

Patch:
patch: @@ -1585,6 +1585,7 @@ php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_MEMORY_POOL_CHUNK * row_buffer,
 	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");
 
@@ -1606,8 +1607,13 @@ php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_MEMORY_POOL_CHUNK * row_buffer,
 		/* 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:
path: ext/mysqlnd/mysqlnd_wireprotocol.c