diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index fd5b7242ba645..a6f86a9eab6f7 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)) { @@ -9250,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 bc098148541fb..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); @@ -48471,6 +48473,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 +48484,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)) { 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 2f3ff6d9009e6..c2a6bd18a5433 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -30,8 +30,15 @@ #include "zend_interfaces.h" #include "zend_weakrefs.h" #include "Zend/Optimizer/zend_optimizer.h" +#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 @@ -364,6 +371,68 @@ static ZEND_FUNCTION(zend_test_crash) php_printf("%s", invalid); } +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 + ; +} + +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(ZT_G(zend_orig_heap), len ZEND_FILE_LINE_EMPTY_CC ZEND_FILE_LINE_EMPTY_CC); +} + +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(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) +{ + if (has_opline(EG(current_execute_data))) { + assert(EG(current_execute_data)->opline->lineno != (uint32_t)-1); + } + return _zend_mm_realloc(ZT_G(zend_orig_heap), ptr, len ZEND_FILE_LINE_EMPTY_CC ZEND_FILE_LINE_EMPTY_CC); +} + +static PHP_INI_MH(OnUpdateZendTestObserveOplineInZendMM) +{ + if (new_value == NULL) { + return FAILURE; + } + + 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( + 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) { ZEND_PARSE_PARAMETERS_NONE(); @@ -558,6 +627,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); @@ -700,6 +770,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/tests/opline_dangling.phpt b/ext/zend_test/tests/opline_dangling.phpt new file mode 100644 index 0000000000000..b411223cecf76 --- /dev/null +++ b/ext/zend_test/tests/opline_dangling.phpt @@ -0,0 +1,33 @@ +--TEST-- +possible segfault in `ZEND_BIND_STATIC` +--DESCRIPTION-- +https://github.com/php/php-src/pull/12758 +--EXTENSIONS-- +zend_test +--INI-- +zend_test.observe_opline_in_zendmm=1 +--FILE-- + +--EXPECT-- +int(1) +string(1) "x" +int(1) +int(5) +Done. 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.