Skip to content

JIT: Fix run_time_cache offset stored without map_ptr dereference#22459

Closed
iliaal wants to merge 1 commit into
php:PHP-8.5from
iliaal:fix/gh-22443-jit-runtime-cache
Closed

JIT: Fix run_time_cache offset stored without map_ptr dereference#22459
iliaal wants to merge 1 commit into
php:PHP-8.5from
iliaal:fix/gh-22443-jit-runtime-cache

Conversation

@iliaal

@iliaal iliaal commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

The tracing JIT could store a raw zend_map_ptr offset into EX(run_time_cache) for a polymorphic dynamic call ($callable(...)) that resolves to a non-closure function or method, so the interpreter dereferenced a bogus address on the next cached lookup. The codegen took the closure direct-pointer shortcut based on TRACE_FRAME_IS_CLOSURE_CALL, but that flag is set for every ZEND_INIT_DYNAMIC_CALL regardless of the resolved callee.

Restricting the shortcut to statically-known closures is safe: an invoked closure object always carries a resolved direct run_time_cache pointer (zend_closures.c), even when its persistent template is offset-tagged under opcache SHM or file_cache. Regression from 6872432; see GH-22443 for the report and the reproduction.

zend_jit_do_fcall() loaded the callee run_time_cache through the closure
direct-pointer shortcut whenever the call frame was flagged as a closure
call. That flag is set for every ZEND_INIT_DYNAMIC_CALL, but a dynamic
call may resolve to a non-closure function whose run_time_cache is a
zend_map_ptr offset. The raw offset was then stored into
EX(run_time_cache) without resolving it through CG(map_ptr_base), and a
later cache lookup dereferenced a bogus address.

Restrict the shortcut to statically-known closures. Unknown dynamic
calls fall through to the general path, which resolves both offsets and
direct pointers.

Fixes phpGH-22443

@arnaud-lb arnaud-lb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

The only case when TRACE_FRAME_IS_CLOSURE_CALL(JIT_G(current_frame)->call)) would be true (for an actual closure) while (func && (func->op_array.fn_flags & ZEND_ACC_CLOSURE)) is not, is when the closure is not in SHM, which should be rare, so the change shouldn't result in performance regression.

The IS_CLOSURE_CALL flag could be removed entirely in master as a follow-up, since this is the only usage and the name is misleading.

@iliaal iliaal closed this in a0ddc2a Jun 26, 2026
@iluuu1994

iluuu1994 commented Jul 6, 2026

Copy link
Copy Markdown
Member

@iliaal This test takes 1 minute with asan on my (very fast) computer. Is that really necessary to trigger the bug? At the very least it should be marked as a slow test.

@iluuu1994

Copy link
Copy Markdown
Member

Should be addressed in 21204bf.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tracing JIT: SIGSEGV in execute_ex (ZEND_INIT_METHOD_CALL) — run_time_cache map_ptr base ~0 on megamorphic calls, regression since 8.5.7

3 participants