Delayed effects#30
Closed
arnaud-lb wants to merge 22 commits into
Closed
Conversation
This is a prototype for fixing a long-standing source of interrupt vulnerabilities: A notice is emitted during execution of an opcode, resulting in an error handling being run. The error handler modifies some data structure the opcode is working on, resulting in UAF or other memory corruption. The idea here is to instead collect notices and only process them after the opcode. This is implemented similarly to exception handling, by switching to a ZEND_HANDLE_DELAYED_ERROR opcode, which will then switch back to the normal opcode stream. Unfortunately, what this prototype implements is not sufficient. Opcodes that acquire direct (INDIRECT) references to zvals require that no interrupts occur between the producing and the consuming opcode. Chains of W/RW opcodes should be executed without interrupt. Currently, the notice is only delayed until after the first opcode, which still results in an illegal interrupt (bug78598.phpt shows a UAF with this change). I'm not sure how to best handle that issue.
There are 3 categories of failing tests here: - Many are throwing in a user error handlers, therefore relied on non-delayed behavior. Fixed these by installing the error handler with promote_to_exception: true. - Some are just printing the error message. Their output changes due to delay. These are fixed by updating the EXPECT section. - And finally, many are testing the adverse effects of mutations in error handlers. These are now irrelevant, but these are also fixed by updating the EXPECT section.
arnaud-lb
commented
May 21, 2026
…_call_frame slow path
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Delay user error handlers and destructors until the next vm_interrupt safe point.
Error handlers
Identified use-cases of user error handlers:
How these use-cases are affected:
display_errorsandlog_errorswill display/log without delay).$errlineand$errfilecallback parameters remain correct.debug_backtrace()may reflect delaying, however.To mitigate use-case 2, we introduce a new
set_error_handler()argument:$delay=true. Whenfalse, errors specified in the$error_levelsparameter are promoted to exceptions (PromotedErrorException), and the error handler is executed when the exception is handled (inZEND_HANDLE_EXCEPTION). Promoting to exception allows internal code to safely unwind (exceptions are already properly handled in code paths that may trigger errors), and to call the user error handler as soon as the current opcode terminates. The user error handler can replace the promoted exception by throwing an other exception.It would be possible to provide backtraces reflecting the origin of the error accurately. Currently backtraces reflect the point at which the error handler is called, which may be a few lines after in use-cases 1 and 3, or in a nested function call if no safe point is reached in the originating function.
Destructors
Identified use-cases of destructors:
Currently, interrupt safe points are:
This would prevent destructors from being executed right when leaving a function. To mitigate this, we move the first safe point to "When leaving a user function", and we introduce a safe point when increasing the VM call stack size, to preserve timeout handling in case of deep recursion:
Backtraces in destructors will reflect the point at which they are called. In case a dtor is called in the "when leaving a user function" point, the backtrace will not reflect the function that was just left. This is already the case for destructors of objects referenced by local variables (which are destroyed after leaving the frame), but this is new for other objects, if no safe point was reached before leaving the function.
Corner cases
call_user_func("static::f"): May emit a deprecation, followed by a TypeError. Currently we ignore the second exception, otherwise it prevents calling user error handlers.Performance
There is a 0.5% regression on the symfony benchmark, but I attribute this to binary layout.
Summary of interrupt changes
TODO