From be54eb7db10c6aa838cef969822a5ae0f4e605e3 Mon Sep 17 00:00:00 2001 From: Xinchen Hui Date: Mon, 10 Aug 2015 17:02:16 +0800 Subject: [PATCH] Fixed bug #70211 (php 7 ZEND_HASH_IF_FULL_DO_RESIZE use after free) --- NEWS | 2 + Zend/zend_hash.c | 14 +++++++ Zend/zend_hash.h | 1 + ext/soap/tests/bug70211.phpt | 24 ++++++++++++ ext/standard/var_unserializer.c | 65 ++++++++++++++++---------------- ext/standard/var_unserializer.re | 5 ++- 6 files changed, 77 insertions(+), 34 deletions(-) create mode 100644 ext/soap/tests/bug70211.phpt diff --git a/NEWS b/NEWS index 8e489897db..bd0a195fbe 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,8 @@ PHP NEWS . Fixed bug #70214 (FASYNC not defined, needs sys/file.h include). (Bob) - Standard: + . Fixed bug #70211 (php 7 ZEND_HASH_IF_FULL_DO_RESIZE use after free). + (Laruence) . Fixed bug #70208 (Assert breaking access on objects). (Bob) 06 Aug 2015, PHP 7.0.0 Beta 3 diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index eb5081d56c..0d3932ebd4 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -807,6 +807,20 @@ static void ZEND_FASTCALL zend_hash_do_resize(HashTable *ht) } } +ZEND_API int ZEND_FASTCALL zend_hash_resize(HashTable *ht, uint32_t size) { + void *old_data = HT_GET_DATA_ADDR(ht); + Bucket *old_buckets = ht->arData; + + HANDLE_BLOCK_INTERRUPTIONS(); + ht->nTableSize = zend_hash_check_size(size); + ht->nTableMask = -ht->nTableSize; + HT_SET_DATA_ADDR(ht, pemalloc(HT_SIZE(ht), ht->u.flags & HASH_FLAG_PERSISTENT)); + memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed); + pefree(old_data, ht->u.flags & HASH_FLAG_PERSISTENT); + zend_hash_rehash(ht); + HANDLE_UNBLOCK_INTERRUPTIONS(); +} + ZEND_API int ZEND_FASTCALL zend_hash_rehash(HashTable *ht) { Bucket *p; diff --git a/Zend/zend_hash.h b/Zend/zend_hash.h index 4a33b959cb..b32309603c 100644 --- a/Zend/zend_hash.h +++ b/Zend/zend_hash.h @@ -212,6 +212,7 @@ ZEND_API zval* ZEND_FASTCALL zend_hash_minmax(const HashTable *ht, compare_func_ #define zend_hash_next_free_element(ht) \ (ht)->nNextFreeElement +ZEND_API int ZEND_FASTCALL zend_hash_resize(HashTable *ht, uint32_t size); ZEND_API int ZEND_FASTCALL zend_hash_rehash(HashTable *ht); ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source); diff --git a/ext/soap/tests/bug70211.phpt b/ext/soap/tests/bug70211.phpt new file mode 100644 index 0000000000..8346b35ab7 --- /dev/null +++ b/ext/soap/tests/bug70211.phpt @@ -0,0 +1,24 @@ +--TEST-- +Bug #70211 (php 7 ZEND_HASH_IF_FULL_DO_RESIZE use after free) +--FILE-- +>= 8; + } + return $out; +} + +$sf = new SoapFault('1', 'string', 'detail', 'header','line', str_repeat("A",232).ptr2str($addr)); +$ob = unserialize("a:3:{i:0;".serialize($sf).'i:1;r:12;i:2;r:10;}'); + +var_dump($ob[1]); +?> +--EXPECT-- +string(6) "detail" diff --git a/ext/standard/var_unserializer.c b/ext/standard/var_unserializer.c index ea1c971081..6bc37a8263 100644 --- a/ext/standard/var_unserializer.c +++ b/ext/standard/var_unserializer.c @@ -470,13 +470,14 @@ static inline int object_common2(UNSERIALIZE_PARAMETER, zend_long elements) { zval retval; zval fname; + HashTable *ht = Z_OBJPROP_P(rval); if (Z_TYPE_P(rval) != IS_OBJECT) { return 0; } - //??? TODO: resize before - if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_OBJPROP_P(rval), elements, 1)) { + zend_hash_resize(ht, zend_hash_num_elements(ht) + elements); + if (!process_nested_data(UNSERIALIZE_PASSTHRU, ht, elements, 1)) { return 0; } @@ -528,7 +529,7 @@ PHPAPI int php_var_unserialize_ex(UNSERIALIZE_PARAMETER) start = cursor; -#line 532 "ext/standard/var_unserializer.c" +#line 533 "ext/standard/var_unserializer.c" { YYCTYPE yych; static const unsigned char yybm[] = { @@ -588,9 +589,9 @@ yy2: yych = *(YYMARKER = ++YYCURSOR); if (yych == ':') goto yy95; yy3: -#line 879 "ext/standard/var_unserializer.re" +#line 880 "ext/standard/var_unserializer.re" { return 0; } -#line 594 "ext/standard/var_unserializer.c" +#line 595 "ext/standard/var_unserializer.c" yy4: yych = *(YYMARKER = ++YYCURSOR); if (yych == ':') goto yy89; @@ -633,13 +634,13 @@ yy13: goto yy3; yy14: ++YYCURSOR; -#line 873 "ext/standard/var_unserializer.re" +#line 874 "ext/standard/var_unserializer.re" { /* this is the case where we have less data than planned */ php_error_docref(NULL, E_NOTICE, "Unexpected end of serialized data"); return 0; /* not sure if it should be 0 or 1 here? */ } -#line 643 "ext/standard/var_unserializer.c" +#line 644 "ext/standard/var_unserializer.c" yy16: yych = *++YYCURSOR; goto yy3; @@ -669,7 +670,7 @@ yy20: yych = *++YYCURSOR; if (yych != '"') goto yy18; ++YYCURSOR; -#line 728 "ext/standard/var_unserializer.re" +#line 729 "ext/standard/var_unserializer.re" { size_t len, len2, len3, maxlen; zend_long elements; @@ -814,7 +815,7 @@ yy20: return object_common2(UNSERIALIZE_PASSTHRU, elements); } -#line 818 "ext/standard/var_unserializer.c" +#line 819 "ext/standard/var_unserializer.c" yy25: yych = *++YYCURSOR; if (yych <= ',') { @@ -839,7 +840,7 @@ yy27: yych = *++YYCURSOR; if (yych != '"') goto yy18; ++YYCURSOR; -#line 720 "ext/standard/var_unserializer.re" +#line 721 "ext/standard/var_unserializer.re" { //??? INIT_PZVAL(rval); @@ -847,7 +848,7 @@ yy27: return object_common2(UNSERIALIZE_PASSTHRU, object_common1(UNSERIALIZE_PASSTHRU, ZEND_STANDARD_CLASS_DEF_PTR)); } -#line 851 "ext/standard/var_unserializer.c" +#line 852 "ext/standard/var_unserializer.c" yy32: yych = *++YYCURSOR; if (yych == '+') goto yy33; @@ -868,7 +869,7 @@ yy34: yych = *++YYCURSOR; if (yych != '{') goto yy18; ++YYCURSOR; -#line 699 "ext/standard/var_unserializer.re" +#line 700 "ext/standard/var_unserializer.re" { zend_long elements = parse_iv(start + 2); /* use iv() not uiv() in order to check data range */ @@ -889,7 +890,7 @@ yy34: return finish_nested_data(UNSERIALIZE_PASSTHRU); } -#line 893 "ext/standard/var_unserializer.c" +#line 894 "ext/standard/var_unserializer.c" yy39: yych = *++YYCURSOR; if (yych == '+') goto yy40; @@ -910,7 +911,7 @@ yy41: yych = *++YYCURSOR; if (yych != '"') goto yy18; ++YYCURSOR; -#line 671 "ext/standard/var_unserializer.re" +#line 672 "ext/standard/var_unserializer.re" { size_t len, maxlen; zend_string *str; @@ -938,7 +939,7 @@ yy41: ZVAL_STR(rval, str); return 1; } -#line 942 "ext/standard/var_unserializer.c" +#line 943 "ext/standard/var_unserializer.c" yy46: yych = *++YYCURSOR; if (yych == '+') goto yy47; @@ -959,7 +960,7 @@ yy48: yych = *++YYCURSOR; if (yych != '"') goto yy18; ++YYCURSOR; -#line 644 "ext/standard/var_unserializer.re" +#line 645 "ext/standard/var_unserializer.re" { size_t len, maxlen; char *str; @@ -986,7 +987,7 @@ yy48: ZVAL_STRINGL(rval, str, len); return 1; } -#line 990 "ext/standard/var_unserializer.c" +#line 991 "ext/standard/var_unserializer.c" yy53: yych = *++YYCURSOR; if (yych <= '/') { @@ -1074,7 +1075,7 @@ yy61: } yy63: ++YYCURSOR; -#line 635 "ext/standard/var_unserializer.re" +#line 636 "ext/standard/var_unserializer.re" { #if SIZEOF_ZEND_LONG == 4 use_double: @@ -1083,7 +1084,7 @@ use_double: ZVAL_DOUBLE(rval, zend_strtod((const char *)start + 2, NULL)); return 1; } -#line 1087 "ext/standard/var_unserializer.c" +#line 1088 "ext/standard/var_unserializer.c" yy65: yych = *++YYCURSOR; if (yych <= ',') { @@ -1142,7 +1143,7 @@ yy73: yych = *++YYCURSOR; if (yych != ';') goto yy18; ++YYCURSOR; -#line 619 "ext/standard/var_unserializer.re" +#line 620 "ext/standard/var_unserializer.re" { *p = YYCURSOR; @@ -1158,7 +1159,7 @@ yy73: return 1; } -#line 1162 "ext/standard/var_unserializer.c" +#line 1163 "ext/standard/var_unserializer.c" yy76: yych = *++YYCURSOR; if (yych == 'N') goto yy73; @@ -1185,7 +1186,7 @@ yy79: if (yych <= '9') goto yy79; if (yych != ';') goto yy18; ++YYCURSOR; -#line 593 "ext/standard/var_unserializer.re" +#line 594 "ext/standard/var_unserializer.re" { #if SIZEOF_ZEND_LONG == 4 int digits = YYCURSOR - start - 3; @@ -1211,7 +1212,7 @@ yy79: ZVAL_LONG(rval, parse_iv(start + 2)); return 1; } -#line 1215 "ext/standard/var_unserializer.c" +#line 1216 "ext/standard/var_unserializer.c" yy83: yych = *++YYCURSOR; if (yych <= '/') goto yy18; @@ -1219,22 +1220,22 @@ yy83: yych = *++YYCURSOR; if (yych != ';') goto yy18; ++YYCURSOR; -#line 587 "ext/standard/var_unserializer.re" +#line 588 "ext/standard/var_unserializer.re" { *p = YYCURSOR; ZVAL_BOOL(rval, parse_iv(start + 2)); return 1; } -#line 1229 "ext/standard/var_unserializer.c" +#line 1230 "ext/standard/var_unserializer.c" yy87: ++YYCURSOR; -#line 581 "ext/standard/var_unserializer.re" +#line 582 "ext/standard/var_unserializer.re" { *p = YYCURSOR; ZVAL_NULL(rval); return 1; } -#line 1238 "ext/standard/var_unserializer.c" +#line 1239 "ext/standard/var_unserializer.c" yy89: yych = *++YYCURSOR; if (yych <= ',') { @@ -1257,7 +1258,7 @@ yy91: if (yych <= '9') goto yy91; if (yych != ';') goto yy18; ++YYCURSOR; -#line 558 "ext/standard/var_unserializer.re" +#line 559 "ext/standard/var_unserializer.re" { zend_long id; @@ -1280,7 +1281,7 @@ yy91: return 1; } -#line 1284 "ext/standard/var_unserializer.c" +#line 1285 "ext/standard/var_unserializer.c" yy95: yych = *++YYCURSOR; if (yych <= ',') { @@ -1303,7 +1304,7 @@ yy97: if (yych <= '9') goto yy97; if (yych != ';') goto yy18; ++YYCURSOR; -#line 536 "ext/standard/var_unserializer.re" +#line 537 "ext/standard/var_unserializer.re" { zend_long id; @@ -1325,9 +1326,9 @@ yy97: return 1; } -#line 1329 "ext/standard/var_unserializer.c" +#line 1330 "ext/standard/var_unserializer.c" } -#line 881 "ext/standard/var_unserializer.re" +#line 882 "ext/standard/var_unserializer.re" return 0; diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index 7d4ca2c803..dc2f84ac96 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -474,13 +474,14 @@ static inline int object_common2(UNSERIALIZE_PARAMETER, zend_long elements) { zval retval; zval fname; + HashTable *ht = Z_OBJPROP_P(rval); if (Z_TYPE_P(rval) != IS_OBJECT) { return 0; } - //??? TODO: resize before - if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_OBJPROP_P(rval), elements, 1)) { + zend_hash_resize(ht, zend_hash_num_elements(ht) + elements); + if (!process_nested_data(UNSERIALIZE_PASSTHRU, ht, elements, 1)) { return 0; } -- 2.40.0