[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