Skip to content

Introduce new zend_module_entry hook: child_startup_func #13551

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ PHP 8.4 UPGRADE NOTES
object. This is no longer possible, and cloning a DOMXPath object now throws
an error.

- FFI:
. Using FFI::load() during preloading ("opcache.preload") is not supported
anymore. Use "ffi.preload" instead.

- MBString:
. mb_encode_numericentity() and mb_decode_numericentity() now check that
the $map is only composed of integers, if not a ValueError is thrown.
Expand Down
10 changes: 10 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ PHP 8.4 INTERNALS UPGRADE NOTES
* The inet_aton() and win32/inet.h on Windows have been removed. Use Windows
native inet_pton() from ws2tcpip.h.

* It is recommended that extensions initializing external libraries or starting
threads during module startup (MINIT) do so in the child startup hook (CHINIT)
instead. A child startup hook can be declared by using the
PHP_MODULE_SET_CHINIT_FUNC() macro during module startup. See
ext/curl/interface.c for an example.

========================
2. Build system changes
========================
Expand Down Expand Up @@ -147,3 +153,7 @@ PHP 8.4 INTERNALS UPGRADE NOTES
========================
5. SAPI changes
========================

* SAPIs must call php_module_child_startup() once per request-handling process,
any time before handling the first request of the process, and must not fork
(without exec()) after that.
117 changes: 117 additions & 0 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@
#include "zend_observer.h"

#include <stdarg.h>
#if ZEND_DEBUG
# if __linux__
# include <sys/types.h>
# include <dirent.h>
# elif __APPLE__
# include <mach/mach_init.h>
# include <mach/task.h>
# include <mach/vm_map.h>
# endif /* __APPLE__ */
#endif

/* these variables are true statics/globals, and have to be mutex'ed on every access */
ZEND_API HashTable module_registry;
Expand Down Expand Up @@ -2292,6 +2302,64 @@ ZEND_API void add_property_zval_ex(zval *arg, const char *key, size_t key_len, z
}
/* }}} */

#if ZEND_DEBUG
# if __linux__
static int zend_thread_count(void)
{
pid_t pid = getpid();
char path[64];
int len = snprintf(path, sizeof(path), "/proc/%d/task", (int)pid);
if (len == -1 || len >= sizeof(path)) {
return -1;
}

DIR *d = opendir(path);
if (d == NULL) {
return -1;
}

struct dirent *entry;
int nthreads = 0;
while ((entry = readdir(d))){
nthreads++;
}

closedir(d);

return nthreads;
}
# elif __APPLE__
static int zend_thread_count(void)
{
pid_t pid = getpid();
mach_port_t me = mach_task_self();
mach_port_t task;
kern_return_t res;
thread_array_t threads;
mach_msg_type_number_t nthreads;

res = task_for_pid(me, pid, &task);
if (res != KERN_SUCCESS) {
return -1;
}

res = task_threads(task, &threads, &nthreads);
if (res != KERN_SUCCESS) {
return -1;
}

vm_deallocate(me, (vm_address_t)threads, nthreads * sizeof(*threads));

return (int) nthreads;
}
# else
static int zend_thread_count(void)
{
return 0;
}
# endif /* __APPLE__ */
#endif

ZEND_API zend_result zend_startup_module_ex(zend_module_entry *module) /* {{{ */
{
size_t name_len;
Expand Down Expand Up @@ -2338,14 +2406,29 @@ ZEND_API zend_result zend_startup_module_ex(zend_module_entry *module) /* {{{ */
#endif
}
if (module->module_startup_func) {
#if ZEND_DEBUG
int thread_count = zend_thread_count();
#endif
EG(current_module) = module;
if (module->module_startup_func(module->type, module->module_number)==FAILURE) {
zend_error_noreturn(E_CORE_ERROR,"Unable to start %s module", module->name);
EG(current_module) = NULL;
return FAILURE;
}
EG(current_module) = NULL;
#if ZEND_DEBUG
if (zend_thread_count() > thread_count) {
zend_error_noreturn(E_CORE_ERROR,
"Thread count increased during the initialization of %s"
" module. This is unsafe as the process may be forked after"
" initialization. Modules whishing to start threads should"
" do so in child_startup_func.",
module->name);
abort();
}
#endif
}

return SUCCESS;
}
/* }}} */
Expand Down Expand Up @@ -2491,6 +2574,40 @@ ZEND_API void zend_destroy_modules(void) /* {{{ */
}
/* }}} */

ZEND_API void zend_child_startup_modules(void) /* {{{ */
{
zend_module_entry *module;

ZEND_HASH_MAP_FOREACH_PTR(&module_registry, module) {
if (module->child_startup_func) {
if (module->child_startup_func(module->type, module->module_number)==FAILURE) {
zend_error(E_CORE_ERROR, "child_startup() for %s module failed", module->name);
exit(1);
}
}
} ZEND_HASH_FOREACH_END();
}
/* }}} */

ZEND_API void zend_module_set_child_startup_func(INIT_FUNC_ARGS, zend_result (*child_startup_func)(INIT_FUNC_ARGS)) /* {{{ */
{
if (!EG(current_module)) {
zend_error(E_CORE_ERROR, "Can not set child startup func after startup");
abort();
}

if (EG(current_module)->module_number != module_number) {
zend_error(E_CORE_ERROR,
"Invalid module_number passed to zend_set_child_startup_func"
" during statup of %s module",
EG(current_module)->name);
abort();
}

EG(current_module)->child_startup_func = child_startup_func;
}
/* }}} */

