[GIT-PULLS] [php-src] PR #23454: Fix zend_analyze_calls() call_stack buffer overrun

[email protected] (Mrmaxmeier)
Newsgroups php.git-pulls
Message-ID <pQXO74VFtlSDuDV5yAl8xpj6IK5pIMAK5nZvnoggyA8@main.internal.php.net>
Pull Request: https://github.com/php/php-src/pull/23454
Author: Mrmaxmeier

Hi,

we ran into an out-of-bounds write with the `php-fuzz-function-jit` fuzzing target:

```php
<?php
function test() {
    new A(new B(new C(new D(match ([]) { 1 => 2 }))));
}
```

<details>

<summary>ASAN backtrace for reproducer</summary>

```
/out/php-fuzz-function-jit: Running 1 inputs 100 time(s) each.
Running: /testcase
=================================================================
==14==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7b62989e2e60 at pc 0x556927643788 bp 0x7ffca0c77cd0 sp 0x7ffca0c77cc8
WRITE of size 8 at 0x7b62989e2e60 thread T0
SCARINESS: 42 (8-byte-write-heap-buffer-overflow)
    #0 0x556927643787 in zend_analyze_calls /src/php-src/Zend/Optimizer/zend_call_graph.c:100:22
    #1 0x5569276440fb in zend_analyze_call_graph /src/php-src/Zend/Optimizer/zend_call_graph.c:253:3
    #2 0x5569276c28c2 in zend_optimize_script /src/php-src/Zend/Optimizer/zend_optimizer.c:1631:6
    #3 0x556926e37b79 in cache_script_in_shared_memory /src/php-src/ext/opcache/ZendAccelerator.c:1598:2
    #4 0x556926e39b6d in persistent_compile_file /src/php-src/ext/opcache/ZendAccelerator.c:2399:24
    #5 0x556927d1538e in fuzzer_do_request_from_buffer /src/php-src/sapi/fuzzer/fuzzer-sapi.c:289:29
    #6 0x556927d13954 in LLVMFuzzerTestOneInput /src/php-src/sapi/fuzzer/fuzzer-function-jit.c:32:2
    [..]

DEDUP_TOKEN: zend_analyze_calls--zend_analyze_call_graph--zend_optimize_script
0x7b62989e2e60 is located 0 bytes after 16-byte region [0x7b62989e2e50,0x7b62989e2e60)
allocated by thread T0 here:
    #0 0x5569269a9df4 in malloc /src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:67:3
    #1 0x5569276e0231 in tracked_malloc /src/php-src/Zend/zend_alloc.c:3016:14
    #2 0x556927642767 in zend_analyze_calls /src/php-src/Zend/Optimizer/zend_call_graph.c:53:15
    #3 0x5569276440fb in zend_analyze_call_graph /src/php-src/Zend/Optimizer/zend_call_graph.c:253:3
    #4 0x5569276c28c2 in zend_optimize_script /src/php-src/Zend/Optimizer/zend_optimizer.c:1631:6
    #5 0x556926e37b79 in cache_script_in_shared_memory /src/php-src/ext/opcache/ZendAccelerator.c:1598:2
    #6 0x556926e39b6d in persistent_compile_file /src/php-src/ext/opcache/ZendAccelerator.c:2399:24
    #7 0x556927d1538e in fuzzer_do_request_from_buffer /src/php-src/sapi/fuzzer/fuzzer-sapi.c:289:29
    #8 0x556927d13954 in LLVMFuzzerTestOneInput /src/php-src/sapi/fuzzer/fuzzer-function-jit.c:32:2
    [..]

DEDUP_TOKEN: __interceptor_malloc--tracked_malloc--zend_analyze_calls
SUMMARY: AddressSanitizer: heap-buffer-overflow /src/php-src/Zend/Optimizer/zend_call_graph.c:100:22 in zend_analyze_calls
```

(Note: The buffer is a `do_alloca()`, so this can either be a stack or heap buffer overflow depending on allocator behaviour. In this setup, ASAN reports it as a heap overflow.)

</details>

`zend_analyze_calls()` assumes that the `call_stack` is at most `op_array->last / 2` deep:

https://github.com/php/php-src/blob/5dcff379188117c965db4d2888a3ef8b20193e8e/Zend/Optimizer/zend_call_graph.c#L53

The implicit assumption here is that every call needs at least two opcodes, an `INIT_*` that pushes an entry and a `DO_FCALL` that pops it again. That assumption stops holding when the optimizer removes `DO_FCALL` opcodes as dead code.

This PR allocates `op_array->last` entries for `call_stack` instead, assuming that each opcode pushes at most one call stack entry.

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.