Skip to content

ZEND_ASSERT violation @ ZEND_INCLUDE_OR_EVAL (include/eval run with a pending exception) #23980

Description

@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 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.

<?PHP var_dump ( "{ include ( float ) @ yield < yield } \uBefore binding \u \u \u \u \uCaught:  \u \u \u \u \u \u" , array : new class { function __destruct ( ) { include - require include [ new static ] ; } } ? : true ) ;

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

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

PHP 8.6.0-dev (master, commit 38956a3f3eeb9d878d578efa4163b74fa20ebbfb)

Operating System

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions