[php-src] PR #23508: Fix block pass dropping ZEND_FAST_CALL OP2 definition

From: Date: Sat, 29 Aug 2026 15:05:24 +0000
Subject: [php-src] PR #23508: Fix block pass dropping ZEND_FAST_CALL OP2 definition
Groups: php.git-pulls 
Request: Send a blank email to git-pulls+get-38203@lists.php.net to get a copy of this message
Pull Request: https://github.com/php/php-src/pull/23508 Author: Mrmaxmeier Hi, we ran into a use of an uninitialized value bug with the fuzzer-function-jit fuzzing target: ```php <?php function test() { $x = 1; try { try { return true ? "returned" : "other"; } finally { undef_fn(); } } catch (Error $e) { echo $e->getMessage(), "\n"; } return "fallback"; } var_dump(test()); ``` Note: MSAN would be the right tool to detect this bug, but oss-fuzz flags this harness as ASAN-only because the JIT doesn't work with MSAN. <details> <summary>ASAN backtrace for reproducer</summary> ``` /out/php-fuzz-function-jit: Running 1 inputs 100 time(s) each. Running: /testcase AddressSanitizer:DEADLYSIGNAL ================================================================= ==14==ERROR: AddressSanitizer: SEGV on unknown address (pc 0x55b7732f8d13 bp 0x7ffd18a57e80 sp 0x7ffd18a57e60 T0) ==14==The signal is caused by a READ memory access. ==14==Hint: this fault was caused by a dereference of a high value address (see register values below). Disassemble the provided pc to learn which register was used. SCARINESS: 20 (wild-addr-read) #0 0x55b7732f8d13 in zend_gc_delref /src/php-src/Zend/zend_types.h:835:2 #1 0x55b7732f8d13 in i_zval_ptr_dtor /src/php-src/Zend/zend_variables.h:43:8 #2 0x55b7732f8d13 in zval_ptr_dtor /src/php-src/Zend/zend_variables.c:83:2 #3 0x55b773170d59 in zend_dispatch_try_catch_finally_helper_SPEC /src/php-src/Zend/zend_vm_execute.h:3359:5 #4 0x55b7733140bb in fuzzer_execute_ex /src/php-src/sapi/fuzzer/fuzzer-execute-common.h:65:12 #5 0x55b772ecc006 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER /src/php-src/Zend/zend_vm_execute.h:2131:4 #6 0x55b7733140bb in fuzzer_execute_ex /src/php-src/sapi/fuzzer/fuzzer-execute-common.h:65:12 #7 0x55b772e3813d in zend_execute /src/php-src/Zend/zend_vm_execute.h:115989:2 #8 0x55b7733153bf in fuzzer_do_request_from_buffer /src/php-src/sapi/fuzzer/fuzzer-sapi.c:293:5 #9 0x55b773313954 in LLVMFuzzerTestOneInput /src/php-src/sapi/fuzzer/fuzzer-function-jit.c:32:2 [..] DEDUP_TOKEN: zend_gc_delref--i_zval_ptr_dtor--zval_ptr_dtor SUMMARY: AddressSanitizer: SEGV /src/php-src/Zend/zend_types.h:835:2 in zend_gc_delref ==14==ABORTING ``` </details> When a return crosses a finally block, PHP first stores the return value in a temporary. Both the ZEND_FAST_CALL that enters the finally block and the later ZEND_RETURN refer to that temporary. The OP2 reference on ZEND_FAST_CALL is needed during exception unwinding: if the finally block throws, the VM uses it to find and destroy the pending return value. The block pass did not account for this extra use. After propagating the value into ZEND_RETURN, it considered the QM_ASSIGN that initialized the temporary dead and removed it. This left the optimized op_array with ``` FAST_CALL finally T3 ``` but no opcode that initialized T3. If the finally block then threw, exception unwinding attempted to destroy the uninitialized temporary. The fix marks the OP2 temporary as ineligible for this source-removal optimization. This uses the same mechanism already used for operands that must remain available to ZEND_FETCH_LIST_R and similar opcodes. Thanks! --- _Found by the CISPA Fandango team while triaging findings in oss-fuzz harnesses._

« previous php.git-pulls (#38203) next »