[php-src] PR #23507: Fix live ranges for ZEND_JMP_SET and ZEND_COALESCE

From: Date: Sat, 29 Aug 2026 14:47:19 +0000
Subject: [php-src] PR #23507: Fix live ranges for ZEND_JMP_SET and ZEND_COALESCE
Groups: php.git-pulls 
Request: Send a blank email to git-pulls+get-38202@lists.php.net to get a copy of this message
Pull Request: https://github.com/php/php-src/pull/23507 Author: Mrmaxmeier Hi, we ran into a use of an uninitialized value bug fuzzer-function-jit fuzzing target: ```php <?php try { $a ?: match (true) { 1 => 1 }; } catch (UnhandledMatchError $e) { } ``` and the ?? variant: ```php <?php try { $a ?? match (true) { 1 => 1 }; } catch (UnhandledMatchError $e) { } ``` 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 (<code>?:</code> / ZEND_JMP_SET)</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 0x55e832e30ebf bp 0x7ffeab63ebd0 sp 0x7ffeab63eb30 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 0x55e832e30ebf in zend_gc_delref /src/php-src/Zend/zend_types.h:835:2 #1 0x55e832e30ebf in zval_delref_p /src/php-src/Zend/zend_types.h:1406:9 #2 0x55e832e30ebf in zval_ptr_dtor_nogc /src/php-src/Zend/zend_variables.h:34:35 #3 0x55e832e30ebf in cleanup_live_vars /src/php-src/Zend/zend_execute.c:4948:6 #4 0x55e833170b00 in zend_dispatch_try_catch_finally_helper_SPEC /src/php-src/Zend/zend_vm_execute.h:3334:4 #5 0x55e8333140bb in fuzzer_execute_ex /src/php-src/sapi/fuzzer/fuzzer-execute-common.h:65:12 #6 0x55e832e3813d in zend_execute /src/php-src/Zend/zend_vm_execute.h:115989:2 #7 0x55e8333153bf in fuzzer_do_request_from_buffer /src/php-src/sapi/fuzzer/fuzzer-sapi.c:293:5 #8 0x55e833313954 in LLVMFuzzerTestOneInput /src/php-src/sapi/fuzzer/fuzzer-function-jit.c:32:2 [..] DEDUP_TOKEN: zend_gc_delref--zval_delref_p--zval_ptr_dtor_nogc SUMMARY: AddressSanitizer: SEGV /src/php-src/Zend/zend_types.h:835:2 in zend_gc_delref ==14==ABORTING ``` </details> <details> <summary>ASAN backtrace for the <code>??</code> / ZEND_COALESCE variant</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 0x55eb3a830ebf bp 0x7ffd7f0cf250 sp 0x7ffd7f0cf1b0 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 0x55eb3a830ebf in zend_gc_delref /src/php-src/Zend/zend_types.h:835:2 #1 0x55eb3a830ebf in zval_delref_p /src/php-src/Zend/zend_types.h:1406:9 #2 0x55eb3a830ebf in zval_ptr_dtor_nogc /src/php-src/Zend/zend_variables.h:34:35 #3 0x55eb3a830ebf in cleanup_live_vars /src/php-src/Zend/zend_execute.c:4948:6 #4 0x55eb3ab70b00 in zend_dispatch_try_catch_finally_helper_SPEC /src/php-src/Zend/zend_vm_execute.h:3334:4 #5 0x55eb3ad140bb in fuzzer_execute_ex /src/php-src/sapi/fuzzer/fuzzer-execute-common.h:65:12 #6 0x55eb3a83813d in zend_execute /src/php-src/Zend/zend_vm_execute.h:115989:2 #7 0x55eb3ad153bf in fuzzer_do_request_from_buffer /src/php-src/sapi/fuzzer/fuzzer-sapi.c:293:5 #8 0x55eb3ad13954 in LLVMFuzzerTestOneInput /src/php-src/sapi/fuzzer/fuzzer-function-jit.c:32:2 [..] DEDUP_TOKEN: zend_gc_delref--zval_delref_p--zval_ptr_dtor_nogc SUMMARY: AddressSanitizer: SEGV /src/php-src/Zend/zend_types.h:835:2 in zend_gc_delref ==14==ABORTING ``` </details> ZEND_JMP_SET (?:) and ZEND_COALESCE (??) both write their result *only* on the branch they take. zend_calc_live_ranges() started their live range right behind the definition, so the range also covered the fall-through path, on which the temporary was never written at all. Normally this is harmless because another opcode redefines the result on the fall-through path, and the backward walk stops at that later definition, hiding the bogus range. Once the optimizer removes that redefinition the range shows up. In the reproducer the QM_ASSIGN of the else branch is gone because the match always throws, so unwinding the UnhandledMatchError makes cleanup_live_vars() destroy uninitialized stack memory. The fix starts the live range at the jump target instead of behind the definition. On the branch that is taken control transfers there directly, so that is exactly the region in which the result is defined. If the result is freed right at the jump target the range is empty and no range is emitted at all. Thanks! --- _Found by the CISPA Fandango team while triaging findings in oss-fuzz harnesses._

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