CVE-2016-5771: Use After Free (CWE-416)
value: spl_array.c in the SPL extension in PHP before 5.5.37 and 5.6.x before 5.6.23 improperly interacts with the unserialize implementation and garbage collection, which allows remote attackers to execute arbitrary code or cause a denial of service (use-after-free and application crash) via crafted serialized data.
CWE description: The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
CVSS v3 base score: 9.8
Published: 2016-08-07T10:59Z
Repository: https://github.com/php/php-src
Language: Other
Files changed: Zend/tests/gc_024.phpt, ext/spl/spl_array.c, ext/standard/tests/strings/bug72433.phpt
Commit date: 2016-06-20 21:26:33 -0700
Commit message: Fixed ##72433: Use After Free Vulnerability in PHP's GC algorithm and unserialize

Diff: diff --git a/Zend/tests/gc_024.phpt b/Zend/tests/gc_024.phpt
index 9a2ceb88f50..ca78da63d3b 100644
--- a/Zend/tests/gc_024.phpt
+++ b/Zend/tests/gc_024.phpt
@@ -11,7 +11,7 @@
 unset($a);
 var_dump(gc_collect_cycles());
 echo "ok\n";
 ?>
 --EXPECT--
-int(1)
+int(2)
 ok
diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c
index c89cf4994b1..4e03c408c66 100644
--- a/ext/spl/spl_array.c
+++ b/ext/spl/spl_array.c
@@ -829,10 +829,20 @@ static HashTable* spl_array_get_debug_info(zval *obj, int *is_temp TSRMLS_DC) /*
 		return intern->debug_info;
 	}
 }
 /* }}} */
 
+static HashTable *spl_array_get_gc(zval *object, zval ***gc_data, int *gc_data_count TSRMLS_DC) /* {{{ */
+{
+	spl_array_object *intern = (spl_array_object*)zend_object_store_get_object(object TSRMLS_CC);
+
+	*gc_data = &intern->array;
+	*gc_data_count = 1;
+	return zend_std_get_properties(object);
+}
+/* }}} */
+
 static zval *spl_array_read_property(zval *object, zval *member, int type, const zend_literal *key TSRMLS_DC) /* {{{ */
 {
 	spl_array_object *intern = (spl_array_object*)zend_object_store_get_object(object TSRMLS_CC);
 
 	if ((intern->ar_flags & SPL_ARRAY_ARRAY_AS_PROPS) != 0
@@ -1959,10 +1969,11 @@ PHP_MINIT_FUNCTION(spl_array)
 	spl_handler_ArrayObject.has_dimension = spl_array_has_dimension;
 	spl_handler_ArrayObject.count_elements = spl_array_object_count_elements;
 
 	spl_handler_ArrayObject.get_properties = spl_array_get_properties;
 	spl_handler_ArrayObject.get_debug_info = spl_array_get_debug_info;
+	spl_handler_ArrayObject.get_gc = spl_array_get_gc;
 	spl_handler_ArrayObject.read_property = spl_array_read_property;
 	spl_handler_ArrayObject.write_property = spl_array_write_property;
 	spl_handler_ArrayObject.get_property_ptr_ptr = spl_array_get_property_ptr_ptr;
 	spl_handler_ArrayObject.has_property = spl_array_has_property;
 	spl_handler_ArrayObject.unset_property = spl_array_unset_property;
diff --git a/ext/standard/tests/strings/bug72433.phpt b/ext/standard/tests/strings/bug72433.phpt
new file mode 100644
index 00000000000..3a2c89701b1
--- /dev/null
+++ b/ext/standard/tests/strings/bug72433.phpt
@@ -0,0 +1,32 @@
+--TEST--
+Bug #72433: Use After Free Vulnerability in PHP's GC algorithm and unserialize
+--FILE--
+<?php
+// Fill any potential freed spaces until now.
+$filler = array();
+for($i = 0; $i < 100; $i++)
+	$filler[] = "";
+// Create our payload and unserialize it.
+$serialized_payload = 'a:3:{i:0;r:1;i:1;r:1;i:2;C:11:"ArrayObject":19:{x:i:0;r:1;;m:a:0:{}}}';
+$free_me = unserialize($serialized_payload);
+// We need to increment the reference counter of our ArrayObject s.t. all reference counters of our unserialized array become 0.
+$inc_ref_by_one = $free_me[2];
+// The call to gc_collect_cycles will free '$free_me'.
+gc_collect_cycles();
+// We now have multiple freed spaces. Fill all of them.
+$fill_freed_space_1 = "filler_zval_1";
+$fill_freed_space_2 = "filler_zval_2";
+var_dump($free_me);
+?>
+--EXPECTF--
+array(3) {
+  [0]=>
+  *RECURSION*
+  [1]=>
+  *RECURSION*
+  [2]=>
+  object(ArrayObject)#%d (1) {
+    ["storage":"ArrayObject":private]=>
+    *RECURSION*
+  }
+}

Vulnerable code: int(1)
Fixed code: int(2)
static HashTable *spl_array_get_gc(zval *object, zval ***gc_data, int *gc_data_count TSRMLS_DC) /* {{{ */
{
	spl_array_object *intern = (spl_array_object*)zend_object_store_get_object(object TSRMLS_CC);

	*gc_data = &intern->array;
	*gc_data_count = 1;
	return zend_std_get_properties(object);
}
/* }}} */

	spl_handler_ArrayObject.get_gc = spl_array_get_gc;
--TEST--
Bug #72433: Use After Free Vulnerability in PHP's GC algorithm and unserialize
--FILE--
<?php
// Fill any potential freed spaces until now.
$filler = array();
for($i = 0; $i < 100; $i++)
	$filler[] = "";
// Create our payload and unserialize it.
$serialized_payload = 'a:3:{i:0;r:1;i:1;r:1;i:2;C:11:"ArrayObject":19:{x:i:0;r:1;;m:a:0:{}}}';
$free_me = unserialize($serialized_payload);
// We need to increment the reference counter of our ArrayObject s.t. all reference counters of our unserialized array become 0.
$inc_ref_by_one = $free_me[2];
// The call to gc_collect_cycles will free '$free_me'.
gc_collect_cycles();
// We now have multiple freed spaces. Fill all of them.
$fill_freed_space_1 = "filler_zval_1";
$fill_freed_space_2 = "filler_zval_2";
var_dump($free_me);
?>
--EXPECTF--
array(3) {
  [0]=>
  *RECURSION*
  [1]=>
  *RECURSION*
  [2]=>
  object(ArrayObject)#%d (1) {
    ["storage":"ArrayObject":private]=>
    *RECURSION*
  }
}