Repository navigation
PHP 8.5+ Windows ZTS: random access violations with OPcache JIT in multi-process setups #24185
Description
Activity
with a little tweak:
run 1: exit 0, output: caught 30 run 2: exit 1, output: ================================================================= ==5396==ERROR: AddressSanitizer: access-violation on unknown address 0x000000000018 (pc 0x7ffbbc7814c1 bp 0x000000000000 sp 0x000b3c9fb058 T0) ==5396==The signal is caused by a READ memory access. ==5396==Hint: address points to the zero page. #0 0x7ffbbc7814c0 (C:\Windows\SYSTEM32\ntdll.dll+0x1800914c0) #1 0x7ffb5882b967 in __asan_wrap_strlen D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\sanitizer_common\sanitizer_common_interceptors.inc:390 #2 0x7ffb5a06f462 in xbuf_format_converter C:\php-sdk\phpdev\vs17\x64\php-src\main\spprintf.c:527 #3 0x7ffb5a06cab8 in php_printf_to_smart_str C:\php-sdk\phpdev\vs17\x64\php-src\main\spprintf.c:756 #4 0x7ffb59776409 in zend_vstrpprintf C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:340 #5 0x7ffb59787297 in zend_error_va_list C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:1639 #6 0x7ffb59778955 in zend_error_noreturn C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:1748 #7 0x7ffb597c24e0 in zend_parse_parameters_debug_error C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1153 #8 0x7ffb597c299c in zend_parse_va_args C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1254 #9 0x7ffb59790350 in zend_parse_parameters C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1321 #10 0x7ffb5a8222c5 in zim_SplFixedArray___construct@@16 C:\php-sdk\phpdev\vs17\x64\php-src\ext\spl\spl_fixedarray.c:555 #11 0x20000800325f (<unknown module>) #12 0x122ab52190ef (<unknown module>) #13 0xb3c9fcc0f (<unknown module>) #14 0x122ab5207677 (<unknown module>) #15 0x122ab520767f (<unknown module>) #16 0x122ab5207687 (<unknown module>) #17 0x122a00000000 (<unknown module>) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: access-violation (C:\Windows\SYSTEM32\ntdll.dll+0x1800914c0) ==5396==ABORTING run 3: exit 0, output: caught 30 run 4: exit 0, output: caught 30 run 5: exit 1, output: ================================================================= ==9784==ERROR: AddressSanitizer: access-violation on unknown address 0xffffffffffffffff (pc 0x20000800310c bp 0x000000000000 sp 0x002b13dfc8b0 T0) ==9784==The signal is caused by a READ memory access. #0 0x20000800310b (<unknown module>) #1 0x1252b881901f (<unknown module>) #2 0x2000008da237 (<unknown module>) #3 0x1252b8807677 (<unknown module>) #4 0x1252b880767f (<unknown module>) #5 0x1252b8807687 (<unknown module>) #6 0x1252b880768b (<unknown module>) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: access-violation (<unknown module>) ==9784==ABORTING run 6: exit 0, output: caught 30 ^C C:\php-sdk\phpdev\vs17\x64\php-src $ .\x64\Debug_TS\php.exe -n -c child.ini repro-parent.php true 50 run 1: exit 0, output: caught 30 run 2: exit 0, output: caught 30 run 3: exit 0, output: caught 30 run 4: exit 0, output: caught 30 run 5: exit 0, output: caught 30 run 6: exit 0, output: caught 30 run 7: exit 1, output: ================================================================= ==9076==ERROR: AddressSanitizer: access-violation on unknown address 0x000000000018 (pc 0x7ffbbc7814c1 bp 0x000000000000 sp 0x00b0393fafa8 T0) ==9076==The signal is caused by a READ memory access. ==9076==Hint: address points to the zero page. #0 0x7ffbbc7814c0 (C:\Windows\SYSTEM32\ntdll.dll+0x1800914c0) #1 0x7ffb5882b967 in __asan_wrap_strlen D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\sanitizer_common\sanitizer_common_interceptors.inc:390 #2 0x7ffb5a06f462 in xbuf_format_converter C:\php-sdk\phpdev\vs17\x64\php-src\main\spprintf.c:527 #3 0x7ffb5a06cab8 in php_printf_to_smart_str C:\php-sdk\phpdev\vs17\x64\php-src\main\spprintf.c:756 #4 0x7ffb59776409 in zend_vstrpprintf C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:340 #5 0x7ffb59787297 in zend_error_va_list C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:1639 #6 0x7ffb59778955 in zend_error_noreturn C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:1748 #7 0x7ffb597c24e0 in zend_parse_parameters_debug_error C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1153 #8 0x7ffb597c299c in zend_parse_va_args C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1254 #9 0x7ffb59790350 in zend_parse_parameters C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1321 #10 0x7ffb5a8222c5 in zim_SplFixedArray___construct@@16 C:\php-sdk\phpdev\vs17\x64\php-src\ext\spl\spl_fixedarray.c:555 #11 0x20000800325f (<unknown module>) #12 0x1325924190ef (<unknown module>) #13 0xb0393fcb5f (<unknown module>) #14 0x132592407677 (<unknown module>) #15 0x13259240767f (<unknown module>) #16 0x132592407687 (<unknown module>) #17 0x132500000000 (<unknown module>) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: access-violation (C:\Windows\SYSTEM32\ntdll.dll+0x1800914c0) ==9076==ABORTING run 8: exit 0, output: run 9: exit 0, output: caught 30 run 10: exit 1, output: ================================================================= ==8564==ERROR: AddressSanitizer: access-violation on unknown address 0xffffffffffffffff (pc 0x20000800310c bp 0x000000000000 sp 0x005d0adfc9d0 T0) ==8564==The signal is caused by a READ memory access. #0 0x20000800310b (<unknown module>) #1 0x12067161901f (<unknown module>) #2 0x2000008da237 (<unknown module>) #3 0x120671607677 (<unknown module>) #4 0x12067160767f (<unknown module>) #5 0x120671607687 (<unknown module>) #6 0x12067160768b (<unknown module>) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: access-violation (<unknown module>) ==8564==ABORTING run 11: exit 0, output: caught 30 run 12: exit 0, output: caught 30 run 13: exit 0, output: caught 30 run 14: exit 0, output: caught 30 run 15: exit 0, output: caught 30 run 16: exit 0, output: caught 30 run 17: exit 0, output: caught 30 run 18: exit 0, output: caught 30 run 19: exit 0, output: caught 30 run 20: exit 0, output: caught 30 run 21: exit 0, output: caught 30 run 22: exit 1, output: ================================================================= ==7328==ERROR: AddressSanitizer: access-violation on unknown address 0xffffffffffffffff (pc 0x20000800310c bp 0x000000000000 sp 0x00bf4b9fc7d0 T0) ==7328==The signal is caused by a READ memory access. #0 0x20000800310b (<unknown module>) #1 0x12780b41901f (<unknown module>) #2 0x2000008da237 (<unknown module>) #3 0x12780b407677 (<unknown module>) #4 0x12780b40767f (<unknown module>) #5 0x12780b407687 (<unknown module>) #6 0x12780b40768b (<unknown module>) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: access-violation (<unknown module>) ==7328==ABORTING run 23: exit 0, output: caught 30 run 24: exit 0, output: caught 30 run 25: exit 1, output: ================================================================= ==7968==ERROR: AddressSanitizer: access-violation on unknown address 0x000000002c40 (pc 0x20000800310c bp 0x000000000000 sp 0x0057917fc940 T0) ==7968==The signal is caused by a READ memory access. #0 0x20000800310b (<unknown module>) #1 0x11e650a1901f (<unknown module>) #2 0x2000008da237 (<unknown module>) #3 0x11e650a07677 (<unknown module>) #4 0x11e650a0767f (<unknown module>) #5 0x11e650a07687 (<unknown module>) #6 0x11e650a0768b (<unknown module>) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: access-violation (<unknown module>) ==7968==ABORTING run 26: exit 0, output: caught 30 run 27: exit 0, output: caught 30 run 28: exit 0, output: caught 30 run 29: exit 1, output: ================================================================= ==7616==ERROR: AddressSanitizer: access-violation on unknown address 0x000000000018 (pc 0x7ffbbc7814c1 bp 0x000000000000 sp 0x00b1d11fb008 T0) ==7616==The signal is caused by a READ memory access. ==7616==Hint: address points to the zero page. #0 0x7ffbbc7814c0 (C:\Windows\SYSTEM32\ntdll.dll+0x1800914c0) #1 0x7ffb5882b967 in __asan_wrap_strlen D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\sanitizer_common\sanitizer_common_interceptors.inc:390 #2 0x7ffb5a06f462 in xbuf_format_converter C:\php-sdk\phpdev\vs17\x64\php-src\main\spprintf.c:527 #3 0x7ffb5a06cab8 in php_printf_to_smart_str C:\php-sdk\phpdev\vs17\x64\php-src\main\spprintf.c:756 #4 0x7ffb59776409 in zend_vstrpprintf C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:340 #5 0x7ffb59787297 in zend_error_va_list C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:1639 #6 0x7ffb59778955 in zend_error_noreturn C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:1748 #7 0x7ffb597c24e0 in zend_parse_parameters_debug_error C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1153 #8 0x7ffb597c299c in zend_parse_va_args C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1254 #9 0x7ffb59790350 in zend_parse_parameters C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1321 #10 0x7ffb5a8222c5 in zim_SplFixedArray___construct@@16 C:\php-sdk\phpdev\vs17\x64\php-src\ext\spl\spl_fixedarray.c:555 #11 0x20000800325f (<unknown module>) #12 0x1259ebc190ef (<unknown module>) #13 0xb1d11fcbbf (<unknown module>) #14 0x1259ebc07677 (<unknown module>) #15 0x1259ebc0767f (<unknown module>) #16 0x1259ebc07687 (<unknown module>) #17 0x125900000000 (<unknown module>) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: access-violation (C:\Windows\SYSTEM32\ntdll.dll+0x1800914c0) ==7616==ABORTING run 30: exit 0, output: caught 30 run 31: exit 0, output: caught 30 run 32: exit 0, output: caught 30 run 33: exit 0, output: caught 30 run 34: exit 0, output: caught 30 run 35: exit 0, output: caught 30 run 36: exit 0, output: caught 30 run 37: exit 0, output: caught 30 run 38: exit 0, output: caught 30 run 39: exit 0, output: caught 30 run 40: exit 0, output: caught 30 run 41: exit 0, output: caught 30 run 42: exit 0, output: caught 30 run 43: exit 0, output: caught 30 run 44: exit 0, output: caught 30 run 45: exit 1, output: ================================================================= ==3424==ERROR: AddressSanitizer: access-violation on unknown address 0x000000000018 (pc 0x7ffbbc7814c1 bp 0x000000000000 sp 0x00f7dcffb1b8 T0) ==3424==The signal is caused by a READ memory access. ==3424==Hint: address points to the zero page. #0 0x7ffbbc7814c0 (C:\Windows\SYSTEM32\ntdll.dll+0x1800914c0) #1 0x7ffb5882b967 in __asan_wrap_strlen D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\sanitizer_common\sanitizer_common_interceptors.inc:390 #2 0x7ffb5a06f462 in xbuf_format_converter C:\php-sdk\phpdev\vs17\x64\php-src\main\spprintf.c:527 #3 0x7ffb5a06cab8 in php_printf_to_smart_str C:\php-sdk\phpdev\vs17\x64\php-src\main\spprintf.c:756 #4 0x7ffb59776409 in zend_vstrpprintf C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:340 #5 0x7ffb59787297 in zend_error_va_list C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:1639 #6 0x7ffb59778955 in zend_error_noreturn C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:1748 #7 0x7ffb597c24e0 in zend_parse_parameters_debug_error C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1153 #8 0x7ffb597c299c in zend_parse_va_args C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1254 #9 0x7ffb59790350 in zend_parse_parameters C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1321 #10 0x7ffb5a8222c5 in zim_SplFixedArray___construct@@16 C:\php-sdk\phpdev\vs17\x64\php-src\ext\spl\spl_fixedarray.c:555 #11 0x20000800325f (<unknown module>) #12 0x128199c190ef (<unknown module>) #13 0xf7dcffcd6f (<unknown module>) #14 0x128199c07677 (<unknown module>) #15 0x128199c0767f (<unknown module>) #16 0x128199c07687 (<unknown module>) #17 0x128100000000 (<unknown module>) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: access-violation (C:\Windows\SYSTEM32\ntdll.dll+0x1800914c0) ==3424==ABORTING run 46: exit 0, output: caught 30 run 47: exit 0, output: caught 30 run 48: exit 1, output: ================================================================= ==9432==ERROR: AddressSanitizer: access-violation on unknown address 0x000000000018 (pc 0x7ffbbc7814c1 bp 0x000000000000 sp 0x00ee997fb188 T0) ==9432==The signal is caused by a READ memory access. ==9432==Hint: address points to the zero page. #0 0x7ffbbc7814c0 (C:\Windows\SYSTEM32\ntdll.dll+0x1800914c0) #1 0x7ffb5882b967 in __asan_wrap_strlen D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\sanitizer_common\sanitizer_common_interceptors.inc:390 #2 0x7ffb5a06f462 in xbuf_format_converter C:\php-sdk\phpdev\vs17\x64\php-src\main\spprintf.c:527 #3 0x7ffb5a06cab8 in php_printf_to_smart_str C:\php-sdk\phpdev\vs17\x64\php-src\main\spprintf.c:756 #4 0x7ffb59776409 in zend_vstrpprintf C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:340 #5 0x7ffb59787297 in zend_error_va_list C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:1639 #6 0x7ffb59778955 in zend_error_noreturn C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend.c:1748 #7 0x7ffb597c24e0 in zend_parse_parameters_debug_error C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1153 #8 0x7ffb597c299c in zend_parse_va_args C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1254 #9 0x7ffb59790350 in zend_parse_parameters C:\php-sdk\phpdev\vs17\x64\php-src\Zend\zend_API.c:1321 #10 0x7ffb5a8222c5 in zim_SplFixedArray___construct@@16 C:\php-sdk\phpdev\vs17\x64\php-src\ext\spl\spl_fixedarray.c:555 #11 0x20000800325f (<unknown module>) #12 0x135f84e190ef (<unknown module>) #13 0xee997fcd3f (<unknown module>) #14 0x135f84e07677 (<unknown module>) #15 0x135f84e0767f (<unknown module>) #16 0x135f84e07687 (<unknown module>) #17 0x135f00000000 (<unknown module>) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: access-violation (C:\Windows\SYSTEM32\ntdll.dll+0x1800914c0) ==9432==ABORTING run 49: exit 0, output: caught 30 run 50: exit 0, output: suppress_errors=true: 0 / 50 runs crashedSo it looks like EG() pointers etc are corrupted on ZTS because the TLS addresses are different between processes.
Add this inzend_jit_resolve_tsrm_ls_cache_offsets():fprintf(stderr, "tls_index=%u offset=%zu\n", _tls_index, offset);
The process that first JITs hastls_index=8 offset=32, and the crashing process hastls_index=7 offset=32.
Turns out that on Windows,tls_indexis not fixed, so by baking the TLS since PHP 8.5 by making opcache bundled, this causes problems.
Reason is that now when PHP starts up we get a TLS index attached, whereas previously we only got it when opcache got loaded, which was at the same address every time by design and so got the same TLS index.The stupid&simple fix is to detach not only on ASLR mismatch but also TLS mismatch. But that's awkward: that means on some processes you get opcache but no JIT...
I believe the right fix is to load the offset at runtime rather than embedding it? That can't be fixed without upstream IR changes, and it makes TLS loads more expensive...
PoC:
diff --git a/ext/opcache/jit/ir/ir_x86.dasc b/ext/opcache/jit/ir/ir_x86.dasc index f5efb66698d..47db8a12f11 100644 --- a/ext/opcache/jit/ir/ir_x86.dasc +++ b/ext/opcache/jit/ir/ir_x86.dasc @@ -10864,13 +10864,17 @@ static void ir_emit_tls(ir_ctx *ctx, ir_ref def, ir_insn *insn) |.if X64WIN | gs | mov Ra(reg), aword [0x58] -| mov Ra(reg), aword [Ra(reg)+insn->op2] -| mov Ra(reg), aword [Ra(reg)+insn->op3] +|| if (insn->op2 != IR_NULL) { +| mov Ra(reg), aword [Ra(reg)+insn->op2] +| mov Ra(reg), aword [Ra(reg)+insn->op3] +|| } |.elif WIN | fs | mov Ra(reg), aword [0x2c] -| mov Ra(reg), aword [Ra(reg)+insn->op2] -| mov Ra(reg), aword [Ra(reg)+insn->op3] +|| if (insn->op2 != IR_NULL) { +| mov Ra(reg), aword [Ra(reg)+insn->op2] +| mov Ra(reg), aword [Ra(reg)+insn->op3] +|| } |.elif X64APPLE | gs || if (insn->op3 == IR_NULL) { diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index c10c904a5a6..59416834c86 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -206,6 +206,10 @@ static size_t tsrm_ls_cache_tcb_offset = 0; static size_t tsrm_tls_index = -1; static size_t tsrm_tls_offset = -1; +# ifdef ZEND_WIN32 +extern uint32_t _tls_index; +# endif + # define EG_TLS_OFFSET(field) \ (executor_globals_offset + offsetof(zend_executor_globals, field)) @@ -522,11 +526,25 @@ static ir_ref jit_TLS(zend_jit_ctx *jit) if (tsrm_ls_cache_tcb_offset == 0 && tsrm_tls_index == -1) { jit->tls = ir_CALL(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_get_tsrm_ls_cache)); +#ifdef ZEND_WIN32 + } else { + ir_ref tls_array = ir_TLS(IR_NULL, IR_NULL); /* NtCurrentTeb()->ThreadLocalStoragePointer */ +# ifdef _WIN64 + ir_ref index = ir_ZEXT_A(ir_LOAD_U32(ir_CONST_ADDR(&_tls_index))); +# else + ir_ref index = ir_LOAD_A(ir_CONST_ADDR(&_tls_index)); +# endif + jit->tls = ir_LOAD_A(ir_ADD_OFFSET( + ir_LOAD_A(ir_ADD_A(tls_array, ir_MUL_A(index, ir_CONST_ADDR(sizeof(void *))))), + tsrm_tls_offset)); + } +#else } else { jit->tls = ir_TLS( tsrm_ls_cache_tcb_offset ? tsrm_ls_cache_tcb_offset : tsrm_tls_index, tsrm_ls_cache_tcb_offset ? IR_NULL : tsrm_tls_offset); } +#endif return jit->tls; }
cc @iluuu1994 @arnaud-lb @TimWolla since you were involved in #18961
Description
Reproduce code:
child.ini(same directory):repro-parent.php(parent should also usechild.ini):repro-child.php(must usechild.ini):Run:
In theory, the child script's expected output should be
caught 30because exceptions should be caught bytry-catch. But in PHP 8.5+ (ZTS, OPcache JIT enabled), this causes ACCESS_VIOLATION for some reason with OPcache JIT enabled.Tests with GitHub actions runner: action log
Full code snippets can be found on this repo
PHP Version
Operating System
Windows 10 Pro 22H2 (10.0.19045), Windows Server 2025 (windows-2025-vs2026 runner image)