Skip to content

Opcodefy Magical __call/_callStatic #1231

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
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
19 changes: 19 additions & 0 deletions Zend/tests/bug68412.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
Bug #68412 (Infinite recursion with __call can make the program crash/segfault)
--FILE--
<?php
class C {
public function __call($x, $y) {
global $z;
$z->bar();
}
}
$z = new C;
function main() {
global $z;
$z->foo();
}
main();
?>
--EXPECTF--
Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %sbug68412.php on line %d
4 changes: 4 additions & 0 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "zend_list.h"
#include "zend_API.h"
#include "zend_exceptions.h"
#include "zend_objects_API.h"
#include "zend_builtin_functions.h"
#include "zend_ini.h"
#include "zend_vm.h"
Expand Down Expand Up @@ -531,6 +532,8 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{
#ifdef ZEND_WIN32
zend_get_windows_version_info(&executor_globals->windows_version_info);
#endif
memset(&EG(proxy_call_func), 0, sizeof(zend_op_array));
zend_init_proxy_call_func(&EG(proxy_call_func), &EG(proxy_call_op));
}
/* }}} */

Expand Down Expand Up @@ -722,6 +725,7 @@ int zend_startup(zend_utility_functions *utility_functions, char **extensions) /
#ifndef ZTS
zend_init_rsrc_plist();
zend_init_exception_op();
zend_init_proxy_call_func(&EG(proxy_call_func), &EG(proxy_call_op));
#endif

