You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Fix GH-21974: Avoid destroying shared functions in ZTS - #24425
FixesGH-21974.
Problem
In ZTS builds, a function disabled through an FPM pool's php_admin_value[disable_functions] setting can cause a use-after-free or crash during worker shutdown.
After startup, a thread's function table contains borrowed pointers to functions owned by the global function table. Deleting a borrowed function through the normal destructor path can free an object that the global table still references.
Fix
When zend_disable_function() identifies a function in the borrowed-function prefix of the ZTS function table, it removes the entry without invoking its destructor, rehashes the table to preserve the borrowed-prefix invariant, and updates CG(copied_functions_count). The existing deletion path remains unchanged for other entries and for non-ZTS builds.
Testing
Added an FPM regression test that disables system in a pool and checks that the worker exits cleanly after its first request.
On PHP-8.4 HEAD, the new test failed on unpatched release and ASan builds and passed on patched builds.
The patched release FPM suite reported 110 passed, 27 skipped, and 0 failed.
Repeated patched regression-test runs and a custom multithreaded embed-SAPI harness passed.
The current validation used macOS builds. Linux/glibc, Windows, PHP-8.3, and a real multithreaded web-server SAPI have not been tested.
Scope
This change addresses the reported worker-shutdown issue. It does not attempt to address separate problems associated with calling zend_disable_function() during an active request.
I investigated the failing LINUX_X64_ASAN_DEBUG_ZTS check further.
In a Linux ARM64 ASan+ZTS reproduction, the unpatched build reports the original heap-use-after-free in zend_function_dtor. The patched build instead exits with code 1 due to a LeakSanitizer report originating in fpm_env_init_main(2,452 bytes across 31 allocations). The same leak occurs with and without disable_functions.
With ASAN_OPTIONS=detect_leaks=0, the patched test passes 3/3 runs, while the unpatched build still fails 3/3 with the original use-after-free.
These results support that the patch addresses the intended regression, while the test also exposes an FPM startup leak during graceful worker shutdown. The reproduction is on Linux ARM64, so I have not yet confirmed the exact cause in the x86-64 CI environment.
I have not changed the test, suppression file, or PR. Would you prefer a targeted test-side LeakSanitizer configuration or a separate entry in .github/lsan-suppressions.txt? I can implement and validate the preferred approach.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes GH-21974.
Problem
In ZTS builds, a function disabled through an FPM pool's php_admin_value[disable_functions] setting can cause a use-after-free or crash during worker shutdown.
After startup, a thread's function table contains borrowed pointers to functions owned by the global function table. Deleting a borrowed function through the normal destructor path can free an object that the global table still references.
Fix
When zend_disable_function() identifies a function in the borrowed-function prefix of the ZTS function table, it removes the entry without invoking its destructor, rehashes the table to preserve the borrowed-prefix invariant, and updates CG(copied_functions_count). The existing deletion path remains unchanged for other entries and for non-ZTS builds.
Testing
The current validation used macOS builds. Linux/glibc, Windows, PHP-8.3, and a real multithreaded web-server SAPI have not been tested.
Scope
This change addresses the reported worker-shutdown issue. It does not attempt to address separate problems associated with calling zend_disable_function() during an active request.