From 3056f4c8e60b90817c05e206e0894181eeb7da57 Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Thu, 23 Nov 2023 16:34:46 +0100 Subject: [PATCH 1/7] fix segfault in `ZEND_BIND_STATIC` In case a `ZEND_BIND_STATIC` is being executed, while the current chunk is full, the `zend_array_dup()` call will trigger a OOM in ZendMM which will crash, as the opline might be a dangling pointer. --- Zend/zend_vm_def.h | 3 ++- Zend/zend_vm_execute.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index fd5b7242ba645..e523424e4aafb 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -8788,6 +8788,8 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, UNUSED, REF) variable_ptr = GET_OP1_ZVAL_PTR_PTR_UNDEF(BP_VAR_W); + SAVE_OPLINE(); + ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr); if (!ht) { ht = zend_array_dup(EX(func)->op_array.static_variables); @@ -8797,7 +8799,6 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, UNUSED, REF) value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))); - SAVE_OPLINE(); if (opline->extended_value & ZEND_BIND_REF) { if (Z_TYPE_P(value) == IS_CONSTANT_AST) { if (UNEXPECTED(zval_update_constant_ex(value, EX(func)->op_array.scope) != SUCCESS)) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index bc098148541fb..69950a2e0a07b 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -48471,6 +48471,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_STATIC_SPEC_CV_UNUSED_HAN variable_ptr = EX_VAR(opline->op1.var); + SAVE_OPLINE(); + ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr); if (!ht) { ht = zend_array_dup(EX(func)->op_array.static_variables); @@ -48480,7 +48482,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_STATIC_SPEC_CV_UNUSED_HAN value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))); - SAVE_OPLINE(); if (opline->extended_value & ZEND_BIND_REF) { if (Z_TYPE_P(value) == IS_CONSTANT_AST) { if (UNEXPECTED(zval_update_constant_ex(value, EX(func)->op_array.scope) != SUCCESS)) { From 0fcb4e40286281cf1cf8af1447f16a3b5118f002 Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Thu, 23 Nov 2023 18:39:10 +0100 Subject: [PATCH 2/7] add missing test --- ext/zend_test/test.c | 62 ++++++++++++++++++++++++ ext/zend_test/test.stub.php | 4 ++ ext/zend_test/test_arginfo.h | 10 +++- ext/zend_test/tests/opline_dangling.phpt | 35 +++++++++++++ 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 ext/zend_test/tests/opline_dangling.phpt diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 2f3ff6d9009e6..71207ececf734 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -30,6 +30,7 @@ #include "zend_interfaces.h" #include "zend_weakrefs.h" #include "Zend/Optimizer/zend_optimizer.h" +#include "Zend/zend_alloc.h" #include "test_arginfo.h" #if defined(HAVE_LIBXML) && !defined(PHP_WIN32) @@ -364,6 +365,67 @@ static ZEND_FUNCTION(zend_test_crash) php_printf("%s", invalid); } +zend_mm_heap* zend_test_heap; +zend_mm_heap* zend_orig_heap; +volatile uint32_t lineno = 0; + +static bool has_opline(zend_execute_data *execute_data) +{ + return execute_data + && execute_data->func + && ZEND_USER_CODE(execute_data->func->type) + && execute_data->opline + ; +} + +#pragma GCC push_options +#pragma GCC optimize("O0") +void * __attribute__((optnone)) zend_test_custom_malloc(size_t len) +{ + if (has_opline(EG(current_execute_data))) { + lineno = EG(current_execute_data)->opline->lineno; + } + return _zend_mm_alloc(zend_orig_heap, len); +} + +void __attribute__((optnone)) zend_test_custom_free(void *ptr) +{ + if (has_opline(EG(current_execute_data))) { + lineno = EG(current_execute_data)->opline->lineno; + } + _zend_mm_free(zend_orig_heap, ptr); +} + +void * __attribute__((optnone)) zend_test_custom_realloc(void * ptr, size_t len) +{ + if (has_opline(EG(current_execute_data))) { + lineno = EG(current_execute_data)->opline->lineno; + } + return _zend_mm_realloc(zend_orig_heap, ptr, len); +} +#pragma GCC pop_options + +static ZEND_FUNCTION(zend_test_observe_opline_in_zendmm) +{ + zend_test_heap = malloc(4096); + memset(zend_test_heap, 0, 4096); + zend_mm_set_custom_handlers( + zend_test_heap, + zend_test_custom_malloc, + zend_test_custom_free, + zend_test_custom_realloc + ); + zend_orig_heap = zend_mm_get_heap(); + zend_mm_set_heap(zend_test_heap); +} + +static ZEND_FUNCTION(zend_test_unobserve_opline_in_zendmm) +{ + free(zend_test_heap); + zend_test_heap = NULL; + zend_mm_set_heap(zend_orig_heap); +} + static ZEND_FUNCTION(zend_test_is_pcre_bundled) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 07d7fa7e19927..13ac8c28845fd 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -122,6 +122,10 @@ function zend_get_map_ptr_last(): int {} function zend_test_crash(?string $message = null): void {} + function zend_test_observe_opline_in_zendmm(): void {} + + function zend_test_unobserve_opline_in_zendmm(): void {} + #if defined(HAVE_LIBXML) && !defined(PHP_WIN32) function zend_test_override_libxml_global_state(): void {} #endif diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 6e3f645e3d143..967867df1ed98 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: ae75eda2b4b40224858d680c3fcf3d7cd2056bb6 */ + * Stub hash: a57c9d1fc85dbea853f4cc910b9495a7a0c72eb3 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -86,6 +86,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_crash, 0, 0, IS_VOID, ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, message, IS_STRING, 1, "null") ZEND_END_ARG_INFO() +#define arginfo_zend_test_observe_opline_in_zendmm arginfo_zend_test_void_return + +#define arginfo_zend_test_unobserve_opline_in_zendmm arginfo_zend_test_void_return + #if defined(HAVE_LIBXML) && !defined(PHP_WIN32) ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_override_libxml_global_state, 0, 0, IS_VOID, 0) ZEND_END_ARG_INFO() @@ -155,6 +159,8 @@ static ZEND_FUNCTION(zend_get_current_func_name); static ZEND_FUNCTION(zend_call_method); static ZEND_FUNCTION(zend_get_map_ptr_last); static ZEND_FUNCTION(zend_test_crash); +static ZEND_FUNCTION(zend_test_observe_opline_in_zendmm); +static ZEND_FUNCTION(zend_test_unobserve_opline_in_zendmm); #if defined(HAVE_LIBXML) && !defined(PHP_WIN32) static ZEND_FUNCTION(zend_test_override_libxml_global_state); #endif @@ -199,6 +205,8 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_call_method, arginfo_zend_call_method) ZEND_FE(zend_get_map_ptr_last, arginfo_zend_get_map_ptr_last) ZEND_FE(zend_test_crash, arginfo_zend_test_crash) + ZEND_FE(zend_test_observe_opline_in_zendmm, arginfo_zend_test_observe_opline_in_zendmm) + ZEND_FE(zend_test_unobserve_opline_in_zendmm, arginfo_zend_test_unobserve_opline_in_zendmm) #if defined(HAVE_LIBXML) && !defined(PHP_WIN32) ZEND_FE(zend_test_override_libxml_global_state, arginfo_zend_test_override_libxml_global_state) #endif diff --git a/ext/zend_test/tests/opline_dangling.phpt b/ext/zend_test/tests/opline_dangling.phpt new file mode 100644 index 0000000000000..48a382144bdca --- /dev/null +++ b/ext/zend_test/tests/opline_dangling.phpt @@ -0,0 +1,35 @@ +--TEST-- +possible segfault in `ZEND_BIND_STATIC` +--DESCRIPTION-- +https://github.com/php/php-src/pull/12758 +--EXTENSIONS-- +zend_test +--FILE-- + +--EXPECT-- +int(1) +string(1) "x" +int(1) +int(5) +Done. From 0e40a225ae15f545af600508d2012c416cb0244b Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Fri, 24 Nov 2023 07:31:40 +0100 Subject: [PATCH 3/7] `assert()`ing seems easier than trying to make the compiler to not optimize --- ext/zend_test/test.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 71207ececf734..a95d45fdf90e1 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -14,6 +14,7 @@ +----------------------------------------------------------------------+ */ +#include #ifdef HAVE_CONFIG_H # include "config.h" #endif @@ -367,7 +368,6 @@ static ZEND_FUNCTION(zend_test_crash) zend_mm_heap* zend_test_heap; zend_mm_heap* zend_orig_heap; -volatile uint32_t lineno = 0; static bool has_opline(zend_execute_data *execute_data) { @@ -378,32 +378,29 @@ static bool has_opline(zend_execute_data *execute_data) ; } -#pragma GCC push_options -#pragma GCC optimize("O0") -void * __attribute__((optnone)) zend_test_custom_malloc(size_t len) +void * zend_test_custom_malloc(size_t len) { if (has_opline(EG(current_execute_data))) { - lineno = EG(current_execute_data)->opline->lineno; + assert(EG(current_execute_data)->opline->lineno != (uint32_t)-1); } return _zend_mm_alloc(zend_orig_heap, len); } -void __attribute__((optnone)) zend_test_custom_free(void *ptr) +void zend_test_custom_free(void *ptr) { if (has_opline(EG(current_execute_data))) { - lineno = EG(current_execute_data)->opline->lineno; + assert(EG(current_execute_data)->opline->lineno != (uint32_t)-1); } _zend_mm_free(zend_orig_heap, ptr); } -void * __attribute__((optnone)) zend_test_custom_realloc(void * ptr, size_t len) +void * zend_test_custom_realloc(void * ptr, size_t len) { if (has_opline(EG(current_execute_data))) { - lineno = EG(current_execute_data)->opline->lineno; + assert(EG(current_execute_data)->opline->lineno != (uint32_t)-1); } return _zend_mm_realloc(zend_orig_heap, ptr, len); } -#pragma GCC pop_options static ZEND_FUNCTION(zend_test_observe_opline_in_zendmm) { From 2449f9e603c82769802406bfd8e2a073a307cf5e Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Fri, 24 Nov 2023 07:44:30 +0100 Subject: [PATCH 4/7] moved from function call to INI setting, so we can use this in other places as well --- ext/zend_test/php_test.h | 3 ++ ext/zend_test/test.c | 58 ++++++++++++++---------- ext/zend_test/test.stub.php | 4 -- ext/zend_test/test_arginfo.h | 10 +--- ext/zend_test/tests/opline_dangling.phpt | 6 +-- 5 files changed, 40 insertions(+), 41 deletions(-) diff --git a/ext/zend_test/php_test.h b/ext/zend_test/php_test.h index 05f658e91bdb6..9d0ca087ce6f7 100644 --- a/ext/zend_test/php_test.h +++ b/ext/zend_test/php_test.h @@ -52,6 +52,9 @@ ZEND_BEGIN_MODULE_GLOBALS(zend_test) HashTable global_weakmap; int replace_zend_execute_ex; int register_passes; + int observe_opline_in_zendmm; + zend_mm_heap* zend_orig_heap; + zend_mm_heap* zend_test_heap; zend_test_fiber *active_fiber; ZEND_END_MODULE_GLOBALS(zend_test) diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index a95d45fdf90e1..d9fda62cf12a1 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -14,7 +14,6 @@ +----------------------------------------------------------------------+ */ -#include #ifdef HAVE_CONFIG_H # include "config.h" #endif @@ -366,9 +365,6 @@ static ZEND_FUNCTION(zend_test_crash) php_printf("%s", invalid); } -zend_mm_heap* zend_test_heap; -zend_mm_heap* zend_orig_heap; - static bool has_opline(zend_execute_data *execute_data) { return execute_data @@ -383,7 +379,7 @@ void * zend_test_custom_malloc(size_t len) if (has_opline(EG(current_execute_data))) { assert(EG(current_execute_data)->opline->lineno != (uint32_t)-1); } - return _zend_mm_alloc(zend_orig_heap, len); + return _zend_mm_alloc(ZT_G(zend_orig_heap), len ZEND_FILE_LINE_EMPTY_CC ZEND_FILE_LINE_EMPTY_CC); } void zend_test_custom_free(void *ptr) @@ -391,7 +387,7 @@ void zend_test_custom_free(void *ptr) if (has_opline(EG(current_execute_data))) { assert(EG(current_execute_data)->opline->lineno != (uint32_t)-1); } - _zend_mm_free(zend_orig_heap, ptr); + _zend_mm_free(ZT_G(zend_orig_heap), ptr ZEND_FILE_LINE_EMPTY_CC ZEND_FILE_LINE_EMPTY_CC); } void * zend_test_custom_realloc(void * ptr, size_t len) @@ -399,28 +395,34 @@ void * zend_test_custom_realloc(void * ptr, size_t len) if (has_opline(EG(current_execute_data))) { assert(EG(current_execute_data)->opline->lineno != (uint32_t)-1); } - return _zend_mm_realloc(zend_orig_heap, ptr, len); + return _zend_mm_realloc(ZT_G(zend_orig_heap), ptr, len ZEND_FILE_LINE_EMPTY_CC ZEND_FILE_LINE_EMPTY_CC); } -static ZEND_FUNCTION(zend_test_observe_opline_in_zendmm) +static PHP_INI_MH(OnUpdateZendTestObserveOplineInZendMM) { - zend_test_heap = malloc(4096); - memset(zend_test_heap, 0, 4096); - zend_mm_set_custom_handlers( - zend_test_heap, - zend_test_custom_malloc, - zend_test_custom_free, - zend_test_custom_realloc - ); - zend_orig_heap = zend_mm_get_heap(); - zend_mm_set_heap(zend_test_heap); -} + if (new_value == NULL) { + return FAILURE; + } -static ZEND_FUNCTION(zend_test_unobserve_opline_in_zendmm) -{ - free(zend_test_heap); - zend_test_heap = NULL; - zend_mm_set_heap(zend_orig_heap); + int int_value = zend_ini_parse_bool(new_value); + + if (int_value == 1) { + ZT_G(zend_test_heap) = malloc(4096); + memset(ZT_G(zend_test_heap), 0, 4096); + zend_mm_set_custom_handlers( + ZT_G(zend_test_heap), + zend_test_custom_malloc, + zend_test_custom_free, + zend_test_custom_realloc + ); + ZT_G(zend_orig_heap) = zend_mm_get_heap(); + zend_mm_set_heap(ZT_G(zend_test_heap)); + } else if (ZT_G(zend_test_heap)) { + free(ZT_G(zend_test_heap)); + ZT_G(zend_test_heap) = NULL; + zend_mm_set_heap(ZT_G(zend_orig_heap)); + } + return OnUpdateBool(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); } static ZEND_FUNCTION(zend_test_is_pcre_bundled) @@ -617,6 +619,7 @@ static ZEND_METHOD(ZendTestChildClassWithMethodWithParameterAttribute, override) PHP_INI_BEGIN() STD_PHP_INI_BOOLEAN("zend_test.replace_zend_execute_ex", "0", PHP_INI_SYSTEM, OnUpdateBool, replace_zend_execute_ex, zend_zend_test_globals, zend_test_globals) STD_PHP_INI_BOOLEAN("zend_test.register_passes", "0", PHP_INI_SYSTEM, OnUpdateBool, register_passes, zend_zend_test_globals, zend_test_globals) + STD_PHP_INI_BOOLEAN("zend_test.observe_opline_in_zendmm", "0", PHP_INI_ALL, OnUpdateZendTestObserveOplineInZendMM, observe_opline_in_zendmm, zend_zend_test_globals, zend_test_globals) PHP_INI_END() void (*old_zend_execute_ex)(zend_execute_data *execute_data); @@ -759,6 +762,13 @@ PHP_RSHUTDOWN_FUNCTION(zend_test) zend_weakrefs_hash_del(&ZT_G(global_weakmap), (zend_object *)(uintptr_t)objptr); } ZEND_HASH_FOREACH_END(); zend_hash_destroy(&ZT_G(global_weakmap)); + + if (ZT_G(zend_test_heap)) { + free(ZT_G(zend_test_heap)); + ZT_G(zend_test_heap) = NULL; + zend_mm_set_heap(ZT_G(zend_orig_heap)); + } + return SUCCESS; } diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 13ac8c28845fd..07d7fa7e19927 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -122,10 +122,6 @@ function zend_get_map_ptr_last(): int {} function zend_test_crash(?string $message = null): void {} - function zend_test_observe_opline_in_zendmm(): void {} - - function zend_test_unobserve_opline_in_zendmm(): void {} - #if defined(HAVE_LIBXML) && !defined(PHP_WIN32) function zend_test_override_libxml_global_state(): void {} #endif diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 967867df1ed98..6e3f645e3d143 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a57c9d1fc85dbea853f4cc910b9495a7a0c72eb3 */ + * Stub hash: ae75eda2b4b40224858d680c3fcf3d7cd2056bb6 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -86,10 +86,6 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_crash, 0, 0, IS_VOID, ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, message, IS_STRING, 1, "null") ZEND_END_ARG_INFO() -#define arginfo_zend_test_observe_opline_in_zendmm arginfo_zend_test_void_return - -#define arginfo_zend_test_unobserve_opline_in_zendmm arginfo_zend_test_void_return - #if defined(HAVE_LIBXML) && !defined(PHP_WIN32) ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_override_libxml_global_state, 0, 0, IS_VOID, 0) ZEND_END_ARG_INFO() @@ -159,8 +155,6 @@ static ZEND_FUNCTION(zend_get_current_func_name); static ZEND_FUNCTION(zend_call_method); static ZEND_FUNCTION(zend_get_map_ptr_last); static ZEND_FUNCTION(zend_test_crash); -static ZEND_FUNCTION(zend_test_observe_opline_in_zendmm); -static ZEND_FUNCTION(zend_test_unobserve_opline_in_zendmm); #if defined(HAVE_LIBXML) && !defined(PHP_WIN32) static ZEND_FUNCTION(zend_test_override_libxml_global_state); #endif @@ -205,8 +199,6 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_call_method, arginfo_zend_call_method) ZEND_FE(zend_get_map_ptr_last, arginfo_zend_get_map_ptr_last) ZEND_FE(zend_test_crash, arginfo_zend_test_crash) - ZEND_FE(zend_test_observe_opline_in_zendmm, arginfo_zend_test_observe_opline_in_zendmm) - ZEND_FE(zend_test_unobserve_opline_in_zendmm, arginfo_zend_test_unobserve_opline_in_zendmm) #if defined(HAVE_LIBXML) && !defined(PHP_WIN32) ZEND_FE(zend_test_override_libxml_global_state, arginfo_zend_test_override_libxml_global_state) #endif diff --git a/ext/zend_test/tests/opline_dangling.phpt b/ext/zend_test/tests/opline_dangling.phpt index 48a382144bdca..b411223cecf76 100644 --- a/ext/zend_test/tests/opline_dangling.phpt +++ b/ext/zend_test/tests/opline_dangling.phpt @@ -4,6 +4,8 @@ possible segfault in `ZEND_BIND_STATIC` https://github.com/php/php-src/pull/12758 --EXTENSIONS-- zend_test +--INI-- +zend_test.observe_opline_in_zendmm=1 --FILE-- --EXPECT-- From c559eae750d6c4be556c048595e4f84fa950e3ae Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Fri, 24 Nov 2023 12:15:52 +0100 Subject: [PATCH 5/7] make `assert()` work no NDEBUG builds --- ext/zend_test/test.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index d9fda62cf12a1..17d1b5da84517 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -33,6 +33,12 @@ #include "Zend/zend_alloc.h" #include "test_arginfo.h" +// `php.h` sets `NDEBUG` when not `PHP_DEBUG` which will make `assert()` from +// assert.h a no-op. In order to have `assert()` working on NDEBUG builds, we +// undefine `NDEBUG` and re-include assert.h +#undef NDEBUG +#include "assert.h" + #if defined(HAVE_LIBXML) && !defined(PHP_WIN32) # include # include From 516ea046e801d10f907567e7e14736ace8156e41 Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Fri, 24 Nov 2023 12:39:10 +0100 Subject: [PATCH 6/7] document magic number --- ext/zend_test/test.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 17d1b5da84517..c2a6bd18a5433 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -413,6 +413,8 @@ static PHP_INI_MH(OnUpdateZendTestObserveOplineInZendMM) int int_value = zend_ini_parse_bool(new_value); if (int_value == 1) { + // `zend_mm_heap` is a private struct, so we have not way to find the + // actual size, but 4096 bytes should be enough ZT_G(zend_test_heap) = malloc(4096); memset(ZT_G(zend_test_heap), 0, 4096); zend_mm_set_custom_handlers( From 8a73bb58ebfc190d78dc60df72e7a9ceb739b87a Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Fri, 24 Nov 2023 14:46:47 +0100 Subject: [PATCH 7/7] fix segfault in `ZEND_FUNC_GET_ARGS` In case a `ZEND_FUNC_GET_ARGS` is being executed, while the current chunk is full, the `zend_new_array()` call will trigger a OOM in ZendMM which will crash, as the opline might be a dangling pointer. --- Zend/zend_vm_def.h | 1 + Zend/zend_vm_execute.h | 2 ++ ext/zend_test/tests/opline_dangling_02.phpt | 36 +++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 ext/zend_test/tests/opline_dangling_02.phpt diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index e523424e4aafb..a6f86a9eab6f7 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9251,6 +9251,7 @@ ZEND_VM_HANDLER(172, ZEND_FUNC_GET_ARGS, UNUSED|CONST, UNUSED) } if (result_size) { + SAVE_OPLINE(); uint32_t first_extra_arg = EX(func)->op_array.num_args; ht = zend_new_array(result_size); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 69950a2e0a07b..5ee1c2c94daf5 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -10700,6 +10700,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSE } if (result_size) { + SAVE_OPLINE(); uint32_t first_extra_arg = EX(func)->op_array.num_args; ht = zend_new_array(result_size); @@ -36064,6 +36065,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUS } if (result_size) { + SAVE_OPLINE(); uint32_t first_extra_arg = EX(func)->op_array.num_args; ht = zend_new_array(result_size); diff --git a/ext/zend_test/tests/opline_dangling_02.phpt b/ext/zend_test/tests/opline_dangling_02.phpt new file mode 100644 index 0000000000000..1537bc68c181a --- /dev/null +++ b/ext/zend_test/tests/opline_dangling_02.phpt @@ -0,0 +1,36 @@ +--TEST-- +possible segfault in `ZEND_FUNC_GET_ARGS` +--DESCRIPTION-- +--EXTENSIONS-- +zend_test +--INI-- +zend_test.observe_opline_in_zendmm=1 +--FILE-- + +--EXPECT-- +int(1) +string(1) "x" +int(1) +array(2) { + [0]=> + string(6) "string" + [1]=> + int(0) +} +Done.