CVE-2016-7411: Improper Restriction of Operations within the Bounds of a Memory Buffer (CWE-119)
value: ext/standard/var_unserializer.re in PHP before 5.6.26 mishandles object-deserialization failures, which allows remote attackers to cause a denial of service (memory corruption) or possibly have unspecified other impact via an unserialize call that references a partially constructed object.
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: 9.8
Published: 2016-09-17T21:59Z
Repository: https://github.com/php/php-src
Language: C
Files changed: Zend/zend_objects_API.c, ext/standard/tests/serialize/bug73052.phpt, ext/standard/var_unserializer.c, ext/standard/var_unserializer.re
Commit date: 2016-09-12 21:04:23 -0700
Commit message: Fix bug #73052 - Memory Corruption in During Deserialized-object Destruction

Diff: diff --git a/Zend/zend_objects_API.c b/Zend/zend_objects_API.c
index 56c47b7f84e..cdc0b69fbf2 100644
--- a/Zend/zend_objects_API.c
+++ b/Zend/zend_objects_API.c
@@ -213,11 +213,11 @@ ZEND_API void zend_objects_store_del_ref_by_handle_ex(zend_object_handle handle,
 					} zend_catch {
 						failure = 1;
 					} zend_end_try();
 				}
 			}
-			
+
 			/* re-read the object from the object store as the store might have been reallocated in the dtor */
 			obj = &EG(objects_store).object_buckets[handle].bucket.obj;
 
 			if (obj->refcount == 1) {
 				GC_REMOVE_ZOBJ_FROM_BUFFER(obj);
@@ -304,12 +304,12 @@ ZEND_API void zend_object_store_set_object(zval *zobject, void *object TSRMLS_DC
 /* Called when the ctor was terminated by an exception */
 ZEND_API void zend_object_store_ctor_failed(zval *zobject TSRMLS_DC)
 {
 	zend_object_handle handle = Z_OBJ_HANDLE_P(zobject);
 	zend_object_store_bucket *obj_bucket = &EG(objects_store).object_buckets[handle];
-	
-	obj_bucket->bucket.obj.handlers = Z_OBJ_HT_P(zobject);;
+
+	obj_bucket->bucket.obj.handlers = Z_OBJ_HT_P(zobject);
 	obj_bucket->destructor_called = 1;
 }
 
 
 /* Proxy objects workings */
diff --git a/ext/standard/tests/serialize/bug73052.phpt b/ext/standard/tests/serialize/bug73052.phpt
new file mode 100644
index 00000000000..63b484bf14d
--- /dev/null
+++ b/ext/standard/tests/serialize/bug73052.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Bug #73052: Memory Corruption in During Deserialized-object Destruction
+--FILE--
+<?php
+
+class obj {
+    var $ryat;
+    public function __destruct() {
+        $this->ryat = null;
+    }
+}
+
+$poc = 'O:3:"obj":1:{';
+var_dump(unserialize($poc));
+?>
+--EXPECTF--
+Notice: unserialize(): Error at offset 13 of 13 bytes in %sbug73052.php on line %d
+bool(false)
diff --git a/ext/standard/var_unserializer.c b/ext/standard/var_unserializer.c
index c8e6f8a0ca9..549149267eb 100644
--- a/ext/standard/var_unserializer.c
+++ b/ext/standard/var_unserializer.c
@@ -435,10 +435,11 @@ static inline int object_common2(UNSERIALIZE_PARAMETER, long elements)
 
 	if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_OBJPROP_PP(rval), elements, 1)) {
 	    /* We've got partially constructed object on our hands here. Wipe it. */
 	    if(Z_TYPE_PP(rval) == IS_OBJECT) {
 	       zend_hash_clean(Z_OBJPROP_PP(rval));
+	       zend_object_store_ctor_failed(*rval TSRMLS_CC);
 	    }
 	    ZVAL_NULL(*rval);
 		return 0;
 	}
 
