Commit d96579371fd for php.net

commit d96579371fdef2c0358fba670926772180573d89
Author: Jorg Sowa <jorg.sowa@gmail.com>
Date:   Tue Apr 14 08:36:04 2026 +0200

    ext/session: fix missing zval_ptr_dtor for retval in PS_GC_FUNC(user)

    PS_GC_FUNC(user) did not call zval_ptr_dtor() on the return value of
    the user GC callback, leaking memory when the callback returned a
    reference-counted value. All other user handlers (write, destroy,
    validate_sid, update_timestamp) already free retval correctly.

    Closes GH-21747

diff --git a/NEWS b/NEWS
index 881a1765b88..d3031f01196 100644
--- a/NEWS
+++ b/NEWS
@@ -56,6 +56,10 @@ PHP                                                                        NEWS
   . Fixed bug GH-21731 (Random\Engine\Xoshiro256StarStar::__unserialize()
     accepts all-zero state). (iliaal)

+- Session:
+  . Fixed memory leak when session GC callback return a refcounted value.
+    (jorgsowa)
+
 - SPL:
   . Fixed bug GH-21499 (RecursiveArrayIterator getChildren UAF after parent
     free). (Girgias)
diff --git a/ext/session/mod_user.c b/ext/session/mod_user.c
index 168c5c7f1d4..78fb6260540 100644
--- a/ext/session/mod_user.c
+++ b/ext/session/mod_user.c
@@ -218,6 +218,7 @@ PS_GC_FUNC(user)
 		/* Anything else is some kind of error */
 		*nrdels = -1; // Error
 	}
+	zval_ptr_dtor(&retval);
 	return *nrdels;
 }

diff --git a/ext/session/tests/user_session_module/gh_gc_retval_leak.phpt b/ext/session/tests/user_session_module/gh_gc_retval_leak.phpt
new file mode 100644
index 00000000000..f04f36c2276
--- /dev/null
+++ b/ext/session/tests/user_session_module/gh_gc_retval_leak.phpt
@@ -0,0 +1,33 @@
+--TEST--
+session_gc(): user handler returning non-bool/non-int does not leak memory
+--INI--
+session.gc_probability=0
+session.save_handler=files
+--EXTENSIONS--
+session
+--FILE--
+<?php
+ob_start();
+
+// Procedural API has no return type enforcement, so gc can return a string
+// (reference-counted), which PS_GC_FUNC(user) previously did not free.
+session_set_save_handler(
+    function(string $path, string $name) { return true; },
+    function() { return true; },
+    function(string $id): string|false { return ""; },
+    function(string $id, string $data) { return true; },
+    function(string $id) { return true; },
+    function(int $max) { return str_repeat("x", random_int(100, 100)); }
+);
+
+session_start();
+$result = session_gc();
+var_dump($result);
+session_write_close();
+
+ob_end_flush();
+?>
+--EXPECTF--
+
+Deprecated: session_set_save_handler(): Providing individual callbacks instead of an object implementing SessionHandlerInterface is deprecated in %s on line %d
+bool(false)