From 9f54cac964505e7f9d4dac8341f4f8a066e583d0 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 2 Jun 2024 01:38:34 +0200 Subject: [PATCH 1/2] Fix GH-14361: Deep recursion in zend_cfg.c causes segfault instead of error Use the same stack limit check already used elsewhere in compilation. --- Zend/Optimizer/zend_cfg.c | 2 ++ Zend/zend_compile.c | 23 +++++++++++++-------- Zend/zend_compile.h | 2 ++ ext/opcache/tests/jit/gh14361.phpt | 33 ++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 ext/opcache/tests/jit/gh14361.phpt diff --git a/Zend/Optimizer/zend_cfg.c b/Zend/Optimizer/zend_cfg.c index ce7d078bb957e..07b33065a93f0 100644 --- a/Zend/Optimizer/zend_cfg.c +++ b/Zend/Optimizer/zend_cfg.c @@ -28,6 +28,8 @@ static void zend_mark_reachable(zend_op *opcodes, zend_cfg *cfg, zend_basic_bloc { zend_basic_block *blocks = cfg->blocks; + zend_check_stack_limit("Try reducing function size"); + while (1) { int i; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 33c15f00b339c..0da707122d10c 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -100,25 +100,32 @@ static void zend_compile_stmt(zend_ast *ast); static void zend_compile_assign(znode *result, zend_ast *ast); #ifdef ZEND_CHECK_STACK_LIMIT -zend_never_inline static void zend_stack_limit_error(void) +zend_never_inline static void zend_stack_limit_error(const char *suggestion) { zend_error_noreturn(E_COMPILE_ERROR, - "Maximum call stack size of %zu bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached during compilation. Try splitting expression", - (size_t) ((uintptr_t) EG(stack_base) - (uintptr_t) EG(stack_limit))); + "Maximum call stack size of %zu bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached during compilation. %s", + (size_t) ((uintptr_t) EG(stack_base) - (uintptr_t) EG(stack_limit)), + suggestion); } -static void zend_check_stack_limit(void) +void zend_check_stack_limit(const char *suggestion) { if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) { - zend_stack_limit_error(); + zend_stack_limit_error(suggestion); } } #else /* ZEND_CHECK_STACK_LIMIT */ -static void zend_check_stack_limit(void) +void zend_check_stack_limit(const char *suggestion) { + ZEND_IGNORE_VALUE(suggestion); } #endif /* ZEND_CHECK_STACK_LIMIT */ +static void zend_check_stack_limit_expression(void) +{ + zend_check_stack_limit("Try splitting expression"); +} + static void init_op(zend_op *op) { MAKE_NOP(op); @@ -10617,7 +10624,7 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ static void zend_compile_expr(znode *result, zend_ast *ast) { - zend_check_stack_limit(); + zend_check_stack_limit_expression(); uint32_t checkpoint = zend_short_circuiting_checkpoint(); zend_compile_expr_inner(result, ast); @@ -10715,7 +10722,7 @@ static void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ return; } - zend_check_stack_limit(); + zend_check_stack_limit_expression(); switch (ast->kind) { case ZEND_AST_BINARY_OP: diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 724b5b8c8a57c..e0ad6dabd9724 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -800,6 +800,8 @@ ZEND_API size_t zend_get_scanned_file_offset(void); ZEND_API zend_string *zend_get_compiled_variable_name(const zend_op_array *op_array, uint32_t var); +void zend_check_stack_limit(const char *suggestion); + #ifdef ZTS const char *zend_get_zendtext(void); int zend_get_zendleng(void); diff --git a/ext/opcache/tests/jit/gh14361.phpt b/ext/opcache/tests/jit/gh14361.phpt new file mode 100644 index 0000000000000..23263e53d5479 --- /dev/null +++ b/ext/opcache/tests/jit/gh14361.phpt @@ -0,0 +1,33 @@ +--TEST-- +GH-14361 (Deep recursion in zend_cfg.c causes segfault instead of error) +--SKIPIF-- + +--EXTENSIONS-- +zend_test +opcache +--INI-- +zend.max_allowed_stack_size=64K +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=tracing +opcache.optimization_level=0x000000a0 +--FILE-- + +--CLEAN-- + +--EXPECTF-- +Fatal error: Maximum call stack size of 32768 bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached during compilation. Try reducing function size in %s on line %d +%A From e1c2d41a783d82aad60785b6e01e84337e13998d Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 2 Jun 2024 11:48:11 +0200 Subject: [PATCH 2/2] Try to make test more reliable --- ext/opcache/tests/jit/gh14361.phpt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ext/opcache/tests/jit/gh14361.phpt b/ext/opcache/tests/jit/gh14361.phpt index 23263e53d5479..260bc58ceb4f2 100644 --- a/ext/opcache/tests/jit/gh14361.phpt +++ b/ext/opcache/tests/jit/gh14361.phpt @@ -3,21 +3,24 @@ GH-14361 (Deep recursion in zend_cfg.c causes segfault instead of error) --SKIPIF-- --EXTENSIONS-- zend_test opcache --INI-- -zend.max_allowed_stack_size=64K +error_reporting=E_COMPILE_ERROR +zend.max_allowed_stack_size=128K opcache.enable=1 opcache.enable_cli=1 opcache.jit=tracing +opcache.jit_buffer_size=64M opcache.optimization_level=0x000000a0 --FILE-- --EXPECTF-- -Fatal error: Maximum call stack size of 32768 bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached during compilation. Try reducing function size in %s on line %d -%A +Fatal error: Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached during compilation. Try reducing function size in %s on line %d