@@ -489,11 +490,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	start = cursor;
 
 
 
 
-#line 495 "ext/standard/var_unserializer.c"
+#line 496 "ext/standard/var_unserializer.c"
 {
 	YYCTYPE yych;
 	static const unsigned char yybm[] = {
 		  0,   0,   0,   0,   0,   0,   0,   0, 
 		  0,   0,   0,   0,   0,   0,   0,   0, 
@@ -549,13 +550,13 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	}
 yy2:
 	yych = *(YYMARKER = ++YYCURSOR);
 	if (yych == ':') goto yy95;
 yy3:
-#line 860 "ext/standard/var_unserializer.re"
+#line 861 "ext/standard/var_unserializer.re"
 	{ return 0; }
-#line 557 "ext/standard/var_unserializer.c"
+#line 558 "ext/standard/var_unserializer.c"
 yy4:
 	yych = *(YYMARKER = ++YYCURSOR);
 	if (yych == ':') goto yy89;
 	goto yy3;
 yy5:
@@ -594,17 +595,17 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	yych = *(YYMARKER = ++YYCURSOR);
 	if (yych == ':') goto yy17;
 	goto yy3;
 yy14:
 	++YYCURSOR;
-#line 854 "ext/standard/var_unserializer.re"
+#line 855 "ext/standard/var_unserializer.re"
 	{
 	/* this is the case where we have less data than planned */
 	php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Unexpected end of serialized data");
 	return 0; /* not sure if it should be 0 or 1 here? */
 }
-#line 606 "ext/standard/var_unserializer.c"
+#line 607 "ext/standard/var_unserializer.c"
 yy16:
 	yych = *++YYCURSOR;
 	goto yy3;
 yy17:
 	yych = *++YYCURSOR;
@@ -631,11 +632,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	if (yych <= '/') goto yy18;
 	if (yych >= ';') goto yy18;
 	yych = *++YYCURSOR;
 	if (yych != '"') goto yy18;
 	++YYCURSOR;
-#line 707 "ext/standard/var_unserializer.re"
+#line 708 "ext/standard/var_unserializer.re"
 	{
 	size_t len, len2, len3, maxlen;
 	long elements;
 	char *class_name;
 	zend_class_entry *ce;
@@ -778,11 +779,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	}
 	efree(class_name);
 
 	return object_common2(UNSERIALIZE_PASSTHRU, elements);
 }
-#line 784 "ext/standard/var_unserializer.c"
+#line 785 "ext/standard/var_unserializer.c"
 yy25:
 	yych = *++YYCURSOR;
 	if (yych <= ',') {
 		if (yych != '+') goto yy18;
 	} else {
@@ -803,20 +804,20 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	if (yych <= '9') goto yy27;
 	if (yych >= ';') goto yy18;
 	yych = *++YYCURSOR;
 	if (yych != '"') goto yy18;
 	++YYCURSOR;
-#line 698 "ext/standard/var_unserializer.re"
+#line 699 "ext/standard/var_unserializer.re"
 	{
     if (!var_hash) return 0;
 
 	INIT_PZVAL(*rval);
 
 	return object_common2(UNSERIALIZE_PASSTHRU,
 			object_common1(UNSERIALIZE_PASSTHRU, ZEND_STANDARD_CLASS_DEF_PTR));
 }
-#line 818 "ext/standard/var_unserializer.c"
+#line 819 "ext/standard/var_unserializer.c"
 yy32:
 	yych = *++YYCURSOR;
 	if (yych == '+') goto yy33;
 	if (yych <= '/') goto yy18;
 	if (yych <= '9') goto yy34;
@@ -833,11 +834,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	if (yych <= '9') goto yy34;
 	if (yych >= ';') goto yy18;
 	yych = *++YYCURSOR;
 	if (yych != '{') goto yy18;
 	++YYCURSOR;
-#line 677 "ext/standard/var_unserializer.re"
+#line 678 "ext/standard/var_unserializer.re"
 	{
 	long elements = parse_iv(start + 2);
 	/* use iv() not uiv() in order to check data range */
 	*p = YYCURSOR;
     if (!var_hash) return 0;
@@ -854,11 +855,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 		return 0;
 	}
 
 	return finish_nested_data(UNSERIALIZE_PASSTHRU);
 }
-#line 860 "ext/standard/var_unserializer.c"
+#line 861 "ext/standard/var_unserializer.c"
 yy39:
 	yych = *++YYCURSOR;
 	if (yych == '+') goto yy40;
 	if (yych <= '/') goto yy18;
 	if (yych <= '9') goto yy41;
