Skip to content

Commit df12728

Browse files
committed
Add VM reentry limit
1 parent dda0cea commit df12728

10 files changed

+128
-2
lines changed

Zend/tests/vm_reentry_limit.phpt

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
--TEST--
2+
VM reentry limit
3+
--INI--
4+
zend.vm_reentry_limit=20
5+
--FILE--
6+
<?php
7+
8+
class Test1 {
9+
public function __destruct() {
10+
new Test1;
11+
}
12+
}
13+
14+
class Test2 {
15+
public function __clone() {
16+
clone $this;
17+
}
18+
}
19+
20+
try {
21+
new Test1;
22+
} catch (Error $e) {
23+
echo $e, "\n";
24+
}
25+
26+
echo "\n";
27+
28+
try {
29+
clone new Test2;
30+
} catch (Error $e) {
31+
echo $e, "\n";
32+
}
33+
34+
?>
35+
--EXPECTF--
36+
Error: VM reentry limit of 20 reached. Infinite recursion? in %s:%d
37+
Stack trace:
38+
#0 %s(%d): Test1->__destruct()
39+
#1 %s(%d): Test1->__destruct()
40+
#2 %s(%d): Test1->__destruct()
41+
#3 %s(%d): Test1->__destruct()
42+
#4 %s(%d): Test1->__destruct()
43+
#5 %s(%d): Test1->__destruct()
44+
#6 %s(%d): Test1->__destruct()
45+
#7 %s(%d): Test1->__destruct()
46+
#8 %s(%d): Test1->__destruct()
47+
#9 %s(%d): Test1->__destruct()
48+
#10 %s(%d): Test1->__destruct()
49+
#11 %s(%d): Test1->__destruct()
50+
#12 %s(%d): Test1->__destruct()
51+
#13 %s(%d): Test1->__destruct()
52+
#14 %s(%d): Test1->__destruct()
53+
#15 %s(%d): Test1->__destruct()
54+
#16 %s(%d): Test1->__destruct()
55+
#17 %s(%d): Test1->__destruct()
56+
#18 %s(%d): Test1->__destruct()
57+
#19 %s(%d): Test1->__destruct()
58+
#20 %s(%d): Test1->__destruct()
59+
#21 {main}
60+
61+
Error: VM reentry limit of 20 reached. Infinite recursion? in %s:%d
62+
Stack trace:
63+
#0 %s(%d): Test2->__clone()
64+
#1 %s(%d): Test2->__clone()
65+
#2 %s(%d): Test2->__clone()
66+
#3 %s(%d): Test2->__clone()
67+
#4 %s(%d): Test2->__clone()
68+
#5 %s(%d): Test2->__clone()
69+
#6 %s(%d): Test2->__clone()
70+
#7 %s(%d): Test2->__clone()
71+
#8 %s(%d): Test2->__clone()
72+
#9 %s(%d): Test2->__clone()
73+
#10 %s(%d): Test2->__clone()
74+
#11 %s(%d): Test2->__clone()
75+
#12 %s(%d): Test2->__clone()
76+
#13 %s(%d): Test2->__clone()
77+
#14 %s(%d): Test2->__clone()
78+
#15 %s(%d): Test2->__clone()
79+
#16 %s(%d): Test2->__clone()
80+
#17 %s(%d): Test2->__clone()
81+
#18 %s(%d): Test2->__clone()
82+
#19 %s(%d): Test2->__clone()
83+
#20 %s(%d): Test2->__clone()
84+
#21 {main}

Zend/zend.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ ZEND_INI_BEGIN()
192192
#endif
193193
STD_ZEND_INI_BOOLEAN("zend.exception_ignore_args", "0", ZEND_INI_ALL, OnUpdateBool, exception_ignore_args, zend_executor_globals, executor_globals)
194194
STD_ZEND_INI_ENTRY("zend.exception_string_param_max_len", "15", ZEND_INI_ALL, OnSetExceptionStringParamMaxLen, exception_string_param_max_len, zend_executor_globals, executor_globals)
195+
STD_ZEND_INI_ENTRY("zend.vm_reentry_limit", "1000", ZEND_INI_ALL, OnUpdateLong, vm_reentry_limit, zend_executor_globals, executor_globals)
195196
ZEND_INI_END()
196197

197198
ZEND_API size_t zend_vspprintf(char **pbuf, size_t max_len, const char *format, va_list ap) /* {{{ */

Zend/zend_execute.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2046,6 +2046,13 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_use_new_element_for_s
20462046
zend_throw_error(NULL, "[] operator not supported for strings");
20472047
}
20482048

