1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Fixed GH-16233: Observer segfault when calling user function in internal function via trampoline

In the test, I have an internal `__call` function for `_ZendTestMagicCallForward` that calls the global function with name `$name` via `call_user_function`.
Note that observer writes the pointer to the previously observed frame in the last temporary of the new call frame (`*prev_observed_frame`).

The following happens:
First, we call `$test->callee`, this will be handled via a trampoline with T=2 for the two arguments. The call frame is allocated at this point. This call frame is not observed because it has `ZEND_ACC_CALL_VIA_TRAMPOLINE` set. Next we use `ZEND_CALL_TRAMPOLINE` to call the trampoline, this reuses the stack frame allocated earlier with T=2, but this time it is observed. The pointer to the previous frame is written outside of the call frame because `T` is too small (should be 3). We are now in the internal function `_ZendTestMagicCallForward::__call` where we call the global function `callee`. This will push a new call frame which will overlap `*prev_observed_frame`. This value gets overwritten by `zend_init_func_execute_data` when `EX(opline)` is set because `*prev_observed_frame` overlaps with `EX(opline)`. From now on, `*prev_observed_frame` is corrupted. When `zend_observer_fcall_end` is called this will result in reading wrong value `*prev_observed_frame` into `current_observed_frame`. This causes issues in `zend_observer_fcall_end_all` leading to the segfault we observe.

Despite function with `ZEND_ACC_CALL_VIA_TRAMPOLINE` not being observed, the reuse of call frames makes problems when `T` is not large enough.
To fix this, we make sure to add 1 to `T` if `ZEND_OBSERVER_ENABLED` is true.

Closes GH-16252.
This commit is contained in:
Niels Dossche
2024-10-05 22:55:09 +02:00
parent df4db5c1b4
commit e715dd0afb
6 changed files with 81 additions and 2 deletions

2
NEWS
View File

@@ -23,6 +23,8 @@ PHP NEWS
nested generator frame). (ilutov)
. Fixed bug GH-15866 (Core dumped in Zend/zend_generators.c). (Arnaud)
. Fixed bug GH-16188 (Assertion failure in Zend/zend_exceptions.c). (Arnaud)
. Fixed bug GH-16233 (Observer segfault when calling user function in
internal function via trampoline). (nielsdos)
- Date:
. Fixed bug GH-15582: Crash when not calling parent constructor of

View File