ZEND_API zend_module_entry* zend_register_module_ex(zend_module_entry *module, int module_type) /* {{{ */
{
size_t name_len;
Expand Down
7 changes: 7 additions & 0 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ typedef struct _zend_fcall_info_cache {
/* Name macros */
#define ZEND_MODULE_STARTUP_N(module) zm_startup_##module
#define ZEND_MODULE_SHUTDOWN_N(module) zm_shutdown_##module
#define ZEND_MODULE_CHILD_STARTUP_N(module) zm_child_startup_##module
#define ZEND_MODULE_ACTIVATE_N(module) zm_activate_##module
#define ZEND_MODULE_DEACTIVATE_N(module) zm_deactivate_##module
#define ZEND_MODULE_POST_ZEND_DEACTIVATE_N(module) zm_post_zend_deactivate_##module
Expand All @@ -231,6 +232,7 @@ typedef struct _zend_fcall_info_cache {
/* Declaration macros */
#define ZEND_MODULE_STARTUP_D(module) zend_result ZEND_MODULE_STARTUP_N(module)(INIT_FUNC_ARGS)
#define ZEND_MODULE_SHUTDOWN_D(module) zend_result ZEND_MODULE_SHUTDOWN_N(module)(SHUTDOWN_FUNC_ARGS)
#define ZEND_MODULE_CHILD_STARTUP_D(module) zend_result ZEND_MODULE_CHILD_STARTUP_N(module)(INIT_FUNC_ARGS)
#define ZEND_MODULE_ACTIVATE_D(module) zend_result ZEND_MODULE_ACTIVATE_N(module)(INIT_FUNC_ARGS)
#define ZEND_MODULE_DEACTIVATE_D(module) zend_result ZEND_MODULE_DEACTIVATE_N(module)(SHUTDOWN_FUNC_ARGS)
#define ZEND_MODULE_POST_ZEND_DEACTIVATE_D(module) zend_result ZEND_MODULE_POST_ZEND_DEACTIVATE_N(module)(void)
Expand All @@ -243,6 +245,9 @@ typedef struct _zend_fcall_info_cache {
ZEND_DLEXPORT zend_module_entry *get_module(void) { return &name##_module_entry; }\
END_EXTERN_C()

#define ZEND_MODULE_SET_CHILD_STARTUP_FUNC(func) \
zend_module_set_child_startup_func(INIT_FUNC_ARGS_PASSTHRU, func)

#define ZEND_BEGIN_MODULE_GLOBALS(module_name) \
typedef struct _zend_##module_name##_globals {
#define ZEND_END_MODULE_GLOBALS(module_name) \
Expand Down Expand Up @@ -384,6 +389,8 @@ ZEND_API zend_result zend_startup_module_ex(zend_module_entry *module);
ZEND_API void zend_startup_modules(void);
ZEND_API void zend_collect_module_handlers(void);
ZEND_API void zend_destroy_modules(void);
ZEND_API void zend_child_startup_modules(void);
ZEND_API void zend_module_set_child_startup_func(INIT_FUNC_ARGS, zend_result (*child_startup_func)(INIT_FUNC_ARGS));
ZEND_API void zend_check_magic_method_implementation(
const zend_class_entry *ce, const zend_function *fptr, zend_string *lcname, int error_type);
ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, zend_string *lcname);
Expand Down
8 changes: 7 additions & 1 deletion Zend/zend_modules.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

#define ZEND_MODULE_BUILD_ID "API" ZEND_TOSTR(ZEND_MODULE_API_NO) ZEND_BUILD_TS ZEND_BUILD_DEBUG ZEND_BUILD_SYSTEM ZEND_BUILD_EXTRA

#define STANDARD_MODULE_PROPERTIES_EX 0, 0, NULL, 0, ZEND_MODULE_BUILD_ID
#define STANDARD_MODULE_PROPERTIES_EX NULL, 0, 0, NULL, 0, ZEND_MODULE_BUILD_ID

#define NO_MODULE_GLOBALS 0, NULL, NULL, NULL

Expand Down Expand Up @@ -77,8 +77,12 @@ struct _zend_module_entry {
const struct _zend_module_dep *deps;
const char *name;
const struct _zend_function_entry *functions;
/* Called once per process, excluding forked processes. This function
* must not start threads. Calling external libraries from this function is
* not recommended. */
zend_result (*module_startup_func)(INIT_FUNC_ARGS);
zend_result (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
/* Called once per request */
zend_result (*request_startup_func)(INIT_FUNC_ARGS);
zend_result (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
Expand All @@ -92,6 +96,8 @@ struct _zend_module_entry {
void (*globals_ctor)(void *global);
void (*globals_dtor)(void *global);
zend_result (*post_deactivate_func)(void);
/* Called once per request-handling process */
zend_result (*child_startup_func)(INIT_FUNC_ARGS);
int module_started;
unsigned char type;
void *handle;
Expand Down
16 changes: 12 additions & 4 deletions ext/curl/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -365,10 +365,22 @@ PHP_MINFO_FUNCTION(curl)
}
/* }}} */

/* {{{ PHP_CHINIT_FUNCTION */
PHP_CHINIT_FUNCTION(curl)
{
if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
return FAILURE;
}

return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(curl)
{
REGISTER_INI_ENTRIES();
PHP_MODULE_SET_CHINIT_FUNC(PHP_CHINIT(curl));

register_curl_symbols(module_number);

Expand All @@ -390,10 +402,6 @@ PHP_MINIT_FUNCTION(curl)
}
#endif

if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
return FAILURE;
}

curl_ce = register_class_CurlHandle();
curl_ce->create_object = curl_create_object;
curl_ce->default_object_handlers = &curl_object_handlers;
Expand Down
2 changes: 1 addition & 1 deletion ext/ffi/ffi.c
Original file line number Diff line number Diff line change
Expand Up @@ -3537,7 +3537,7 @@ ZEND_METHOD(FFI, load) /* {{{ */
ZEND_PARSE_PARAMETERS_END();

if (CG(compiler_options) & ZEND_COMPILE_PRELOAD_IN_CHILD) {
zend_throw_error(zend_ffi_exception_ce, "FFI::load() doesn't work in conjunction with \"opcache.preload_user\". Use \"ffi.preload\" instead.");
zend_throw_error(zend_ffi_exception_ce, "FFI::load() doesn't work in conjunction with \"opcache.preload\". Use \"ffi.preload\" instead.");
RETURN_THROWS();
}

Expand Down
25 changes: 0 additions & 25 deletions ext/ffi/tests/300.phpt

This file was deleted.

9 changes: 1 addition & 8 deletions ext/ffi/tests/bug78761.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,8 @@
Bug #78761 (Zend memory heap corruption with preload and casting)
--EXTENSIONS--
ffi
posix
--SKIPIF--
<?php
if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
if (posix_geteuid() == 0) die('skip Cannot run test as root.');
?>
--INI--
opcache.enable_cli=1
opcache.preload={PWD}/bug78761_preload.php
ffi.preload={PWD}/bug78761_preload.h
--FILE--
<?php
try {
Expand Down
3 changes: 0 additions & 3 deletions ext/ffi/tests/bug78761_preload.php

This file was deleted.

Loading