2049+
static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_vm_reentry_limit_error()
2050+
{
2051+
zend_throw_error(NULL,
2052+
"VM reentry limit of " ZEND_ULONG_FMT " reached. Infinite recursion?",
2053+
EG(vm_reentry_limit));
2054+
}
2055+
20492056
static ZEND_COLD void zend_binary_assign_op_dim_slow(zval *container, zval *dim OPLINE_DC EXECUTE_DATA_DC)
20502057
{
20512058
if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) {

Zend/zend_execute_API.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ void init_executor(void) /* {{{ */
175175

176176
EG(fake_scope) = NULL;
177177
EG(trampoline).common.function_name = NULL;
178+
EG(vm_reentry_count) = 0;
178179

179180
EG(ht_iterators_count) = sizeof(EG(ht_iterators_slots)) / sizeof(HashTableIterator);
180181
EG(ht_iterators_used) = 0;

Zend/zend_globals.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,9 @@ struct _zend_executor_globals {
249249

250250
zend_get_gc_buffer get_gc_buffer;
251251

252+
zend_ulong vm_reentry_count;
253+
zend_ulong vm_reentry_limit;
254+
252255
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
253256
};
254257

Zend/zend_vm_execute.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54488,6 +54488,12 @@ ZEND_API void execute_ex(zend_execute_data *ex)
5448854488
LOAD_OPLINE();
5448954489
ZEND_VM_LOOP_INTERRUPT_CHECK();
5449054490

54491+
if (EG(vm_reentry_count)++ > EG(vm_reentry_limit)) {
54492+
zend_vm_reentry_limit_error();
54493+
LOAD_OPLINE();
54494+
/* Fall through to handle exception below. */
54495+
}
54496+
5449154497
while (1) {
5449254498
#if !defined(ZEND_VM_FP_GLOBAL_REG) || !defined(ZEND_VM_IP_GLOBAL_REG)
5449354499
int ret;
@@ -58983,6 +58989,7 @@ ZEND_API void execute_ex(zend_execute_data *ex)
5898358989
#ifdef ZEND_VM_IP_GLOBAL_REG
5898458990
opline = vm_stack_data.orig_opline;
5898558991
#endif
58992+
EG(vm_reentry_count)--;
5898658993
return;
5898758994
HYBRID_DEFAULT:
5898858995
VM_TRACE(ZEND_NULL)
@@ -58994,6 +59001,7 @@ ZEND_API void execute_ex(zend_execute_data *ex)
5899459001
# ifdef ZEND_VM_IP_GLOBAL_REG
5899559002
opline = vm_stack_data.orig_opline;
5899659003
# endif
59004+
EG(vm_reentry_count)--;
5899759005
return;
5899859006
#else
5899959007
if (EXPECTED(ret > 0)) {
@@ -59003,6 +59011,7 @@ ZEND_API void execute_ex(zend_execute_data *ex)
5900359011
# ifdef ZEND_VM_IP_GLOBAL_REG
5900459012
opline = vm_stack_data.orig_opline;
5900559013
# endif
59014+
EG(vm_reentry_count)--;
5900659015
return;
5900759016
}
5900859017
#endif

Zend/zend_vm_execute.skl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ ZEND_API void {%EXECUTOR_NAME%}_ex(zend_execute_data *ex)
1616
LOAD_OPLINE();
1717
ZEND_VM_LOOP_INTERRUPT_CHECK();
1818

19+
if (EG(vm_reentry_count)++ > EG(vm_reentry_limit)) {
20+
zend_vm_reentry_limit_error();
21+
LOAD_OPLINE();
22+
/* Fall through to handle exception below. */
23+
}
24+
1925
while (1) {
2026
{%ZEND_VM_CONTINUE_LABEL%}
2127
{%ZEND_VM_DISPATCH%} {

Zend/zend_vm_gen.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,6 +1787,7 @@ function gen_executor_code($f, $spec, $kind, $prolog, &$switch_labels = array())
17871787
out($f,"#ifdef ZEND_VM_IP_GLOBAL_REG\n");
17881788
out($f,"\t\t\t\topline = vm_stack_data.orig_opline;\n");
17891789
out($f,"#endif\n");
1790+
out($f,"\t\t\t\tEG(vm_reentry_count)--;\n");
17901791
out($f,"\t\t\t\treturn;\n");
17911792
out($f,"\t\t\tHYBRID_DEFAULT:\n");
17921793
out($f,"\t\t\t\tVM_TRACE(ZEND_NULL)\n");
@@ -1984,7 +1985,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name)
19841985
out($f,"#define HANDLE_EXCEPTION() ZEND_ASSERT(EG(exception)); LOAD_OPLINE(); ZEND_VM_CONTINUE()\n");
19851986
out($f,"#define HANDLE_EXCEPTION_LEAVE() ZEND_ASSERT(EG(exception)); LOAD_OPLINE(); ZEND_VM_LEAVE()\n");
19861987
out($f,"#define ZEND_VM_CONTINUE() goto zend_vm_continue\n");
1987-
out($f,"#define ZEND_VM_RETURN() return\n");
1988+
out($f,"#define ZEND_VM_RETURN() EG(vm_reentry_count)--; return\n");
19881989
out($f,"#define ZEND_VM_ENTER_EX() ZEND_VM_INTERRUPT_CHECK(); ZEND_VM_CONTINUE()\n");
19891990
out($f,"#define ZEND_VM_ENTER() execute_data = EG(current_execute_data); LOAD_OPLINE(); ZEND_VM_ENTER_EX()\n");
19901991
out($f,"#define ZEND_VM_LEAVE() ZEND_VM_CONTINUE()\n");
@@ -2015,7 +2016,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name)
20152016
out($f,"#define HANDLE_EXCEPTION_LEAVE() ZEND_ASSERT(EG(exception)); goto ZEND_HANDLE_EXCEPTION_LABEL\n");
20162017
}
20172018
out($f,"#define ZEND_VM_CONTINUE() goto *(void**)(OPLINE->handler)\n");
2018-
out($f,"#define ZEND_VM_RETURN() return\n");
2019+
out($f,"#define ZEND_VM_RETURN() EG(vm_reentry_count)--; return\n");
20192020
out($f,"#define ZEND_VM_ENTER_EX() ZEND_VM_INTERRUPT_CHECK(); ZEND_VM_CONTINUE()\n");
20202021
out($f,"#define ZEND_VM_ENTER() execute_data = EG(current_execute_data); LOAD_OPLINE(); ZEND_VM_ENTER_EX()\n");
20212022
out($f,"#define ZEND_VM_LEAVE() ZEND_VM_CONTINUE()\n");
@@ -2184,6 +2185,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name)
21842185
"# ifdef ZEND_VM_IP_GLOBAL_REG\n" .
21852186
$m[1]."opline = vm_stack_data.orig_opline;\n" .
21862187
"# endif\n" .
2188+
$m[1]."EG(vm_reentry_count)--;\n" .
21872189
$m[1]."return;\n" .
21882190
"#else\n" .
21892191
$m[1]."if (EXPECTED(ret > 0)) {\n" .
@@ -2193,6 +2195,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name)
21932195
"# ifdef ZEND_VM_IP_GLOBAL_REG\n" .
21942196
$m[1]."\topline = vm_stack_data.orig_opline;\n" .
21952197
"# endif\n".
2198+
$m[1]."\tEG(vm_reentry_count)--;\n".
21962199
$m[1]."\treturn;\n".
21972200
$m[1]."}\n".
21982201
"#endif\n");

