diff options
author | Stanislav Malyshev <stas@php.net> | 2015-08-31 21:28:11 -0700 |
---|---|---|
committer | Stanislav Malyshev <stas@php.net> | 2015-08-31 23:26:14 -0700 |
commit | e8429400d40e3c3aa4b22ba701991d698a2f3b2f (patch) | |
tree | 198f715c679c65671ba3b6c0851076b4ae871d7c | |
parent | e201f01ac17243a1e5fb6a3911ed8e21b1619ac1 (diff) | |
download | php-git-e8429400d40e3c3aa4b22ba701991d698a2f3b2f.tar.gz |
Fix bug #70172 - Use After Free Vulnerability in unserialize()
-rw-r--r-- | ext/standard/tests/serialize/bug70172.phpt | 52 | ||||
-rw-r--r-- | ext/standard/var.c | 23 | ||||
-rw-r--r-- | ext/standard/var_unserializer.c | 76 | ||||
-rw-r--r-- | ext/standard/var_unserializer.re | 12 |
4 files changed, 121 insertions, 42 deletions
diff --git a/ext/standard/tests/serialize/bug70172.phpt b/ext/standard/tests/serialize/bug70172.phpt new file mode 100644 index 0000000000..0e9d7ed89a --- /dev/null +++ b/ext/standard/tests/serialize/bug70172.phpt @@ -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 diff --git a/ext/standard/var.c b/ext/standard/var.c index 7603ff2ee0..33b976f42d 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -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); } /* }}} */ diff --git a/ext/standard/var_unserializer.c b/ext/standard/var_unserializer.c index ffaf680c51..5f2336ec14 100644 --- a/ext/standard/var_unserializer.c +++ b/ext/standard/var_unserializer.c @@ -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; diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index f02602cd7e..ed821521e0 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -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; } |