Description
PHP 8.5 ZTS Bug Report — Apache worker crash via UAF in zend_error_zstr_at when deprecation warning emitted from OnUpdateUseTransSid during TSRM thread setup
Submit to: https://github.com/php/php-src/issues/new/choose
(use "Bug Report" template)
Summary
Apache 2.4 (mpm_winnt) worker children crash deterministically with
STATUS_ACCESS_VIOLATION (0xC0000005) inside php8ts.dll when all
three of the following are configured:
- PHP 8.5.x ZTS Windows VS17 x86 (verified on 8.5.5 and 8.5.6)
session.use_trans_sid = 1 (the php.ini-recommended default)
- Apache
AddDefaultCharset UTF-8 directive active
The crash fires during TSRM thread setup, specifically when
OnUpdateUseTransSid emits the deprecation warning for use_trans_sid
under PHP 8.5. The error-reporting path then performs a zval_ptr_dtor
on a zval whose ecx-relative refcount slot points to integer 0x0d
(13) instead of a valid heap address.
The faulting instruction is sub dword ptr [ecx],1 — zend_gc_delref()
inlined into zend_error_zstr_at — and the value at [ecx] is fully
unmapped (????????).
Faulting context
ERROR_CODE: 0xC0000005 STATUS_ACCESS_VIOLATION
WRITE_ADDRESS: 0x0000000d
eax=00001520 ebx=18206000 ecx=0000000d edx=00001520 esi=0f6a1ee0 edi=20201ad0
eip=741d6304 esp=16fbfa30 ebp=16fbfae4
php8ts!zend_gc_delref [inlined in php8ts!zend_error_zstr_at+0x244]:
741d6304 832901 sub dword ptr [ecx],1 ds:002b:0000000d=????????
FAULTING_SOURCE_FILE: Zend\zend.c
FAULTING_SOURCE_LINE_NUMBER: 1499
SYMBOL_NAME: php8ts!zend_error_zstr_at+244
FAILURE_BUCKET_ID: NULL_CLASS_PTR_WRITE_c0000005_php8ts.dll!zend_error_zstr_at
Stack trace (fully symbolicated against matching PDB)
# inlined:
php8ts!zend_gc_delref
php8ts!i_zval_ptr_dtor +0x11
php8ts!zval_ptr_dtor +0x11
# outer frames:
00 php8ts!zend_error_zstr_at +0x244 ← crash here
01 php8ts!zend_error_zstr +0x25
02 php8ts!php_verror +0x524
03 php8ts!php_error_docref +0x1b
04 php8ts!OnUpdateUseTransSid +0x9d ← deprecation warning emitted here
05 php8ts!zend_ini_refresh_caches +0x5b
06 php8ts!zend_new_thread_end_handler+0x0c
07 php8ts!allocate_new_resource +0x121
08 php8ts!ts_resource_ex +0xee
09 php8apache2_4!? +0x2f42
0a libapr_1!apr_table_get +0x94 (stack unwind unreliable here)
0b libhttpd!ap_sys_privileges_handlers+0xcba
0c libhttpd!ap_run_handler +0x29
The stack from ts_resource_ex up through OnUpdateUseTransSid is
exactly the per-thread INI cache refresh path. The deprecation warning
must therefore fire before the worker's zval slots are fully
initialised — the zval at ecx is still the raw IS_LONG(13) it was
seeded with, but zval_ptr_dtor treats it as a refcounted value.
Reproduction
Minimum setup that reproduces:
- Windows Server 2019 (build 17763) — also expected on Win 10/11
- Apache 2.4.66 (Apache Lounge VS18, mpm_winnt with default
ThreadsPerChild 250)
- PHP 8.5.5 or 8.5.6 ZTS VS17 x86, loaded as
mod_php via
LoadModule php_module .../php8apache2_4.dll
php.ini:
session.use_trans_sid = 1 (this is the value shipped in the
php.ini-recommended template!)
httpd.conf:
Step:
- Start Apache. Worker children spawn cleanly. So far stable.
- Issue any HTTP request that triggers a login flow involving session
start (session_start() called from a typical app). In our case
POST /index.php followed by GET /top.php was enough.
- Within seconds of the request completing successfully (HTTP 200 was
logged), the worker child dies with status 3221225477 and Apache
restarts it (AH00428: Parent: child process … exited with status 3221225477 -- Restarting).
- Crash repeats every 1-3 minutes under normal request load.
Removing any one of the three trigger conditions stops the crash:
- Setting
session.use_trans_sid = 0 — stable
- Commenting out
AddDefaultCharset UTF-8 — stable
- Downgrading to PHP 8.4.x ZTS — stable
Workaround
session.use_trans_sid = 0
This avoids triggering the deprecation warning during TSRM thread
setup. (As a bonus, it eliminates a long-standing security hazard —
session IDs in URLs are unsafe regardless.)
Hypothesis
OnUpdateUseTransSid runs from inside zend_ini_refresh_caches, which
itself runs from zend_new_thread_end_handler during
allocate_new_resource. At this very specific point of TSRM init the
per-thread zval slots referenced by the error-formatting code path are
not yet fully refcounted — they still carry whatever initial value
the TSRM allocator placed there (looks like a small integer here:
0x0000000d).
When php_error_docref → php_verror → zend_error_zstr → zend_error_zstr_at
returns and zval_ptr_dtor runs on a buffer/arg zval, it dereferences
that integer as a refcounted pointer and crashes at the
Z_REFCOUNT_DEC operation.
This is consistent with the fact that the AddDefaultCharset UTF-8
directive alone — independent of any PHP-side charset setting — is
enough to make the bug manifest. The charset directive presumably
changes the request handler init ordering enough to push the
OnUpdateUseTransSid callback into the unsafe TSRM-init window.
Crashdump
A 242 MB full crashdump (Windows minidump format, httpd.exe.8004.dmp)
is available on request. PDB-symbolicated stack matches the trace
above bit-for-bit, including the line-number resolution to
Zend/zend.c:1499.
Related but distinct issues
The current report is a distinct, reproducible crash that we believe
has not been filed: the unique signature is OnUpdateUseTransSid →
zend_error_zstr_at → zval_ptr_dtor UAF during TSRM thread setup.
Recommendation for fixers
Either:
- Defer the
session.use_trans_sid deprecation warning until after
TSRM thread init is complete (don't emit warnings from INI cache
refresh during allocate_new_resource), or
- Initialise the temporary zval slots used by the warning-formatter
path with IS_NULL (or properly refcounted strings) before any
user-visible INI callback can fire.
PHP Version
PHP 8.5.6 (cli) (built: May 6 2026 09:28:51) (ZTS Visual C++ 2022 x86)
Copyright (c) The PHP Group
Built by The PHP Group
Zend Engine v4.5.6, Copyright (c) Zend Technologies
with Zend OPcache v8.5.6, Copyright (c), by Zend Technologies
Operating System
Windows Server 2019 (build 17763)
Description
PHP 8.5 ZTS Bug Report — Apache worker crash via UAF in
zend_error_zstr_atwhen deprecation warning emitted fromOnUpdateUseTransSidduring TSRM thread setupSubmit to: https://github.com/php/php-src/issues/new/choose
(use "Bug Report" template)
Summary
Apache 2.4 (
mpm_winnt) worker children crash deterministically withSTATUS_ACCESS_VIOLATION(0xC0000005) insidephp8ts.dllwhen allthree of the following are configured:
session.use_trans_sid = 1(the php.ini-recommended default)AddDefaultCharset UTF-8directive activeThe crash fires during TSRM thread setup, specifically when
OnUpdateUseTransSidemits the deprecation warning foruse_trans_sidunder PHP 8.5. The error-reporting path then performs a
zval_ptr_dtoron a zval whose
ecx-relative refcount slot points to integer0x0d(13) instead of a valid heap address.
The faulting instruction is
sub dword ptr [ecx],1—zend_gc_delref()inlined into
zend_error_zstr_at— and the value at[ecx]is fullyunmapped (
????????).Faulting context
Stack trace (fully symbolicated against matching PDB)
The stack from
ts_resource_exup throughOnUpdateUseTransSidisexactly the per-thread INI cache refresh path. The deprecation warning
must therefore fire before the worker's zval slots are fully
initialised — the zval at
ecxis still the raw IS_LONG(13) it wasseeded with, but
zval_ptr_dtortreats it as a refcounted value.Reproduction
Minimum setup that reproduces:
ThreadsPerChild 250)mod_phpviaLoadModule php_module .../php8apache2_4.dllphp.ini:session.use_trans_sid = 1(this is the value shipped in thephp.ini-recommendedtemplate!)httpd.conf:AddDefaultCharset UTF-8Step:
start (
session_start()called from a typical app). In our casePOST /index.phpfollowed byGET /top.phpwas enough.logged), the worker child dies with status
3221225477and Apacherestarts it (
AH00428: Parent: child process … exited with status 3221225477 -- Restarting).Removing any one of the three trigger conditions stops the crash:
session.use_trans_sid = 0— stableAddDefaultCharset UTF-8— stableWorkaround
session.use_trans_sid = 0This avoids triggering the deprecation warning during TSRM thread
setup. (As a bonus, it eliminates a long-standing security hazard —
session IDs in URLs are unsafe regardless.)
Hypothesis
OnUpdateUseTransSidruns from insidezend_ini_refresh_caches, whichitself runs from
zend_new_thread_end_handlerduringallocate_new_resource. At this very specific point of TSRM init theper-thread zval slots referenced by the error-formatting code path are
not yet fully refcounted — they still carry whatever initial value
the TSRM allocator placed there (looks like a small integer here:
0x0000000d).When
php_error_docref → php_verror → zend_error_zstr → zend_error_zstr_atreturns and
zval_ptr_dtorruns on a buffer/arg zval, it dereferencesthat integer as a refcounted pointer and crashes at the
Z_REFCOUNT_DECoperation.This is consistent with the fact that the
AddDefaultCharset UTF-8directive alone — independent of any PHP-side charset setting — is
enough to make the bug manifest. The charset directive presumably
changes the request handler init ordering enough to push the
OnUpdateUseTransSidcallback into the unsafe TSRM-init window.Crashdump
A 242 MB full crashdump (Windows minidump format,
httpd.exe.8004.dmp)is available on request. PDB-symbolicated stack matches the trace
above bit-for-bit, including the line-number resolution to
Zend/zend.c:1499.Related but distinct issues
Apache restart" (different stack)
emalloc()(different stack)
stack)
The current report is a distinct, reproducible crash that we believe
has not been filed: the unique signature is OnUpdateUseTransSid →
zend_error_zstr_at → zval_ptr_dtor UAF during TSRM thread setup.
Recommendation for fixers
Either:
session.use_trans_siddeprecation warning until afterTSRM thread init is complete (don't emit warnings from INI cache
refresh during
allocate_new_resource), orpath with
IS_NULL(or properly refcounted strings) before anyuser-visible INI callback can fire.
PHP Version
Operating System
Windows Server 2019 (build 17763)