Commit ae27423c336 for php

commit ae27423c33678ccb5b415a776d0311aaa6744532
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Wed Sep 30 10:26:27 2026 -0400

    ext/session: Close save handler when session encoding fails

    Since 86b49211577, php_session_save_current_state() returns without calling
    the save handler's close callback when the serializer returns NULL, e.g. for
    a $_SESSION key containing '|'. session_write_close() still returns true, but
    the files handler keeps its LOCK_EX on the session file until request
    shutdown, blocking concurrent requests for the same session, and a user
    handler extending SessionHandler has its close() run at shutdown after the
    session is gone. Close the handler on that return as the rest of the function
    and session_regenerate_id() do; with an exception pending the engine already
    refuses to call a user close() callback.

    Closes GH-24022

diff --git a/NEWS b/NEWS
index 4b109061430..61631670a40 100644
--- a/NEWS
+++ b/NEWS
@@ -39,6 +39,10 @@ PHP                                                                        NEWS
   . Fixed GH-24166 (Double-free in Phar::webPhar() in CGI without PATH_INFO).
     (RigelYoung, Jakub Zelenka)

+- Session:
+  . Fixed session_write_close() keeping the save handler open when the
+    session data cannot be encoded. (Ilia Alshanetsky)
+
 - Standard:
   . Fixed chown() and lchown() failing to resolve user names in ZTS builds
     when getpwnam_r() needs a larger buffer. (Ilia Alshanetsky)
diff --git a/ext/session/session.c b/ext/session/session.c
index a37a6d12c2c..0ee8c94cf33 100644
--- a/ext/session/session.c
+++ b/ext/session/session.c
@@ -526,6 +526,7 @@ static void php_session_save_current_state(bool write)
 				/* Not being able to encode the session data means there is some kind of issue that prevents a write
 				 * (e.g. a key containing the '|' character with the default serialization) */
 				if (UNEXPECTED(val == NULL)) {
+					PS(mod)->s_close(&PS(mod_data));
 					return;
 				}

diff --git a/ext/session/tests/session_write_close_encode_failure.phpt b/ext/session/tests/session_write_close_encode_failure.phpt
new file mode 100644
index 00000000000..1c562f401df
--- /dev/null
+++ b/ext/session/tests/session_write_close_encode_failure.phpt
@@ -0,0 +1,76 @@
+--TEST--
+session_write_close() closes the save handler when encoding the session data fails
+--EXTENSIONS--
+session
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--INI--
+session.use_cookies=0
+session.cache_limiter=
+session.serialize_handler=php
+session.save_handler=files
+--FILE--
+<?php
+ob_start();
+$dir = __DIR__ . '/session_write_close_encode_failure';
+@mkdir($dir);
+session_save_path($dir);
+session_start();
+$file = $dir . '/sess_' . session_id();
+$_SESSION['bad|key'] = 'value';
+var_dump(session_write_close());
+
+$fp = fopen($file, 'r');
+var_dump(flock($fp, LOCK_EX | LOCK_NB));
+fclose($fp);
+
+session_start();
+$file = $dir . '/sess_' . session_id();
+$_SESSION['closure'] = function () {};
+$_SESSION['bad|key'] = 'value';
+try {
+    session_write_close();
+} catch (Exception $e) {
+    echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+$fp = fopen($file, 'r');
+var_dump(flock($fp, LOCK_EX | LOCK_NB));
+fclose($fp);
+
+class Handler extends SessionHandler
+{
+    public function close(): bool
+    {
+        echo "close\n";
+        return parent::close();
+    }
+}
+
+session_set_save_handler(new Handler());
+session_start();
+$_SESSION['bad|key'] = 'value';
+var_dump(session_write_close());
+echo "done\n";
+?>
+--CLEAN--
+<?php
+$dir = __DIR__ . '/session_write_close_encode_failure';
+foreach (glob($dir . '/sess_*') as $file) {
+    unlink($file);
+}
+@rmdir($dir);
+?>
+--EXPECTF--
+Warning: session_write_close(): Failed to write session data. Data contains invalid key "bad|key" in %s on line %d
+bool(true)
+bool(true)
+
+Warning: session_write_close(): Failed to write session data. Data contains invalid key "bad|key" in %s on line %d
+Exception: Serialization of 'Closure' is not allowed
+bool(true)
+
+Warning: session_write_close(): Failed to write session data. Data contains invalid key "bad|key" in %s on line %d
+close
+bool(true)
+done