zend_ini_startup();
Expand Down
6 changes: 2 additions & 4 deletions Zend/zend_builtin_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -1285,16 +1285,14 @@ ZEND_FUNCTION(method_exists)
&& Z_OBJ_HT_P(klass)->get_method != NULL
&& (func = Z_OBJ_HT_P(klass)->get_method(&Z_OBJ_P(klass), method_name, NULL)) != NULL
) {
if (func->type == ZEND_INTERNAL_FUNCTION
&& (func->common.fn_flags & ZEND_ACC_CALL_VIA_HANDLER) != 0
) {
if ((func->common.fn_flags & ZEND_ACC_CALL_VIA_HANDLER) != 0) {
/* Returns true to the fake Closure's __invoke */
RETVAL_BOOL(func->common.scope == zend_ce_closure
&& zend_string_equals_literal(method_name, ZEND_INVOKE_FUNC_NAME));

zend_string_release(lcname);
zend_string_release(func->common.function_name);
efree(func);
zend_free_proxy_call_func(func);
return;
}
zend_string_release(lcname);
Expand Down
5 changes: 5 additions & 0 deletions Zend/zend_execute_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,7 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
}

if (func->type == ZEND_USER_FUNCTION) {
int call_via_handler = (func->common.fn_flags & ZEND_ACC_CALL_VIA_HANDLER) != 0;
EG(scope) = func->common.scope;
call->symbol_table = fci->symbol_table;
if (UNEXPECTED(func->op_array.fn_flags & ZEND_ACC_CLOSURE)) {
Expand All @@ -839,6 +840,10 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
} else {
zend_generator_create_zval(call, &func->op_array, fci->retval);
}
if (call_via_handler) {
/* We must re-initialize function again */
fci_cache->initialized = 0;
}
} else if (func->type == ZEND_INTERNAL_FUNCTION) {
int call_via_handler = (func->common.fn_flags & ZEND_ACC_CALL_VIA_HANDLER) != 0;
ZVAL_NULL(fci->retval);
Expand Down
3 changes: 3 additions & 0 deletions Zend/zend_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ struct _zend_executor_globals {
XPFPA_CW_DATATYPE saved_fpu_cw;
#endif

zend_op_array proxy_call_func;
zend_op proxy_call_op;

void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};

Expand Down
36 changes: 2 additions & 34 deletions Zend/zend_object_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -1036,23 +1036,7 @@ ZEND_API int zend_check_protected(zend_class_entry *ce, zend_class_entry *scope)

static inline union _zend_function *zend_get_user_call_function(zend_class_entry *ce, zend_string *method_name) /* {{{ */
{
zend_internal_function *call_user_call = emalloc(sizeof(zend_internal_function));
call_user_call->type = ZEND_INTERNAL_FUNCTION;
call_user_call->module = (ce->type == ZEND_INTERNAL_CLASS) ? ce->info.internal.module : NULL;
call_user_call->handler = zend_std_call_user_call;
call_user_call->arg_info = NULL;
call_user_call->num_args = 0;
call_user_call->scope = ce;
call_user_call->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
//??? keep compatibility for "\0" characters
//??? see: Zend/tests/bug46238.phpt
if (UNEXPECTED(strlen(method_name->val) != method_name->len)) {
call_user_call->function_name = zend_string_init(method_name->val, strlen(method_name->val), 0);
} else {
call_user_call->function_name = zend_string_copy(method_name);
}

return (union _zend_function *)call_user_call;
return (union _zend_function *)zend_get_proxy_call_func(ce, method_name, 0);
}
/* }}} */

Expand Down Expand Up @@ -1183,23 +1167,7 @@ ZEND_API void zend_std_callstatic_user_call(INTERNAL_FUNCTION_PARAMETERS) /* {{{

static inline union _zend_function *zend_get_user_callstatic_function(zend_class_entry *ce, zend_string *method_name) /* {{{ */
{
zend_internal_function *callstatic_user_call = emalloc(sizeof(zend_internal_function));
callstatic_user_call->type = ZEND_INTERNAL_FUNCTION;
callstatic_user_call->module = (ce->type == ZEND_INTERNAL_CLASS) ? ce->info.internal.module : NULL;
callstatic_user_call->handler = zend_std_callstatic_user_call;
callstatic_user_call->arg_info = NULL;
callstatic_user_call->num_args = 0;
callstatic_user_call->scope = ce;
callstatic_user_call->fn_flags = ZEND_ACC_STATIC | ZEND_ACC_PUBLIC | ZEND_ACC_CALL_VIA_HANDLER;
//??? keep compatibility for "\0" characters
//??? see: Zend/tests/bug46238.phpt
if (UNEXPECTED(strlen(method_name->val) != method_name->len)) {
callstatic_user_call->function_name = zend_string_init(method_name->val, strlen(method_name->val), 0);
} else {
callstatic_user_call->function_name = zend_string_copy(method_name);
}

return (zend_function *)callstatic_user_call;
return (union _zend_function *)zend_get_proxy_call_func(ce, method_name, 1);
}
/* }}} */

Expand Down
52 changes: 52 additions & 0 deletions Zend/zend_objects_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "zend.h"
#include "zend_globals.h"
#include "zend_variables.h"
#include "zend_vm.h"
#include "zend_API.h"
#include "zend_objects_API.h"

Expand Down Expand Up @@ -224,6 +225,57 @@ ZEND_API zend_object_handlers *zend_get_std_object_handlers(void)
return &std_object_handlers;
}

ZEND_API void zend_init_proxy_call_func(zend_op_array *func, zend_op *opline)
{
func->type = ZEND_USER_FUNCTION;

func->fn_flags = ZEND_ACC_CALL_VIA_HANDLER | ZEND_ACC_PUBLIC;
func->this_var = -1;

opline->opcode = ZEND_PROXY_CALL;
opline->op1_type = IS_UNUSED;
opline->op2_type = IS_UNUSED;
opline->result_type = IS_UNUSED;

ZEND_VM_SET_OPCODE_HANDLER(opline);

func->opcodes = opline;
}

ZEND_API zend_op_array *zend_get_proxy_call_func(zend_class_entry *ce, zend_string *method_name, int is_static)
{
zend_op_array *func;
zend_function *fbc = is_static? ce->__callstatic : ce->__call;

ZEND_ASSERT(fbc);

if (EXPECTED(EG(proxy_call_func).function_name == NULL)) {
func = &EG(proxy_call_func);
} else {
func = ecalloc(1, ZEND_MM_ALIGNED_SIZE(sizeof(zend_op_array)) + sizeof(zend_op));
zend_init_proxy_call_func(func, (zend_op *)((char *)func + ZEND_MM_ALIGNED_SIZE(sizeof(zend_op_array))));
}

if (is_static) {
func->fn_flags |= ZEND_ACC_STATIC;
} else {
func->fn_flags &= ~ZEND_ACC_STATIC;
}

func->scope = ce;
func->prototype = fbc;
func->filename = (fbc->type == ZEND_USER_FUNCTION)? fbc->op_array.filename : STR_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;
if (UNEXPECTED(strlen(method_name->val) != method_name->len)) {
func->function_name = zend_string_init(method_name->val, strlen(method_name->val), 0);
} else {
func->function_name = zend_string_copy(method_name);
}

return func;
}

/*
* Local variables:
* tab-width: 4
Expand Down
13 changes: 13 additions & 0 deletions Zend/zend_objects_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,21 @@ ZEND_API void zend_objects_store_free_object_storage(zend_objects_store *objects
ZEND_API zend_object *zend_object_create_proxy(zval *object, zval *member);

ZEND_API zend_object_handlers *zend_get_std_object_handlers(void);

ZEND_API void zend_init_proxy_call_func(zend_op_array *func, zend_op *opline);

ZEND_API zend_op_array *zend_get_proxy_call_func(zend_class_entry *ce, zend_string *method_name, int is_static);

END_EXTERN_C()

#define zend_free_proxy_call_func(func) do { \
if (((zend_op_array *)func) == &EG(proxy_call_func)) { \
((zend_op_array *)func)->function_name = NULL; \
} else { \
efree(func); \
} \
} while (0)

static zend_always_inline void zend_object_release(zend_object *obj)
{
if (--GC_REFCOUNT(obj) == 0) {
Expand Down
94 changes: 91 additions & 3 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -2376,6 +2376,7 @@ ZEND_VM_HELPER(zend_leave_helper, ANY, ANY)
}
OBJ_RELEASE(object);
}

EG(scope) = EX(func)->op_array.scope;

if (UNEXPECTED(EG(exception) != NULL)) {
Expand Down Expand Up @@ -2417,7 +2418,7 @@ ZEND_VM_HELPER(zend_leave_helper, ANY, ANY)
EG(current_execute_data) = EX(prev_execute_data);
if (EX(func)->op_array.fn_flags & ZEND_ACC_CLOSURE) {
OBJ_RELEASE((zend_object*)EX(func)->op_array.prototype);
}
}
} else /* if (call_kind == ZEND_CALL_TOP_CODE) */ {
zend_array *symbol_table = EX(symbol_table);

Expand Down Expand Up @@ -3768,8 +3769,8 @@ ZEND_VM_HANDLER(62, ZEND_RETURN, CONST|TMP|VAR|CV, ANY)
zend_free_op free_op1;

SAVE_OPLINE();
retval_ptr = GET_OP1_ZVAL_PTR(BP_VAR_R);

retval_ptr = GET_OP1_ZVAL_PTR(BP_VAR_R);
if (!EX(return_value)) {
FREE_OP1();
} else {
Expand Down Expand Up @@ -6939,7 +6940,7 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY)
}
if (call->func->common.fn_flags & ZEND_ACC_CALL_VIA_HANDLER) {
zend_string_release(call->func->common.function_name);
efree(call->func);
zend_free_proxy_call_func(call->func);
}

EX(call) = call->prev_execute_data;
Expand Down Expand Up @@ -7558,3 +7559,90 @@ ZEND_VM_HANDLER(157, ZEND_FETCH_CLASS_NAME, ANY, ANY)
ZEND_VM_NEXT_OPCODE();
}

ZEND_VM_HANDLER(158, ZEND_PROXY_CALL, ANY, ANY)
{
zval args;
zend_function *fbc = EX(func);
zend_object *obj = Z_OBJ(EX(This));
zval *ret = EX(return_value);
zend_call_kind call_kind = EX_CALL_KIND();
zend_class_entry *scope = EX(called_scope);
uint32_t num_args = EX_NUM_ARGS();
zend_execute_data *call, *prev_execute_data = EX(prev_execute_data);

array_init_size(&args, num_args);
if (num_args) {
zval *p;
zend_hash_real_init(Z_ARRVAL(args), 1);

p = ZEND_CALL_ARG(execute_data, 1);
ZEND_HASH_FILL_PACKED(Z_ARRVAL(args)) {
uint32_t i;
for (i = 0; i < num_args; ++i) {
ZEND_HASH_FILL_ADD(p);
p++;
}
} ZEND_HASH_FILL_END();
}

zend_vm_stack_free_call_frame(execute_data);

call = zend_vm_stack_push_call_frame(call_kind, fbc->common.prototype, 2, scope, obj, prev_execute_data);

ZVAL_STR(ZEND_CALL_ARG(call, 1), fbc->common.function_name);
ZVAL_COPY_VALUE(ZEND_CALL_ARG(call, 2), &args);
if (call->func->type == ZEND_USER_FUNCTION) {
call->symbol_table = NULL;
i_init_func_execute_data(call, &call->func->op_array,
ret, (call->func->common.fn_flags & ZEND_ACC_STATIC) == 0);

zend_free_proxy_call_func(fbc);

/* the previously call to current execute_data already check zend_execute_ex */
ZEND_VM_ENTER();
} else {
zval retval;

ZEND_ASSERT(call->func->type == ZEND_INTERNAL_FUNCTION);
SAVE_OPLINE();

if (ret == NULL) {
ZVAL_NULL(&retval);
ret = &retval;
}

Z_VAR_FLAGS_P(ret) = (call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0 ? IS_VAR_RET_REF : 0;

EG(current_execute_data) = call;

if (!zend_execute_internal) {
/* saves one function call if zend_execute_internal is not used */
fbc->internal_function.handler(call, ret);
} else {
zend_execute_internal(call, ret);
}

execute_data = EG(current_execute_data) = call->prev_execute_data;

zend_free_proxy_call_func(fbc);

zend_vm_stack_free_args(call);
zend_vm_stack_free_call_frame(call);

if (ret == &retval) {
zval_ptr_dtor(ret);
}

if (UNEXPECTED(EG(exception) != NULL)) {
zend_throw_exception_internal(NULL);
if (ret != &retval) {
zval_ptr_dtor(ret);
}
HANDLE_EXCEPTION_LEAVE();
}

LOAD_OPLINE();
ZEND_VM_INC_OPCODE();
ZEND_VM_LEAVE();
}
}
Loading