php.ini-development

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,12 @@ zend.exception_ignore_args = Off
386386
; Production Value: 0
387387
zend.exception_string_param_max_len = 15
388388

389+
; Limit for recursion via VM reentry, used to prevent stack overflow.
390+
; This only affects recursion through magic method calls and similar mechanisms.
391+
; Some profiling, debugging or APM extensions might make this limit apply to plain
392+
; recursion as well, in which case you may wish to raise it.
393+
;zend.vm_reentry_limit = 1000
394+
389395
;;;;;;;;;;;;;;;;;
390396
; Miscellaneous ;
391397
;;;;;;;;;;;;;;;;;

php.ini-production

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,12 @@ zend.exception_ignore_args = On
388388
; of sensitive information in stack traces.
389389
zend.exception_string_param_max_len = 0
390390

391+
; Limit for recursion via VM reentry, used to prevent stack overflow.
392+
; This only affects recursion through magic method calls and similar mechanism.
393+
; Some profiling, debugging or APM extensions might make this limit apply to plain
394+
; recursion as well, in which case you may wish to raise it.
395+
;zend.vm_reentry_limit = 1000
396+
391397
;;;;;;;;;;;;;;;;;
392398
; Miscellaneous ;
393399
;;;;;;;;;;;;;;;;;

0 commit comments

Comments
 (0)