Skip to content

Fix free of uninitialized var in ext/date - #25127

Closed
dunglas wants to merge 1 commit into
php:PHP-8.4from
dunglas:date-ginit-last-errors
Closed

dunglas wants to merge 1 commit into
php:PHP-8.4from
dunglas:date-ginit-last-errors

Conversation

@dunglas

@dunglas dunglas commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

In ext/date, initializes last_errors in GINIT to prevent a free of the initialized var in ZTS builds.

Details Claude-generated details:

In ZTS builds, TSRM allocates each thread's module globals with malloc(), and date's GINIT doesn't initialize last_errors: only RINIT sets it to NULL. ZEND_MODULE_POST_ZEND_DEACTIVATE_D(date) frees it when it isn't NULL, and php_request_shutdown() runs the post-deactivate hooks even when the modules were never activated.

So when a thread's first request fails in php_request_startup() before zend_activate_modules() (for instance, a memory limit bailout in sapi_activate() or php_hash_environment()), the shutdown calls timelib_error_container_dtor() on uninitialized memory and the process crashes. Non-ZTS builds aren't affected because their globals live in zeroed static storage.

This initializes last_errors in GINIT like the other pointers.

Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-CDG8FSAB. I made the PR public because the issue can only trigger a crash, under specific conditions.

…rtup

In ZTS builds, a thread's date globals are malloc'd and last_errors is only
set by RINIT, while the post-deactivate hook frees it. When the first request
of a thread fails before modules are activated, php_request_shutdown() still
runs the post-deactivate hooks and frees a garbage pointer.
@dunglas
dunglas requested a review from derickr as a code owner November 1, 2026 13:49
bukka pushed a commit to bukka/php-src that referenced this pull request Nov 1, 2026
* PHP-8.4:
  Fix free of uninitialized date last_errors after a failed request startup (php#25127)
thesamesam pushed a commit to thesamesam/php-src that referenced this pull request Nov 3, 2026
…rtup (php#25127)

In ZTS builds, date globals are allocated per thread, but last_errors
was only initialized in RINIT. If the first request fails before module
activation, the post-deactivate hook can free an uninitialized pointer.
Initialize last_errors in GINIT alongside the other pointers.

Closes php#25127
dunglas added a commit to php/frankenphp that referenced this pull request Nov 4, 2026
* fix: don't restart the thread when request startup fails

A client hanging up while php_request_startup() displays a warning
(e.g. "Input variables exceeded") made the SAPI bail out, and a POST
exhausting memory_limit while $_POST is built fails the startup too.
php_thread() then forced a request shutdown, freed the TSRM context and
started a new thread. On a context where no RINIT ever ran, ext/date's
post-deactivate hook freed the uninitialized DATEG(last_errors) and the
process crashed.

Don't bail out on a closed connection during request startup, and
finish a request whose startup failed with a regular shutdown and a 500
status while keeping the thread.

Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-CDG8FSAB.

* fix: initialize ext/date's last_errors when a thread starts

php_request_shutdown() runs the post-deactivate hooks even when the
startup failed before the modules' RINIT ran. On the first request of a
new thread, ext/date then freed DATEG(last_errors), which its GINIT
leaves uninitialized, so a POST exhausting memory_limit while $_POST
was built still crashed the process.

Initialize the global when the thread starts. Running RINIT before the
shutdown instead would run it twice for the modules activated before a
failing one, which deadlocks with session.auto_start. PHP fixes this in
8.4.28 and 8.5.13 (php/php-src#25127), but the older versions
FrankenPHP supports still need it.

Reported by Daybreak Red.
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.

4 participants