diff --git a/ext/session/php_session.h b/ext/session/php_session.h index 08c08b9a024a..752199198953 100644 --- a/ext/session/php_session.h +++ b/ext/session/php_session.h @@ -148,6 +148,7 @@ typedef struct _php_ps_globals { int module_number; php_random_status_state_pcgoneseq128xslrr64 random_state; php_random_algo_with_state random; + bool gc_random_seeded; /* false until first GC check; reseeds after fork */ zend_long gc_probability; zend_long gc_divisor; zend_long gc_maxlifetime; diff --git a/ext/session/session.c b/ext/session/session.c index 1fdfc5d1073f..9c2735cb03c1 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -392,8 +392,22 @@ static zend_long php_session_gc(bool immediate) /* GC must be done before reading session data. */ 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) { + /* Reseed PS(random) on first use per process. GINIT runs in the + * master process before SAPIs like PHP-FPM fork their workers, so + * all workers would otherwise inherit and replay the same PCG + * sequence. One CSPRNG call here breaks the correlation. */ + if (UNEXPECTED(!PS(gc_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(gc_random_seeded) = true; + } collect = php_random_range(PS(random), 0, PS(gc_divisor) - 1) < PS(gc_probability); } @@ -2891,14 +2905,7 @@ static PHP_GINIT_FUNCTION(ps) .algo = &php_random_algo_pcgoneseq128xslrr64, .state = &ps_globals->random_state, }; - 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); + ps_globals->gc_random_seeded = false; } static PHP_MINIT_FUNCTION(session)