From 462b52fab27354b1a7127ddba044c68f08b121e4 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Mon, 2 Nov 2026 08:34:10 -0500 Subject: [PATCH] ext/pdo_dblib: Release persistent handle error strings at request end The DB-Lib error and message handlers estrdup() server and library text into the connection's pdo_dblib_err. For a persistent connection, errors raised through PDO::exec() and the transaction and lastInsertId() calls stay there until the next statement execute, so a request that ends after such an error leaves pointers into the reset request heap. The next request efree()s them from the message or error handler or the cursor closer, and module shutdown does from the handle closer. Free them from persistent_shutdown once the last PDO object using the connection is released. --- NEWS | 4 ++ ext/pdo_dblib/dblib_driver.c | 11 ++++- ...ersistent_error_state_across_requests.phpt | 44 +++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 ext/pdo_dblib/tests/persistent_error_state_across_requests.phpt diff --git a/NEWS b/NEWS index daf52ae14d95..353307087019 100644 --- a/NEWS +++ b/NEWS @@ -233,6 +233,10 @@ PHP NEWS . Fixed a duplicate warning when ATTR_STATEMENT_CLASS is rejected on a persistent connection. (Ilia Alshanetsky) +- PDO_DBLIB: + . Fixed a crash on persistent connections after an error raised through + PDO::exec(). (Ilia Alshanetsky) + - PDO_Firebird: . Fixed bug GH-25006 (getColumnMeta() reads out of bounds for an out-of-range column index). (Ilia Alshanetsky) diff --git a/ext/pdo_dblib/dblib_driver.c b/ext/pdo_dblib/dblib_driver.c index dd8fb1c320c6..79fe467bb1e7 100644 --- a/ext/pdo_dblib/dblib_driver.c +++ b/ext/pdo_dblib/dblib_driver.c @@ -92,6 +92,15 @@ static void dblib_handle_closer(pdo_dbh_t *dbh) } } +static void dblib_handle_request_shutdown(pdo_dbh_t *dbh) +{ + pdo_dblib_db_handle *H = (pdo_dblib_db_handle *)dbh->driver_data; + + if (H && dbh->refcount <= 2) { + pdo_dblib_err_dtor(&H->err); + } +} + static bool dblib_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) { pdo_dblib_db_handle *H = (pdo_dblib_db_handle *)dbh->driver_data; @@ -450,7 +459,7 @@ static const struct pdo_dbh_methods dblib_methods = { dblib_get_attribute, /* get attr */ NULL, /* check liveness */ NULL, /* get driver methods */ - NULL, /* request shutdown */ + dblib_handle_request_shutdown, NULL, /* in transaction, use PDO's internal tracking mechanism */ NULL, /* get gc */ NULL /* scanner */ diff --git a/ext/pdo_dblib/tests/persistent_error_state_across_requests.phpt b/ext/pdo_dblib/tests/persistent_error_state_across_requests.phpt new file mode 100644 index 000000000000..2c72db11e586 --- /dev/null +++ b/ext/pdo_dblib/tests/persistent_error_state_across_requests.phpt @@ -0,0 +1,44 @@ +--TEST-- +PDO_DBLIB: Persistent handle error state does not outlive the request +--EXTENSIONS-- +pdo_dblib +--SKIPIF-- + +--ENV-- +USE_ZEND_ALLOC=0 +USE_TRACKED_ALLOC=1 +--FILE-- + true, PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT]); +$stmt = $db->prepare('SELECT 1'); +$stmt->execute(); +var_dump($stmt->fetchColumn()); +$db->exec('SELECT * FROM ' . $_GET['table']); +echo $db->errorInfo()[2], "\n"; +PHP, var_export(__DIR__ . '/config.inc', true)); + +$args = []; +foreach (['pdo', 'pdo_dblib'] as $ext) { + if (file_exists(ini_get('extension_dir') . '/' . $ext . '.' . PHP_SHLIB_SUFFIX)) { + $args[] = '-dextension=' . ini_get('extension_dir') . '/' . $ext . '.' . PHP_SHLIB_SUFFIX; + } +} +php_cli_server_start($code, null, $args); + +foreach (['pdo_dblib_missing_first', 'pdo_dblib_missing_second'] as $table) { + echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/index.php?table=' . $table); +} +?> +--EXPECTF-- +int(1) +Invalid object name 'pdo_dblib_missing_first'. [%d] (severity %d) [] +int(1) +Invalid object name 'pdo_dblib_missing_second'. [%d] (severity %d) []