From f95433afbeac411a4186d98c6b95bb1f4c9af0ab Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Sat, 3 Jan 2026 16:47:35 +0100 Subject: [PATCH 1/2] Fix GH-20727: User code can run after module request shutdown via the output layer If user code runs after modules executed RSHUTDOWN, it can be dangerous because user code can rely on module globals that have already been invalidated. We should not run user code after RSHUTDOWN. This is shown by the test by using putenv(). The original report demonstrated a silent failure via mbstring. There are more test cases possible but this is by far the simplest. An alternative solution would be to try to separate the user code running via php_header() from the output layer shutdown, to make sure user code runs earlier. However, that becomes an ugly complex solution. This PR's solution keeps things simple but this can be a BC break if extensions produce output in their RSHUTDOWN handler (However, that may have been unsafe in the first place). --- ext/standard/tests/general_functions/gh20727.phpt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 ext/standard/tests/general_functions/gh20727.phpt diff --git a/ext/standard/tests/general_functions/gh20727.phpt b/ext/standard/tests/general_functions/gh20727.phpt new file mode 100644 index 0000000000000..f5d8f69dadf96 --- /dev/null +++ b/ext/standard/tests/general_functions/gh20727.phpt @@ -0,0 +1,11 @@ +--TEST-- +GH-20727 (User code can run after module request shutdown via the output layer) +--FILE-- + +--EXPECT-- +bool(true) From db12174b173f1b957f8556a11610ec781ecbcba9 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Sat, 3 Jan 2026 17:05:51 +0100 Subject: [PATCH 2/2] forgot to push this somehow, sorry --- main/main.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/main/main.c b/main/main.c index f190eab3d094f..7c45782807f18 100644 --- a/main/main.c +++ b/main/main.c @@ -1997,16 +1997,16 @@ void php_request_shutdown(void *dummy) zend_unset_timeout(); } zend_end_try(); - /* 5. Call all extensions RSHUTDOWN functions */ - if (PG(modules_activated)) { - zend_deactivate_modules(); - } - - /* 6. Shutdown output layer (send the set HTTP headers, cleanup output handlers, etc.) */ + /* 5. Shutdown output layer (send the set HTTP headers, cleanup output handlers, etc.) */ zend_try { php_output_deactivate(); } zend_end_try(); + /* 6. Call all extensions RSHUTDOWN functions */ + if (PG(modules_activated)) { + zend_deactivate_modules(); + } + /* 7. Free shutdown functions */ if (PG(modules_activated)) { php_free_shutdown_functions();