]> granicus.if.org Git - php/commitdiff
Bug #72663 - part 2
authorNikita Popov <nikic@php.net>
Wed, 10 Aug 2016 12:30:16 +0000 (14:30 +0200)
committerStanislav Malyshev <stas@php.net>
Wed, 17 Aug 2016 07:47:02 +0000 (00:47 -0700)
If a (nested) unserialize() call fails, we remove all the values
that were inserted into var_hash during that call. This prevents
their use in other unserializations in the same context.

ext/standard/tests/serialize/bug72663_2.phpt [new file with mode: 0644]
ext/standard/var_unserializer.c
ext/standard/var_unserializer.re

diff --git a/ext/standard/tests/serialize/bug72663_2.phpt b/ext/standard/tests/serialize/bug72663_2.phpt
new file mode 100644 (file)
index 0000000..8825dc5
--- /dev/null
@@ -0,0 +1,27 @@
+--TEST--
+Bug #72663 (2): Don't allow references into failed unserialize
+--FILE--
+<?php
+
+class obj implements Serializable {
+    public $data;
+    function serialize() {
+        return serialize($this->data);
+    }
+    function unserialize($data) {
+        $this->data = unserialize($data);
+    }
+}
+
+$inner = 'a:1:{i:0;O:9:"Exception":2:{s:7:"'."\0".'*'."\0".'file";R:4;}';
+$exploit = 'a:2:{i:0;C:3:"obj":'.strlen($inner).':{'.$inner.'}i:1;R:4;}';
+var_dump(unserialize($exploit));
+
+?>
+--EXPECTF--
+Notice: unserialize(): Unexpected end of serialized data in %s on line %d
+
+Notice: unserialize(): Error at offset 46 of 47 bytes in %s on line %d
+
+Notice: unserialize(): Error at offset 79 of 80 bytes in %s on line %d
+bool(false)
index 317c3f9c487e164d6f5e2407e2569c35645373ef..6b33d84bd2eed5fe26d7914dab362611de051f57 100644 (file)
@@ -301,6 +301,8 @@ static inline size_t parse_uiv(const unsigned char *p)
 #define UNSERIALIZE_PARAMETER zval *rval, const unsigned char **p, const unsigned char *max, php_unserialize_data_t *var_hash, HashTable *classes
 #define UNSERIALIZE_PASSTHRU rval, p, max, var_hash, classes
 
+static int php_var_unserialize_internal(UNSERIALIZE_PARAMETER);
+
 static zend_always_inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, zend_long elements, int objprops)
 {
        while (elements-- > 0) {
@@ -309,7 +311,7 @@ static zend_always_inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTab
 
                ZVAL_UNDEF(&key);
 
-               if (!php_var_unserialize_ex(&key, p, max, NULL, classes)) {
+               if (!php_var_unserialize_internal(&key, p, max, NULL, classes)) {
                        zval_dtor(&key);
                        return 0;
                }
@@ -365,7 +367,7 @@ string_key:
                        }
                }
 
-               if (!php_var_unserialize_ex(data, p, max, var_hash, classes)) {
+               if (!php_var_unserialize_internal(data, p, max, var_hash, classes)) {
                        zval_dtor(&key);
                        return 0;
                }
@@ -502,8 +504,34 @@ PHPAPI int php_var_unserialize(zval *rval, const unsigned char **p, const unsign
        return php_var_unserialize_ex(UNSERIALIZE_PASSTHRU);
 }
 
-
 PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER)
+{
+       var_entries *orig_var_entries = (*var_hash)->last;
+       zend_long orig_used_slots = orig_var_entries ? orig_var_entries->used_slots : 0;
+       int result;
+       
+       result = php_var_unserialize_internal(UNSERIALIZE_PASSTHRU);
+
+       if (!result) {
+               /* If the unserialization failed, mark all elements that have been added to var_hash
+                * as NULL. This will forbid their use by other unserialize() calls in the same
+                * unserialization context. */
+               var_entries *e = orig_var_entries;
+               zend_long s = orig_used_slots;
+               while (e) {
+                       for (; s < e->used_slots; s++) {
+                               e->data[s] = NULL;
+                       }
+
+                       e = e->next;
+                       s = 0;
+               }
+       }
+
+       return result;
+}
+
+static int php_var_unserialize_internal(UNSERIALIZE_PARAMETER)
 {
        const unsigned char *cursor, *limit, *marker, *start;
        zval *rval_ref;
@@ -522,7 +550,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER)
        start = cursor;
 
 
-#line 526 "ext/standard/var_unserializer.c"
+#line 554 "ext/standard/var_unserializer.c"
 {
        YYCTYPE yych;
        static const unsigned char yybm[] = {
index fa8cce888057084524aef65e75eceaae8c7d5da3..9051894643165dd1852e4feb55ee80fc3e2da33e 100644 (file)
@@ -305,6 +305,8 @@ static inline size_t parse_uiv(const unsigned char *p)
 #define UNSERIALIZE_PARAMETER zval *rval, const unsigned char **p, const unsigned char *max, php_unserialize_data_t *var_hash, HashTable *classes
 #define UNSERIALIZE_PASSTHRU rval, p, max, var_hash, classes
 
+static int php_var_unserialize_internal(UNSERIALIZE_PARAMETER);
+
 static zend_always_inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, zend_long elements, int objprops)
 {
        while (elements-- > 0) {
@@ -313,7 +315,7 @@ static zend_always_inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTab
 
                ZVAL_UNDEF(&key);
 
-               if (!php_var_unserialize_ex(&key, p, max, NULL, classes)) {
+               if (!php_var_unserialize_internal(&key, p, max, NULL, classes)) {
                        zval_dtor(&key);
                        return 0;
                }
@@ -369,7 +371,7 @@ string_key:
                        }
                }
 
-               if (!php_var_unserialize_ex(data, p, max, var_hash, classes)) {
+               if (!php_var_unserialize_internal(data, p, max, var_hash, classes)) {
                        zval_dtor(&key);
                        return 0;
                }
@@ -506,8 +508,34 @@ PHPAPI int php_var_unserialize(zval *rval, const unsigned char **p, const unsign
        return php_var_unserialize_ex(UNSERIALIZE_PASSTHRU);
 }
 
-
 PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER)
+{
+       var_entries *orig_var_entries = (*var_hash)->last;
+       zend_long orig_used_slots = orig_var_entries ? orig_var_entries->used_slots : 0;
+       int result;
+       
+       result = php_var_unserialize_internal(UNSERIALIZE_PASSTHRU);
+
+       if (!result) {
+               /* If the unserialization failed, mark all elements that have been added to var_hash
+                * as NULL. This will forbid their use by other unserialize() calls in the same
+                * unserialization context. */
+               var_entries *e = orig_var_entries;
+               zend_long s = orig_used_slots;
+               while (e) {
+                       for (; s < e->used_slots; s++) {
+                               e->data[s] = NULL;
+                       }
+
+                       e = e->next;
+                       s = 0;
+               }
+       }
+
+       return result;
+}
+
+static int php_var_unserialize_internal(UNSERIALIZE_PARAMETER)
 {
        const unsigned char *cursor, *limit, *marker, *start;
        zval *rval_ref;