[php-src] Issue #24534: Segfault in zend_try_early_bind when a parent class autoloads its cached child during linking (opcache)
| From: | wimwinterberg | Date: | Thu, 22 Oct 2026 12:38:01 +0000 |
| Subject: | [php-src] Issue #24534: Segfault in zend_try_early_bind when a parent class autoloads its cached child during linking (opcache) | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253087@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24534
Author: wimwinterberg
### Description
With opcache enabled, a worker segfaults when a parent class is linked first and, during its
variance check, autoloads its own child class whose file is already cached in opcache. Opcache then
performs delayed early binding of the cached child against the parent that is still being linked,
and copies from a NULL pointer.
Without opcache (
opcache.enable=0) the same code works.
Reproduced with the official images php:8.3-cli (8.3.36), php:8.4-cli
(8.4.26) and php:8.5-cli (8.5.12), and in production on 8.4.27 FPM (Ubuntu 24.04,
deb.sury.org). JIT is not involved (opcache.jit=disable in production).
**src/EntityInterface.php**
```php
<?php
interface EntityInterface
{
public static function getById(int $id): ?self;
}
```
**src/Entity.php**
```php
<?php
abstract class Entity implements EntityInterface
{
protected array $data = [];
protected bool $loaded = false;
}
```
**src/UserBase.php**
```php
<?php
abstract class UserBase extends Entity
{
public const TABLE = 'user';
protected ?int $userId = null;
protected ?string $name = null;
protected ?int $position = null;
public static function getById(int $id): ?User
{
return null;
}
}
```
**src/User.php**
```php
<?php
class User extends UserBase
{
}
```
**public/autoload.php**
```php
<?php
spl_autoload_register(static function (string $class): void {
$file = __DIR__ . '/../src/' . $class . '.php';
if (is_file($file)) {
require $file;
}
});
```
**public/base-first.php**
```php
<?php
require __DIR__ . '/autoload.php';
echo 'base-first: ', UserBase::TABLE, "\n";
```
**public/child-first.php**
```php
<?php
require __DIR__ . '/autoload.php';
echo 'child-first: ', User::TABLE, "\n";
```
Run the built-in server with opcache and request the pages in this order:
```sh
php -d opcache.enable=1 -d opcache.enable_cli=1 -S 127.0.0.1:8080 -t public &
curl http://127.0.0.1:8080/base-first.php # 200
"base-first: user" (User.php compiled fresh)
curl http://127.0.0.1:8080/child-first.php #
200 "child-first: user" (User.php now cached)
curl http://127.0.0.1:8080/base-first.php #
server crashes
```
(On the official images opcache has to be loaded with -d zend_extension=opcache for
8.3/8.4.)
Resulted in this output:
```
base-first: user
child-first: user
base-first:
```
and the server process exits with signal 11 (exit code 139).
But I expected this output instead:
```
base-first: user
child-first: user
base-first: user
```
Backtrace from a production core dump (PHP 8.4.27 FPM, binary without debug symbols, frame names
from .dynsym):
```
#0 __memmove_avx_unaligned (libc) rsi (src) = 0x0, rdx (n) = 0x28
#1 ?? (php-fpm8.4)
#2 zend_try_early_bind ()
#3 ?? () from opcache.so
#4 compile_filename ()
#5-#10 execute_ex / zend_call_function / zend_lookup_class_ex (autoload of the child)
#11 zend_lookup_class_ex ()
#12 ?? () from opcache.so
#13 zend_do_link_class () (linking the parent)
#14 zend_bind_class_in_slot ()
#15-#29 execute_ex / zend_execute_script / php_execute_script_ex
```
PHP stack at the crash: UserBase::TABLE -> autoload UserBase ->
linking UserBase -> autoload User -> include of the cached
User.php.
Workaround in user land: reference the child class (User::TABLE) instead of the parent,
so the child is always linked first.
### PHP Version
PHP 8.3.36, 8.4.26, 8.4.27, 8.5.12
### Operating System
Debian (official Docker images), Ubuntu 24.04