diff --git a/NEWS b/NEWS index 2aaa3c4c3876..c23210813435 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.7.0alpha1 +- Core: + . Fixed bug GH-23725 (use-after-free when __toString() destroys an argument + of a frameless function call). (Ilia Alshanetsky) + - Intl: . Fixed Collator attribute and strength methods not rejecting an unconstructed Collator. (Ilia Alshanetsky) diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index e02e0afe2bfc..576fb824dbb6 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -1089,7 +1089,7 @@ ZEND_FRAMELESS_FUNCTION(property_exists, 2) zend_string *property; Z_FLF_PARAM_ZVAL(1, object); - Z_FLF_PARAM_STR(2, property, property_tmp); + Z_FLF_PARAM_STR_EX(2, property, property_tmp, Z_TYPE_P(arg1) == IS_STRING); _property_exists(return_value, object, property); diff --git a/Zend/zend_frameless_function.h b/Zend/zend_frameless_function.h index b6f361f104b0..d9f4d3bb3214 100644 --- a/Zend/zend_frameless_function.h +++ b/Zend/zend_frameless_function.h @@ -43,23 +43,30 @@ #define Z_FLF_PARAM_ZVAL(arg_num, dest) \ dest = arg ## arg_num; -#define Z_FLF_PARAM_ARRAY(arg_num, dest) \ - if (!zend_parse_arg_array(arg ## arg_num, &dest, /* null_check */ false, /* or_object */ false)) { \ +#define Z_FLF_PARAM_ARRAY(arg_num, dest_ht) \ + if (!zend_parse_arg_array_ht(arg ## arg_num, &dest_ht, /* null_check */ false, /* or_object */ false, /* separate */ false)) { \ zend_wrong_parameter_type_error(arg_num, Z_EXPECTED_ARRAY, arg ## arg_num); \ goto flf_clean; \ - } -#define Z_FLF_PARAM_ARRAY_OR_NULL(arg_num, dest) \ - if (!zend_parse_arg_array(arg ## arg_num, &dest, /* null_check */ true, /* or_object */ false)) { \ + } \ + GC_TRY_ADDREF(dest_ht); +#define Z_FLF_PARAM_ARRAY_OR_NULL(arg_num, dest_ht) \ + if (!zend_parse_arg_array_ht(arg ## arg_num, &dest_ht, /* null_check */ true, /* or_object */ false, /* separate */ false)) { \ zend_wrong_parameter_type_error(arg_num, Z_EXPECTED_ARRAY_OR_NULL, arg ## arg_num); \ goto flf_clean; \ + } \ + if (dest_ht) { \ + GC_TRY_ADDREF(dest_ht); \ } #define Z_FLF_PARAM_ARRAY_HT_OR_STR(arg_num, dest_ht, dest_str, str_tmp) \ if (Z_TYPE_P(arg ## arg_num) == IS_STRING) { \ dest_ht = NULL; \ + ZVAL_COPY(&str_tmp, arg ## arg_num); \ + arg ## arg_num = &str_tmp; \ dest_str = Z_STR_P(arg ## arg_num); \ } else if (EXPECTED(Z_TYPE_P(arg ## arg_num) == IS_ARRAY)) { \ dest_ht = Z_ARRVAL_P(arg ## arg_num); \ dest_str = NULL; \ + GC_TRY_ADDREF(dest_ht); \ } else { \ dest_ht = NULL; \ ZVAL_COPY(&str_tmp, arg ## arg_num); \ @@ -85,7 +92,13 @@ goto flf_clean; \ } #define Z_FLF_PARAM_STR(arg_num, dest, tmp) \ + Z_FLF_PARAM_STR_EX(arg_num, dest, tmp, false) +#define Z_FLF_PARAM_STR_EX(arg_num, dest, tmp, pin) \ if (Z_TYPE_P(arg ## arg_num) == IS_STRING) { \ + if (UNEXPECTED(pin)) { \ + ZVAL_COPY(&tmp, arg ## arg_num); \ + arg ## arg_num = &tmp; \ + } \ dest = Z_STR_P(arg ## arg_num); \ } else { \ ZVAL_COPY(&tmp, arg ## arg_num); \ @@ -97,7 +110,25 @@ } #define Z_FLF_PARAM_FREE_STR(arg_num, tmp) \ if (UNEXPECTED(arg ## arg_num == &tmp)) { \ - zval_ptr_dtor(arg ## arg_num); \ + if (EXPECTED(Z_TYPE(tmp) == IS_STRING)) { \ + zend_string_release_ex(Z_STR(tmp), false); \ + } else { \ + zval_ptr_dtor(&tmp); \ + } \ + } +#define Z_FLF_PARAM_FREE_ARRAY(dest_ht) \ + if (dest_ht) { \ + GC_TRY_DTOR_NO_REF(dest_ht); \ + } +#define Z_FLF_PARAM_FREE_ARRAY_HT_OR_STR(arg_num, dest_ht, str_tmp) \ + if (dest_ht) { \ + GC_TRY_DTOR_NO_REF(dest_ht); \ + } else if (arg ## arg_num == &str_tmp) { \ + if (EXPECTED(Z_TYPE(str_tmp) == IS_STRING)) { \ + zend_string_release_ex(Z_STR(str_tmp), false); \ + } else { \ + zval_ptr_dtor(&str_tmp); \ + } \ } BEGIN_EXTERN_C() diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c index 4c63ab0920ae..c9ef6f9b51a4 100644 --- a/ext/pcre/php_pcre.c +++ b/ext/pcre/php_pcre.c @@ -1487,7 +1487,7 @@ ZEND_FRAMELESS_FUNCTION(preg_match, 2) zval regex_tmp, subject_tmp; zend_string *regex, *subject; - Z_FLF_PARAM_STR(1, regex, regex_tmp); + Z_FLF_PARAM_STR_EX(1, regex, regex_tmp, Z_TYPE_P(arg2) != IS_STRING); Z_FLF_PARAM_STR(2, subject, subject_tmp); /* Compile regex or get it from cache. */ @@ -2369,9 +2369,19 @@ PHP_FUNCTION(preg_replace) ZEND_FRAMELESS_FUNCTION(preg_replace, 3) { zend_string *regex_str, *replace_str, *subject_str; - HashTable *regex_ht, *replace_ht, *subject_ht; + HashTable *regex_ht = NULL, *replace_ht = NULL, *subject_ht = NULL; zval regex_tmp, replace_tmp, subject_tmp; + if (EXPECTED(Z_TYPE_P(arg1) == IS_STRING && Z_TYPE_P(arg2) == IS_STRING && Z_TYPE_P(arg3) == IS_STRING)) { + _preg_replace_common( + return_value, + NULL, Z_STR_P(arg1), + NULL, Z_STR_P(arg2), + NULL, Z_STR_P(arg3), + -1, NULL, false); + return; + } + Z_FLF_PARAM_ARRAY_HT_OR_STR(1, regex_ht, regex_str, regex_tmp); Z_FLF_PARAM_ARRAY_HT_OR_STR(2, replace_ht, replace_str, replace_tmp); Z_FLF_PARAM_ARRAY_HT_OR_STR(3, subject_ht, subject_str, subject_tmp); @@ -2384,9 +2394,9 @@ ZEND_FRAMELESS_FUNCTION(preg_replace, 3) /* limit */ -1, /* zcount */ NULL, /* is_filter */ false); flf_clean:; - Z_FLF_PARAM_FREE_STR(1, regex_tmp); - Z_FLF_PARAM_FREE_STR(2, replace_tmp); - Z_FLF_PARAM_FREE_STR(3, subject_tmp); + Z_FLF_PARAM_FREE_ARRAY_HT_OR_STR(1, regex_ht, regex_tmp); + Z_FLF_PARAM_FREE_ARRAY_HT_OR_STR(2, replace_ht, replace_tmp); + Z_FLF_PARAM_FREE_ARRAY_HT_OR_STR(3, subject_ht, subject_tmp); } /* {{{ Perform Perl-style regular expression replacement using replacement callback. */ diff --git a/ext/pcre/tests/gh23725.phpt b/ext/pcre/tests/gh23725.phpt new file mode 100644 index 000000000000..c0cd3f31c7e4 --- /dev/null +++ b/ext/pcre/tests/gh23725.phpt @@ -0,0 +1,129 @@ +--TEST-- +GH-23725 (Use-after-free when __toString() destroys a preg_replace() argument) +--FILE-- +getMessage(), "\n"; + } + var_dump($patterns); +} + +destroyedPatternArray(); +destroyedReplacementArray(); +destroyedSubjectArray(); +destroyedPatternString(); +appendedPatternArray(); +threw(); +?> +--EXPECT-- +pattern array: string(3) "zzz" +NULL +replacement array: string(3) "zyy" +NULL +subject array: array(2) { + [0]=> + string(3) "zbc" + [1]=> + string(3) "zbc" +} +NULL +pattern string: string(3) "zbc" +NULL +appended: string(3) "XXz" +count: 3 +Exception: boom +NULL diff --git a/ext/standard/array.c b/ext/standard/array.c index a434589771b3..86f2fe241317 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -1579,7 +1579,7 @@ PHP_FUNCTION(array_walk_recursive) * 0 = return boolean * 1 = return key */ -static zend_always_inline void _php_search_array(zval *return_value, zval *value, zval *array, bool strict, int behavior) /* {{{ */ +static zend_always_inline void _php_search_array(zval *return_value, zval *value, HashTable *array, bool strict, int behavior) /* {{{ */ { zval *entry; /* pointer to array entry */ zend_ulong num_idx; @@ -1587,7 +1587,7 @@ static zend_always_inline void _php_search_array(zval *return_value, zval *value if (strict) { if (Z_TYPE_P(value) == IS_LONG) { - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) { + ZEND_HASH_FOREACH_KEY_VAL(array, num_idx, str_idx, entry) { ZVAL_DEREF(entry); if (Z_TYPE_P(entry) == IS_LONG && Z_LVAL_P(entry) == Z_LVAL_P(value)) { if (behavior == 0) { @@ -1602,7 +1602,7 @@ static zend_always_inline void _php_search_array(zval *return_value, zval *value } } ZEND_HASH_FOREACH_END(); } else { - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) { + ZEND_HASH_FOREACH_KEY_VAL(array, num_idx, str_idx, entry) { ZVAL_DEREF(entry); if (fast_is_identical_function(value, entry)) { if (behavior == 0) { @@ -1619,7 +1619,7 @@ static zend_always_inline void _php_search_array(zval *return_value, zval *value } } else { if (Z_TYPE_P(value) == IS_LONG) { - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) { + ZEND_HASH_FOREACH_KEY_VAL(array, num_idx, str_idx, entry) { if (fast_equal_check_long(value, entry)) { if (behavior == 0) { RETURN_TRUE; @@ -1633,7 +1633,7 @@ static zend_always_inline void _php_search_array(zval *return_value, zval *value } } ZEND_HASH_FOREACH_END(); } else if (Z_TYPE_P(value) == IS_STRING) { - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) { + ZEND_HASH_FOREACH_KEY_VAL(array, num_idx, str_idx, entry) { if (fast_equal_check_string(value, entry)) { if (behavior == 0) { RETURN_TRUE; @@ -1647,7 +1647,7 @@ static zend_always_inline void _php_search_array(zval *return_value, zval *value } } ZEND_HASH_FOREACH_END(); } else { - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) { + ZEND_HASH_FOREACH_KEY_VAL(array, num_idx, str_idx, entry) { if (fast_equal_check_function(value, entry)) { if (behavior == 0) { RETURN_TRUE; @@ -1673,13 +1673,13 @@ static zend_always_inline void _php_search_array(zval *return_value, zval *value */ static inline void php_search_array(INTERNAL_FUNCTION_PARAMETERS, int behavior) { - zval *value, /* value to check for */ - *array; /* array to check in */ + zval *value; /* value to check for */ + HashTable *array; /* array to check in */ bool strict = 0; /* strict comparison or not */ ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_ZVAL(value) - Z_PARAM_ARRAY(array) + Z_PARAM_ARRAY_HT(array) Z_PARAM_OPTIONAL Z_PARAM_BOOL(strict) ZEND_PARSE_PARAMETERS_END(); @@ -1696,7 +1696,8 @@ PHP_FUNCTION(in_array) ZEND_FRAMELESS_FUNCTION(in_array, 2) { - zval *value, *array; + zval *value; + HashTable *array = NULL; Z_FLF_PARAM_ZVAL(1, value); Z_FLF_PARAM_ARRAY(2, array); @@ -1704,11 +1705,13 @@ ZEND_FRAMELESS_FUNCTION(in_array, 2) _php_search_array(return_value, value, array, false, 0); flf_clean:; + Z_FLF_PARAM_FREE_ARRAY(array); } ZEND_FRAMELESS_FUNCTION(in_array, 3) { - zval *value, *array; + zval *value; + HashTable *array = NULL; bool strict; Z_FLF_PARAM_ZVAL(1, value); @@ -1718,6 +1721,7 @@ ZEND_FRAMELESS_FUNCTION(in_array, 3) _php_search_array(return_value, value, array, strict, 0); flf_clean:; + Z_FLF_PARAM_FREE_ARRAY(array); } /* {{{ Searches the array for a given value and returns the corresponding key if successful */ diff --git a/ext/standard/string.c b/ext/standard/string.c index bfe0c71795bf..45a2f607cfaf 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -651,7 +651,7 @@ ZEND_FRAMELESS_FUNCTION(trim, 2) zval str_tmp, what_tmp; zend_string *str, *what; - Z_FLF_PARAM_STR(1, str, str_tmp); + Z_FLF_PARAM_STR_EX(1, str, str_tmp, Z_TYPE_P(arg2) != IS_STRING); Z_FLF_PARAM_STR(2, what, what_tmp); ZVAL_STR(return_value, php_trim_int(str, ZSTR_VAL(what), ZSTR_LEN(what), /* mode */ 3)); @@ -686,7 +686,7 @@ ZEND_FRAMELESS_FUNCTION(rtrim, 2) zval str_tmp, what_tmp; zend_string *str, *what; - Z_FLF_PARAM_STR(1, str, str_tmp); + Z_FLF_PARAM_STR_EX(1, str, str_tmp, Z_TYPE_P(arg2) != IS_STRING); Z_FLF_PARAM_STR(2, what, what_tmp); ZVAL_STR(return_value, php_trim_int(str, ZSTR_VAL(what), ZSTR_LEN(what), /* mode */ 2)); @@ -721,7 +721,7 @@ ZEND_FRAMELESS_FUNCTION(ltrim, 2) zval str_tmp, what_tmp; zend_string *str, *what; - Z_FLF_PARAM_STR(1, str, str_tmp); + Z_FLF_PARAM_STR_EX(1, str, str_tmp, Z_TYPE_P(arg2) != IS_STRING); Z_FLF_PARAM_STR(2, what, what_tmp); ZVAL_STR(return_value, php_trim_int(str, ZSTR_VAL(what), ZSTR_LEN(what), /* mode */ 1)); @@ -993,6 +993,7 @@ PHPAPI void php_implode(const zend_string *glue, HashTable *pieces, zval *return zval *tmp; uint32_t numelems; zend_string *str; + zend_string *held_glue = NULL; char *cptr; size_t len = 0; struct { @@ -1016,9 +1017,6 @@ PHPAPI void php_implode(const zend_string *glue, HashTable *pieces, zval *return uint32_t flags = ZSTR_GET_COPYABLE_CONCAT_PROPERTIES(glue); - /* Converting an element may call __toString(), which can destroy pieces. */ - GC_TRY_ADDREF(pieces); - ZEND_HASH_FOREACH_VAL(pieces, tmp) { if (EXPECTED(Z_TYPE_P(tmp) == IS_STRING)) { ptr->str = Z_STR_P(tmp); @@ -1040,6 +1038,9 @@ PHPAPI void php_implode(const zend_string *glue, HashTable *pieces, zval *return len++; } } else { + if (!held_glue) { + held_glue = zend_string_copy((zend_string *) glue); + } ptr->str = zval_get_string_func(tmp); len += ZSTR_LEN(ptr->str); ptr->lval = 1; @@ -1082,7 +1083,9 @@ PHPAPI void php_implode(const zend_string *glue, HashTable *pieces, zval *return } free_alloca(strings, use_heap); - GC_TRY_DTOR_NO_REF(pieces); + if (held_glue) { + zend_string_release_ex(held_glue, 0); + } RETURN_NEW_STR(str); } /* }}} */ @@ -1138,8 +1141,11 @@ ZEND_FRAMELESS_FUNCTION(implode, 1) } zend_string *str = ZSTR_EMPTY_ALLOC(); + HashTable *ht = Z_ARR_P(pieces); - php_implode(str, Z_ARR_P(pieces), return_value); + GC_TRY_ADDREF(ht); + php_implode(str, ht, return_value); + GC_TRY_DTOR_NO_REF(ht); flf_clean:; } @@ -1148,7 +1154,7 @@ ZEND_FRAMELESS_FUNCTION(implode, 2) { zval str_tmp; zend_string *str; - zval *pieces; + HashTable *pieces = NULL; Z_FLF_PARAM_STR(1, str, str_tmp); Z_FLF_PARAM_ARRAY_OR_NULL(2, pieces); @@ -1162,10 +1168,11 @@ ZEND_FRAMELESS_FUNCTION(implode, 2) goto flf_clean; } - php_implode(str, Z_ARR_P(pieces), return_value); + php_implode(str, pieces, return_value); flf_clean:; Z_FLF_PARAM_FREE_STR(1, str_tmp); + Z_FLF_PARAM_FREE_ARRAY(pieces); } #define STRTOK_TABLE(p) BG(strtok_table)[(unsigned char) *p] @@ -1642,7 +1649,7 @@ ZEND_FRAMELESS_FUNCTION(dirname, 2) zend_string *str; zend_long levels; - Z_FLF_PARAM_STR(1, str, str_tmp); + Z_FLF_PARAM_STR_EX(1, str, str_tmp, Z_TYPE_P(arg2) != IS_LONG); Z_FLF_PARAM_LONG(2, levels); _zend_dirname(return_value, str, levels); @@ -1850,7 +1857,7 @@ ZEND_FRAMELESS_FUNCTION(strstr, 2) zval haystack_tmp, needle_tmp; zend_string *haystack, *needle; - Z_FLF_PARAM_STR(1, haystack, haystack_tmp); + Z_FLF_PARAM_STR_EX(1, haystack, haystack_tmp, Z_TYPE_P(arg2) != IS_STRING); Z_FLF_PARAM_STR(2, needle, needle_tmp); _zend_strstr(return_value, haystack, needle, /* part */ false); @@ -1866,8 +1873,8 @@ ZEND_FRAMELESS_FUNCTION(strstr, 3) zend_string *haystack, *needle; bool part; - Z_FLF_PARAM_STR(1, haystack, haystack_tmp); - Z_FLF_PARAM_STR(2, needle, needle_tmp); + Z_FLF_PARAM_STR_EX(1, haystack, haystack_tmp, Z_TYPE_P(arg2) != IS_STRING || (Z_TYPE_P(arg3) != IS_FALSE && Z_TYPE_P(arg3) != IS_TRUE)); + Z_FLF_PARAM_STR_EX(2, needle, needle_tmp, Z_TYPE_P(arg3) != IS_FALSE && Z_TYPE_P(arg3) != IS_TRUE); Z_FLF_PARAM_BOOL(3, part); _zend_strstr(return_value, haystack, needle, part); @@ -1896,7 +1903,7 @@ ZEND_FRAMELESS_FUNCTION(str_contains, 2) zval haystack_tmp, needle_tmp; zend_string *haystack, *needle; - Z_FLF_PARAM_STR(1, haystack, haystack_tmp); + Z_FLF_PARAM_STR_EX(1, haystack, haystack_tmp, Z_TYPE_P(arg2) != IS_STRING); Z_FLF_PARAM_STR(2, needle, needle_tmp); RETVAL_BOOL(php_memnstr(ZSTR_VAL(haystack), ZSTR_VAL(needle), ZSTR_LEN(needle), ZSTR_VAL(haystack) + ZSTR_LEN(haystack))); @@ -1925,7 +1932,7 @@ ZEND_FRAMELESS_FUNCTION(str_starts_with, 2) zval haystack_tmp, needle_tmp; zend_string *haystack, *needle; - Z_FLF_PARAM_STR(1, haystack, haystack_tmp); + Z_FLF_PARAM_STR_EX(1, haystack, haystack_tmp, Z_TYPE_P(arg2) != IS_STRING); Z_FLF_PARAM_STR(2, needle, needle_tmp); RETVAL_BOOL(zend_string_starts_with(haystack, needle)); @@ -1954,7 +1961,7 @@ ZEND_FRAMELESS_FUNCTION(str_ends_with, 2) zval haystack_tmp, needle_tmp; zend_string *haystack, *needle; - Z_FLF_PARAM_STR(1, haystack, haystack_tmp); + Z_FLF_PARAM_STR_EX(1, haystack, haystack_tmp, Z_TYPE_P(arg2) != IS_STRING); Z_FLF_PARAM_STR(2, needle, needle_tmp); RETVAL_BOOL(zend_string_ends_with(haystack, needle)); @@ -2008,7 +2015,7 @@ ZEND_FRAMELESS_FUNCTION(strpos, 2) zval haystack_tmp, needle_tmp; zend_string *haystack, *needle; - Z_FLF_PARAM_STR(1, haystack, haystack_tmp); + Z_FLF_PARAM_STR_EX(1, haystack, haystack_tmp, Z_TYPE_P(arg2) != IS_STRING); Z_FLF_PARAM_STR(2, needle, needle_tmp); _zend_strpos(return_value, haystack, needle, 0); @@ -2024,8 +2031,8 @@ ZEND_FRAMELESS_FUNCTION(strpos, 3) zend_string *haystack, *needle; zend_long offset; - Z_FLF_PARAM_STR(1, haystack, haystack_tmp); - Z_FLF_PARAM_STR(2, needle, needle_tmp); + Z_FLF_PARAM_STR_EX(1, haystack, haystack_tmp, Z_TYPE_P(arg2) != IS_STRING || Z_TYPE_P(arg3) != IS_LONG); + Z_FLF_PARAM_STR_EX(2, needle, needle_tmp, Z_TYPE_P(arg3) != IS_LONG); Z_FLF_PARAM_LONG(3, offset); _zend_strpos(return_value, haystack, needle, offset); @@ -2359,7 +2366,7 @@ ZEND_FRAMELESS_FUNCTION(substr, 2) zend_string *str; zend_long f; - Z_FLF_PARAM_STR(1, str, str_tmp); + Z_FLF_PARAM_STR_EX(1, str, str_tmp, Z_TYPE_P(arg2) != IS_LONG); Z_FLF_PARAM_LONG(2, f); _zend_substr(return_value, str, f, /* len_is_null */ true, 0); @@ -2375,7 +2382,7 @@ ZEND_FRAMELESS_FUNCTION(substr, 3) zend_long f, l; bool len_is_null; - Z_FLF_PARAM_STR(1, str, str_tmp); + Z_FLF_PARAM_STR_EX(1, str, str_tmp, Z_TYPE_P(arg2) != IS_LONG || (Z_TYPE_P(arg3) != IS_LONG && Z_TYPE_P(arg3) != IS_NULL)); Z_FLF_PARAM_LONG(2, f); Z_FLF_PARAM_LONG_OR_NULL(3, len_is_null, l); @@ -3474,9 +3481,6 @@ static void php_strtr_array(zval *return_value, zend_string *str, HashTable *fro RETURN_STR_COPY(str); } - /* Converting a replacement may call __toString(), which can destroy from_ht. */ - GC_TRY_ADDREF(from_ht); - if (zend_hash_num_elements(from_ht) == 1) { zend_long num_key; zend_string *str_key, *tmp_str, *replace, *tmp_replace; @@ -3511,8 +3515,6 @@ static void php_strtr_array(zval *return_value, zend_string *str, HashTable *fro } else { php_strtr_array_ex(return_value, str, from_ht); } - - GC_TRY_DTOR_NO_REF(from_ht); } /* {{{ Translates characters in str using given translation tables */ @@ -3556,9 +3558,9 @@ ZEND_FRAMELESS_FUNCTION(strtr, 2) { zval str_tmp; zend_string *str; - zval *from; + HashTable *from = NULL; - Z_FLF_PARAM_STR(1, str, str_tmp); + Z_FLF_PARAM_STR_EX(1, str, str_tmp, true); Z_FLF_PARAM_ARRAY(2, from); if (ZSTR_LEN(str) == 0) { @@ -3566,10 +3568,11 @@ ZEND_FRAMELESS_FUNCTION(strtr, 2) goto flf_clean; } - php_strtr_array(return_value, str, Z_ARR_P(from)); + php_strtr_array(return_value, str, from); flf_clean: Z_FLF_PARAM_FREE_STR(1, str_tmp); + Z_FLF_PARAM_FREE_ARRAY(from); } ZEND_FRAMELESS_FUNCTION(strtr, 3) @@ -3577,8 +3580,8 @@ ZEND_FRAMELESS_FUNCTION(strtr, 3) zval str_tmp, from_tmp, to_tmp; zend_string *str, *from, *to; - Z_FLF_PARAM_STR(1, str, str_tmp); - Z_FLF_PARAM_STR(2, from, from_tmp); + Z_FLF_PARAM_STR_EX(1, str, str_tmp, Z_TYPE_P(arg2) != IS_STRING || Z_TYPE_P(arg3) != IS_STRING); + Z_FLF_PARAM_STR_EX(2, from, from_tmp, Z_TYPE_P(arg3) != IS_STRING); Z_FLF_PARAM_STR(3, to, to_tmp); if (ZSTR_LEN(str) == 0) { @@ -4576,16 +4579,6 @@ static void _php_str_replace_common( RETURN_THROWS(); } - /* Converting an element may call __toString(), which can destroy the arrays. */ - if (search_ht) { - GC_TRY_ADDREF(search_ht); - } - if (replace_ht) { - GC_TRY_ADDREF(replace_ht); - } - if (subject_ht) { - GC_TRY_ADDREF(subject_ht); - } /* if subject is an array */ if (subject_ht) { @@ -4613,16 +4606,6 @@ static void _php_str_replace_common( if (zcount) { ZEND_TRY_ASSIGN_REF_LONG(zcount, count); } - - if (search_ht) { - GC_TRY_DTOR_NO_REF(search_ht); - } - if (replace_ht) { - GC_TRY_DTOR_NO_REF(replace_ht); - } - if (subject_ht) { - GC_TRY_DTOR_NO_REF(subject_ht); - } } /* {{{ php_str_replace_common */ @@ -4658,9 +4641,14 @@ PHP_FUNCTION(str_replace) ZEND_FRAMELESS_FUNCTION(str_replace, 3) { zend_string *search_str, *replace_str, *subject_str; - HashTable *search_ht, *replace_ht, *subject_ht; + HashTable *search_ht = NULL, *replace_ht = NULL, *subject_ht = NULL; zval search_tmp, replace_tmp, subject_tmp; + if (EXPECTED(Z_TYPE_P(arg1) == IS_STRING && Z_TYPE_P(arg2) == IS_STRING && Z_TYPE_P(arg3) == IS_STRING)) { + _php_str_replace_common(return_value, NULL, Z_STR_P(arg1), NULL, Z_STR_P(arg2), NULL, Z_STR_P(arg3), NULL, true); + return; + } + Z_FLF_PARAM_ARRAY_HT_OR_STR(1, search_ht, search_str, search_tmp); Z_FLF_PARAM_ARRAY_HT_OR_STR(2, replace_ht, replace_str, replace_tmp); Z_FLF_PARAM_ARRAY_HT_OR_STR(3, subject_ht, subject_str, subject_tmp); @@ -4668,9 +4656,9 @@ ZEND_FRAMELESS_FUNCTION(str_replace, 3) _php_str_replace_common(return_value, search_ht, search_str, replace_ht, replace_str, subject_ht, subject_str, /* zcount */ NULL, /* case_sensitivity */ true); flf_clean:; - Z_FLF_PARAM_FREE_STR(1, search_tmp); - Z_FLF_PARAM_FREE_STR(2, replace_tmp); - Z_FLF_PARAM_FREE_STR(3, subject_tmp); + Z_FLF_PARAM_FREE_ARRAY_HT_OR_STR(1, search_ht, search_tmp); + Z_FLF_PARAM_FREE_ARRAY_HT_OR_STR(2, replace_ht, replace_tmp); + Z_FLF_PARAM_FREE_ARRAY_HT_OR_STR(3, subject_ht, subject_tmp); } /* {{{ Replaces all occurrences of search in haystack with replace / case-insensitive */ diff --git a/ext/standard/tests/array/gh23722.phpt b/ext/standard/tests/array/gh23722.phpt new file mode 100644 index 000000000000..5371109980fa --- /dev/null +++ b/ext/standard/tests/array/gh23722.phpt @@ -0,0 +1,84 @@ +--TEST-- +GH-23722 (Use-after-free when __toString() destroys the in_array() haystack) +--FILE-- +getMessage(), "\n"; + } + var_dump($a); +} + +destroyed(); +destroyedWithStrictArg(); +appended(); +threw(); +?> +--EXPECT-- +destroyed: bool(false) +NULL +destroyed, with strict argument: bool(false) +NULL +appended: bool(false) +count: 6 +Exception: boom +NULL diff --git a/ext/standard/tests/strings/gh21639.phpt b/ext/standard/tests/strings/gh21639.phpt new file mode 100644 index 000000000000..a347c74dffc5 --- /dev/null +++ b/ext/standard/tests/strings/gh21639.phpt @@ -0,0 +1,65 @@ +--TEST-- +GH-21639 (Volatile string arguments in frameless calls) +--FILE-- +var] = null; + return $this->ret; + } +} + +class Modify { + public function __toString(): string { + global $a; + $a[0] = '!'; + return '0'; + } +} + +$a = str_repeat('foo', 2); +var_dump(strtr($a, 'o', new Modify)); +var_dump($a); + +$a = str_repeat('foo', 2); +var_dump(strtr($a, 'o', new Clobber('a', 'x'))); +var_dump($a); + +$a = str_repeat('ab', 2); +var_dump(str_contains($a, new Clobber('a', 'b'))); + +$a = str_repeat('ab', 2); +var_dump(strpos($a, new Clobber('a', 'b'))); + +$sep = str_repeat('-', 2); +var_dump(implode($sep, [new Clobber('sep', 'x'), 'y'])); + +set_error_handler(function (int $errno, string $errstr) { + $GLOBALS['a'] = null; + echo $errstr, PHP_EOL; + return true; +}); +$a = str_repeat('ab', 2); +var_dump(substr($a, 1.5)); +restore_error_handler(); + +spl_autoload_register(function (string $class) { + $GLOBALS['p'] = null; + eval("class $class { public \$xx; }"); +}); +$p = str_repeat('x', 2); +var_dump(property_exists('Autoloaded', $p)); +?> +--EXPECT-- +string(6) "f00f00" +string(6) "!oofoo" +string(6) "fxxfxx" +NULL +bool(true) +int(1) +string(4) "x--y" +Implicit conversion from float 1.5 to int loses precision +string(3) "bab" +bool(true) diff --git a/ext/standard/tests/strings/gh23204.phpt b/ext/standard/tests/strings/gh23204.phpt index e2ae20592c5a..2586b7868d08 100644 --- a/ext/standard/tests/strings/gh23204.phpt +++ b/ext/standard/tests/strings/gh23204.phpt @@ -16,6 +16,10 @@ $a = [new Unset_, 2, 3, 4]; echo "destroyed: ", implode(",", $a), "\n"; var_dump($a); +$a = [new Unset_, 2, 3, 4]; +echo "destroyed single arg: ", implode($a), "\n"; +var_dump($a); + class Append implements Stringable { public function __toString(): string { global $b; @@ -94,6 +98,8 @@ var_dump(str_replace("a", "z", $h)); --EXPECT-- destroyed: X,2,3,4 NULL +destroyed single arg: X234 +NULL appended: X,2,3,4 count: 5 Exception: boom diff --git a/ext/standard/tests/strings/gh23725.phpt b/ext/standard/tests/strings/gh23725.phpt new file mode 100644 index 000000000000..c0844f50566f --- /dev/null +++ b/ext/standard/tests/strings/gh23725.phpt @@ -0,0 +1,49 @@ +--TEST-- +GH-23725 (Use-after-free when __toString() destroys a str_replace() string argument) +--FILE-- + +--EXPECT-- +search string, freed by replacement: string(2) "zz" +NULL +search string, freed by subject: string(2) "zz" +NULL