]> granicus.if.org Git - php/commitdiff
Fix bug #70172 - Use After Free Vulnerability in unserialize()
authorStanislav Malyshev <stas@php.net>
Tue, 1 Sep 2015 04:28:11 +0000 (21:28 -0700)
committerStanislav Malyshev <stas@php.net>
Tue, 1 Sep 2015 06:26:14 +0000 (23:26 -0700)
ext/standard/tests/serialize/bug70172.phpt [new file with mode: 0644]
ext/standard/var.c
ext/standard/var_unserializer.c
ext/standard/var_unserializer.re

diff --git a/ext/standard/tests/serialize/bug70172.phpt b/ext/standard/tests/serialize/bug70172.phpt
new file mode 100644 (file)
index 0000000..0e9d7ed
--- /dev/null
@@ -0,0 +1,52 @@
+--TEST--
+Bug #70172 - Use After Free Vulnerability in unserialize()
+--FILE--
+<?php
+class obj implements Serializable {
+       var $data;
+       function serialize() {
+               return serialize($this->data);
+       }
+       function unserialize($data) {
+               $this->data = unserialize($data);
+       }
+}
+
+$fakezval = ptr2str(1122334455);
+$fakezval .= ptr2str(0);
+$fakezval .= "\x00\x00\x00\x00";
+$fakezval .= "\x01";
+$fakezval .= "\x00";
+$fakezval .= "\x00\x00";
+
+$inner = 'r:2;';
+$exploit = 'a:2:{i:0;i:1;i:1;C:3:"obj":'.strlen($inner).':{'.$inner.'}}';
+
+$data = unserialize($exploit);
+
+for ($i = 0; $i < 5; $i++) {
+       $v[$i] = $fakezval.$i;
+}
+
+var_dump($data);
+
+function ptr2str($ptr)
+{
+       $out = '';
+       for ($i = 0; $i < 8; $i++) {
+               $out .= chr($ptr & 0xff);
+               $ptr >>= 8;
+       }
+       return $out;
+}
+?>
+--EXPECTF--
+array(2) {
+  [0]=>
+  int(1)
+  [1]=>
+  object(obj)#%d (1) {
+    ["data"]=>
+    int(1)
+  }
+}
\ No newline at end of file
index 7603ff2ee093d986e16f3c421ba2ba7a8fd6fb83..33b976f42dff8dc388b92124a1b0c236a23fc259 100644 (file)
@@ -373,7 +373,7 @@ static int php_array_element_export(zval **zv TSRMLS_DC, int num_args, va_list a
 
        smart_str_appendc(buf, ',');
        smart_str_appendc(buf, '\n');
-       
+
        return 0;
 }
 /* }}} */
@@ -392,7 +392,7 @@ static int php_object_element_export(zval **zv TSRMLS_DC, int num_args, va_list
                const char *pname;
                char *pname_esc;
                int  pname_esc_len;
-               
+
                zend_unmangle_property_name(hash_key->arKey, hash_key->nKeyLength - 1,
                                &class_name, &pname);
                pname_esc = php_addcslashes(pname, strlen(pname), &pname_esc_len, 0,
@@ -469,7 +469,7 @@ PHPAPI void php_var_export_ex(zval **struc, int level, smart_str *buf TSRMLS_DC)
                        buffer_append_spaces(buf, level - 1);
                }
                smart_str_appendc(buf, ')');
-    
+
                break;
 
        case IS_OBJECT:
@@ -802,7 +802,7 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, HashTable *var
                                        BG(serialize_lock)++;
                                        res = call_user_function_ex(CG(function_table), &struc, &fname, &retval_ptr, 0, 0, 1, NULL TSRMLS_CC);
                                        BG(serialize_lock)--;
-                    
+
                                        if (EG(exception)) {
                                                if (retval_ptr) {
                                                        zval_ptr_dtor(&retval_ptr);
@@ -951,6 +951,8 @@ PHP_FUNCTION(unserialize)
        int buf_len;
        const unsigned char *p;
        php_unserialize_data_t var_hash;
+       int oldlevel;
+       zval *old_rval = return_value;
 
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &buf, &buf_len) == FAILURE) {
                RETURN_FALSE;
@@ -970,6 +972,19 @@ PHP_FUNCTION(unserialize)
                }
                RETURN_FALSE;
        }
+       if (return_value != old_rval) {
+               /*
+                * Terrible hack due to the fact that executor passes us zval *,
+                * but unserialize with r/R wants to replace it with another zval *
+                */
+               zval_dtor(old_rval);
+               *old_rval = *return_value;
+               zval_copy_ctor(old_rval);
+               var_push_dtor_no_addref(&var_hash, &return_value);
+               var_push_dtor_no_addref(&var_hash, &old_rval);
+       } else {
+               var_push_dtor(&var_hash, &return_value);
+       }
        PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
 }
 /* }}} */
