From 14b740f87772143219971dab421c5c0e1866bc6c Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Mon, 24 Aug 2026 13:40:19 -0400 Subject: [PATCH] ext/session: Close the save handler when create_sid() fails php_session_regenerate_id() opened the handler with s_open() and then threw when s_create_sid() returned no id, leaving mod_data open until request shutdown and double-opening it on the next session start. The collision-retry failure path in the same function already closes the handler; do the same on the null-id path. --- NEWS | 4 + ext/session/session.c | 1 + ..._regenerate_id_create_sid_fails_close.phpt | 78 +++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 ext/session/tests/user_session_module/session_regenerate_id_create_sid_fails_close.phpt diff --git a/NEWS b/NEWS index 982945e5ebfc..2be9ff7b38c4 100644 --- a/NEWS +++ b/NEWS @@ -48,6 +48,10 @@ PHP NEWS . Fixed a heap over-read in the interactive shell prompt when cli.prompt is set to an empty string. (Ilia Alshanetsky) +- Session: + . Fixed session_regenerate_id() leaving the save handler open after a + failed create_sid(). (Ilia Alshanetsky) + - Sockets: . Fixed socket_select() silently truncating sets larger than FD_SETSIZE on Windows. (David Carlier) diff --git a/ext/session/session.c b/ext/session/session.c index 2073ea55fe1f..f406cfdd9407 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -2430,6 +2430,7 @@ PHP_FUNCTION(session_regenerate_id) PS(id) = PS(mod)->s_create_sid(&PS(mod_data)); if (!PS(id)) { + PS(mod)->s_close(&PS(mod_data)); PS(session_status) = php_session_none; if (!EG(exception)) { zend_throw_error(NULL, "Failed to create new session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path)); diff --git a/ext/session/tests/user_session_module/session_regenerate_id_create_sid_fails_close.phpt b/ext/session/tests/user_session_module/session_regenerate_id_create_sid_fails_close.phpt new file mode 100644 index 000000000000..e77d61c3b3ee --- /dev/null +++ b/ext/session/tests/user_session_module/session_regenerate_id_create_sid_fails_close.phpt @@ -0,0 +1,78 @@ +--TEST-- +session_regenerate_id() closes the save handler when create_sid fails +--INI-- +session.use_cookies=0 +session.gc_probability=0 +session.save_path= +--EXTENSIONS-- +session +--FILE-- +opens++; + return true; + } + + public function close(): bool + { + $this->closes++; + return true; + } + + public function read($id): string + { + return ''; + } + + public function write($id, $data): bool + { + return true; + } + + public function destroy($id): bool + { + return true; + } + + public function gc($maxlifetime): int|false + { + return 0; + } + + public function create_sid(): string + { + if (++$this->createCalls === 2) { + throw new RuntimeException('create_sid failed'); + } + return parent::create_sid(); + } +} + +$h = new CountingHandler(); +session_set_save_handler($h, true); +$started = session_start(); +try { + session_regenerate_id(); +} catch (Throwable $e) { + $msg = $e::class . ': ' . $e->getMessage(); +} +session_module_name('files'); +echo $msg ?? 'no exception', PHP_EOL; +var_dump($started); +var_dump(session_status() === PHP_SESSION_NONE); +echo "open={$h->opens} close={$h->closes}\n"; + +?> +--EXPECT-- +Error: Session id must be a string +bool(true) +bool(true) +open=2 close=1