[php-src] Issue #23980: ZEND_ASSERT violation @ ZEND_INCLUDE_OR_EVAL (include/eval run with a pending exception)
| From: | rlaaudrb1104 | Date: | Tue, 29 Sep 2026 08:29:12 +0000 |
| Subject: | [php-src] Issue #23980: ZEND_ASSERT violation @ ZEND_INCLUDE_OR_EVAL (include/eval run with a pending exception) | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-252848@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/23980
Author: rlaaudrb1104
### Description
## Overview
| Field | Value |
|---|---|
| Target |
php @ 38956a3 |
| Defect type | ZEND_ASSERT violation → SIGABRT (**debug build only**) |
| Crash site | ZEND_INCLUDE_OR_EVAL_SPEC_TMP_HANDLER@Zend/zend_vm_execute.h:17655 —
assert(!EG(exception)) |
| Reproduction | reproduced (php --enable-debug + ASan, same commit) |
| Latest release | compiled out in release: NDEBUG drops the assert (see Impact) |
| PoC | pocs/06_php_zend_call_known.php (223 bytes) |
## Full Output (debug-build reproduction)
Running the PoC on a --enable-debug + ASan php (CLI) terminates as follows.
```
Fatal error: Uncaught ArgumentCountError: Internal function var_dump() does not accept named
variadic arguments in .../06_php_zend_call_known.php:1
Stack trace:
#0 .../06_php_zend_call_known.php(1): var_dump('{ include ( flo...', array:
Object(class@anonymous))
#1 {main}
thrown in .../06_php_zend_call_known.php on line 1
Warning: include(): Failed opening 'Array' for inclusion ... (repeated)
php: Zend/zend_vm_execute.h:17655: const zend_op
*ZEND_INCLUDE_OR_EVAL_SPEC_TMP_HANDLER(zend_execute_data *, const zend_op *): Assertion
`!(executor_globals.exception)' failed.
==ABORTING (SIGABRT)
```
Command: USE_ZEND_ALLOC=0 ASAN_OPTIONS='symbolize=1:handle_segv=1' sapi/cli/php
pocs/06_php_zend_call_known.php
## Root Cause
The include/eval opcode handler runs while an exception is still pending,
violating its invariant assert !EG(exception).
Frame by frame:
1. The PoC's var_dump("...", array: new class{...} ?: true) passes a
named argument (array:) to the internal function var_dump(). Internal
functions do not accept named variadic arguments, so the engine throws
ArgumentCountError (stack frame #0).
2. While that exception unwinds the stack, the anonymous class instance built as an argument is
destroyed and its __destruct() runs. The destructor body (include - require
include [ new static ]) executes an include (ZEND_INCLUDE_OR_EVAL) opcode.
3. ZEND_INCLUDE_OR_EVAL_SPEC_TMP_HANDLER (zend_vm_execute.h:17655) asserts
EG(exception)==NULL on entry. But the ArgumentCountError from step 1 is
still pending, so the assert fails and the process abort()s (SIGABRT).
Broken invariant: "the include/eval opcode only runs with no pending exception" breaks on
the destructor-during-unwinding path. The engine did not anticipate a destructor executing
include/eval while an exception is being unwound.
## Trigger
(a) Pass a named argument to an internal function to raise an exception, and (b) make one of the
call arguments an object whose __destruct runs include/eval.
During exception unwinding, the destructor's include trips the assert. The 223-byte PoC
combines both conditions.
## Impact
- Debug build (--enable-debug): ZEND_ASSERT violation →
abort(), so this is a DoS (crash). It is a diagnostic assert that does not exist in
release.
- Release build (NDEBUG): the assert is compiled out, so the handler proceeds with a pending
exception. Whether this leads to memory corruption is undetermined — the handler may soon process
the exception (HANDLE_EXCEPTION) harmlessly, though on release the crash surfaces at a different
site. The real release impact (info disclosure / RCE) would need a separate release ASan build to
assess.
- In short, what is confirmed is a debug-only internal-invariant violation; a release memory
vulnerability is undetermined. Still, running include while an exception is pending is a genuine
engine logic bug worth reporting upstream, even if it is not a security issue.
## Reproduction
```bash
git clone https://github.com/php/php-src.git
php-src && cd php-src
git checkout 38956a3
./buildconf --force
CC=clang CFLAGS="-g -O1 -fsanitize=address -fno-omit-frame-pointer"
LDFLAGS="-fsanitize=address" \
./configure --disable-all --enable-cli --enable-debug --enable-tokenizer --enable-mbstring
--enable-phar --enable-fileinfo
make -j$(nproc)
USE_ZEND_ALLOC=0 ASAN_OPTIONS='symbolize=1:handle_segv=1' sapi/cli/php
pocs/06_php_zend_call_known.php
```
**--enable-debug is required.** Without it the assert is compiled out and the issue
does not reproduce (the non-debug build handles it as a Warning loop on include failure).
### PHP Version
```plain
PHP 8.6.0-dev (master, commit 38956a3f3eeb9d878d578efa4163b74fa20ebbfb)
```
### Operating System
_No response_