From 4daa42cbe772d4057a62308b108b12ca672d4fb7 Mon Sep 17 00:00:00 2001 From: Max Semenik Date: Sun, 5 Jul 2020 00:16:04 +0300 Subject: [PATCH] Fix bug #79783: segfault in str_replace() This change addresses one of two problems: that this function crashes when passed anything but a reference as a 4th parameter. Another problem is that it's possible to trick PHP into passing a non-reference with defined(), but it will be addressed separately. The solution in this change is applicable to other functions, they will be upgraded later. --- Zend/zend_API.h | 33 ++++++++++++++++++++++++ ext/standard/string.c | 2 +- ext/standard/tests/strings/bug79783.phpt | 13 ++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 ext/standard/tests/strings/bug79783.phpt diff --git a/Zend/zend_API.h b/Zend/zend_API.h index ae0363f7657bc..8bf802dd898ab 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -1219,6 +1219,8 @@ static zend_always_inline zval *zend_try_array_init(zval *zv) _(Z_EXPECTED_STRING_OR_LONG_OR_NULL, "of type string|int|null") \ _(Z_EXPECTED_CLASS_NAME_OR_OBJECT, "a valid class name or object") \ _(Z_EXPECTED_CLASS_NAME_OR_OBJECT_OR_NULL, "a valid class name, object, or null") \ + _(Z_EXPECTED_REF, "a reference") \ + _(Z_EXPECTED_REF_OR_NULL, "a reference or null") \ #define Z_EXPECTED_TYPE @@ -1696,6 +1698,20 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_value_error(uint32_t arg_num #define Z_PARAM_STR_OR_LONG_OR_NULL(dest_str, dest_long, is_null) \ Z_PARAM_STR_OR_LONG_EX(dest_str, dest_long, is_null, 1); +#define Z_PARAM_REF_EX(dest, is_null, allow_null) \ + Z_PARAM_PROLOGUE(0, 0); \ + if (UNEXPECTED(!zend_parse_arg_ref(_arg, &dest, &is_null, allow_null))) { \ + _expected_type = allow_null ? Z_EXPECTED_REF_OR_NULL : Z_EXPECTED_REF; \ + _error_code = ZPP_ERROR_WRONG_ARG; \ + break; \ + } + +#define Z_PARAM_REF(dest) \ + Z_PARAM_REF_EX(dest, _dummy, 0); + +#define Z_PARAM_REF_OR_NULL(dest, is_null) \ + Z_PARAM_REF_EX(dest, is_null, 1); + /* End of new parameter parsing API */ /* Inlined implementations shared by new and old parameter parsing APIs */ @@ -1972,6 +1988,23 @@ static zend_always_inline int zend_parse_arg_class_name_or_obj( return 1; } +static zend_always_inline int zend_parse_arg_ref(zval *arg, zval **dest, zend_bool *is_null, int allow_null) +{ + if (allow_null) { + *is_null = 0; + } + if (EXPECTED(Z_TYPE_P(arg) == IS_REFERENCE)) { + *dest = arg; + } else if (allow_null && EXPECTED(Z_TYPE_P(arg) == IS_NULL)) { + *is_null = 1; + *dest = NULL; + } else { + return 0; + } + + return 1; +} + END_EXTERN_C() #endif /* ZEND_API_H */ diff --git a/ext/standard/string.c b/ext/standard/string.c index 1178b08730bcd..eacedcbfc8d36 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -4335,7 +4335,7 @@ static void php_str_replace_common(INTERNAL_FUNCTION_PARAMETERS, int case_sensit Z_PARAM_ZVAL(replace) Z_PARAM_STR_OR_ARRAY_HT(subject_str, subject_ht) Z_PARAM_OPTIONAL - Z_PARAM_ZVAL(zcount) + Z_PARAM_REF(zcount) ZEND_PARSE_PARAMETERS_END(); /* Make sure we're dealing with strings and do the replacement. */ diff --git a/ext/standard/tests/strings/bug79783.phpt b/ext/standard/tests/strings/bug79783.phpt new file mode 100644 index 0000000000000..4089ae5d48138 --- /dev/null +++ b/ext/standard/tests/strings/bug79783.phpt @@ -0,0 +1,13 @@ +--TEST-- +Bug #79783 (segfault in str_replace) +--FILE-- +