Commit 087a08d9701 for php.net
commit 087a08d970109a80752f0114e242a76d15e36705
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Sat Jun 20 21:47:27 2026 -0400
Fix session save-handler argv leak on recursive rejection
ps_call_handler() returned on the recursive-call rejection branch before
reaching the argv cleanup loop, leaking one ref per owned argument. The
read/write/destroy/validate_sid/update_timestamp callers copy the session
id and data into argv and rely on ps_call_handler() to release them, so a
handler that re-enters session machinery (for example calling
session_destroy() from within a write handler) leaks those strings. Fold
the handler call into an else branch so the cleanup loop always runs.
Closes GH-22382
diff --git a/ext/session/mod_user.c b/ext/session/mod_user.c
index 78fb6260540..71b18612683 100644
--- a/ext/session/mod_user.c
+++ b/ext/session/mod_user.c
@@ -30,16 +30,16 @@ static void ps_call_handler(zval *func, int argc, zval *argv, zval *retval)
PS(in_save_handler) = 0;
ZVAL_UNDEF(retval);
php_error_docref(NULL, E_WARNING, "Cannot call session save handler in a recursive manner");
- return;
- }
- PS(in_save_handler) = 1;
- if (call_user_function(NULL, NULL, func, retval, argc, argv) == FAILURE) {
- zval_ptr_dtor(retval);
- ZVAL_UNDEF(retval);
- } else if (Z_ISUNDEF_P(retval)) {
- ZVAL_NULL(retval);
+ } else {
+ PS(in_save_handler) = 1;
+ if (call_user_function(NULL, NULL, func, retval, argc, argv) == FAILURE) {
+ zval_ptr_dtor(retval);
+ ZVAL_UNDEF(retval);
+ } else if (Z_ISUNDEF_P(retval)) {
+ ZVAL_NULL(retval);
+ }
+ PS(in_save_handler) = 0;
}
- PS(in_save_handler) = 0;
for (i = 0; i < argc; i++) {
zval_ptr_dtor(&argv[i]);
}
diff --git a/ext/session/tests/user_session_module/recursive_handler_argv_leak.phpt b/ext/session/tests/user_session_module/recursive_handler_argv_leak.phpt
new file mode 100644
index 00000000000..2b954494e87
--- /dev/null
+++ b/ext/session/tests/user_session_module/recursive_handler_argv_leak.phpt
@@ -0,0 +1,31 @@
+--TEST--
+ps_call_handler() releases argv when a recursive save-handler call is rejected
+--INI--
+session.save_path=
+session.name=PHPSESSID
+--EXTENSIONS--
+session
+--FILE--
+<?php
+class H extends SessionHandler {
+ public bool $tripped = false;
+ public function write($id, $data): bool {
+ if (!$this->tripped) {
+ $this->tripped = true;
+ session_destroy();
+ }
+ return true;
+ }
+}
+
+session_set_save_handler(new H, true);
+session_start();
+$_SESSION['x'] = 1;
+session_write_close();
+echo "done\n";
+?>
+--EXPECTF--
+Warning: session_destroy(): Cannot call session save handler in a recursive manner in %s on line %d
+
+Warning: session_destroy(): Session object destruction failed in %s on line %d
+done