Skip to content

Commit

Permalink
Limit stack usage
Browse files Browse the repository at this point in the history
  • Loading branch information
arnaud-lb committed Jul 22, 2022
1 parent 2673c1d commit 347448e
Show file tree
Hide file tree
Showing 21 changed files with 572 additions and 4 deletions.
47 changes: 46 additions & 1 deletion Zend/Zend.m4
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,52 @@ _LT_AC_TRY_DLOPEN_SELF([
])
dnl Checks for library functions.
AC_CHECK_FUNCS(getpid kill sigsetjmp)
AC_CHECK_FUNCS(getpid kill sigsetjmp pthread_getattr_np)
dnl Test whether the target system has __libc_stack_end
AC_MSG_CHECKING(whether libc has __libc_stack_end)
AC_RUN_IFELSE([AC_LANG_SOURCE([[
extern void* __libc_stack_end;
int main()
{
void *test = __libc_stack_end;
return 0;
}
]])], [
AC_DEFINE([HAVE_LIBC_STACK_END], 1, [Define if the target system has __libc_stack_end])
AC_MSG_RESULT(yes)
], [
AC_MSG_RESULT(no)
], [
AC_MSG_RESULT(no)
])
dnl Test whether the stack grows downwards
dnl Assumes stack is contiguous
AC_MSG_CHECKING(whether the stack grows downwards)
AC_RUN_IFELSE([AC_LANG_SOURCE([[
int stack_grows_downwards(void *arg) {
void *local;
if (arg == (void*)0) {
return stack_grows_downwards(&local);
}
return (void*)&local < arg;
}
int main() {
return stack_grows_downwards((void*)0) ? 0 : 1;
}
]])], [
AC_DEFINE([ZEND_STACK_GROWS_DOWNWARDS], 1, [Define if the stack grows downwards])
AC_DEFINE([ZEND_CHECK_STACK_LIMIT], 1, [Define if checking the stack limit is supported])
AC_MSG_RESULT(yes)
], [
AC_MSG_RESULT(no)
], [
AC_MSG_RESULT(no)
])
ZEND_CHECK_FLOAT_PRECISION
Expand Down
46 changes: 46 additions & 0 deletions Zend/tests/stack_limit_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
--TEST--
Stack limit 001
--FILE--
<?php

class Test1 {
public function __destruct() {
new Test1;
}
}

class Test2 {
public function __clone() {
clone $this;
}
}

function replace() {
return preg_replace_callback('#.#', function () {
return replace();
}, 'x');
}

