Skip to content

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 #22023

@rschweigardconcept

Description

@rschweigardconcept

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:

  1. PHP 8.5.x ZTS Windows VS17 x86 (verified on 8.5.5 and 8.5.6)
  2. session.use_trans_sid = 1 (the php.ini-recommended default)
  3. 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],1zend_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:
    • AddDefaultCharset UTF-8

Step:

  1. Start Apache. Worker children spawn cleanly. So far stable.
  2. 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.
  3. 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).
  4. 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:

  1. 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
  2. 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)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions