From 1ce597d6cc680c8a93439955a21fea8ab59780ce Mon Sep 17 00:00:00 2001 From: Sree24-ui Date: Fri, 13 Nov 2026 16:00:47 +0530 Subject: [PATCH] Fix GH-21974: Avoid destroying shared functions in ZTS --- NEWS | 2 + Zend/zend_API.c | 20 ++++++++ ...974-disable-functions-worker-shutdown.phpt | 48 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 sapi/fpm/tests/gh21974-disable-functions-worker-shutdown.phpt diff --git a/NEWS b/NEWS index b5dbe65a83ca..887adaeb421c 100644 --- a/NEWS +++ b/NEWS @@ -219,6 +219,8 @@ PHP NEWS values. (Marcel Hernandez) . Fixed null pointer dereference in fpm_get_status() when the scoreboard copy fails. (Marcel Hernandez) + . Fixed bug GH-21974 (Crash on worker shutdown in ZTS builds when + disable_functions is set in a pool configuration). (Sree24-ui) - Fileinfo: . Fixed bug GH-24289 (finfo::file() reports the wrong argument number for diff --git a/Zend/zend_API.c b/Zend/zend_API.c index d8ea2c5ed553..011d2f183e0d 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3672,6 +3672,26 @@ static void zend_disable_function(const char *function_name, size_t function_nam zend_error(E_WARNING, "Cannot disable function %s()", function_name); return; } +#ifdef ZTS + /* After zend_post_startup() the function table of a thread borrows the + * functions of the global function table, which destroys them on shutdown + * (see compiler_globals_ctor()). Remove them without destroying them. */ + HashTable *function_table = CG(function_table); + zval *zv = zend_hash_str_find(function_table, function_name, function_name_length); + if (zv && (uint32_t) ((Bucket *) zv - function_table->arData) < CG(copied_functions_count)) { + dtor_func_t dtor = function_table->pDestructor; + + function_table->pDestructor = NULL; + zend_hash_str_del(function_table, function_name, function_name_length); + function_table->pDestructor = dtor; + + /* Keep the remaining copied functions at the start of the table, + * as expected by compiler_globals_dtor(). */ + zend_hash_rehash(function_table); + CG(copied_functions_count)--; + return; + } +#endif zend_hash_str_del(CG(function_table), function_name, function_name_length); } diff --git a/sapi/fpm/tests/gh21974-disable-functions-worker-shutdown.phpt b/sapi/fpm/tests/gh21974-disable-functions-worker-shutdown.phpt new file mode 100644 index 000000000000..26c7aad92187 --- /dev/null +++ b/sapi/fpm/tests/gh21974-disable-functions-worker-shutdown.phpt @@ -0,0 +1,48 @@ +--TEST-- +FPM: GH-21974 - disable_functions set in a pool must not break shutdown of the worker +lsan disabled: a worker that exits normally reports a leak from fpm_env_init_main(). +--SKIPIF-- + +--ENV-- +LSAN_OPTIONS=detect_leaks=0 +--FILE-- +start(); +$tester->expectLogStartNotices(); +$tester->request()->expectBody('disabled'); +$tester->expectLogNotice('child \d+ exited with code 0 after \d+\.\d+ seconds from start', 'unconfined'); +$tester->expectLogNotice('child \d+ started', 'unconfined'); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +?> +Done +--EXPECT-- +Done +--CLEAN-- +