diff --git a/NEWS b/NEWS index 052f33c44fae5..a3e4f956f5975 100644 --- a/NEWS +++ b/NEWS @@ -55,6 +55,8 @@ PHP NEWS . Fixed bug GH-17577 (JIT packed type guard crash). (nielsdos, Dmitry) . Fixed bug GH-17747 (Exception on reading property in register-based FETCH_OBJ_R breaks JIT). (Dmitry, nielsdos) + . Fixed bug GH-17715 (Null pointer deref in observer API when calling + cases() method on preloaded enum). (Bob) - PDO_SQLite: . Fixed GH-17837 ()::getColumnMeta() on unexecuted statement segfaults). @@ -76,7 +78,7 @@ PHP NEWS . Fix memory leak on overflow in _php_stream_scandir(). (nielsdos) - Windows: - . Fixed phpize for Windows 11 (24H2). (bwoebi) + . Fixed phpize for Windows 11 (24H2). (Bob) . Fixed GH-17855 (CURL_STATICLIB flag set even if linked with shared lib). (cmb) diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index 8259441fd4228..ccafca48fe9b8 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -418,7 +418,10 @@ static void zend_enum_register_func(zend_class_entry *ce, zend_known_string_id n zif->module = EG(current_module); zif->scope = ce; zif->T = ZEND_OBSERVER_ENABLED; - if (EG(active)) { // at run-time + if (EG(active)) { // at run-time + if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) { + zif->fn_flags |= ZEND_ACC_PRELOADED; + } ZEND_MAP_PTR_INIT(zif->run_time_cache, zend_arena_calloc(&CG(arena), 1, zend_internal_run_time_cache_reserved_size())); } else { #ifdef ZTS diff --git a/Zend/zend_map_ptr.h b/Zend/zend_map_ptr.h index ebcda89411d0e..4dfa0e5043ef4 100644 --- a/Zend/zend_map_ptr.h +++ b/Zend/zend_map_ptr.h @@ -70,6 +70,9 @@ typedef struct _zend_string zend_string; } while (0) # define ZEND_MAP_PTR_BIASED_BASE(real_base) \ ((void*)(((uintptr_t)(real_base)) + zend_map_ptr_static_size * sizeof(void *) - 1)) +/* Note: chunked like: [8192..12287][4096..8191][0..4095] */ +#define ZEND_MAP_PTR_STATIC_NUM_TO_PTR(num) \ + ((void **)CG(map_ptr_real_base) + zend_map_ptr_static_size - ZEND_MM_ALIGNED_SIZE_EX((num) + 1, 4096) + ((num) & 4095)) #else # error "Unknown ZEND_MAP_PTR_KIND" #endif diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 364440639b604..6e53b987d6761 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2614,6 +2614,7 @@ static void zend_reset_cache_vars(void) ZCSG(restart_pending) = false; ZCSG(force_restart_time) = 0; ZCSG(map_ptr_last) = CG(map_ptr_last); + ZCSG(map_ptr_static_last) = zend_map_ptr_static_last; } static void accel_reset_pcre_cache(void) @@ -2629,7 +2630,7 @@ static void accel_reset_pcre_cache(void) } ZEND_HASH_FOREACH_END(); } -zend_result accel_activate(INIT_FUNC_ARGS) +ZEND_RINIT_FUNCTION(zend_accelerator) { if (!ZCG(enabled) || !accel_startup_ok) { ZCG(accelerator_enabled) = false; @@ -2961,12 +2962,15 @@ static void accel_globals_ctor(zend_accel_globals *accel_globals) GC_MAKE_PERSISTENT_LOCAL(accel_globals->key); } -#ifdef ZTS static void accel_globals_dtor(zend_accel_globals *accel_globals) { +#ifdef ZTS zend_string_free(accel_globals->key); -} #endif + if (accel_globals->preloaded_internal_run_time_cache) { + pefree(accel_globals->preloaded_internal_run_time_cache, 1); + } +} #ifdef HAVE_HUGE_CODE_PAGES # ifndef _WIN32 @@ -3407,6 +3411,8 @@ void accel_shutdown(void) if (!ZCG(enabled) || !accel_startup_ok) { #ifdef ZTS ts_free_id(accel_globals_id); +#else + accel_globals_dtor(&accel_globals); #endif return; } @@ -3421,6 +3427,8 @@ void accel_shutdown(void) #ifdef ZTS ts_free_id(accel_globals_id); +#else + accel_globals_dtor(&accel_globals); #endif if (!_file_cache_only) { @@ -4318,7 +4326,7 @@ static zend_persistent_script* preload_script_in_shared_memory(zend_persistent_s return new_persistent_script; } -static void preload_load(void) +static void preload_load(size_t orig_map_ptr_static_last) { /* Load into process tables */ zend_script *script = &ZCSG(preload_script)->script; @@ -4353,14 +4361,42 @@ static void preload_load(void) if (EG(class_table)) { EG(persistent_classes_count) = EG(class_table)->nNumUsed; } - if (CG(map_ptr_last) != ZCSG(map_ptr_last)) { - size_t old_map_ptr_last = CG(map_ptr_last); + + size_t old_map_ptr_last = CG(map_ptr_last); + if (zend_map_ptr_static_last != ZCSG(map_ptr_static_last) || old_map_ptr_last != ZCSG(map_ptr_last)) { CG(map_ptr_last) = ZCSG(map_ptr_last); - CG(map_ptr_size) = ZEND_MM_ALIGNED_SIZE_EX(CG(map_ptr_last) + 1, 4096); - CG(map_ptr_real_base) = perealloc(CG(map_ptr_real_base), CG(map_ptr_size) * sizeof(void*), 1); + CG(map_ptr_size) = ZEND_MM_ALIGNED_SIZE_EX(ZCSG(map_ptr_last) + 1, 4096); + zend_map_ptr_static_last = ZCSG(map_ptr_static_last); + + /* Grow map_ptr table as needed, but allocate once for static + regular map_ptrs */ + size_t new_static_size = ZEND_MM_ALIGNED_SIZE_EX(zend_map_ptr_static_last, 4096); + if (zend_map_ptr_static_size != new_static_size) { + void *new_base = pemalloc((new_static_size + CG(map_ptr_size)) * sizeof(void *), 1); + if (CG(map_ptr_real_base)) { + memcpy((void **) new_base + new_static_size - zend_map_ptr_static_size, CG(map_ptr_real_base), (old_map_ptr_last + zend_map_ptr_static_size) * sizeof(void *)); + pefree(CG(map_ptr_real_base), 1); + } + CG(map_ptr_real_base) = new_base; + zend_map_ptr_static_size = new_static_size; + } else { + CG(map_ptr_real_base) = perealloc(CG(map_ptr_real_base), (zend_map_ptr_static_size + CG(map_ptr_size)) * sizeof(void *), 1); + } + + memset((void **) CG(map_ptr_real_base) + zend_map_ptr_static_size + old_map_ptr_last, 0, (CG(map_ptr_last) - old_map_ptr_last) * sizeof(void *)); CG(map_ptr_base) = ZEND_MAP_PTR_BIASED_BASE(CG(map_ptr_real_base)); - memset((void **) CG(map_ptr_real_base) + old_map_ptr_last, 0, - (CG(map_ptr_last) - old_map_ptr_last) * sizeof(void *)); + } + + if (orig_map_ptr_static_last != zend_map_ptr_static_last) { + /* preloaded static entries currently are all runtime cache pointers, just assign them as such */ + size_t runtime_cache_size = zend_internal_run_time_cache_reserved_size(); + ZCG(preloaded_internal_run_time_cache_size) = (zend_map_ptr_static_last - orig_map_ptr_static_last) * runtime_cache_size; + char *cache = pemalloc(ZCG(preloaded_internal_run_time_cache_size), 1); + ZCG(preloaded_internal_run_time_cache) = cache; + + for (size_t cur_static_map_ptr = orig_map_ptr_static_last; cur_static_map_ptr < zend_map_ptr_static_last; ++cur_static_map_ptr) { + *ZEND_MAP_PTR_STATIC_NUM_TO_PTR(cur_static_map_ptr) = cache; + cache += runtime_cache_size; + } } } @@ -4369,7 +4405,7 @@ static zend_result accel_preload(const char *config, bool in_child) zend_file_handle file_handle; zend_result ret; char *orig_open_basedir; - size_t orig_map_ptr_last; + size_t orig_map_ptr_last, orig_map_ptr_static_last; uint32_t orig_compiler_options; ZCG(enabled) = false; @@ -4380,6 +4416,7 @@ static zend_result accel_preload(const char *config, bool in_child) accelerator_orig_compile_file = preload_compile_file; orig_map_ptr_last = CG(map_ptr_last); + orig_map_ptr_static_last = zend_map_ptr_static_last; /* Compile and execute preloading script */ zend_stream_init_filename(&file_handle, (char *) config); @@ -4559,7 +4596,7 @@ static zend_result accel_preload(const char *config, bool in_child) SHM_PROTECT(); HANDLE_UNBLOCK_INTERRUPTIONS(); - preload_load(); + preload_load(orig_map_ptr_static_last); /* Store individual scripts with unlinked classes */ HANDLE_BLOCK_INTERRUPTIONS(); @@ -4811,7 +4848,7 @@ static zend_result accel_finish_startup(void) if (ZCSG(preload_script)) { /* Preloading was done in another process */ - preload_load(); + preload_load(zend_map_ptr_static_last); zend_shared_alloc_unlock(); return SUCCESS; } @@ -4839,7 +4876,7 @@ static zend_result accel_finish_startup(void) } if (ZCSG(preload_script)) { - preload_load(); + preload_load(zend_map_ptr_static_last); } zend_shared_alloc_unlock(); @@ -4853,6 +4890,12 @@ static zend_result accel_finish_startup(void) #endif /* ZEND_WIN32 */ } +static void accel_activate(void) { + if (ZCG(preloaded_internal_run_time_cache)) { + memset(ZCG(preloaded_internal_run_time_cache), 0, ZCG(preloaded_internal_run_time_cache_size)); + } +} + ZEND_EXT_API zend_extension zend_extension_entry = { ACCELERATOR_PRODUCT_NAME, /* name */ PHP_VERSION, /* version */ @@ -4861,7 +4904,7 @@ ZEND_EXT_API zend_extension zend_extension_entry = { "Copyright (c)", /* copyright */ accel_startup, /* startup */ NULL, /* shutdown */ - NULL, /* per-script activation */ + accel_activate, /* per-script activation */ #ifdef HAVE_JIT accel_deactivate, /* per-script deactivation */ #else diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index 54d55e10e4f5c..486074ef0012b 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -50,6 +50,7 @@ #include "zend_extensions.h" #include "zend_compile.h" +#include "zend_API.h" #include "Optimizer/zend_optimizer.h" #include "zend_accelerator_hash.h" @@ -216,6 +217,8 @@ typedef struct _zend_accel_globals { #ifndef ZEND_WIN32 zend_ulong root_hash; #endif + void *preloaded_internal_run_time_cache; + size_t preloaded_internal_run_time_cache_size; /* preallocated shared-memory block to save current script */ void *mem; zend_persistent_script *current_persistent_script; @@ -251,6 +254,7 @@ typedef struct _zend_accel_shared_globals { zend_accel_hash hash; /* hash table for cached scripts */ size_t map_ptr_last; + size_t map_ptr_static_last; /* Directives & Maintenance */ time_t start_time; @@ -310,7 +314,7 @@ extern const char *zps_api_failure_reason; BEGIN_EXTERN_C() void accel_shutdown(void); -zend_result accel_activate(INIT_FUNC_ARGS); +ZEND_RINIT_FUNCTION(zend_accelerator); zend_result accel_post_deactivate(void); void zend_accel_schedule_restart(zend_accel_restart_reason reason); void zend_accel_schedule_restart_if_necessary(zend_accel_restart_reason reason); diff --git a/ext/opcache/tests/preload_enum_observed.phpt b/ext/opcache/tests/preload_enum_observed.phpt new file mode 100644 index 0000000000000..7898fa084a5ff --- /dev/null +++ b/ext/opcache/tests/preload_enum_observed.phpt @@ -0,0 +1,55 @@ +--TEST-- +Enum preloading with observers +--EXTENSIONS-- +opcache +zend_test +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.preload={PWD}/preload_enum.inc +zend_test.observer.enabled=1 +zend_test.observer.show_output=1 +zend_test.observer.observe_all=1 +--SKIPIF-- + +--FILE-- + +--EXPECTF-- + + + + +enum(MyEnum::Bar) + + + + + + + + + + + + +array(2) { + [0]=> + enum(MyEnum::Foo) + [1]=> + enum(MyEnum::Bar) +} + + diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index a59502eb0d5fc..ffa09aaf9e679 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -572,7 +572,7 @@ static zend_module_entry accel_module_entry = { ext_functions, ZEND_MINIT(zend_accelerator), ZEND_MSHUTDOWN(zend_accelerator), - accel_activate, + ZEND_RINIT(zend_accelerator), NULL, zend_accel_info, PHP_VERSION, diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index c5ddc040b22d8..1c21e031a1958 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -735,7 +735,11 @@ static zend_op_array *zend_persist_class_method(zend_op_array *op_array, zend_cl // Real dynamically created internal functions like enum methods must have their own run_time_cache pointer. They're always on the same scope as their defining class. // However, copies - as caused by inheritance of internal methods - must retain the original run_time_cache pointer, shared with the source function. if (!op_array->scope || (op_array->scope == ce && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE))) { - ZEND_MAP_PTR_NEW(op_array->run_time_cache); + if (op_array->fn_flags & ZEND_ACC_PRELOADED) { + ZEND_MAP_PTR_NEW_STATIC(op_array->run_time_cache); + } else { + ZEND_MAP_PTR_NEW(op_array->run_time_cache); + } } } } @@ -1413,6 +1417,7 @@ zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script if (for_shm) { ZCSG(map_ptr_last) = CG(map_ptr_last); + ZCSG(map_ptr_static_last) = zend_map_ptr_static_last; } #ifdef HAVE_JIT