[php-src] master: Merge branch 'PHP-8.5'

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-08-18T19:38:35-04:00

Commit: https://github.com/php/php-src/commit/018cf3a791aaafeea3af42ee45c41852147c01f9
Raw diff: https://github.com/php/php-src/commit/018cf3a791aaafeea3af42ee45c41852147c01f9.diff

Merge branch 'PHP-8.5'

* PHP-8.5:
  Fix leak when persistent PDO liveness check fails

Changed paths:
  A  ext/pdo_mysql/tests/persistent_liveness_evict.phpt
  M  NEWS
  M  ext/pdo/pdo_dbh.c


Diff:

diff --git a/NEWS b/NEWS
index 2e921b6c26f8..494eede74865 100644
--- a/NEWS
+++ b/NEWS
@@ -28,6 +28,10 @@ PHP                                                                        NEWS
   . Fixed a use-after-free when Dom\Element::setAttributeNS() replaces the
     value of an attribute whose child still has a live wrapper. (iliaal)
 
+- PDO:
+  . Fixed a leak when a persistent connection failed a liveness check
+    with no other live PDO handle. (iliaal)
+
 - PDO_PGSQL:
   . Fixed several lazy fetch (PDO::ATTR_PREFETCH => 0) defects: an infinite
     loop when cleaning up a fetch left in a COPY, a use-after-free when a
diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c
index b7e2887e03eb..358ac81eb88f 100644
--- a/ext/pdo/pdo_dbh.c
+++ b/ext/pdo/pdo_dbh.c
@@ -411,9 +411,12 @@ PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zen
 
 					/* is the connection still alive ? */
 					if (pdbh->methods->check_liveness && FAILURE == (pdbh->methods->check_liveness)(pdbh)) {
-						/* nope... need to kill it */
-						pdbh->refcount--;
-						zend_list_close(le);
+						if (pdbh->refcount > 1) {
+							pdbh->refcount--;
+							zend_list_close(le);
+						} else {
+							zend_hash_del(&EG(persistent_list), hash_key);
+						}
 						pdbh = NULL;
 					}
 				}
diff --git a/ext/pdo_mysql/tests/persistent_liveness_evict.phpt b/ext/pdo_mysql/tests/persistent_liveness_evict.phpt
new file mode 100644
index 000000000000..545c16ebdbdb
--- /dev/null
+++ b/ext/pdo_mysql/tests/persistent_liveness_evict.phpt
@@ -0,0 +1,42 @@
+--TEST--
+Persistent reconnect after a dead cached handle does not leak the old pdo_dbh_t
+--EXTENSIONS--
+pdo_mysql
+--ENV--
+VALGRIND_OPTS=--leak-check=full
+--SKIPIF--
+<?php
+require_once __DIR__ . '/inc/mysql_pdo_test.inc';
+MySQLPDOTest::skip();
+if (!MySQLPDOTest::isPDOMySQLnd()) {
+    die('skip mysqlnd only (libmysql mysql_ping auto-reconnects)');
+}
+?>
+--FILE--
+<?php
+require_once __DIR__ . '/inc/mysql_pdo_test.inc';
+
+$dsn = MySQLPDOTest::getDSN();
+$user = PDO_MYSQL_TEST_USER;
+$pass = PDO_MYSQL_TEST_PASS;
+$opts = [
+    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+    PDO::ATTR_PERSISTENT => true,
+];
+
+$cached = new PDO($dsn, $user, $pass, $opts);
+$id = (int) $cached->query('SELECT CONNECTION_ID()')->fetchColumn();
+unset($cached);
+
+$killer = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
+$killer->exec('KILL ' . $id);
+unset($killer);
+
+$next = new PDO($dsn, $user, $pass, $opts);
+$nextId = (int) $next->query('SELECT CONNECTION_ID()')->fetchColumn();
+var_dump($nextId !== $id);
+echo $next->query('SELECT 1')->fetchColumn(), "\n";
+?>
+--EXPECT--
+bool(true)
+1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.