Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ PHP NEWS
?? ??? ????, PHP 8.6.0RC1

- Core:
. Fixed bug GH-23725 (use-after-free when __toString() destroys an array or
string argument of a frameless in_array(), preg_replace() or
str_replace() call). (Ilia Alshanetsky)
. Fixed incorrect internal pointer and foreach iterator positions when
compacting arrays with holes. (Weilin Du)
. Fix handling of references to typed properties during unserialization
Expand Down
27 changes: 22 additions & 5 deletions Zend/zend_frameless_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -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); \
Expand Down Expand Up @@ -99,6 +106,16 @@
if (UNEXPECTED(arg ## arg_num == &tmp)) { \
zval_ptr_dtor(arg ## arg_num); \
}
#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) { \
zval_ptr_dtor(arg ## arg_num); \
}

BEGIN_EXTERN_C()

Expand Down
8 changes: 4 additions & 4 deletions ext/pcre/php_pcre.c
Original file line number Diff line number Diff line change
Expand Up @@ -2378,7 +2378,7 @@ 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;

Z_FLF_PARAM_ARRAY_HT_OR_STR(1, regex_ht, regex_str, regex_tmp);
Expand All @@ -2393,9 +2393,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. */
Expand Down
129 changes: 129 additions & 0 deletions ext/pcre/tests/gh23725.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
--TEST--
GH-23725 (Use-after-free when __toString() destroys a preg_replace() argument)
--FILE--
<?php
class UnsetPatterns implements Stringable {
public function __toString(): string {
global $patterns;
$patterns = null;
return "/a/";
}
}

class UnsetReplacements implements Stringable {
public function __toString(): string {
global $replacements;
$replacements = null;
return "z";
}
}

class UnsetSubjects implements Stringable {
public function __toString(): string {
global $subjects;
$subjects = null;
return "abc";
}
}

class UnsetPatternString implements Stringable {
public function __toString(): string {
global $pattern;
$pattern = null;
return "z";
}
}

class AppendPatterns implements Stringable {
public function __toString(): string {
global $patterns;
$patterns[] = "/z/";
return "/a/";
}
}

class Boom implements Stringable {
public function __toString(): string {
global $patterns;
$patterns = null;
throw new Exception("boom");
}
}

function destroyedPatternArray(): void {
global $patterns;
$patterns = [new UnsetPatterns, "/b/", "/c/"];
echo "pattern array: ";
var_dump(preg_replace($patterns, "z", "abc"));
var_dump($patterns);
}

function destroyedReplacementArray(): void {
global $replacements;
$replacements = [new UnsetReplacements, "y", "y"];
echo "replacement array: ";
var_dump(preg_replace(["/a/", "/b/", "/c/"], $replacements, "abc"));
var_dump($replacements);
}

function destroyedSubjectArray(): void {
global $subjects;
$subjects = [new UnsetSubjects, "abc"];
echo "subject array: ";
var_dump(preg_replace("/a/", "z", $subjects));
var_dump($subjects);
}

function destroyedPatternString(): void {
global $pattern;
$sep = "/";
$pattern = $sep . "a" . $sep;
echo "pattern string: ";
var_dump(preg_replace($pattern, new UnsetPatternString, "abc"));
var_dump($pattern);
}

function appendedPatternArray(): void {
global $patterns;
$patterns = [new AppendPatterns, "/b/"];
echo "appended: ";
var_dump(preg_replace($patterns, "X", "abz"));
echo "count: ", count($patterns), "\n";
}

function threw(): void {
global $patterns;
$patterns = [new Boom, "/b/"];
try {
var_dump(preg_replace($patterns, "X", "ab"));
} catch (Exception $e) {
echo $e::class, ': ', $e->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
26 changes: 15 additions & 11 deletions ext/standard/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -1579,15 +1579,15 @@ 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;
zend_string *str_idx;

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) {
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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();
Expand All @@ -1696,19 +1696,22 @@ 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);

_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);
Expand All @@ -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 */
Expand Down
18 changes: 10 additions & 8 deletions ext/standard/string.c
Original file line number Diff line number Diff line change
Expand Up @@ -1131,7 +1131,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);
Expand All @@ -1145,10 +1145,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]
Expand Down Expand Up @@ -3558,7 +3559,7 @@ 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_ARRAY(2, from);
Expand All @@ -3568,10 +3569,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)
Expand Down Expand Up @@ -4660,7 +4662,7 @@ 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;

Z_FLF_PARAM_ARRAY_HT_OR_STR(1, search_ht, search_str, search_tmp);
Expand All @@ -4670,9 +4672,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 */
Expand Down
Loading
Loading