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)