Skip to content

Commit 73855f7

Browse files
committed
Opcode-fy magical __call
1 parent 871ff65 commit 73855f7

File tree

8 files changed

+176
-50
lines changed

8 files changed

+176
-50
lines changed

Zend/tests/bug68412.phpt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Bug #68412 (Infinite recursion with __call can make the program crash/segfault)
3+
--FILE--
4+
<?php
5+
class C {
6+
public function __call($x, $y) {
7+
global $z;
8+
$z->bar();
9+
}
10+
}
11+
$z = new C;
12+
function main() {
13+
global $z;
14+
$z->foo();
15+
}
16+
main();
17+
?>
18+
--EXPECTF--
19+
Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %sbug68412.php on line %d

Zend/zend_builtin_functions.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,9 +1285,7 @@ ZEND_FUNCTION(method_exists)
12851285
&& Z_OBJ_HT_P(klass)->get_method != NULL
12861286
&& (func = Z_OBJ_HT_P(klass)->get_method(&Z_OBJ_P(klass), method_name, NULL)) != NULL
12871287
) {
1288-
if (func->type == ZEND_INTERNAL_FUNCTION
1289-
&& (func->common.fn_flags & ZEND_ACC_CALL_VIA_HANDLER) != 0
1290-
) {
1288+
if ((func->common.fn_flags & ZEND_ACC_CALL_VIA_HANDLER) != 0) {
12911289
/* Returns true to the fake Closure's __invoke */
12921290
RETVAL_BOOL(func->common.scope == zend_ce_closure
12931291
&& zend_string_equals_literal(method_name, ZEND_INVOKE_FUNC_NAME));

Zend/zend_execute_API.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,7 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
827827
}
828828

829829
if (func->type == ZEND_USER_FUNCTION) {
830+
int call_via_handler = (func->common.fn_flags & ZEND_ACC_CALL_VIA_HANDLER) != 0;
830831
EG(scope) = func->common.scope;
831832
call->symbol_table = fci->symbol_table;
832833
if (UNEXPECTED(func->op_array.fn_flags & ZEND_ACC_CLOSURE)) {
@@ -839,6 +840,10 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
839840
} else {
840841
zend_generator_create_zval(call, &func->op_array, fci->retval);
841842
}
843+
if (call_via_handler) {
844+
/* We must re-initialize function again */
845+
fci_cache->initialized = 0;
846+
}
842847
} else if (func->type == ZEND_INTERNAL_FUNCTION) {
843848
int call_via_handler = (func->common.fn_flags & ZEND_ACC_CALL_VIA_HANDLER) != 0;
844849
ZVAL_NULL(fci->retval);

Zend/zend_object_handlers.c

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "zend_interfaces.h"
3030
#include "zend_closures.h"
3131
#include "zend_compile.h"
32+
#include "zend_vm.h"
3233
#include "zend_hash.h"
3334

3435
#define DEBUG_OBJECT_HANDLERS 0
@@ -1036,23 +1037,45 @@ ZEND_API int zend_check_protected(zend_class_entry *ce, zend_class_entry *scope)
10361037

10371038
static inline union _zend_function *zend_get_user_call_function(zend_class_entry *ce, zend_string *method_name) /* {{{ */
10381039
{
1039-
zend_internal_function *call_user_call = emalloc(sizeof(zend_internal_function));
1040-
call_user_call->type = ZEND_INTERNAL_FUNCTION;
1041-
call_user_call->module = (ce->type == ZEND_INTERNAL_CLASS) ? ce->info.internal.module : NULL;
1042-
call_user_call->handler = zend_std_call_user_call;
1043-
call_user_call->arg_info = NULL;
1044-
call_user_call->num_args = 0;
1045-
call_user_call->scope = ce;
1046-
call_user_call->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
1047-
//??? keep compatibility for "\0" characters
1048-
//??? see: Zend/tests/bug46238.phpt
1049-
if (UNEXPECTED(strlen(method_name->val) != method_name->len)) {
1050-
call_user_call->function_name = zend_string_init(method_name->val, strlen(method_name->val), 0);
1040+
if (ce->type == ZEND_USER_CLASS) {
1041+
zend_op_array *call_user_call = ecalloc(1, ZEND_MM_ALIGNED_SIZE(sizeof(zend_op_array)) + sizeof(zend_op));
1042+
call_user_call->type = ZEND_USER_FUNCTION;
1043+
call_user_call->scope = ce;
1044+
call_user_call->prototype = ce->__call;
1045+
call_user_call->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
1046+
call_user_call->this_var = -1;
1047+
call_user_call->filename = ce->__call->op_array.filename;
1048+
call_user_call->opcodes = (zend_op *)((char *)call_user_call + ZEND_MM_ALIGNED_SIZE(sizeof(zend_op_array)));
1049+
call_user_call->opcodes[0].opcode = ZEND_PROXY_CALL;
1050+
call_user_call->opcodes[0].op1_type = IS_UNUSED;
1051+
call_user_call->opcodes[0].op2_type = IS_UNUSED;
1052+
call_user_call->opcodes[0].result_type = IS_UNUSED;
1053+
ZEND_VM_SET_OPCODE_HANDLER(&call_user_call->opcodes[0]);
1054+
1055+
if (UNEXPECTED(strlen(method_name->val) != method_name->len)) {
1056+
call_user_call->function_name = zend_string_init(method_name->val, strlen(method_name->val), 0);
1057+
} else {
1058+
call_user_call->function_name = zend_string_copy(method_name);
1059+
}
1060+
return (union _zend_function *)call_user_call;
10511061
} else {
1052-
call_user_call->function_name = zend_string_copy(method_name);
1062+
zend_internal_function *call_user_call = emalloc(sizeof(zend_internal_function));
1063+
call_user_call->type = ZEND_INTERNAL_FUNCTION;
1064+
call_user_call->module = (ce->type == ZEND_INTERNAL_CLASS) ? ce->info.internal.module : NULL;
1065+
call_user_call->handler = zend_std_call_user_call;
1066+
call_user_call->arg_info = NULL;
1067+
call_user_call->num_args = 0;
1068+
call_user_call->scope = ce;
1069+
call_user_call->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
1070+
//??? keep compatibility for "\0" characters
1071+
//??? see: Zend/tests/bug46238.phpt
1072+
if (UNEXPECTED(strlen(method_name->val) != method_name->len)) {
1073+
call_user_call->function_name = zend_string_init(method_name->val, strlen(method_name->val), 0);
1074+
} else {
1075+
call_user_call->function_name = zend_string_copy(method_name);
1076+
}
1077+
return (union _zend_function *)call_user_call;
10531078
}
1054-
1055-
return (union _zend_function *)call_user_call;
10561079
}
10571080
/* }}} */
10581081

Zend/zend_vm_def.h

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2376,6 +2376,7 @@ ZEND_VM_HELPER(zend_leave_helper, ANY, ANY)
23762376
}
23772377
OBJ_RELEASE(object);
23782378
}
2379+
23792380
EG(scope) = EX(func)->op_array.scope;
23802381

23812382
if (UNEXPECTED(EG(exception) != NULL)) {
@@ -2417,7 +2418,7 @@ ZEND_VM_HELPER(zend_leave_helper, ANY, ANY)
24172418
EG(current_execute_data) = EX(prev_execute_data);
24182419
if (EX(func)->op_array.fn_flags & ZEND_ACC_CLOSURE) {
24192420
OBJ_RELEASE((zend_object*)EX(func)->op_array.prototype);
2420-
}
2421+
}
24212422
} else /* if (call_kind == ZEND_CALL_TOP_CODE) */ {
24222423
zend_array *symbol_table = EX(symbol_table);
24232424

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

37703771
SAVE_OPLINE();
3771-
retval_ptr = GET_OP1_ZVAL_PTR(BP_VAR_R);
37723772

3773+
retval_ptr = GET_OP1_ZVAL_PTR(BP_VAR_R);
37733774
if (!EX(return_value)) {
37743775
FREE_OP1();
37753776
} else {
@@ -7558,3 +7559,42 @@ ZEND_VM_HANDLER(157, ZEND_FETCH_CLASS_NAME, ANY, ANY)
75587559
ZEND_VM_NEXT_OPCODE();
75597560
}
75607561

7562+
ZEND_VM_HANDLER(158, ZEND_PROXY_CALL, ANY, ANY)
7563+
{
7564+
zval args;
7565+
zend_function *fbc = EX(func);
7566+
zend_object *obj = Z_OBJ(EX(This));
7567+
zval *return_value = EX(return_value);
7568+
zend_call_kind call_kind = EX_CALL_KIND();
7569+
uint32_t num_args = EX_NUM_ARGS();
7570+
zend_execute_data *call, *prev_execute_data = EX(prev_execute_data);
7571+
7572+
array_init_size(&args, num_args);
7573+
if (num_args) {
7574+
zval *p;
7575+
zend_hash_real_init(Z_ARRVAL(args), 1);
7576+
7577+
p = ZEND_CALL_ARG(execute_data, 1);
7578+
ZEND_HASH_FILL_PACKED(Z_ARRVAL(args)) {
7579+
uint32_t i;
7580+
for (i = 0; i < num_args; ++i) {
7581+
ZEND_HASH_FILL_ADD(p);
7582+
p++;
7583+
}
7584+
} ZEND_HASH_FILL_END();
7585+
}
7586+
7587+
zend_vm_stack_free_call_frame(execute_data);
7588+
call = zend_vm_stack_push_call_frame(call_kind,
7589+
fbc->common.prototype, 2, fbc->common.scope, obj, prev_execute_data);
7590+
7591+
ZVAL_STR(ZEND_CALL_ARG(call, 1), fbc->common.function_name);
7592+
ZVAL_COPY_VALUE(ZEND_CALL_ARG(call, 2), &args);
7593+
7594+
efree(fbc);
7595+
7596+
call->symbol_table = NULL;
7597+
i_init_func_execute_data(call, &call->func->op_array, return_value, 1);
7598+
7599+
ZEND_VM_ENTER();
7600+
}

Zend/zend_vm_execute.h

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper_SPEC(ZEND_OPCODE_
466466
}
467467
OBJ_RELEASE(object);
468468
}
469+
469470
EG(scope) = EX(func)->op_array.scope;
470471

471472
if (UNEXPECTED(EG(exception) != NULL)) {
@@ -1772,6 +1773,45 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_NAME_SPEC_HANDLER(
17721773
ZEND_VM_NEXT_OPCODE();
17731774
}
17741775

1776+
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_PROXY_CALL_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
1777+
{
1778+
zval args;
1779+
zend_function *fbc = EX(func);
1780+
zend_object *obj = Z_OBJ(EX(This));
1781+
zval *return_value = EX(return_value);
1782+
zend_call_kind call_kind = EX_CALL_KIND();
1783+
uint32_t num_args = EX_NUM_ARGS();
1784+
zend_execute_data *call, *prev_execute_data = EX(prev_execute_data);
1785+
1786+
array_init_size(&args, num_args);
1787+
if (num_args) {
1788+
zval *p;
1789+
zend_hash_real_init(Z_ARRVAL(args), 1);
1790+
1791+
p = ZEND_CALL_ARG(execute_data, 1);
1792+
ZEND_HASH_FILL_PACKED(Z_ARRVAL(args)) {
1793+
uint32_t i;
1794+
for (i = 0; i < num_args; ++i) {
1795+
ZEND_HASH_FILL_ADD(p);
1796+
p++;
1797+
}
1798+
} ZEND_HASH_FILL_END();
1799+
}
1800+
1801+
zend_vm_stack_free_call_frame(execute_data);
1802+
call = zend_vm_stack_push_call_frame(call_kind,
1803+
fbc->common.prototype, 2, fbc->common.scope, obj, prev_execute_data);
1804+
1805+
ZVAL_STR(ZEND_CALL_ARG(call, 1), fbc->common.function_name);
1806+
ZVAL_COPY_VALUE(ZEND_CALL_ARG(call, 2), &args);
1807+
1808+
efree(fbc);
1809+
1810+
call->symbol_table = NULL;
1811+
i_init_func_execute_data(call, &call->func->op_array, return_value, 1);
1812+
1813+
ZEND_VM_ENTER();
1814+
}
17751815
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
17761816
{
17771817
USE_OPLINE
@@ -2813,8 +2853,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RETURN_SPEC_CONST_HANDLER(ZEND
28132853

28142854

28152855
SAVE_OPLINE();
2816-
retval_ptr = EX_CONSTANT(opline->op1);
28172856

2857+
retval_ptr = EX_CONSTANT(opline->op1);
28182858
if (!EX(return_value)) {
28192859

28202860
} else {
@@ -10617,8 +10657,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RETURN_SPEC_TMP_HANDLER(ZEND_O
1061710657
zend_free_op free_op1;
1061810658

1061910659
SAVE_OPLINE();
10620-
retval_ptr = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1);
1062110660

10661+
retval_ptr = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1);
1062210662
if (!EX(return_value)) {
1062310663
zval_ptr_dtor_nogc(free_op1);
1062410664
} else {
@@ -13649,8 +13689,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RETURN_SPEC_VAR_HANDLER(ZEND_O
1364913689
zend_free_op free_op1;
1365013690

1365113691
SAVE_OPLINE();
13652-
retval_ptr = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1);
1365313692

13693+
retval_ptr = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1);
1365413694
if (!EX(return_value)) {
1365513695
zval_ptr_dtor_nogc(free_op1);
1365613696
} else {
@@ -27222,8 +27262,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RETURN_SPEC_CV_HANDLER(ZEND_OP
2722227262

2722327263

2722427264
SAVE_OPLINE();
27225-
retval_ptr = _get_zval_ptr_cv_BP_VAR_R(execute_data, opline->op1.var);
2722627265

27266+
retval_ptr = _get_zval_ptr_cv_BP_VAR_R(execute_data, opline->op1.var);
2722727267
if (!EX(return_value)) {
2722827268

2722927269
} else {
@@ -47346,31 +47386,31 @@ void zend_init_opcodes_handlers(void)
4734647386
ZEND_FETCH_CLASS_NAME_SPEC_HANDLER,
4734747387
ZEND_FETCH_CLASS_NAME_SPEC_HANDLER,
4734847388
ZEND_FETCH_CLASS_NAME_SPEC_HANDLER,
47349-
ZEND_NULL_HANDLER,
47350-
ZEND_NULL_HANDLER,
47351-
ZEND_NULL_HANDLER,
47352-
ZEND_NULL_HANDLER,
47353-
ZEND_NULL_HANDLER,
47354-
ZEND_NULL_HANDLER,
47355-
ZEND_NULL_HANDLER,
47356-
ZEND_NULL_HANDLER,
47357-
ZEND_NULL_HANDLER,
47358-
ZEND_NULL_HANDLER,
47359-
ZEND_NULL_HANDLER,
47360-
ZEND_NULL_HANDLER,
47361-
ZEND_NULL_HANDLER,
47362-
ZEND_NULL_HANDLER,
47363-
ZEND_NULL_HANDLER,
47364-
ZEND_NULL_HANDLER,
47365-
ZEND_NULL_HANDLER,
47366-
ZEND_NULL_HANDLER,
47367-
ZEND_NULL_HANDLER,
47368-
ZEND_NULL_HANDLER,
47369-
ZEND_NULL_HANDLER,
47370-
ZEND_NULL_HANDLER,
47371-
ZEND_NULL_HANDLER,
47372-
ZEND_NULL_HANDLER,
47373-
ZEND_NULL_HANDLER,
47389+
ZEND_PROXY_CALL_SPEC_HANDLER,
47390+
ZEND_PROXY_CALL_SPEC_HANDLER,
47391+
ZEND_PROXY_CALL_SPEC_HANDLER,
47392+
ZEND_PROXY_CALL_SPEC_HANDLER,
47393+
ZEND_PROXY_CALL_SPEC_HANDLER,
47394+
ZEND_PROXY_CALL_SPEC_HANDLER,
47395+
ZEND_PROXY_CALL_SPEC_HANDLER,
47396+
ZEND_PROXY_CALL_SPEC_HANDLER,
47397+
ZEND_PROXY_CALL_SPEC_HANDLER,
47398+
ZEND_PROXY_CALL_SPEC_HANDLER,
47399+
ZEND_PROXY_CALL_SPEC_HANDLER,
47400+
ZEND_PROXY_CALL_SPEC_HANDLER,
47401+
ZEND_PROXY_CALL_SPEC_HANDLER,
47402+
ZEND_PROXY_CALL_SPEC_HANDLER,
47403+
ZEND_PROXY_CALL_SPEC_HANDLER,
47404+
ZEND_PROXY_CALL_SPEC_HANDLER,
47405+
ZEND_PROXY_CALL_SPEC_HANDLER,
47406+
ZEND_PROXY_CALL_SPEC_HANDLER,
47407+
ZEND_PROXY_CALL_SPEC_HANDLER,
47408+
ZEND_PROXY_CALL_SPEC_HANDLER,
47409+
ZEND_PROXY_CALL_SPEC_HANDLER,
47410+
ZEND_PROXY_CALL_SPEC_HANDLER,
47411+
ZEND_PROXY_CALL_SPEC_HANDLER,
47412+
ZEND_PROXY_CALL_SPEC_HANDLER,
47413+
ZEND_PROXY_CALL_SPEC_HANDLER,
4737447414
ZEND_DISCARD_EXCEPTION_SPEC_HANDLER,
4737547415
ZEND_DISCARD_EXCEPTION_SPEC_HANDLER,
4737647416
ZEND_DISCARD_EXCEPTION_SPEC_HANDLER,

Zend/zend_vm_opcodes.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ const char *zend_vm_opcodes_map[171] = {
180180
"ZEND_BIND_TRAITS",
181181
"ZEND_SEPARATE",
182182
"ZEND_FETCH_CLASS_NAME",
183-
NULL,
183+
"ZEND_PROXY_CALL",
184184
"ZEND_DISCARD_EXCEPTION",
185185
"ZEND_YIELD",
186186
"ZEND_GENERATOR_RETURN",

Zend/zend_vm_opcodes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ END_EXTERN_C()
188188
#define ZEND_BIND_TRAITS 155
189189
#define ZEND_SEPARATE 156
190190
#define ZEND_FETCH_CLASS_NAME 157
191+
#define ZEND_PROXY_CALL 158
191192
#define ZEND_DISCARD_EXCEPTION 159
192193
#define ZEND_YIELD 160
193194
#define ZEND_GENERATOR_RETURN 161

0 commit comments

Comments
 (0)