-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Fixed GH-16233: Observer segfault when calling user function in internal function via trampoline #16252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ternal function via trampoline In the test, I have an internal `__call` function for `_ZendTestMagicCallForward` that calls the global function with name `$name` via `call_user_function`. Note that observer writes the pointer to the previously observed frame in the last temporary of the new call frame (`*prev_observed_frame`). The following happens: First, we call `$test->callee`, this will be handled via a trampoline with T=2 for the two arguments. The call frame is allocated at this point. This call frame is not observed because it has `ZEND_ACC_CALL_VIA_TRAMPOLINE` set. Next we use `ZEND_CALL_TRAMPOLINE` to call the trampoline, this reuses the stack frame allocated earlier with T=2, but this time it is observed. The pointer to the previous frame is written outside of the call frame because `T` is too small (should be 3). We are now in the internal function `_ZendTestMagicCallForward::__call` where we call the global function `callee`. This will push a new call frame which will overlap `*prev_observed_frame`. This value gets overwritten by `zend_init_func_execute_data` when `EX(opline)` is set because `*prev_observed_frame` overlaps with `EX(opline)`. From now on, `*prev_observed_frame` is corrupted. When `zend_observer_fcall_end` is called this will result in reading wrong value `*prev_observed_frame` into `current_observed_frame`. This causes issues in `zend_observer_fcall_end_all` leading to the segfault we observe. Despite function with `ZEND_ACC_CALL_VIA_TRAMPOLINE` not being observed, the reuse of call frames makes problems when `T` is not large enough. To fix this, we make sure to add 1 to `T` if `ZEND_OBSERVER_ENABLED` is true.
Probably, this is right. cc: @bwoebi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks right.
As a side-note: while looking at this I found that EG(trampoline) T and such isn't reset at all for property hooks trampoline. It's not a problem because it's an upper bound and prop hooks don't need temporaries on their own, it'll just alloc more slots than needed. |
I'll have a look at the property hook thing. |
…polines As pointed out in php#16252 (comment)
…polines (#16287) As pointed out in #16252 (comment)
In the test, I have an internal
__call
function for_ZendTestMagicCallForward
that calls the global function with name$name
viacall_user_function
. Note that observer writes the pointer to the previously observed frame in the last temporary of the new call frame (*prev_observed_frame
).The following happens:
First, we call
$test->callee
, this will be handled via a trampoline with T=2 for the two arguments. The call frame is allocated at this point. This call frame is not observed because it hasZEND_ACC_CALL_VIA_TRAMPOLINE
set. Next we useZEND_CALL_TRAMPOLINE
to call the trampoline, this reuses the stack frame allocated earlier with T=2, but this time it is observed. The pointer to the previous frame is written outside of the call frame becauseT
is too small (should be 3). We are now in the internal function_ZendTestMagicCallForward::__call
where we call the global functioncallee
. This will push a new call frame which will overlap*prev_observed_frame
. This value gets overwritten byzend_init_func_execute_data
whenEX(opline)
is set because*prev_observed_frame
overlaps withEX(opline)
. From now on,*prev_observed_frame
is corrupted. Whenzend_observer_fcall_end
is called this will result in reading wrong value*prev_observed_frame
intocurrent_observed_frame
. This causes issues inzend_observer_fcall_end_all
leading to the segfault we observe.Despite function with
ZEND_ACC_CALL_VIA_TRAMPOLINE
not being observed, the reuse of call frames makes problems whenT
is not large enough. To fix this, we make sure to add 1 toT
ifZEND_OBSERVER_ENABLED
is true.