From 126ef836a46b69c582b67a692cf8a15b2e99c68d Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Sat, 24 Oct 2026 16:06:52 +0200 Subject: [PATCH 1/8] Add per-extension child initialization hooks and reset session GC state after fork --- UPGRADING.INTERNALS | 6 ++ Zend/zend_modules.h | 9 ++- ext/session/session.c | 7 ++- ext/session/tests/gh22616.phpt | 80 ++++++++++++++++++++++++++ ext/zend_test/php_test.h | 1 + ext/zend_test/test.c | 14 ++++- ext/zend_test/test.stub.php | 2 + ext/zend_test/test_arginfo.h | 19 ++---- ext/zend_test/test_decl.h | 8 +-- ext/zend_test/test_legacy_arginfo.h | 19 ++---- ext/zend_test/tests/child_init.phpt | 39 +++++++++++++ main/main.c | 8 +++ sapi/fpm/tests/gh22616-child-init.phpt | 43 ++++++++++++++ 13 files changed, 219 insertions(+), 36 deletions(-) create mode 100644 ext/session/tests/gh22616.phpt create mode 100644 ext/zend_test/tests/child_init.phpt create mode 100644 sapi/fpm/tests/gh22616-child-init.phpt diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index b8ed6552d4f2..144b4aaa1e73 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -14,6 +14,12 @@ PHP 8.7 INTERNALS UPGRADE NOTES 1. Internal API changes ======================== +- Added zend_module_entry.child_init_func, an optional void callback invoked by + php_child_init() in the child process after core child initialization. It runs + in module dependency order for started modules and may run before a request + starts or during an active request. Use + STANDARD_MODULE_PROPERTIES_EX_WITH_CHILD_INIT(callback) to initialize it. + Modules without a callback continue to use STANDARD_MODULE_PROPERTIES_EX. - Removed the ZEND_LOG_XOR() macro. - Removed the ZEND_TRUTH() macro. - Removed zend_execute_scripts(). Manually call zend_execute_script() in a loop diff --git a/Zend/zend_modules.h b/Zend/zend_modules.h index bb3050927992..222295582db5 100644 --- a/Zend/zend_modules.h +++ b/Zend/zend_modules.h @@ -30,7 +30,7 @@ #define ZEND_MODULE_INFO_FUNC_ARGS zend_module_entry *zend_module #define ZEND_MODULE_INFO_FUNC_ARGS_PASSTHRU zend_module -#define ZEND_MODULE_API_NO 20260925 +#define ZEND_MODULE_API_NO 20261024 #ifdef ZTS #define USING_ZTS 1 #else @@ -45,7 +45,11 @@ #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_WITH_CHILD_INIT(child_init) \ + 0, 0, NULL, 0, ZEND_MODULE_BUILD_ID, child_init + +#define STANDARD_MODULE_PROPERTIES_EX \ + STANDARD_MODULE_PROPERTIES_EX_WITH_CHILD_INIT(NULL) #define NO_MODULE_GLOBALS 0, NULL, NULL, NULL @@ -96,6 +100,7 @@ struct _zend_module_entry { void *handle; int module_number; const char *build_id; + void (*child_init_func)(void); }; #define MODULE_DEP_REQUIRED 1 diff --git a/ext/session/session.c b/ext/session/session.c index 702b56020ae9..43284abaa565 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -3454,6 +3454,11 @@ static zend_result php_session_rfc1867_callback(unsigned int event, void *event_ return retval; } +static void php_session_child_init(void) +{ + PS(random_seeded) = false; +} + zend_module_entry session_module_entry = { STANDARD_MODULE_HEADER, "session", @@ -3466,7 +3471,7 @@ zend_module_entry session_module_entry = { PHP_GINIT(ps), NULL, NULL, - STANDARD_MODULE_PROPERTIES_EX + STANDARD_MODULE_PROPERTIES_EX_WITH_CHILD_INIT(php_session_child_init) }; #ifdef COMPILE_DL_SESSION diff --git a/ext/session/tests/gh22616.phpt b/ext/session/tests/gh22616.phpt new file mode 100644 index 000000000000..6eb3acb1bd91 --- /dev/null +++ b/ext/session/tests/gh22616.phpt @@ -0,0 +1,80 @@ +--TEST-- +GH-22616: Session GC randomness is independent after a fork +--EXTENSIONS-- +session +pcntl +--INI-- +session.gc_probability=1 +session.gc_divisor=2 +session.use_cookies=0 +session.cache_limiter= +--FILE-- +collected = true; + return 0; + } +}; +session_set_save_handler($handler); +session_id('gh22616'); + +function sequence(): string { + global $handler; + if (session_status() === PHP_SESSION_ACTIVE) { + session_write_close(); + } + $result = ''; + for ($i = 0; $i < 256; $i++) { + $handler->collected = false; + session_start(); + session_write_close(); + $result .= (int) $handler->collected; + } + return $result; +} + +$results = []; +foreach (['unseeded', 'seeded', 'active'] as $state) { + if ($state === 'active') { + session_start(); + } + $file = tempnam(sys_get_temp_dir(), 'gh22616'); + $pid = pcntl_fork(); + if ($pid === -1) { + throw new Exception('fork failed'); + } + if ($pid === 0) { + file_put_contents($file, sequence()); + exit; + } + $parent = sequence(); + pcntl_waitpid($pid, $status); + $child = file_get_contents($file); + unlink($file); + $results[$state] = pcntl_wifexited($status) + && pcntl_wexitstatus($status) === 0 + && strlen($child) === 256 + && $parent !== $child; +} +var_dump($results); +?> +--EXPECT-- +array(3) { + ["unseeded"]=> + bool(true) + ["seeded"]=> + bool(true) + ["active"]=> + bool(true) +} diff --git a/ext/zend_test/php_test.h b/ext/zend_test/php_test.h index 591121b4c02f..454210adafec 100644 --- a/ext/zend_test/php_test.h +++ b/ext/zend_test/php_test.h @@ -29,6 +29,7 @@ ZEND_TSRMLS_CACHE_EXTERN() #endif ZEND_BEGIN_MODULE_GLOBALS(zend_test) + zend_long child_init_count; int observer_enabled; int observer_show_output; int observer_observe_all; diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 661cba8b8c1e..16c9cb150e03 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -1615,6 +1615,13 @@ static PHP_INI_MH(OnUpdateZendTestObserveOplineInZendMM) return OnUpdateBool(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); } +static ZEND_FUNCTION(zend_test_child_init_count) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + RETURN_LONG(ZT_G(child_init_count)); +} + static ZEND_FUNCTION(zend_test_fill_packed_array) { HashTable *parameter; @@ -2554,6 +2561,11 @@ PHP_MINFO_FUNCTION(zend_test) DISPLAY_INI_ENTRIES(); } +static void zend_test_child_init(void) +{ + ZT_G(child_init_count)++; +} + zend_module_entry zend_test_module_entry = { STANDARD_MODULE_HEADER, "zend_test", @@ -2568,7 +2580,7 @@ zend_module_entry zend_test_module_entry = { PHP_GINIT(zend_test), PHP_GSHUTDOWN(zend_test), NULL, - STANDARD_MODULE_PROPERTIES_EX + STANDARD_MODULE_PROPERTIES_EX_WITH_CHILD_INIT(zend_test_child_init) }; #ifdef COMPILE_DL_ZEND_TEST diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 8719c6935a33..4d5ceeeffa71 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -282,6 +282,8 @@ function zend_test_nodiscard(): int {} #[\NoDiscard(message: "custom message 2")] function zend_test_deprecated_nodiscard(): int {} + function zend_test_child_init_count(): int {} + /** @alias zend_test_void_return */ function zend_test_aliased(): void {} diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index d6fa8896c42b..20616b20dda0 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 55bb2e6d209ea4030e3edfd604fc5ce4d14267f0 + * Stub hash: 1b25893580205fc2db824a265839da14f5d2f0f0 * Has decl header: yes */ #include "zend_attributes.h" @@ -20,10 +20,6 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_void_return, 0, 0, IS_VOID, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_forward_reference, 1, 1, IS_MIXED, 0) - ZEND_ARG_TYPE_INFO(1, value, IS_MIXED, 0) -ZEND_END_ARG_INFO() - ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_compile_string, 0, 3, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, source_string, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) @@ -41,6 +37,8 @@ ZEND_END_ARG_INFO() #define arginfo_zend_test_deprecated_nodiscard arginfo_zend_test_nodiscard +#define arginfo_zend_test_child_init_count arginfo_zend_test_nodiscard + #define arginfo_zend_test_aliased arginfo_zend_test_void_return #define arginfo_zend_test_deprecated_aliased arginfo_zend_test_void_return @@ -363,11 +361,6 @@ ZEND_END_ARG_INFO() #define arginfo_zend_new_singleton_array arginfo_zend_test_array_return -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_set_local_var, 0, 2, _IS_BOOL, 0) - ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) - ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) -ZEND_END_ARG_INFO() - #define arginfo_ZendTestNS2_namespaced_func arginfo_zend_test_is_pcre_bundled #define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_test_void_return @@ -461,12 +454,12 @@ static ZEND_FUNCTION(zend_trigger_bailout); static ZEND_FUNCTION(zend_test_array_return); static ZEND_FUNCTION(zend_test_nullable_array_return); static ZEND_FUNCTION(zend_test_void_return); -static ZEND_FUNCTION(zend_test_forward_reference); static ZEND_FUNCTION(zend_test_compile_string); static ZEND_FUNCTION(zend_test_deprecated); static ZEND_FUNCTION(zend_test_deprecated_attr); static ZEND_FUNCTION(zend_test_nodiscard); static ZEND_FUNCTION(zend_test_deprecated_nodiscard); +static ZEND_FUNCTION(zend_test_child_init_count); static ZEND_FUNCTION(zend_create_unterminated_string); static ZEND_FUNCTION(zend_terminate_string); static ZEND_FUNCTION(zend_leak_variable); @@ -566,7 +559,6 @@ static ZEND_FUNCTION(zend_test_uri_parser); static ZEND_FUNCTION(zend_test_gh19792); static ZEND_FUNCTION(zend_test_handle_post_data); static ZEND_FUNCTION(zend_new_singleton_array); -static ZEND_FUNCTION(zend_test_set_local_var); static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); @@ -611,7 +603,6 @@ static const zend_function_entry ext_functions[] = { #endif #endif ZEND_FE(zend_test_void_return, arginfo_zend_test_void_return) - ZEND_FE(zend_test_forward_reference, arginfo_zend_test_forward_reference) ZEND_FE(zend_test_compile_string, arginfo_zend_test_compile_string) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY("zend_test_deprecated", zif_zend_test_deprecated, arginfo_zend_test_deprecated, ZEND_ACC_DEPRECATED, NULL, NULL) @@ -641,6 +632,7 @@ static const zend_function_entry ext_functions[] = { #else ZEND_RAW_FENTRY("zend_test_deprecated_nodiscard", zif_zend_test_deprecated_nodiscard, arginfo_zend_test_deprecated_nodiscard, ZEND_ACC_DEPRECATED) #endif + ZEND_FE(zend_test_child_init_count, arginfo_zend_test_child_init_count) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY("zend_test_aliased", zif_zend_test_void_return, arginfo_zend_test_aliased, 0, NULL, NULL) #else @@ -758,7 +750,6 @@ static const zend_function_entry ext_functions[] = { #endif ZEND_FE(zend_test_handle_post_data, arginfo_zend_test_handle_post_data) ZEND_FE(zend_new_singleton_array, arginfo_zend_new_singleton_array) - ZEND_FE(zend_test_set_local_var, arginfo_zend_test_set_local_var) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY(ZEND_NS_NAME("ZendTestNS2", "namespaced_func"), zif_ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func, 0, NULL, NULL) #else diff --git a/ext/zend_test/test_decl.h b/ext/zend_test/test_decl.h index 35e929d1a306..87d1cc26286d 100644 --- a/ext/zend_test/test_decl.h +++ b/ext/zend_test/test_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 55bb2e6d209ea4030e3edfd604fc5ce4d14267f0 */ + * Stub hash: 1b25893580205fc2db824a265839da14f5d2f0f0 */ -#ifndef ZEND_TEST_DECL_55bb2e6d209ea4030e3edfd604fc5ce4d14267f0_H -#define ZEND_TEST_DECL_55bb2e6d209ea4030e3edfd604fc5ce4d14267f0_H +#ifndef ZEND_TEST_DECL_1b25893580205fc2db824a265839da14f5d2f0f0_H +#define ZEND_TEST_DECL_1b25893580205fc2db824a265839da14f5d2f0f0_H typedef enum zend_enum_ZendTestUnitEnum { ZEND_ENUM_ZendTestUnitEnum_Foo = 1, @@ -27,4 +27,4 @@ typedef enum zend_enum_ZendTestEnumWithInterface { ZEND_ENUM_ZendTestEnumWithInterface_Bar = 2, } zend_enum_ZendTestEnumWithInterface; -#endif /* ZEND_TEST_DECL_55bb2e6d209ea4030e3edfd604fc5ce4d14267f0_H */ +#endif /* ZEND_TEST_DECL_1b25893580205fc2db824a265839da14f5d2f0f0_H */ diff --git a/ext/zend_test/test_legacy_arginfo.h b/ext/zend_test/test_legacy_arginfo.h index 9bb20c85e95a..7b5efcf05218 100644 --- a/ext/zend_test/test_legacy_arginfo.h +++ b/ext/zend_test/test_legacy_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 55bb2e6d209ea4030e3edfd604fc5ce4d14267f0 + * Stub hash: 1b25893580205fc2db824a265839da14f5d2f0f0 * Has decl header: yes */ #include "zend_constants.h" @@ -16,10 +16,6 @@ ZEND_END_ARG_INFO() #define arginfo_zend_test_void_return arginfo_zend_trigger_bailout -ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_test_forward_reference, 0, 1, 1) - ZEND_ARG_INFO(1, value) -ZEND_END_ARG_INFO() - ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_test_compile_string, 0, 0, 3) ZEND_ARG_INFO(0, source_string) ZEND_ARG_INFO(0, filename) @@ -36,6 +32,8 @@ ZEND_END_ARG_INFO() #define arginfo_zend_test_deprecated_nodiscard arginfo_zend_trigger_bailout +#define arginfo_zend_test_child_init_count arginfo_zend_trigger_bailout + #define arginfo_zend_test_aliased arginfo_zend_trigger_bailout #define arginfo_zend_test_deprecated_aliased arginfo_zend_trigger_bailout @@ -300,11 +298,6 @@ ZEND_END_ARG_INFO() #define arginfo_zend_new_singleton_array arginfo_zend_trigger_bailout -ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_test_set_local_var, 0, 0, 2) - ZEND_ARG_INFO(0, name) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - #define arginfo_ZendTestNS2_namespaced_func arginfo_zend_trigger_bailout #define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_trigger_bailout @@ -385,12 +378,12 @@ static ZEND_FUNCTION(zend_trigger_bailout); static ZEND_FUNCTION(zend_test_array_return); static ZEND_FUNCTION(zend_test_nullable_array_return); static ZEND_FUNCTION(zend_test_void_return); -static ZEND_FUNCTION(zend_test_forward_reference); static ZEND_FUNCTION(zend_test_compile_string); static ZEND_FUNCTION(zend_test_deprecated); static ZEND_FUNCTION(zend_test_deprecated_attr); static ZEND_FUNCTION(zend_test_nodiscard); static ZEND_FUNCTION(zend_test_deprecated_nodiscard); +static ZEND_FUNCTION(zend_test_child_init_count); static ZEND_FUNCTION(zend_create_unterminated_string); static ZEND_FUNCTION(zend_terminate_string); static ZEND_FUNCTION(zend_leak_variable); @@ -490,7 +483,6 @@ static ZEND_FUNCTION(zend_test_uri_parser); static ZEND_FUNCTION(zend_test_gh19792); static ZEND_FUNCTION(zend_test_handle_post_data); static ZEND_FUNCTION(zend_new_singleton_array); -static ZEND_FUNCTION(zend_test_set_local_var); static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); @@ -527,7 +519,6 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_array_return, arginfo_zend_test_array_return) ZEND_FE(zend_test_nullable_array_return, arginfo_zend_test_nullable_array_return) ZEND_FE(zend_test_void_return, arginfo_zend_test_void_return) - ZEND_FE(zend_test_forward_reference, arginfo_zend_test_forward_reference) ZEND_FE(zend_test_compile_string, arginfo_zend_test_compile_string) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY("zend_test_deprecated", zif_zend_test_deprecated, arginfo_zend_test_deprecated, ZEND_ACC_DEPRECATED, NULL, NULL) @@ -537,6 +528,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_deprecated_attr, arginfo_zend_test_deprecated_attr) ZEND_FE(zend_test_nodiscard, arginfo_zend_test_nodiscard) ZEND_FE(zend_test_deprecated_nodiscard, arginfo_zend_test_deprecated_nodiscard) + ZEND_FE(zend_test_child_init_count, arginfo_zend_test_child_init_count) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY("zend_test_aliased", zif_zend_test_void_return, arginfo_zend_test_aliased, 0, NULL, NULL) #else @@ -646,7 +638,6 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_gh19792, arginfo_zend_test_gh19792) ZEND_FE(zend_test_handle_post_data, arginfo_zend_test_handle_post_data) ZEND_FE(zend_new_singleton_array, arginfo_zend_new_singleton_array) - ZEND_FE(zend_test_set_local_var, arginfo_zend_test_set_local_var) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY(ZEND_NS_NAME("ZendTestNS2", "namespaced_func"), zif_ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func, 0, NULL, NULL) #else diff --git a/ext/zend_test/tests/child_init.phpt b/ext/zend_test/tests/child_init.phpt new file mode 100644 index 000000000000..60fd93ff8101 --- /dev/null +++ b/ext/zend_test/tests/child_init.phpt @@ -0,0 +1,39 @@ +--TEST-- +GH-22616: Extension child initialization runs once per fork in the child +--EXTENSIONS-- +zend_test +pcntl +--FILE-- + +--EXPECT-- +int(0) +int(1) +int(2) +bool(true) +int(1) +bool(true) +int(0) diff --git a/main/main.c b/main/main.c index 1e4acfc8aa98..3508df8db788 100644 --- a/main/main.c +++ b/main/main.c @@ -1844,9 +1844,17 @@ static void sigchld_handler(int apar) PHPAPI void php_child_init(void) { + zend_module_entry *module; + refresh_memory_manager(); zend_max_execution_timer_init(); php_clear_stat_cache(true, NULL, 0); + + ZEND_HASH_MAP_FOREACH_PTR(&module_registry, module) { + if (module->module_started && module->child_init_func) { + module->child_init_func(); + } + } ZEND_HASH_FOREACH_END(); } /* {{{ php_request_startup */ diff --git a/sapi/fpm/tests/gh22616-child-init.phpt b/sapi/fpm/tests/gh22616-child-init.phpt new file mode 100644 index 000000000000..e9acab083950 --- /dev/null +++ b/sapi/fpm/tests/gh22616-child-init.phpt @@ -0,0 +1,43 @@ +--TEST-- +GH-22616: Extension child initialization runs before FPM requests +--EXTENSIONS-- +zend_test +--SKIPIF-- + +--FILE-- +start(extensions: ['zend_test']); +$tester->expectLogStartNotices(); +for ($i = 0; $i < 5; $i++) { + $tester->request()->expectBody('1'); +} +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); +?> +Done +--EXPECT-- +Done +--CLEAN-- + From 7abe3f062aa4f4673db6ac71e4b23402ba2e6394 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Mon, 26 Oct 2026 22:40:50 +0100 Subject: [PATCH 2/8] Seed session GC randomness during GINIT --- ext/session/php_session.h | 2 -- ext/session/session.c | 25 +++++++++++-------------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/ext/session/php_session.h b/ext/session/php_session.h index ee36eb291e52..7711d9432dc3 100644 --- a/ext/session/php_session.h +++ b/ext/session/php_session.h @@ -183,8 +183,6 @@ typedef struct _php_ps_globals { bool mod_user_is_open; bool mod_user_uses_object_methods_as_handlers; bool use_trans_sid; /* contains the INI value of whether to use trans-sid */ - bool random_seeded; - bool in_rfc1867_update; /* php_session_rfc1867_update() is running */ } php_ps_globals; diff --git a/ext/session/session.c b/ext/session/session.c index 43284abaa565..e046c735c2cf 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -409,18 +409,6 @@ static zend_long php_session_gc(bool immediate) if ((PS(mod_data) || PS(mod_user_implemented))) { /* Use probability-based GC if not forced and probability is configured */ if (!collect && PS(gc_probability) > 0) { - /* Seed lazily on first GC draw per process. */ - if (UNEXPECTED(!PS(random_seeded))) { - php_random_uint128_t seed; - if (php_random_bytes_silent(&seed, sizeof(seed)) == FAILURE) { - seed = php_random_uint128_constant( - php_random_generate_fallback_seed(), - php_random_generate_fallback_seed() - ); - } - php_random_pcgoneseq128xslrr64_seed128(PS(random).state, seed); - PS(random_seeded) = true; - } collect = php_random_range(PS(random), 0, PS(gc_divisor) - 1) < PS(gc_probability); } @@ -3019,7 +3007,15 @@ static PHP_GINIT_FUNCTION(ps) .algo = &php_random_algo_pcgoneseq128xslrr64, .state = &ps_globals->random_state, }; - ps_globals->random_seeded = false; + + php_random_uint128_t seed; + if (php_random_bytes_silent(&seed, sizeof(seed)) == FAILURE) { + seed = php_random_uint128_constant( + php_random_generate_fallback_seed(), + php_random_generate_fallback_seed() + ); + } + php_random_pcgoneseq128xslrr64_seed128(ps_globals->random.state, seed); } /* Interfaces extending the given one are not flattened into ce->interfaces before they are @@ -3456,7 +3452,8 @@ static zend_result php_session_rfc1867_callback(unsigned int event, void *event_ static void php_session_child_init(void) { - PS(random_seeded) = false; + /* Move the child's GC sequence forward without requesting new entropy. */ + php_random_pcgoneseq128xslrr64_advance(PS(random).state, 1); } zend_module_entry session_module_entry = { From ae60649f3ddc28f8835a6e24caba570d6a32c915 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Mon, 26 Oct 2026 22:47:21 +0100 Subject: [PATCH 3/8] Separate session GC streams across forked children --- ext/session/session.c | 5 ++-- ext/session/tests/gh22616.phpt | 43 ++++++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/ext/session/session.c b/ext/session/session.c index e046c735c2cf..7a7c79f85b94 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -3452,8 +3452,9 @@ static zend_result php_session_rfc1867_callback(unsigned int event, void *event_ static void php_session_child_init(void) { - /* Move the child's GC sequence forward without requesting new entropy. */ - php_random_pcgoneseq128xslrr64_advance(PS(random).state, 1); + /* Give each child a distinct, widely separated GC sequence without requesting new entropy. */ + uint64_t advance = (uint64_t) getpid() << 32; + php_random_pcgoneseq128xslrr64_advance(PS(random).state, advance); } zend_module_entry session_module_entry = { diff --git a/ext/session/tests/gh22616.phpt b/ext/session/tests/gh22616.phpt index 6eb3acb1bd91..13235dede567 100644 --- a/ext/session/tests/gh22616.phpt +++ b/ext/session/tests/gh22616.phpt @@ -49,23 +49,42 @@ foreach (['unseeded', 'seeded', 'active'] as $state) { if ($state === 'active') { session_start(); } - $file = tempnam(sys_get_temp_dir(), 'gh22616'); - $pid = pcntl_fork(); - if ($pid === -1) { + $file1 = tempnam(sys_get_temp_dir(), 'gh22616'); + $pid1 = pcntl_fork(); + if ($pid1 === -1) { throw new Exception('fork failed'); } - if ($pid === 0) { - file_put_contents($file, sequence()); + if ($pid1 === 0) { + file_put_contents($file1, sequence()); exit; } + + $file2 = tempnam(sys_get_temp_dir(), 'gh22616'); + $pid2 = pcntl_fork(); + if ($pid2 === -1) { + throw new Exception('fork failed'); + } + if ($pid2 === 0) { + file_put_contents($file2, sequence()); + exit; + } + $parent = sequence(); - pcntl_waitpid($pid, $status); - $child = file_get_contents($file); - unlink($file); - $results[$state] = pcntl_wifexited($status) - && pcntl_wexitstatus($status) === 0 - && strlen($child) === 256 - && $parent !== $child; + pcntl_waitpid($pid1, $status1); + pcntl_waitpid($pid2, $status2); + $child1 = file_get_contents($file1); + $child2 = file_get_contents($file2); + unlink($file1); + unlink($file2); + $results[$state] = pcntl_wifexited($status1) + && pcntl_wexitstatus($status1) === 0 + && pcntl_wifexited($status2) + && pcntl_wexitstatus($status2) === 0 + && strlen($child1) === 256 + && strlen($child2) === 256 + && $parent !== $child1 + && $parent !== $child2 + && $child1 !== $child2; } var_dump($results); ?> From 3d46385133ada822ebddfa979a94e6de345a2fff Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Fri, 30 Oct 2026 15:52:48 +0100 Subject: [PATCH 4/8] Add macros for child init hooks --- ext/session/session.c | 4 ++-- main/php.h | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/session/session.c b/ext/session/session.c index 7a7c79f85b94..4e5a84fd07c8 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -3450,7 +3450,7 @@ static zend_result php_session_rfc1867_callback(unsigned int event, void *event_ return retval; } -static void php_session_child_init(void) +PHP_CINIT_FUNCTION(session) { /* Give each child a distinct, widely separated GC sequence without requesting new entropy. */ uint64_t advance = (uint64_t) getpid() << 32; @@ -3469,7 +3469,7 @@ zend_module_entry session_module_entry = { PHP_GINIT(ps), NULL, NULL, - STANDARD_MODULE_PROPERTIES_EX_WITH_CHILD_INIT(php_session_child_init) + STANDARD_MODULE_PROPERTIES_EX_WITH_CHILD_INIT(PHP_CINIT(session)) }; #ifdef COMPILE_DL_SESSION diff --git a/main/php.h b/main/php.h index 89ceac346b50..012eaeb7e940 100644 --- a/main/php.h +++ b/main/php.h @@ -377,6 +377,8 @@ END_EXTERN_C() #define PHP_MINFO_FUNCTION ZEND_MODULE_INFO_D #define PHP_GINIT_FUNCTION ZEND_GINIT_FUNCTION #define PHP_GSHUTDOWN_FUNCTION ZEND_GSHUTDOWN_FUNCTION +#define PHP_CINIT_FUNCTION(module) static void php_##module##_child_init(void) +#define PHP_CINIT(module) php_##module##_child_init #define PHP_MODULE_GLOBALS ZEND_MODULE_GLOBALS From c46c7594af3234b6542706bdd8273770c294271e Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Fri, 30 Oct 2026 17:35:31 +0100 Subject: [PATCH 5/8] Fix zend_test atomic stores --- ext/zend_test/test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 16c9cb150e03..d2725d8cc0ac 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -2237,7 +2237,7 @@ static void (*zend_test_prev_interrupt_function)(zend_execute_data *execute_data * leaves an interrupt pending at all times. The VM must still make progress. */ static void zend_test_rearm_interrupt_function(zend_execute_data *execute_data) { - zend_atomic_bool_store_ex(&EG(vm_interrupt), true); + atomic_store(&EG(vm_interrupt), true); if (zend_test_prev_interrupt_function) { zend_test_prev_interrupt_function(execute_data); @@ -2515,7 +2515,7 @@ PHP_RINIT_FUNCTION(zend_test) zend_test_mm_custom_handlers_rinit(); if (ZT_G(rearm_vm_interrupt)) { - zend_atomic_bool_store_ex(&EG(vm_interrupt), true); + atomic_store(&EG(vm_interrupt), true); } return SUCCESS; From 21a39b75a85b056c36e4c060ddac6e67e92151ff Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Fri, 30 Oct 2026 17:47:46 +0100 Subject: [PATCH 6/8] Regenerate zend_test arginfo entries --- ext/zend_test/test_arginfo.h | 13 +++++++++++++ ext/zend_test/test_legacy_arginfo.h | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 20616b20dda0..a1148708ca2e 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -20,6 +20,10 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_void_return, 0, 0, IS_VOID, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_forward_reference, 1, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(1, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_compile_string, 0, 3, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, source_string, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) @@ -361,6 +365,11 @@ ZEND_END_ARG_INFO() #define arginfo_zend_new_singleton_array arginfo_zend_test_array_return +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_set_local_var, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + #define arginfo_ZendTestNS2_namespaced_func arginfo_zend_test_is_pcre_bundled #define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_test_void_return @@ -454,6 +463,7 @@ static ZEND_FUNCTION(zend_trigger_bailout); static ZEND_FUNCTION(zend_test_array_return); static ZEND_FUNCTION(zend_test_nullable_array_return); static ZEND_FUNCTION(zend_test_void_return); +static ZEND_FUNCTION(zend_test_forward_reference); static ZEND_FUNCTION(zend_test_compile_string); static ZEND_FUNCTION(zend_test_deprecated); static ZEND_FUNCTION(zend_test_deprecated_attr); @@ -559,6 +569,7 @@ static ZEND_FUNCTION(zend_test_uri_parser); static ZEND_FUNCTION(zend_test_gh19792); static ZEND_FUNCTION(zend_test_handle_post_data); static ZEND_FUNCTION(zend_new_singleton_array); +static ZEND_FUNCTION(zend_test_set_local_var); static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); @@ -603,6 +614,7 @@ static const zend_function_entry ext_functions[] = { #endif #endif ZEND_FE(zend_test_void_return, arginfo_zend_test_void_return) + ZEND_FE(zend_test_forward_reference, arginfo_zend_test_forward_reference) ZEND_FE(zend_test_compile_string, arginfo_zend_test_compile_string) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY("zend_test_deprecated", zif_zend_test_deprecated, arginfo_zend_test_deprecated, ZEND_ACC_DEPRECATED, NULL, NULL) @@ -750,6 +762,7 @@ static const zend_function_entry ext_functions[] = { #endif ZEND_FE(zend_test_handle_post_data, arginfo_zend_test_handle_post_data) ZEND_FE(zend_new_singleton_array, arginfo_zend_new_singleton_array) + ZEND_FE(zend_test_set_local_var, arginfo_zend_test_set_local_var) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY(ZEND_NS_NAME("ZendTestNS2", "namespaced_func"), zif_ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func, 0, NULL, NULL) #else diff --git a/ext/zend_test/test_legacy_arginfo.h b/ext/zend_test/test_legacy_arginfo.h index 7b5efcf05218..7b1895eb9211 100644 --- a/ext/zend_test/test_legacy_arginfo.h +++ b/ext/zend_test/test_legacy_arginfo.h @@ -16,6 +16,10 @@ ZEND_END_ARG_INFO() #define arginfo_zend_test_void_return arginfo_zend_trigger_bailout +ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_test_forward_reference, 0, 1, 1) + ZEND_ARG_INFO(1, value) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_test_compile_string, 0, 0, 3) ZEND_ARG_INFO(0, source_string) ZEND_ARG_INFO(0, filename) @@ -298,6 +302,11 @@ ZEND_END_ARG_INFO() #define arginfo_zend_new_singleton_array arginfo_zend_trigger_bailout +ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_test_set_local_var, 0, 0, 2) + ZEND_ARG_INFO(0, name) + ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO() + #define arginfo_ZendTestNS2_namespaced_func arginfo_zend_trigger_bailout #define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_trigger_bailout @@ -378,6 +387,7 @@ static ZEND_FUNCTION(zend_trigger_bailout); static ZEND_FUNCTION(zend_test_array_return); static ZEND_FUNCTION(zend_test_nullable_array_return); static ZEND_FUNCTION(zend_test_void_return); +static ZEND_FUNCTION(zend_test_forward_reference); static ZEND_FUNCTION(zend_test_compile_string); static ZEND_FUNCTION(zend_test_deprecated); static ZEND_FUNCTION(zend_test_deprecated_attr); @@ -483,6 +493,7 @@ static ZEND_FUNCTION(zend_test_uri_parser); static ZEND_FUNCTION(zend_test_gh19792); static ZEND_FUNCTION(zend_test_handle_post_data); static ZEND_FUNCTION(zend_new_singleton_array); +static ZEND_FUNCTION(zend_test_set_local_var); static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); @@ -519,6 +530,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_array_return, arginfo_zend_test_array_return) ZEND_FE(zend_test_nullable_array_return, arginfo_zend_test_nullable_array_return) ZEND_FE(zend_test_void_return, arginfo_zend_test_void_return) + ZEND_FE(zend_test_forward_reference, arginfo_zend_test_forward_reference) ZEND_FE(zend_test_compile_string, arginfo_zend_test_compile_string) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY("zend_test_deprecated", zif_zend_test_deprecated, arginfo_zend_test_deprecated, ZEND_ACC_DEPRECATED, NULL, NULL) @@ -638,6 +650,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_gh19792, arginfo_zend_test_gh19792) ZEND_FE(zend_test_handle_post_data, arginfo_zend_test_handle_post_data) ZEND_FE(zend_new_singleton_array, arginfo_zend_new_singleton_array) + ZEND_FE(zend_test_set_local_var, arginfo_zend_test_set_local_var) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY(ZEND_NS_NAME("ZendTestNS2", "namespaced_func"), zif_ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func, 0, NULL, NULL) #else From af2c46603b66274735e00b530bdbe6ad713b7216 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Fri, 30 Oct 2026 18:35:41 +0100 Subject: [PATCH 7/8] Update zend_test generated stub hashes --- ext/zend_test/test_arginfo.h | 2 +- ext/zend_test/test_decl.h | 8 ++++---- ext/zend_test/test_legacy_arginfo.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index a1148708ca2e..d2c3ad7da6ef 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 1b25893580205fc2db824a265839da14f5d2f0f0 + * Stub hash: cae664762197fca1452b7ad24887bf54926c9d1e * Has decl header: yes */ #include "zend_attributes.h" diff --git a/ext/zend_test/test_decl.h b/ext/zend_test/test_decl.h index 87d1cc26286d..9ee7e970a6d0 100644 --- a/ext/zend_test/test_decl.h +++ b/ext/zend_test/test_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 1b25893580205fc2db824a265839da14f5d2f0f0 */ + * Stub hash: cae664762197fca1452b7ad24887bf54926c9d1e */ -#ifndef ZEND_TEST_DECL_1b25893580205fc2db824a265839da14f5d2f0f0_H -#define ZEND_TEST_DECL_1b25893580205fc2db824a265839da14f5d2f0f0_H +#ifndef ZEND_TEST_DECL_cae664762197fca1452b7ad24887bf54926c9d1e_H +#define ZEND_TEST_DECL_cae664762197fca1452b7ad24887bf54926c9d1e_H typedef enum zend_enum_ZendTestUnitEnum { ZEND_ENUM_ZendTestUnitEnum_Foo = 1, @@ -27,4 +27,4 @@ typedef enum zend_enum_ZendTestEnumWithInterface { ZEND_ENUM_ZendTestEnumWithInterface_Bar = 2, } zend_enum_ZendTestEnumWithInterface; -#endif /* ZEND_TEST_DECL_1b25893580205fc2db824a265839da14f5d2f0f0_H */ +#endif /* ZEND_TEST_DECL_cae664762197fca1452b7ad24887bf54926c9d1e_H */ diff --git a/ext/zend_test/test_legacy_arginfo.h b/ext/zend_test/test_legacy_arginfo.h index 7b1895eb9211..6693ae29c657 100644 --- a/ext/zend_test/test_legacy_arginfo.h +++ b/ext/zend_test/test_legacy_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: 1b25893580205fc2db824a265839da14f5d2f0f0 + * Stub hash: cae664762197fca1452b7ad24887bf54926c9d1e * Has decl header: yes */ #include "zend_constants.h" From 17bab85907c364ce1a254dbc586809bbbbc74085 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Fri, 30 Oct 2026 18:37:33 +0100 Subject: [PATCH 8/8] Fix generated arginfo and TLS test race --- ext/openssl/tests/stream_server_reneg_limit_trampoline.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/openssl/tests/stream_server_reneg_limit_trampoline.phpt b/ext/openssl/tests/stream_server_reneg_limit_trampoline.phpt index a44e9fdffe65..c4d21123f621 100644 --- a/ext/openssl/tests/stream_server_reneg_limit_trampoline.phpt +++ b/ext/openssl/tests/stream_server_reneg_limit_trampoline.phpt @@ -13,7 +13,7 @@ if(substr(PHP_OS, 0, 3) == 'WIN') { ?> --FILE-- run($serverCode, $clientCode, false); ?> --CLEAN-- --EXPECTF-- resource(%d) of type (stream)