@@ -875,11 +876,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	if (yych <= '9') goto yy41;
 	if (yych >= ';') goto yy18;
 	yych = *++YYCURSOR;
 	if (yych != '"') goto yy18;
 	++YYCURSOR;
-#line 642 "ext/standard/var_unserializer.re"
+#line 643 "ext/standard/var_unserializer.re"
 	{
 	size_t len, maxlen;
 	char *str;
 
 	len = parse_uiv(start + 2);
@@ -910,11 +911,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 
 	INIT_PZVAL(*rval);
 	ZVAL_STRINGL(*rval, str, len, 0);
 	return 1;
 }
-#line 916 "ext/standard/var_unserializer.c"
+#line 917 "ext/standard/var_unserializer.c"
 yy46:
 	yych = *++YYCURSOR;
 	if (yych == '+') goto yy47;
 	if (yych <= '/') goto yy18;
 	if (yych <= '9') goto yy48;
@@ -931,11 +932,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	if (yych <= '9') goto yy48;
 	if (yych >= ';') goto yy18;
 	yych = *++YYCURSOR;
 	if (yych != '"') goto yy18;
 	++YYCURSOR;
-#line 609 "ext/standard/var_unserializer.re"
+#line 610 "ext/standard/var_unserializer.re"
 	{
 	size_t len, maxlen;
 	char *str;
 
 	len = parse_uiv(start + 2);
@@ -964,11 +965,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 
 	INIT_PZVAL(*rval);
 	ZVAL_STRINGL(*rval, str, len, 1);
 	return 1;
 }
-#line 970 "ext/standard/var_unserializer.c"
+#line 971 "ext/standard/var_unserializer.c"
 yy53:
 	yych = *++YYCURSOR;
 	if (yych <= '/') {
 		if (yych <= ',') {
 			if (yych == '+') goto yy57;
@@ -1052,21 +1053,21 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 			goto yy18;
 		}
 	}
 yy63:
 	++YYCURSOR;
-#line 599 "ext/standard/var_unserializer.re"
+#line 600 "ext/standard/var_unserializer.re"
 	{
 #if SIZEOF_LONG == 4
 use_double:
 #endif
 	*p = YYCURSOR;
 	INIT_PZVAL(*rval);
 	ZVAL_DOUBLE(*rval, zend_strtod((const char *)start + 2, NULL));
 	return 1;
 }