try {
new Test1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
clone new Test2;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
replace();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECTF--
Maximum call stack size reached. Infinite recursion?
Maximum call stack size reached. Infinite recursion?
Maximum call stack size reached. Infinite recursion?
50 changes: 50 additions & 0 deletions Zend/tests/stack_limit_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--TEST--
Stack limit 002
--FILE--
<?php

class Test1 {
public function __destruct() {
new Test1;
}
}

class Test2 {
public function __clone() {
clone $this;
}
}

function replace() {
return preg_replace_callback('#.#', function () {
return replace();
}, 'x');
}

$fiber = new Fiber(function (): void {
try {
new Test1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
clone new Test2;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
replace();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
});

$fiber->start();

?>
--EXPECTF--
Maximum call stack size reached. Infinite recursion?
Maximum call stack size reached. Infinite recursion?
Maximum call stack size reached. Infinite recursion?
48 changes: 48 additions & 0 deletions Zend/tests/stack_limit_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
--TEST--
Stack limit 003
--INI--
zend.max_allowed_stack_size=40K
--FILE--
<?php

class Test1 {
public function __destruct() {
new Test1;
}
}

class Test2 {
public function __clone() {
clone $this;
}
}

function replace() {
return preg_replace_callback('#.#', function () {
return replace();
}, 'x');
}

try {
new Test1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
clone new Test2;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
replace();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECTF--
Maximum call stack size reached. Infinite recursion?
Maximum call stack size reached. Infinite recursion?
Maximum call stack size reached. Infinite recursion?
36 changes: 36 additions & 0 deletions Zend/tests/stack_limit_004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
--TEST--
Stack limit 004
--FILE--
<?php

class Test1 {
public function __destruct() {
new Test1;
}
}

$callback = function (): int {
try {
new Test1;
} catch (Error $e) {
return count($e->getTrace());
}

throw new \Exception();
};

ini_set('fiber.stack_size', '100K');
$fiber = new Fiber($callback);
$fiber->start();
$depth1 = $fiber->getReturn();

ini_set('fiber.stack_size', '50K');
$fiber = new Fiber($callback);
$fiber->start();
$depth2 = $fiber->getReturn();

var_dump($depth1 > $depth2);

?>
--EXPECT--
bool(true)
37 changes: 37 additions & 0 deletions Zend/tests/stack_limit_005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--TEST--
Stack limit 005
--INI--
zend.max_allowed_stack_size=50K
--FILE--
<?php

$test
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
->f()->f()->f()->f()->f()->f()->f()->f()->f()->f()
;

--EXPECTF--
Fatal error: Maximum call stack size reached. Try splitting expression in %s on line 3
48 changes: 46 additions & 2 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "zend_attributes.h"
#include "zend_observer.h"
#include "zend_fibers.h"
#include "zend_call_stack.h"
#include "Optimizer/zend_optimizer.h"

static size_t global_map_ptr_last = 0;
Expand Down Expand Up @@ -173,13 +174,50 @@ static ZEND_INI_MH(OnSetExceptionStringParamMaxLen) /* {{{ */
}
/* }}} */

#ifdef ZEND_CHECK_STACK_LIMIT
static ZEND_INI_MH(OnUpdateMaxAllowedStackSize) /* {{{ */
{
EG(max_allowed_stack_size) = (size_t) zend_ini_parse_uquantity_warn(new_value, entry->name);
if (EG(max_allowed_stack_size)) {
return SUCCESS;
}

/* Don't detect the stack size if we can't detect the stack base */
if (zend_call_stack_base() != NULL) {
size_t limit = zend_call_stack_limit();
if (limit != 0) {
EG(max_allowed_stack_size) = limit;
return SUCCESS;
}
}

EG(max_allowed_stack_size) = 2 * 1024 * 1024;

return SUCCESS;
}
/* }}} */

static ZEND_INI_MH(OnUpdateReservedStackSize) /* {{{ */
{
EG(reserved_stack_size) = (size_t) zend_ini_parse_uquantity_warn(new_value, entry->name);

return SUCCESS;
}
/* }}} */
#endif /* ZEND_CHECK_STACK_LIMIT */

static ZEND_INI_MH(OnUpdateFiberStackSize) /* {{{ */
{
zend_long size;

if (new_value) {
EG(fiber_stack_size) = zend_ini_parse_quantity_warn(new_value, entry->name);
size = zend_ini_parse_quantity_warn(new_value, entry->name);
} else {
EG(fiber_stack_size) = ZEND_FIBER_DEFAULT_C_STACK_SIZE;
size = ZEND_FIBER_DEFAULT_C_STACK_SIZE;
}

EG(fiber_stack_size) = size;

return SUCCESS;
}
/* }}} */
Expand All @@ -203,6 +241,12 @@ ZEND_INI_BEGIN()
STD_ZEND_INI_BOOLEAN("zend.exception_ignore_args", "0", ZEND_INI_ALL, OnUpdateBool, exception_ignore_args, zend_executor_globals, executor_globals)
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)
STD_ZEND_INI_ENTRY("fiber.stack_size", NULL, ZEND_INI_ALL, OnUpdateFiberStackSize, fiber_stack_size, zend_executor_globals, executor_globals)
#ifdef ZEND_CHECK_STACK_LIMIT
/* The maximum allowed call stack size. 0: auto detect. For fibers, this is fiber.stack_size. */
STD_ZEND_INI_ENTRY("zend.max_allowed_stack_size", "0", ZEND_INI_PERDIR, OnUpdateMaxAllowedStackSize, max_allowed_stack_size, zend_executor_globals, executor_globals)
/* Substracted from the max allowed stack size, as a buffer, when checking for overflow. */
STD_ZEND_INI_ENTRY("zend.reserved_stack_size", "12288", ZEND_INI_PERDIR, OnUpdateReservedStackSize, reserved_stack_size, zend_executor_globals, executor_globals)
#endif

ZEND_INI_END()

Expand Down
Loading

0 comments on commit 347448e

Please sign in to comment.