From df6db27580025e2089a27e09615389ec03ab8a3d Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 23 Dec 2024 18:51:08 +0100 Subject: [PATCH] Fix GH-17246: GC during SCCP causes segfault This bug happens because of a nested `SHM_UNPROTECT()` sequence. In particular: ``` unprotect memory at ext/opcache/ZendAccelerator.c:2127 protect memory at ext/opcache/ZendAccelerator.c:2160 unprotect memory at ext/opcache/ZendAccelerator.c:2164 unprotect memory at ext/opcache/jit/zend_jit_trace.c:7464 ^^^ Nested protect memory at ext/opcache/jit/zend_jit_trace.c:7591 ^^^ Problem is here: it should not protect again due to the nested unprotect protect memory at ext/opcache/ZendAccelerator.c:2191 ^^^ This one should actually protect, not the previous one ``` The reason this nesting happen is because: 1. We try to include the script, this eventually calls `cache_script_in_shared_memory` 2. `zend_optimize_script` will eventually run SCCP as part of the DFA pass. 3. SCCP will try to replace constants, but can also run destructors when a partial array is destructed here: https://github.com/php/php-src/blob/4e9cde758eadf30cc4d596d6398c2c34c64197b4/Zend/Optimizer/sccp.c#L2387-L2389 In this case, this destruction invokes the GC which invokes the tracing JIT, leading to the nested unprotects. This patch disables the GC to prevent invoking user code, as user code is not supposed to run during the optimizer pipeline. Closes GH-17249. Co-authored-by: Dmitry Stogov --- NEWS | 1 + ext/opcache/ZendAccelerator.c | 3 +++ ext/opcache/tests/jit/gh17246.inc | 8 ++++++ ext/opcache/tests/jit/gh17246.phpt | 39 ++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+) create mode 100644 ext/opcache/tests/jit/gh17246.inc create mode 100755 ext/opcache/tests/jit/gh17246.phpt diff --git a/NEWS b/NEWS index f2a0b0246d1fc..257623b283397 100644 --- a/NEWS +++ b/NEWS @@ -51,6 +51,7 @@ PHP NEWS - Opcache: . opcache_get_configuration() properly reports jit_prof_threshold. (cmb) + . Fixed bug GH-17246 (GC during SCCP causes segfault). (Dmitry) - PCNTL: . Fix memory leak in cleanup code of pcntl_exec() when a non stringable diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index fb886d39e60ac..66c10021442ad 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2153,7 +2153,10 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) */ from_shared_memory = false; if (persistent_script) { + /* See GH-17246: we disable GC so that user code cannot be executed during the optimizer run. */ + bool orig_gc_state = gc_enable(false); persistent_script = cache_script_in_shared_memory(persistent_script, key, &from_shared_memory); + gc_enable(orig_gc_state); } /* Caching is disabled, returning op_array; diff --git a/ext/opcache/tests/jit/gh17246.inc b/ext/opcache/tests/jit/gh17246.inc new file mode 100644 index 0000000000000..f9e7d78fc165a --- /dev/null +++ b/ext/opcache/tests/jit/gh17246.inc @@ -0,0 +1,8 @@ +field = function() {}; + } + + public function __destruct() + { + // Necessary because we need to invoke tracing JIT during destruction + } +} + +for ($i = 0; $i < 10000; ++$i) { + $obj = new Test(); +} + +require __DIR__.'/gh17246.inc'; + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Class "NonExistentClass" not found in %s:%d +Stack trace: +#0 %s(%d): require() +#1 {main} + thrown in %s on line %d