php-src/main/php_ticks.c
Niels Dossche 51faf04dbd Fix GH-10737: PHP 8.1.16 segfaults on line 597 of sapi/apache2handler/sapi_apache2.c
The TSRM keeps a hashtable mapping the thread IDs to the thread resource pointers.
It's possible that the thread disappears without us knowing, and then another thread
gets spawned some time later with the same ID as the disappeared thread.
Note that since it's a new thread the TSRM key pointer and cached pointer will be NULL.

The Apache request handler `php_handler()` will try to fetch some fields from the SAPI globals.
It uses a lazy thread resource allocation by calling `ts_resource(0);`.
This allocates a thread resource and sets up the TSRM pointers if they haven't been set up yet.

At least, that's what's supposed to happen. But since we are in a situation where the thread ID
still has the resources of the *old* thread associated in the hashtable,
the loop in `ts_resource_ex` will find that thread resource and assume the thread has been setup
already. But this is not the case since this thread is actually a new thread, just reusing the ID
of the old one, without any relation whatsoever to the old thread.
Because of this assumption, the TSRM pointers will not be setup, leading to a
NULL pointer dereference when trying to access the SAPI globals.

We can easily detect this scenario: if we're in the fallback path, and the pointer is NULL,
and we're looking for our own thread resource, we know we're actually reusing a thread ID.
In that case, we'll free up the old thread resources gracefully (gracefully because
there might still be resources open like database connection which need to be
shut down cleanly). After freeing the resources, we'll create the new resources for
this thread as if the stale resources never existed in the first place.
From that point forward, it is as if that situation never occurred.
The fact that this situation happens isn't that bad because a child process containing
threads will eventually be respawned anyway by the SAPI, so the stale thread resources
won't remain forever.

Note that we can't simply assign our own TSRM pointers to the existing
thread resource for our ID, since it was actually from a different thread
(just with the same ID!). Furthermore, the dynamically loaded extensions
have their own pointer, which is only set when their constructor is
called, so we'd have to call their constructor anyway...
I also tried to call the dtor and then the ctor again for those resources
on the pre-existing thread resource to reuse storage, but that didn't work properly
because other code doesn't expect something like that to happen, which breaks assumptions,
and this in turn caused Valgrind to (rightfully) complain about memory bugs.

Note 2: I also had to fix a bug in the core globals destruction because it
always assumed that the thread destroying them was the owning thread,
which on TSRM shutdown isn't always the case. A similar bug was fixed
recently with the JIT globals.

Closes GH-10863.
2023-04-08 16:34:07 +02:00

71 lines
2.3 KiB
C

/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Stig Bakken <ssb@php.net> |
+----------------------------------------------------------------------+
*/
#include "php.h"
#include "php_ticks.h"
struct st_tick_function
{
void (*func)(int, void *);
void *arg;
};
int php_startup_ticks(void)
{
zend_llist_init(&PG(tick_functions), sizeof(struct st_tick_function), NULL, 1);
return SUCCESS;
}
void php_deactivate_ticks(void)
{
zend_llist_clean(&PG(tick_functions));
}
void php_shutdown_ticks(php_core_globals *core_globals)
{
zend_llist_destroy(&core_globals->tick_functions);
}
static int php_compare_tick_functions(void *elem1, void *elem2)
{
struct st_tick_function *e1 = (struct st_tick_function *)elem1;
struct st_tick_function *e2 = (struct st_tick_function *)elem2;
return e1->func == e2->func && e1->arg == e2->arg;
}
PHPAPI void php_add_tick_function(void (*func)(int, void*), void * arg)
{
struct st_tick_function tmp = {func, arg};
zend_llist_add_element(&PG(tick_functions), (void *)&tmp);
}
PHPAPI void php_remove_tick_function(void (*func)(int, void *), void * arg)
{
struct st_tick_function tmp = {func, arg};
zend_llist_del_element(&PG(tick_functions), (void *)&tmp, (int(*)(void*, void*))php_compare_tick_functions);
}
static void php_tick_iterator(void *d, void *arg)
{
struct st_tick_function *data = (struct st_tick_function *)d;
data->func(*((int *)arg), data->arg);
}
void php_run_ticks(int count)
{
zend_llist_apply_with_argument(&PG(tick_functions), (llist_apply_with_arg_func_t) php_tick_iterator, &count);
}