-#line 1068 "ext/standard/var_unserializer.c"
+#line 1069 "ext/standard/var_unserializer.c"
 yy65:
 	yych = *++YYCURSOR;
 	if (yych <= ',') {
 		if (yych != '+') goto yy18;
 	} else {
@@ -1121,11 +1122,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	if (yych != 'F') goto yy18;
 yy73:
 	yych = *++YYCURSOR;
 	if (yych != ';') goto yy18;
 	++YYCURSOR;
-#line 584 "ext/standard/var_unserializer.re"
+#line 585 "ext/standard/var_unserializer.re"
 	{
 	*p = YYCURSOR;
 	INIT_PZVAL(*rval);
 
 	if (!strncmp(start + 2, "NAN", 3)) {
@@ -1136,11 +1137,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 		ZVAL_DOUBLE(*rval, -php_get_inf());
 	}
 
 	return 1;
 }
-#line 1142 "ext/standard/var_unserializer.c"
+#line 1143 "ext/standard/var_unserializer.c"
 yy76:
 	yych = *++YYCURSOR;
 	if (yych == 'N') goto yy73;
 	goto yy18;
 yy77:
@@ -1163,11 +1164,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	yych = *YYCURSOR;
 	if (yych <= '/') goto yy18;
 	if (yych <= '9') goto yy79;
 	if (yych != ';') goto yy18;
 	++YYCURSOR;
-#line 557 "ext/standard/var_unserializer.re"
+#line 558 "ext/standard/var_unserializer.re"
 	{
 #if SIZEOF_LONG == 4
 	int digits = YYCURSOR - start - 3;
 
 	if (start[2] == '-' || start[2] == '+') {
@@ -1190,36 +1191,36 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	*p = YYCURSOR;
 	INIT_PZVAL(*rval);
 	ZVAL_LONG(*rval, parse_iv(start + 2));
 	return 1;
 }
-#line 1196 "ext/standard/var_unserializer.c"
+#line 1197 "ext/standard/var_unserializer.c"
 yy83:
 	yych = *++YYCURSOR;
 	if (yych <= '/') goto yy18;
 	if (yych >= '2') goto yy18;
 	yych = *++YYCURSOR;
 	if (yych != ';') goto yy18;
 	++YYCURSOR;
-#line 550 "ext/standard/var_unserializer.re"
+#line 551 "ext/standard/var_unserializer.re"
 	{
 	*p = YYCURSOR;
 	INIT_PZVAL(*rval);
 	ZVAL_BOOL(*rval, parse_iv(start + 2));
 	return 1;
 }
-#line 1211 "ext/standard/var_unserializer.c"
+#line 1212 "ext/standard/var_unserializer.c"
 yy87:
 	++YYCURSOR;
-#line 543 "ext/standard/var_unserializer.re"
+#line 544 "ext/standard/var_unserializer.re"
 	{
 	*p = YYCURSOR;
 	INIT_PZVAL(*rval);
 	ZVAL_NULL(*rval);
 	return 1;
 }
-#line 1221 "ext/standard/var_unserializer.c"
+#line 1222 "ext/standard/var_unserializer.c"
 yy89:
 	yych = *++YYCURSOR;
 	if (yych <= ',') {
 		if (yych != '+') goto yy18;
 	} else {
@@ -1238,11 +1239,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	yych = *YYCURSOR;
 	if (yych <= '/') goto yy18;
 	if (yych <= '9') goto yy91;
 	if (yych != ';') goto yy18;
 	++YYCURSOR;
-#line 520 "ext/standard/var_unserializer.re"
+#line 521 "ext/standard/var_unserializer.re"
 	{
 	long id;
 
  	*p = YYCURSOR;
 	if (!var_hash) return 0;
@@ -1261,11 +1262,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	Z_ADDREF_PP(rval);
 	Z_UNSET_ISREF_PP(rval);
 
 	return 1;
 }
-#line 1267 "ext/standard/var_unserializer.c"
+#line 1268 "ext/standard/var_unserializer.c"
 yy95:
 	yych = *++YYCURSOR;
 	if (yych <= ',') {
 		if (yych != '+') goto yy18;
 	} else {
@@ -1284,11 +1285,11 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	yych = *YYCURSOR;
 	if (yych <= '/') goto yy18;
 	if (yych <= '9') goto yy97;
 	if (yych != ';') goto yy18;
 	++YYCURSOR;
-#line 499 "ext/standard/var_unserializer.re"
+#line 500 "ext/standard/var_unserializer.re"
 	{
 	long id;
 
  	*p = YYCURSOR;
 	if (!var_hash) return 0;
@@ -1305,12 +1306,12 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 	Z_ADDREF_PP(rval);
 	Z_SET_ISREF_PP(rval);
 
 	return 1;
 }
-#line 1311 "ext/standard/var_unserializer.c"
+#line 1312 "ext/standard/var_unserializer.c"
 }
-#line 862 "ext/standard/var_unserializer.re"
+#line 863 "ext/standard/var_unserializer.re"
 
 
 	return 0;
 }
diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re
index 11b93c522b2..ce84bf5eb40 100644
--- a/ext/standard/var_unserializer.re
+++ b/ext/standard/var_unserializer.re
@@ -439,10 +439,11 @@ static inline int object_common2(UNSERIALIZE_PARAMETER, long elements)
 
 	if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_OBJPROP_PP(rval), elements, 1)) {
 	    /* We've got partially constructed object on our hands here. Wipe it. */
 	    if(Z_TYPE_PP(rval) == IS_OBJECT) {
 	       zend_hash_clean(Z_OBJPROP_PP(rval));
+	       zend_object_store_ctor_failed(*rval TSRMLS_CC);
 	    }
 	    ZVAL_NULL(*rval);
 		return 0;
 	}
 

