[php-src] Issue #22878: Heap use-after-free in array callable validation during autoload

[email protected] (Aurora-glyph) Fri, 24 Jul 2026 12:51:42 +0000
Newsgroups php.bugs
Message-ID <[email protected]>
Issue: https://github.com/php/php-src/issues/22878
Author: Aurora-glyph

### Description

### Problem

I found a use-after-free in `zend_is_callable_at_frame()`. The function borrows
pointers to the class and method elements of an array callable. Validating the
class can invoke an autoloader, and that autoloader can release the array. PHP
then passes the freed method element to `zend_is_callable_check_func()`.

Both `call_user_func()` and `call_user_func_array()` reach the same vulnerable
code, so I have included both entry points in this report.

### Minimal reproducers

`call_user_func()`:

```php
<?php

spl_autoload_register(function (string $class): void {
    $GLOBALS['cb'] = null;
    eval('class TriggerAutoload { public static function __callStatic($n, $a) {} }');
});

$method = str_repeat('m', 256);
$cb = ['TriggerAutoload', $method];
unset($method);
call_user_func($cb);
```

`call_user_func_array()`:

```php
<?php

spl_autoload_register(function (string $class): void {
    $GLOBALS['cb'] = null;
    eval('class TriggerAutoload { public static function __callStatic($n, $a) {} }');
});

$method = str_repeat('m', 256);
$cb = ['TriggerAutoload', $method];
unset($method);
call_user_func_array($cb, []);
```

I built PHP 8.5.8 from the official source archive with:

```sh
CC=gcc CFLAGS="-O2 -g -fno-omit-frame-pointer" \
  ./configure --enable-cli --disable-all --disable-cgi --disable-phpdbg \
  --disable-fpm --without-sqlite3 --without-pdo-sqlite
make -j8
```

Run both files:

```sh
sapi/cli/php -n poc-call-user-func.php
echo $?
sapi/cli/php -n poc-call-user-func-array.php
echo $?
```

### Expected behavior

Each call should resolve `TriggerAutoload`, dispatch through `__callStatic()`,
and exit normally. Validation must not access the callable after its storage
has been freed.

### Actual behavior

Each reproducer exits with status 139 (`SIGSEGV`) on an optimized PHP 8.5.8
build. Both results reproduced in 100 of 100 fresh processes on macOS arm64.
The official `php:8.5.8-cli-bookworm` image also crashed in 100 of 100 fresh
processes for each entry point on Linux x86_64.

### PHP Version

```plain
Primary tested release:

PHP 8.5.8 (cli) (built: Jul 24 2026 17:30:12) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.5.8, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.8, Copyright (c), by Zend Technologies
```

### Operating System

macOS 26.5.2, build 25F84, Darwin 25.5.0, arm64