[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._
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.