Vulnerable code: 			
	
	obj_bucket->bucket.obj.handlers = Z_OBJ_HT_P(zobject);;
#line 495 "ext/standard/var_unserializer.c"
#line 860 "ext/standard/var_unserializer.re"
#line 557 "ext/standard/var_unserializer.c"
#line 854 "ext/standard/var_unserializer.re"
#line 606 "ext/standard/var_unserializer.c"
#line 707 "ext/standard/var_unserializer.re"
#line 784 "ext/standard/var_unserializer.c"
#line 698 "ext/standard/var_unserializer.re"
#line 818 "ext/standard/var_unserializer.c"
#line 677 "ext/standard/var_unserializer.re"
#line 860 "ext/standard/var_unserializer.c"
#line 642 "ext/standard/var_unserializer.re"
#line 916 "ext/standard/var_unserializer.c"
#line 609 "ext/standard/var_unserializer.re"
#line 970 "ext/standard/var_unserializer.c"
#line 599 "ext/standard/var_unserializer.re"
#line 1068 "ext/standard/var_unserializer.c"
#line 584 "ext/standard/var_unserializer.re"
#line 1142 "ext/standard/var_unserializer.c"
#line 557 "ext/standard/var_unserializer.re"
#line 1196 "ext/standard/var_unserializer.c"
#line 550 "ext/standard/var_unserializer.re"
#line 1211 "ext/standard/var_unserializer.c"
#line 543 "ext/standard/var_unserializer.re"
#line 1221 "ext/standard/var_unserializer.c"
#line 520 "ext/standard/var_unserializer.re"
#line 1267 "ext/standard/var_unserializer.c"
#line 499 "ext/standard/var_unserializer.re"
#line 1311 "ext/standard/var_unserializer.c"
#line 862 "ext/standard/var_unserializer.re"
Fixed code: 

	obj_bucket->bucket.obj.handlers = Z_OBJ_HT_P(zobject);
--TEST--
Bug #73052: Memory Corruption in During Deserialized-object Destruction
--FILE--
<?php

class obj {
    var $ryat;
    public function __destruct() {
        $this->ryat = null;
    }
}

$poc = 'O:3:"obj":1:{';
var_dump(unserialize($poc));
?>
--EXPECTF--
Notice: unserialize(): Error at offset 13 of 13 bytes in %sbug73052.php on line %d
bool(false)
	       zend_object_store_ctor_failed(*rval TSRMLS_CC);
#line 496 "ext/standard/var_unserializer.c"
#line 861 "ext/standard/var_unserializer.re"
#line 558 "ext/standard/var_unserializer.c"
#line 855 "ext/standard/var_unserializer.re"
#line 607 "ext/standard/var_unserializer.c"
#line 708 "ext/standard/var_unserializer.re"
#line 785 "ext/standard/var_unserializer.c"
#line 699 "ext/standard/var_unserializer.re"
#line 819 "ext/standard/var_unserializer.c"
#line 678 "ext/standard/var_unserializer.re"
#line 861 "ext/standard/var_unserializer.c"
#line 643 "ext/standard/var_unserializer.re"
#line 917 "ext/standard/var_unserializer.c"
#line 610 "ext/standard/var_unserializer.re"
#line 971 "ext/standard/var_unserializer.c"
#line 600 "ext/standard/var_unserializer.re"
#line 1069 "ext/standard/var_unserializer.c"
#line 585 "ext/standard/var_unserializer.re"
#line 1143 "ext/standard/var_unserializer.c"
#line 558 "ext/standard/var_unserializer.re"
#line 1197 "ext/standard/var_unserializer.c"
#line 551 "ext/standard/var_unserializer.re"
#line 1212 "ext/standard/var_unserializer.c"
#line 544 "ext/standard/var_unserializer.re"
#line 1222 "ext/standard/var_unserializer.c"
#line 521 "ext/standard/var_unserializer.re"
#line 1268 "ext/standard/var_unserializer.c"
#line 500 "ext/standard/var_unserializer.re"
#line 1312 "ext/standard/var_unserializer.c"
#line 863 "ext/standard/var_unserializer.re"
	       zend_object_store_ctor_failed(*rval TSRMLS_CC);