From 14ea81268041d1ae2843efb02761d56c0c73eff0 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Tue, 22 Sep 2026 13:20:16 -0400 Subject: [PATCH] ext/session: Preserve exceptions thrown by create_sid() Do not replace pending exceptions with return-value validation errors in the user handler adapter, including exceptions raised while destroying invalid return values. This preserves the original exception for session_start(), session_create_id(), and session_regenerate_id(). --- NEWS | 4 ++ ext/session/mod_user.c | 8 ++- .../session_create_id_create_sid_throws.phpt | 5 +- ...n_create_sid_return_destructor_throws.phpt | 37 +++++++++++ ...ssion_regenerate_id_create_sid_throws.phpt | 57 +++++++++++++++++ .../session_start_create_sid_throws.phpt | 64 +++++++++++++++++++ 6 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 ext/session/tests/user_session_module/session_create_sid_return_destructor_throws.phpt create mode 100644 ext/session/tests/user_session_module/session_regenerate_id_create_sid_throws.phpt create mode 100644 ext/session/tests/user_session_module/session_start_create_sid_throws.phpt diff --git a/NEWS b/NEWS index 4450776530c4..1d94b2bd5837 100644 --- a/NEWS +++ b/NEWS @@ -68,6 +68,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 exceptions from user-defined create_sid() handlers being replaced + by return-value validation errors. (Ilia Alshanetsky) + - Sockets: . Fixed socket_select() silently truncating sets larger than FD_SETSIZE on Windows. (David Carlier) diff --git a/ext/session/mod_user.c b/ext/session/mod_user.c index 71b18612683d..61db72b4cf12 100644 --- a/ext/session/mod_user.c +++ b/ext/session/mod_user.c @@ -237,12 +237,16 @@ PS_CREATE_SID_FUNC(user) } zval_ptr_dtor(&retval); } else { - zend_throw_error(NULL, "No session id returned by function"); + if (!EG(exception)) { + zend_throw_error(NULL, "No session id returned by function"); + } return NULL; } if (!id) { - zend_throw_error(NULL, "Session id must be a string"); + if (!EG(exception)) { + zend_throw_error(NULL, "Session id must be a string"); + } return NULL; } diff --git a/ext/session/tests/user_session_module/session_create_id_create_sid_throws.phpt b/ext/session/tests/user_session_module/session_create_id_create_sid_throws.phpt index b65c0671d940..a8648fdb5d68 100644 --- a/ext/session/tests/user_session_module/session_create_id_create_sid_throws.phpt +++ b/ext/session/tests/user_session_module/session_create_id_create_sid_throws.phpt @@ -36,14 +36,13 @@ try { session_create_id(); } catch (Throwable $e) { echo $e::class, ": ", $e->getMessage(), PHP_EOL; - $previous = $e->getPrevious(); - echo $previous::class, ": ", $previous->getMessage(), PHP_EOL; + var_dump($e->getPrevious()); } var_dump(session_status() === PHP_SESSION_ACTIVE); ?> --EXPECT-- -Error: Session id must be a string Exception: create_sid failed +NULL bool(true) diff --git a/ext/session/tests/user_session_module/session_create_sid_return_destructor_throws.phpt b/ext/session/tests/user_session_module/session_create_sid_return_destructor_throws.phpt new file mode 100644 index 000000000000..18bb97779e00 --- /dev/null +++ b/ext/session/tests/user_session_module/session_create_sid_return_destructor_throws.phpt @@ -0,0 +1,37 @@ +--TEST-- +Exceptions from destruction of an invalid create_sid() return value are preserved +--EXTENSIONS-- +session +--FILE-- +getMessage(), PHP_EOL; +} +?> +--EXPECT-- +RuntimeException: destructor failed diff --git a/ext/session/tests/user_session_module/session_regenerate_id_create_sid_throws.phpt b/ext/session/tests/user_session_module/session_regenerate_id_create_sid_throws.phpt new file mode 100644 index 000000000000..21e796ca13d0 --- /dev/null +++ b/ext/session/tests/user_session_module/session_regenerate_id_create_sid_throws.phpt @@ -0,0 +1,57 @@ +--TEST-- +session_regenerate_id() preserves exceptions from create_sid(), including collision retries +--EXTENSIONS-- +session +--INI-- +session.use_cookies=0 +session.cache_limiter= +session.use_strict_mode=1 +session.gc_probability=0 +--FILE-- +calls === $this->throwAt) { + throw new RuntimeException('create_sid failed'); + } + return 'session' . $this->calls; + } +} + +foreach ([2, 3] as $throwAt) { + $handler = new FailingHandler(); + $handler->throwAt = $throwAt; + session_set_save_handler($handler); + session_id(''); + session_start(); + + try { + session_regenerate_id(); + } catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; + } + echo 'create_sid calls: ', $handler->calls, PHP_EOL; +} +?> +--EXPECT-- +RuntimeException: create_sid failed +create_sid calls: 2 +RuntimeException: create_sid failed +create_sid calls: 3 diff --git a/ext/session/tests/user_session_module/session_start_create_sid_throws.phpt b/ext/session/tests/user_session_module/session_start_create_sid_throws.phpt new file mode 100644 index 000000000000..b7cd663007e0 --- /dev/null +++ b/ext/session/tests/user_session_module/session_start_create_sid_throws.phpt @@ -0,0 +1,64 @@ +--TEST-- +session_start() preserves exceptions from create_sid() +--EXTENSIONS-- +session +--INI-- +session.use_cookies=0 +session.cache_limiter= +session.gc_probability=0 +--FILE-- +invalidReturn) { + return []; + } + throw new RuntimeException('create_sid failed'); + } + + public function validateId(string $id): bool + { + return false; + } +} + +$handler = new FailingHandler(); +session_set_save_handler($handler); + +foreach ([false, true] as $strict) { + foreach ([false, true] as $invalidReturn) { + echo 'strict mode: ', (int) $strict, ', invalid return: ', (int) $invalidReturn, PHP_EOL; + $handler->invalidReturn = $invalidReturn; + session_id($strict ? 'rejected' : ''); + try { + session_start(['use_strict_mode' => $strict]); + } catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; + } + } +} +?> +--EXPECT-- +strict mode: 0, invalid return: 0 +RuntimeException: create_sid failed +strict mode: 0, invalid return: 1 +TypeError: FailingHandler::create_sid(): Return value must be of type string, array returned +strict mode: 1, invalid return: 0 +RuntimeException: create_sid failed +strict mode: 1, invalid return: 1 +TypeError: FailingHandler::create_sid(): Return value must be of type string, array returned