[GIT-PULLS] [php-src] PR #23508: Fix block pass dropping ZEND_FAST_CALL OP2 definition
[email protected] (Mrmaxmeier)
| Newsgroups | php.git-pulls |
|---|---|
| Message-ID | <[email protected]> |
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._