index ffaf680c516b1e4503e6f81b6de1b0a49169e30d..5f2336ec1454b16ddceaedd3e7858877e3fb0ea9 100644 (file)
@@ -1,4 +1,4 @@
-/* Generated by re2c 0.13.7.5 on Sun Aug 23 19:50:03 2015 */
+/* Generated by re2c 0.13.7.5 on Mon Aug 31 23:15:46 2015 */
 #line 1 "ext/standard/var_unserializer.re"
 /*
   +----------------------------------------------------------------------+
@@ -69,7 +69,7 @@ PHPAPI void var_push_dtor(php_unserialize_data_t *var_hashx, zval **rval)
 
        var_hash = (*var_hashx)->last_dtor;
 #if VAR_ENTRIES_DBG
-       fprintf(stderr, "var_push_dtor(%ld): %d\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval));
+       fprintf(stderr, "var_push_dtor(%p, %ld): %d\n", *rval, var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval));
 #endif
 
        if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) {
@@ -100,7 +100,7 @@ PHPAPI void var_push_dtor_no_addref(php_unserialize_data_t *var_hashx, zval **rv
 
     var_hash = (*var_hashx)->last_dtor;
 #if VAR_ENTRIES_DBG
-       fprintf(stderr, "var_push_dtor_no_addref(%ld): %d (%d)\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval), Z_REFCOUNT_PP(rval));
+       fprintf(stderr, "var_push_dtor_no_addref(%p, %ld): %d (%d)\n", *rval, var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval), Z_REFCOUNT_PP(rval));
 #endif
 
        if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) {
@@ -179,6 +179,9 @@ PHPAPI void var_destroy(php_unserialize_data_t *var_hashx)
 
        while (var_hash) {
                for (i = 0; i < var_hash->used_slots; i++) {
+#if VAR_ENTRIES_DBG
+    fprintf(stderr, "var_destroy dtor(%p, %ld)\n", var_hash->data[i], Z_REFCOUNT_P(var_hash->data[i]));
+#endif
                        zval_ptr_dtor(&var_hash->data[i]);
                }
                next = var_hash->next;
@@ -239,7 +242,7 @@ static char *unserialize_str(const unsigned char **p, size_t *len, size_t maxlen
 #define YYMARKER marker
 
 
-#line 247 "ext/standard/var_unserializer.re"
+#line 250 "ext/standard/var_unserializer.re"
 
 
 
@@ -484,7 +487,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
 
 
 
-#line 488 "ext/standard/var_unserializer.c"
+#line 491 "ext/standard/var_unserializer.c"
 {
        YYCTYPE yych;
        static const unsigned char yybm[] = {
@@ -544,9 +547,9 @@ yy2:
        yych = *(YYMARKER = ++YYCURSOR);
        if (yych == ':') goto yy95;
 yy3:
-#line 839 "ext/standard/var_unserializer.re"
+#line 845 "ext/standard/var_unserializer.re"
        { return 0; }
-#line 550 "ext/standard/var_unserializer.c"
+#line 553 "ext/standard/var_unserializer.c"
 yy4:
        yych = *(YYMARKER = ++YYCURSOR);
        if (yych == ':') goto yy89;
@@ -589,13 +592,13 @@ yy13:
        goto yy3;
 yy14:
        ++YYCURSOR;
-#line 833 "ext/standard/var_unserializer.re"
+#line 839 "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 599 "ext/standard/var_unserializer.c"
+#line 602 "ext/standard/var_unserializer.c"
 yy16:
        yych = *++YYCURSOR;
        goto yy3;
@@ -626,7 +629,7 @@ yy20:
        yych = *++YYCURSOR;
        if (yych != '"') goto yy18;
        ++YYCURSOR;
-#line 687 "ext/standard/var_unserializer.re"
+#line 692 "ext/standard/var_unserializer.re"
        {
        size_t len, len2, len3, maxlen;
        long elements;
@@ -642,6 +645,7 @@ yy20:
        zval **args[1];
        zval *arg_func_name;
 
+    if (!var_hash) return 0;
        if (*start == 'C') {
                custom_object = 1;
        }
@@ -772,7 +776,7 @@ yy20:
 
        return object_common2(UNSERIALIZE_PASSTHRU, elements);
 }
-#line 776 "ext/standard/var_unserializer.c"
+#line 780 "ext/standard/var_unserializer.c"
 yy25:
        yych = *++YYCURSOR;
        if (yych <= ',') {
@@ -797,15 +801,16 @@ yy27:
        yych = *++YYCURSOR;
        if (yych != '"') goto yy18;
        ++YYCURSOR;
-#line 679 "ext/standard/var_unserializer.re"
+#line 683 "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 809 "ext/standard/var_unserializer.c"
+#line 814 "ext/standard/var_unserializer.c"
 yy32:
        yych = *++YYCURSOR;
        if (yych == '+') goto yy33;
@@ -826,11 +831,12 @@ yy34:
        yych = *++YYCURSOR;
        if (yych != '{') goto yy18;
        ++YYCURSOR;
-#line 659 "ext/standard/var_unserializer.re"
+#line 662 "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;
 
        if (elements < 0) {
                return 0;
@@ -846,7 +852,7 @@ yy34:
 
        return finish_nested_data(UNSERIALIZE_PASSTHRU);
 }
-#line 850 "ext/standard/var_unserializer.c"
+#line 856 "ext/standard/var_unserializer.c"
 yy39:
        yych = *++YYCURSOR;
        if (yych == '+') goto yy40;
@@ -867,7 +873,7 @@ yy41:
        yych = *++YYCURSOR;
        if (yych != '"') goto yy18;
        ++YYCURSOR;
-#line 630 "ext/standard/var_unserializer.re"
+#line 633 "ext/standard/var_unserializer.re"
        {
        size_t len, maxlen;
        char *str;
@@ -896,7 +902,7 @@ yy41:
        ZVAL_STRINGL(*rval, str, len, 0);
        return 1;
 }
-#line 900 "ext/standard/var_unserializer.c"
+#line 906 "ext/standard/var_unserializer.c"
 yy46:
        yych = *++YYCURSOR;
        if (yych == '+') goto yy47;
@@ -917,7 +923,7 @@ yy48:
        yych = *++YYCURSOR;
        if (yych != '"') goto yy18;
        ++YYCURSOR;
-#line 602 "ext/standard/var_unserializer.re"
+#line 605 "ext/standard/var_unserializer.re"
        {
        size_t len, maxlen;
        char *str;
@@ -945,7 +951,7 @@ yy48:
        ZVAL_STRINGL(*rval, str, len, 1);
        return 1;
 }
-#line 949 "ext/standard/var_unserializer.c"
+#line 955 "ext/standard/var_unserializer.c"
 yy53:
        yych = *++YYCURSOR;
        if (yych <= '/') {
@@ -1033,7 +1039,7 @@ yy61:
        }
 yy63:
        ++YYCURSOR;
-#line 592 "ext/standard/var_unserializer.re"
+#line 595 "ext/standard/var_unserializer.re"
        {
 #if SIZEOF_LONG == 4
 use_double:
@@ -1043,7 +1049,7 @@ use_double:
        ZVAL_DOUBLE(*rval, zend_strtod((const char *)start + 2, NULL));
        return 1;
 }
-#line 1047 "ext/standard/var_unserializer.c"
+#line 1053 "ext/standard/var_unserializer.c"
 yy65:
        yych = *++YYCURSOR;
        if (yych <= ',') {
@@ -1102,7 +1108,7 @@ yy73:
        yych = *++YYCURSOR;
        if (yych != ';') goto yy18;
        ++YYCURSOR;
-#line 577 "ext/standard/var_unserializer.re"
+#line 580 "ext/standard/var_unserializer.re"
        {
        *p = YYCURSOR;
        INIT_PZVAL(*rval);
@@ -1117,7 +1123,7 @@ yy73:
 
        return 1;
 }
-#line 1121 "ext/standard/var_unserializer.c"
+#line 1127 "ext/standard/var_unserializer.c"
 yy76:
        yych = *++YYCURSOR;
        if (yych == 'N') goto yy73;
@@ -1144,7 +1150,7 @@ yy79:
        if (yych <= '9') goto yy79;
        if (yych != ';') goto yy18;
        ++YYCURSOR;
-#line 550 "ext/standard/var_unserializer.re"
+#line 553 "ext/standard/var_unserializer.re"
        {
 #if SIZEOF_LONG == 4
        int digits = YYCURSOR - start - 3;
@@ -1171,7 +1177,7 @@ yy79:
        ZVAL_LONG(*rval, parse_iv(start + 2));
        return 1;
 }
-#line 1175 "ext/standard/var_unserializer.c"
+#line 1181 "ext/standard/var_unserializer.c"
 yy83:
        yych = *++YYCURSOR;
        if (yych <= '/') goto yy18;
@@ -1179,24 +1185,24 @@ yy83:
        yych = *++YYCURSOR;
        if (yych != ';') goto yy18;
        ++YYCURSOR;
-#line 543 "ext/standard/var_unserializer.re"
+#line 546 "ext/standard/var_unserializer.re"
        {
        *p = YYCURSOR;
        INIT_PZVAL(*rval);
        ZVAL_BOOL(*rval, parse_iv(start + 2));
        return 1;
 }
-#line 1190 "ext/standard/var_unserializer.c"
+#line 1196 "ext/standard/var_unserializer.c"
 yy87:
        ++YYCURSOR;
-#line 536 "ext/standard/var_unserializer.re"
+#line 539 "ext/standard/var_unserializer.re"
        {
        *p = YYCURSOR;
        INIT_PZVAL(*rval);
        ZVAL_NULL(*rval);
        return 1;
 }
-#line 1200 "ext/standard/var_unserializer.c"
+#line 1206 "ext/standard/var_unserializer.c"
 yy89:
        yych = *++YYCURSOR;
        if (yych <= ',') {
@@ -1219,7 +1225,7 @@ yy91:
        if (yych <= '9') goto yy91;
        if (yych != ';') goto yy18;
        ++YYCURSOR;
-#line 513 "ext/standard/var_unserializer.re"
+#line 516 "ext/standard/var_unserializer.re"
        {
        long id;
 
@@ -1242,7 +1248,7 @@ yy91:
 
        return 1;
 }
-#line 1246 "ext/standard/var_unserializer.c"
+#line 1252 "ext/standard/var_unserializer.c"
 yy95:
        yych = *++YYCURSOR;
        if (yych <= ',') {
@@ -1265,7 +1271,7 @@ yy97:
        if (yych <= '9') goto yy97;
        if (yych != ';') goto yy18;
        ++YYCURSOR;
-#line 492 "ext/standard/var_unserializer.re"
+#line 495 "ext/standard/var_unserializer.re"
        {
        long id;
 
@@ -1278,7 +1284,7 @@ yy97:
        }
 
        if (*rval != NULL) {
-               zval_ptr_dtor(rval);
+               var_push_dtor_no_addref(var_hash, rval);
        }
        *rval = *rval_ref;
        Z_ADDREF_PP(rval);
@@ -1286,9 +1292,9 @@ yy97:
 
        return 1;
 }
-#line 1290 "ext/standard/var_unserializer.c"
+#line 1296 "ext/standard/var_unserializer.c"
 }
-#line 841 "ext/standard/var_unserializer.re"
+#line 847 "ext/standard/var_unserializer.re"
 
 
        return 0;
index f02602cd7e76c9c9ac3c39e80a2da9951c9e1489..ed821521e0fc203a74d66367c4226e3df7ead3c4 100644 (file)
@@ -67,7 +67,7 @@ PHPAPI void var_push_dtor(php_unserialize_data_t *var_hashx, zval **rval)
 
        var_hash = (*var_hashx)->last_dtor;
 #if VAR_ENTRIES_DBG
-       fprintf(stderr, "var_push_dtor(%ld): %d\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval));
+       fprintf(stderr, "var_push_dtor(%p, %ld): %d\n", *rval, var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval));
 #endif
 
        if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) {
@@ -98,7 +98,7 @@ PHPAPI void var_push_dtor_no_addref(php_unserialize_data_t *var_hashx, zval **rv
 
     var_hash = (*var_hashx)->last_dtor;
 #if VAR_ENTRIES_DBG
-       fprintf(stderr, "var_push_dtor_no_addref(%ld): %d (%d)\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval), Z_REFCOUNT_PP(rval));
+       fprintf(stderr, "var_push_dtor_no_addref(%p, %ld): %d (%d)\n", *rval, var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval), Z_REFCOUNT_PP(rval));
 #endif
 
        if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) {
@@ -177,6 +177,9 @@ PHPAPI void var_destroy(php_unserialize_data_t *var_hashx)
 
        while (var_hash) {
                for (i = 0; i < var_hash->used_slots; i++) {
+#if VAR_ENTRIES_DBG
+    fprintf(stderr, "var_destroy dtor(%p, %ld)\n", var_hash->data[i], Z_REFCOUNT_P(var_hash->data[i]));
+#endif
                        zval_ptr_dtor(&var_hash->data[i]);
                }
                next = var_hash->next;
@@ -501,7 +504,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
        }
 
        if (*rval != NULL) {
-               zval_ptr_dtor(rval);
+               var_push_dtor_no_addref(var_hash, rval);
        }
        *rval = *rval_ref;
        Z_ADDREF_PP(rval);
@@ -660,6 +663,7 @@ use_double:
        long elements = parse_iv(start + 2);
        /* use iv() not uiv() in order to check data range */
        *p = YYCURSOR;
+    if (!var_hash) return 0;
 
        if (elements < 0) {
                return 0;
@@ -677,6 +681,7 @@ use_double:
 }
 
 "o:" iv ":" ["] {
+    if (!var_hash) return 0;
 
        INIT_PZVAL(*rval);
 
@@ -699,6 +704,7 @@ object ":" uiv ":" ["]      {
        zval **args[1];
        zval *arg_func_name;
 
+    if (!var_hash) return 0;
        if (*start == 'C') {
                custom_object = 1;
        }