[php-src] Issue #23980: ZEND_ASSERT violation @ ZEND_INCLUDE_OR_EVAL (include/eval run with a pending exception)

From: 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_

« previous php.bugs (#252848) next »