Skip to content

Commit f6d9fd5

Browse files
committed
Add VM reentry limit
1 parent 1698057 commit f6d9fd5

10 files changed

+127
-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
@@ -175,6 +175,7 @@ ZEND_INI_BEGIN()
175175
STD_ZEND_INI_BOOLEAN("zend.signal_check", "0", ZEND_INI_SYSTEM, OnUpdateBool, check, zend_signal_globals_t, zend_signal_globals)
176176
#endif
177177
STD_ZEND_INI_BOOLEAN("zend.exception_ignore_args", "0", ZEND_INI_ALL, OnUpdateBool, exception_ignore_args, zend_executor_globals, executor_globals)
178+
STD_ZEND_INI_ENTRY("zend.vm_reentry_limit", "1000", ZEND_INI_ALL, OnUpdateLong, vm_reentry_limit, zend_executor_globals, executor_globals)
178179
ZEND_INI_END()
179180

180181
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
@@ -2009,6 +2009,13 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_use_new_element_for_s
20092009
zend_throw_error(NULL, "[] operator not supported for strings");
20102010
}
20112011

2012+
static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_vm_reentry_limit_error()
2013+
{
2014+
zend_throw_error(NULL,
2015+
"VM reentry limit of " ZEND_ULONG_FMT " reached. Infinite recursion?",
2016+
EG(vm_reentry_limit));
2017+
}
2018+
20122019
static ZEND_COLD void zend_binary_assign_op_dim_slow(zval *container, zval *dim OPLINE_DC EXECUTE_DATA_DC)
20132020
{
20142021
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
@@ -174,6 +174,7 @@ void init_executor(void) /* {{{ */
174174

175175
EG(fake_scope) = NULL;
176176
EG(trampoline).common.function_name = NULL;
177+
EG(vm_reentry_count) = 0;
177178

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

Zend/zend_globals.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ struct _zend_executor_globals {
237237
HashTable weakrefs;
238238

239239
zend_bool exception_ignore_args;
240+
zend_ulong vm_reentry_count;
241+
zend_ulong vm_reentry_limit;
240242

241243
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
242244
};

Zend/zend_vm_execute.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51683,6 +51683,12 @@ ZEND_API void execute_ex(zend_execute_data *ex)
5168351683
LOAD_OPLINE();
5168451684
ZEND_VM_LOOP_INTERRUPT_CHECK();
5168551685

51686+
if (EG(vm_reentry_count)++ > EG(vm_reentry_limit)) {
51687+
zend_vm_reentry_limit_error();
51688+
LOAD_OPLINE();
51689+
/* Fall through to handle exception below. */
51690+
}
51691+
5168651692
while (1) {
5168751693
#if !defined(ZEND_VM_FP_GLOBAL_REG) || !defined(ZEND_VM_IP_GLOBAL_REG)
5168851694
int ret;
@@ -55921,6 +55927,7 @@ ZEND_API void execute_ex(zend_execute_data *ex)
5592155927
#ifdef ZEND_VM_IP_GLOBAL_REG
5592255928
opline = orig_opline;
5592355929
#endif
55930+
EG(vm_reentry_count)--;
5592455931
return;
5592555932
HYBRID_DEFAULT:
5592655933
VM_TRACE(ZEND_NULL)
@@ -55932,6 +55939,7 @@ ZEND_API void execute_ex(zend_execute_data *ex)
5593255939
# ifdef ZEND_VM_IP_GLOBAL_REG
5593355940
opline = orig_opline;
5593455941
# endif
55942+
EG(vm_reentry_count)--;
5593555943
return;
5593655944
#else
5593755945
if (EXPECTED(ret > 0)) {
@@ -55941,6 +55949,7 @@ ZEND_API void execute_ex(zend_execute_data *ex)
5594155949
# ifdef ZEND_VM_IP_GLOBAL_REG
5594255950
opline = orig_opline;
5594355951
# endif
55952+
EG(vm_reentry_count)--;
5594455953
return;
5594555954
}
5594655955
#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
@@ -1753,6 +1753,7 @@ function gen_executor_code($f, $spec, $kind, $prolog, &$switch_labels = array())
17531753
out($f,"#ifdef ZEND_VM_IP_GLOBAL_REG\n");
17541754
out($f,"\t\t\t\topline = orig_opline;\n");
17551755
out($f,"#endif\n");
1756+
out($f,"\t\t\t\tEG(vm_reentry_count)--;\n");
17561757
out($f,"\t\t\t\treturn;\n");
17571758
out($f,"\t\t\tHYBRID_DEFAULT:\n");
17581759
out($f,"\t\t\t\tVM_TRACE(ZEND_NULL)\n");
@@ -1958,7 +1959,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name)
19581959
out($f,"#define HANDLE_EXCEPTION() LOAD_OPLINE(); ZEND_VM_CONTINUE()\n");
19591960
out($f,"#define HANDLE_EXCEPTION_LEAVE() LOAD_OPLINE(); ZEND_VM_LEAVE()\n");
19601961
out($f,"#define ZEND_VM_CONTINUE() goto zend_vm_continue\n");
1961-
out($f,"#define ZEND_VM_RETURN() return\n");
1962+
out($f,"#define ZEND_VM_RETURN() EG(vm_reentry_count)--; return\n");
19621963
out($f,"#define ZEND_VM_ENTER_EX() ZEND_VM_INTERRUPT_CHECK(); ZEND_VM_CONTINUE()\n");
19631964
out($f,"#define ZEND_VM_ENTER() execute_data = EG(current_execute_data); LOAD_OPLINE(); ZEND_VM_ENTER_EX()\n");
19641965
out($f,"#define ZEND_VM_LEAVE() ZEND_VM_CONTINUE()\n");
@@ -1999,7 +2000,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name)
19992000
out($f,"#define HANDLE_EXCEPTION_LEAVE() goto ZEND_HANDLE_EXCEPTION_LABEL\n");
20002001
}
20012002
out($f,"#define ZEND_VM_CONTINUE() goto *(void**)(OPLINE->handler)\n");
2002-
out($f,"#define ZEND_VM_RETURN() return\n");
2003+
out($f,"#define ZEND_VM_RETURN() EG(vm_reentry_count)--; return\n");
20032004
out($f,"#define ZEND_VM_ENTER_EX() ZEND_VM_INTERRUPT_CHECK(); ZEND_VM_CONTINUE()\n");
20042005
out($f,"#define ZEND_VM_ENTER() execute_data = EG(current_execute_data); LOAD_OPLINE(); ZEND_VM_ENTER_EX()\n");
20052006
out($f,"#define ZEND_VM_LEAVE() ZEND_VM_CONTINUE()\n");
@@ -2149,6 +2150,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name)
21492150
"# ifdef ZEND_VM_IP_GLOBAL_REG\n" .
21502151
$m[1]."opline = orig_opline;\n" .
21512152
"# endif\n" .
2153+
$m[1]."EG(vm_reentry_count)--;\n" .
21522154
$m[1]."return;\n" .
21532155
"#else\n" .
21542156
$m[1]."if (EXPECTED(ret > 0)) {\n" .
@@ -2158,6 +2160,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name)
21582160
"# ifdef ZEND_VM_IP_GLOBAL_REG\n" .
21592161
$m[1]."\topline = orig_opline;\n" .
21602162
"# endif\n".
2163+
$m[1]."\tEG(vm_reentry_count)--;\n".
21612164
$m[1]."\treturn;\n".
21622165
$m[1]."}\n".
21632166
"#endif\n");

php.ini-development

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,12 @@ zend.enable_gc = On
365365
; Default: Off
366366
zend.exception_ignore_args = Off
367367

368+
; Limit for recursion via VM reentry, used to prevent stack overflow.
369+
; This only affects recursion through magic method calls and similar mechanism.
370+
; Some profiling, debugging or APM extensions might make this limit apply to plain
371+
; recursion as well, in which case you may wish to raise it.
372+
;zend.vm_reentry_limit = 1000
373+
368374
;;;;;;;;;;;;;;;;;
369375
; Miscellaneous ;
370376
;;;;;;;;;;;;;;;;;

php.ini-production

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,12 @@ zend.enable_gc = On
367367
; of sensitive information in stack traces
368368
zend.exception_ignore_args = On
369369

370+
; Limit for recursion via VM reentry, used to prevent stack overflow.
371+
; This only affects recursion through magic method calls and similar mechanism.
372+
; Some profiling, debugging or APM extensions might make this limit apply to plain
373+
; recursion as well, in which case you may wish to raise it.
374+
;zend.vm_reentry_limit = 1000
375+
370376
;;;;;;;;;;;;;;;;;
371377
; Miscellaneous ;
372378
;;;;;;;;;;;;;;;;;

0 commit comments

Comments
 (0)