Fix GH-21639: Guard frameless handler reentry#21815
Fix GH-21639: Guard frameless handler reentry#21815prateekbhujel wants to merge 1 commit intophp:PHP-8.4from
Conversation
|
Thanks for the PR.
I'm afraid this will need a more general solution. My hope was to avoid overhead in the VM, but it might be unavoidable for a proper fix, even if this is a largely artificial issue. |
35148db to
15ec47b
Compare
|
Thanks @iluuu1994. I reworked this so it no longer copies every frameless call. The updated patch adds a While checking the frameless list I also included |
15ec47b to
c818fa7
Compare
| { | ||
| zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline); | ||
| function(result, arg1); | ||
| if (UNEXPECTED(ZEND_FLF_USES_ARG_COPY(opline))) { |
There was a problem hiding this comment.
This has high overhead even if not needed - type analysis should be used, when the arg is non-object, copying/protecting the argument is useless.
There was a problem hiding this comment.
Agreed. I ended up dropping this VM-level copy path entirely.
The current branch keeps the normal frameless dispatch/JIT path untouched and only pins arguments inside the handler paths that can actually re-enter, such as parse-time sibling conversion, array element string conversion, and the zend_compare() fallback. So this overhead should no longer apply to the updated patch.
|
@iluuu1994 I pushed a different version that drops the VM/metadata/JIT approach. The current patch keeps frameless dispatch as-is and guards only the local reentry boundaries in the affected handlers. In particular, I reran the focused tests and checked the hot cases from the earlier benchmark locally. This should address the main issue with the previous version without turning the bug fix into a broader engine-level change. |
c818fa7 to
08cc08f
Compare
08cc08f to
38e2314
Compare
|
The other alternative would be checking inside the tostring handler whether the parent frame is currently at a frameless opcode and then safely copy its CV args to a buffer, set EG(vm_interrupt) and free them on the next EG(vm_interrupt), completely moving the overhead off the main paths and be a truly generic solution. Obviously comes at a small tostring handler cost, but I'd really rather see overhead there...? |
Fixes GH-21639.
Frameless handlers borrow their arguments from the caller frame. That is fine while the handler stays in C, but it becomes unsafe when the handler can re-enter userland before it is done with another borrowed argument. The original zval can be overwritten during string conversion, loose comparison, or array element conversion.
This keeps the frameless VM/JIT dispatch path unchanged. The guards are local to the handler paths that can re-enter:
implode()andpreg_replace()protect arrays/values only around element conversionsin_array(),min(), andmax()keep their scalar fast paths and only pin arguments before thezend_compare()fallbackTests run: