[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._