From 424e426643d5f9cd36f74ea7153fcbd01a676d06 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 13 Feb 2024 21:55:24 +0100 Subject: [PATCH 1/3] Workaround ZTS persistent resource crashes (PHP 8.3 and lower) For master (8.4-dev) I merged GH-13381. But that PR changes public API of TSRM, so cannot be used on lower branches. This patch is a safe workaround for the issue, in combination with a pre-existing fix using `ifdef ZTS + if (module_started)` inside pgsql and odbc. The idea is to delay unloading modules until the persistent resources are destroyed. This will keep the destructor code accessible in memory. This is not a proper fix on its own, because we still need the workaround of not accessing globals after module destruction. The proper fix is in master. --- Zend/zend.c | 28 ++++++++++++++++++++++++++++ Zend/zend_API.c | 16 +++++++++++----- Zend/zend_modules.h | 2 +- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index b222467393e94..dc0edd05fea01 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1091,8 +1091,33 @@ zend_result zend_post_startup(void) /* {{{ */ } /* }}} */ +/* Returns a NULL-terminated array of module entries that were dynamically loaded. */ +static zend_module_entry **zend_collect_dl_loaded_module_entries(void) +{ + zend_module_entry **modules = malloc(sizeof(zend_module_entry *) * (zend_hash_num_elements(&module_registry) + 1)); + zend_module_entry *module; + unsigned int index = 0; + ZEND_HASH_MAP_REVERSE_FOREACH_PTR(&module_registry, module) { + if (module->handle) { + modules[index++] = module; + } + } ZEND_HASH_FOREACH_END(); + modules[index] = NULL; + return modules; +} + +static void zend_unload_modules(zend_module_entry **modules) +{ + while (*modules) { + module_registry_unload(*modules); + modules++; + } +} + void zend_shutdown(void) /* {{{ */ { + zend_module_entry **modules = zend_collect_dl_loaded_module_entries(); + zend_vm_dtor(); zend_destroy_rsrc_list(&EG(persistent_list)); @@ -1141,6 +1166,9 @@ void zend_shutdown(void) /* {{{ */ #endif zend_destroy_rsrc_list_dtors(); + zend_unload_modules(modules); + free(modules); + zend_optimizer_shutdown(); startup_done = false; } diff --git a/Zend/zend_API.c b/Zend/zend_API.c index ece58b1dfab53..55cb42dab37f7 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3073,17 +3073,22 @@ void module_destructor(zend_module_entry *module) /* {{{ */ clean_module_functions(module); } +#if ZEND_RC_DEBUG + zend_rc_debug = orig_rc_debug; +#endif +} +/* }}} */ + +void module_registry_unload(const zend_module_entry *module) +{ #if HAVE_LIBDL if (module->handle && !getenv("ZEND_DONT_UNLOAD_MODULES")) { DL_UNLOAD(module->handle); } -#endif - -#if ZEND_RC_DEBUG - zend_rc_debug = orig_rc_debug; +#else + ZEND_IGNORE_VALUE(module); #endif } -/* }}} */ ZEND_API void zend_activate_modules(void) /* {{{ */ { @@ -3147,6 +3152,7 @@ ZEND_API void zend_post_deactivate_modules(void) /* {{{ */ break; } module_destructor(module); + module_registry_unload(module); zend_string_release_ex(key, 0); } ZEND_HASH_MAP_FOREACH_END_DEL(); } else { diff --git a/Zend/zend_modules.h b/Zend/zend_modules.h index 7d80ad4c0608e..43999e5ee4245 100644 --- a/Zend/zend_modules.h +++ b/Zend/zend_modules.h @@ -125,7 +125,7 @@ extern ZEND_API HashTable module_registry; void module_destructor(zend_module_entry *module); int module_registry_request_startup(zend_module_entry *module); -int module_registry_unload_temp(const zend_module_entry *module); +void module_registry_unload(const zend_module_entry *module); END_EXTERN_C() #endif From 611fe964cf88d825137121d4bbb543d4801813cf Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 19 Feb 2024 22:32:04 +0100 Subject: [PATCH 2/3] Move unload code, move collect code into zend_collect_module_handlers --- Zend/zend.c | 28 +--------------------------- Zend/zend.h | 1 + Zend/zend_API.c | 28 ++++++++++++++++++++++++++-- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index dc0edd05fea01..fc092b66b9e2a 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1091,33 +1091,8 @@ zend_result zend_post_startup(void) /* {{{ */ } /* }}} */ -/* Returns a NULL-terminated array of module entries that were dynamically loaded. */ -static zend_module_entry **zend_collect_dl_loaded_module_entries(void) -{ - zend_module_entry **modules = malloc(sizeof(zend_module_entry *) * (zend_hash_num_elements(&module_registry) + 1)); - zend_module_entry *module; - unsigned int index = 0; - ZEND_HASH_MAP_REVERSE_FOREACH_PTR(&module_registry, module) { - if (module->handle) { - modules[index++] = module; - } - } ZEND_HASH_FOREACH_END(); - modules[index] = NULL; - return modules; -} - -static void zend_unload_modules(zend_module_entry **modules) -{ - while (*modules) { - module_registry_unload(*modules); - modules++; - } -} - void zend_shutdown(void) /* {{{ */ { - zend_module_entry **modules = zend_collect_dl_loaded_module_entries(); - zend_vm_dtor(); zend_destroy_rsrc_list(&EG(persistent_list)); @@ -1166,8 +1141,7 @@ void zend_shutdown(void) /* {{{ */ #endif zend_destroy_rsrc_list_dtors(); - zend_unload_modules(modules); - free(modules); + zend_unload_modules(); zend_optimizer_shutdown(); startup_done = false; diff --git a/Zend/zend.h b/Zend/zend.h index 1c3ba9157d067..f57ce27adff9c 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -277,6 +277,7 @@ void zend_shutdown(void); void zend_register_standard_ini_entries(void); zend_result zend_post_startup(void); void zend_set_utility_values(zend_utility_values *utility_values); +void zend_unload_modules(void); ZEND_API ZEND_COLD ZEND_NORETURN void _zend_bailout(const char *filename, uint32_t lineno); ZEND_API size_t zend_get_page_size(void); diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 55cb42dab37f7..ead182dd34042 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -41,6 +41,7 @@ ZEND_API HashTable module_registry; static zend_module_entry **module_request_startup_handlers; static zend_module_entry **module_request_shutdown_handlers; static zend_module_entry **module_post_deactivate_handlers; +static zend_module_entry **modules_dl_loaded; static zend_class_entry **class_cleanup_handlers; @@ -2292,6 +2293,7 @@ ZEND_API void zend_collect_module_handlers(void) /* {{{ */ int startup_count = 0; int shutdown_count = 0; int post_deactivate_count = 0; + int dl_loaded_count = 0; zend_class_entry *ce; int class_count = 0; @@ -2306,6 +2308,9 @@ ZEND_API void zend_collect_module_handlers(void) /* {{{ */ if (module->post_deactivate_func) { post_deactivate_count++; } + if (module->handle) { + dl_loaded_count++; + } } ZEND_HASH_FOREACH_END(); module_request_startup_handlers = (zend_module_entry**)realloc( module_request_startup_handlers, @@ -2318,6 +2323,9 @@ ZEND_API void zend_collect_module_handlers(void) /* {{{ */ module_request_shutdown_handlers[shutdown_count] = NULL; module_post_deactivate_handlers = module_request_shutdown_handlers + shutdown_count + 1; module_post_deactivate_handlers[post_deactivate_count] = NULL; + /* Cannot reuse module_request_startup_handlers because it is freed in zend_destroy_modules, which happens before zend_unload_modules. */ + modules_dl_loaded = realloc(modules_dl_loaded, sizeof(zend_module_entry*) * (dl_loaded_count + 1)); + modules_dl_loaded[dl_loaded_count] = NULL; startup_count = 0; ZEND_HASH_MAP_FOREACH_PTR(&module_registry, module) { @@ -2330,6 +2338,9 @@ ZEND_API void zend_collect_module_handlers(void) /* {{{ */ if (module->post_deactivate_func) { module_post_deactivate_handlers[--post_deactivate_count] = module; } + if (module->handle) { + modules_dl_loaded[--dl_loaded_count] = module; + } } ZEND_HASH_FOREACH_END(); /* Collect internal classes with static members */ @@ -3082,7 +3093,7 @@ void module_destructor(zend_module_entry *module) /* {{{ */ void module_registry_unload(const zend_module_entry *module) { #if HAVE_LIBDL - if (module->handle && !getenv("ZEND_DONT_UNLOAD_MODULES")) { + if (!getenv("ZEND_DONT_UNLOAD_MODULES")) { DL_UNLOAD(module->handle); } #else @@ -3134,6 +3145,17 @@ ZEND_API void zend_deactivate_modules(void) /* {{{ */ } /* }}} */ +void zend_unload_modules(void) /* {{{ */ +{ + zend_module_entry **modules = modules_dl_loaded; + while (*modules) { + module_registry_unload(*modules); + modules++; + } + free(modules_dl_loaded); +} +/* }}} */ + ZEND_API void zend_post_deactivate_modules(void) /* {{{ */ { if (EG(full_tables_cleanup)) { @@ -3152,7 +3174,9 @@ ZEND_API void zend_post_deactivate_modules(void) /* {{{ */ break; } module_destructor(module); - module_registry_unload(module); + if (module->handle) { + module_registry_unload(module); + } zend_string_release_ex(key, 0); } ZEND_HASH_MAP_FOREACH_END_DEL(); } else { From 600c59b58f003af9cfef1f2c38b8de661d8837dd Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 19 Feb 2024 22:57:28 +0100 Subject: [PATCH 3/3] Forgot to set to NULL --- Zend/zend_API.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index ead182dd34042..4c447845e5474 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3153,6 +3153,7 @@ void zend_unload_modules(void) /* {{{ */ modules++; } free(modules_dl_loaded); + modules_dl_loaded = NULL; } /* }}} */