@@ -30,6 +30,7 @@
#include "zend_closures.h"
#include "zend_compile.h"
#include "zend_hash.h"
#include "zend_observer.h"
#define DEBUG_OBJECT_HANDLERS 0
@@ -1294,7 +1295,8 @@ ZEND_API zend_function *zend_get_call_trampoline_func(zend_class_entry *ce, zend
* value so that it doesn't contain garbage when the engine allocates space for the next stack
* frame. This didn't cause any issues until now due to "lucky" structure layout. */
func->last_var = 0;
func->T = (fbc->type == ZEND_USER_FUNCTION)? MAX(fbc->op_array.last_var + fbc->op_array.T, 2) : 2;
uint32_t min_T = 2 + ZEND_OBSERVER_ENABLED;
func->T = (fbc->type == ZEND_USER_FUNCTION)? MAX(fbc->op_array.last_var + fbc->op_array.T, min_T) : min_T;
func->filename = (fbc->type == ZEND_USER_FUNCTION)? fbc->op_array.filename : ZSTR_EMPTY_ALLOC();
func->line_start = (fbc->type == ZEND_USER_FUNCTION)? fbc->op_array.line_start : 0;
func->line_end = (fbc->type == ZEND_USER_FUNCTION)? fbc->op_array.line_end : 0;

View File

@@ -879,6 +879,23 @@ static ZEND_METHOD(_ZendTestMagicCall, __call)
RETURN_ARR(zend_new_pair(&name_zv, arguments));
}
static ZEND_METHOD(_ZendTestMagicCallForward, __call)
{
zend_string *name;
zval *arguments;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STR(name)
Z_PARAM_ARRAY(arguments)
ZEND_PARSE_PARAMETERS_END();
ZEND_IGNORE_VALUE(arguments);
zval func;
ZVAL_STR(&func, name);
call_user_function(NULL, NULL, &func, return_value, 0, NULL);
}
PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN("zend_test.replace_zend_execute_ex", "0", PHP_INI_SYSTEM, OnUpdateBool, replace_zend_execute_ex, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_BOOLEAN("zend_test.register_passes", "0", PHP_INI_SYSTEM, OnUpdateBool, register_passes, zend_zend_test_globals, zend_test_globals)
@@ -993,6 +1010,8 @@ PHP_MINIT_FUNCTION(zend_test)
zend_test_magic_call = register_class__ZendTestMagicCall();
register_class__ZendTestMagicCallForward();
zend_register_functions(NULL, ext_function_legacy, NULL, EG(current_module)->type);
// Loading via dl() not supported with the observer API

View File

@@ -52,6 +52,11 @@ namespace {
public function __call(string $name, array $args): mixed {}
}
class _ZendTestMagicCallForward
{
public function __call(string $name, array $args): mixed {}
}
class _ZendTestChildClass extends _ZendTestClass
{
public function returnsThrowable(): Exception {}

View File

@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 07ce28cd75080118509ac0d30d8ce5ef54110747 */
* Stub hash: 5d861e05edfd57c385167b11b8b1ea977ed130a2 */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0)
ZEND_END_ARG_INFO()
@@ -161,6 +161,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class__ZendTestMagicCall___call,
ZEND_ARG_TYPE_INFO(0, args, IS_ARRAY, 0)
ZEND_END_ARG_INFO()
#define arginfo_class__ZendTestMagicCallForward___call arginfo_class__ZendTestMagicCall___call
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class__ZendTestChildClass_returnsThrowable, 0, 0, Exception, 0)
ZEND_END_ARG_INFO()
@@ -245,6 +247,7 @@ static ZEND_METHOD(_ZendTestClass, returnsStatic);
static ZEND_METHOD(_ZendTestClass, returnsThrowable);
static ZEND_METHOD(_ZendTestClass, variadicTest);
static ZEND_METHOD(_ZendTestMagicCall, __call);
static ZEND_METHOD(_ZendTestMagicCallForward, __call);
static ZEND_METHOD(_ZendTestChildClass, returnsThrowable);
static ZEND_METHOD(_ZendTestTrait, testMethod);
static ZEND_METHOD(ZendTestParameterAttribute, __construct);
@@ -332,6 +335,12 @@ static const zend_function_entry class__ZendTestMagicCall_methods[] = {
};
static const zend_function_entry class__ZendTestMagicCallForward_methods[] = {
ZEND_ME(_ZendTestMagicCallForward, __call, arginfo_class__ZendTestMagicCallForward___call, ZEND_ACC_PUBLIC)
ZEND_FE_END
};
static const zend_function_entry class__ZendTestChildClass_methods[] = {
ZEND_ME(_ZendTestChildClass, returnsThrowable, arginfo_class__ZendTestChildClass_returnsThrowable, ZEND_ACC_PUBLIC)
ZEND_FE_END
@@ -532,6 +541,16 @@ static zend_class_entry *register_class__ZendTestMagicCall(void)
return class_entry;
}
static zend_class_entry *register_class__ZendTestMagicCallForward(void)
{
zend_class_entry ce, *class_entry;
INIT_CLASS_ENTRY(ce, "_ZendTestMagicCallForward", class__ZendTestMagicCallForward_methods);
class_entry = zend_register_internal_class_ex(&ce, NULL);
return class_entry;
}
static zend_class_entry *register_class__ZendTestChildClass(zend_class_entry *class_entry__ZendTestClass)
{
zend_class_entry ce, *class_entry;

View File

@@ -0,0 +1,32 @@
--TEST--
GH-16233 (Observer segfault when calling user function in internal function via trampoline)
--EXTENSIONS--
zend_test
--INI--
zend_test.observer.enabled=1
zend_test.observer.show_output=1
zend_test.observer.observe_all=1
--FILE--
<?php
function callee() {
echo "in callee\n";
}
$test = new _ZendTestMagicCallForward;
$test->callee();
echo "done\n";
?>
--EXPECTF--
<!-- init '%sgh16233.php' -->
<file '%sgh16233.php'>
<!-- init _ZendTestMagicCallForward::__call() -->
<_ZendTestMagicCallForward::__call>
<!-- init callee() -->
<callee>
in callee
</callee>
</_ZendTestMagicCallForward::__call>
done
</file '%sgh16233.php'>