[php-src] PHP-8.5: Fix GH-22665: pdo_odbc OOB write on oversized diagnostic length

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-07-14T16:18:29-04:00

Commit: https://github.com/php/php-src/commit/345b88deebf59574023020f5c1feebabfe0767f3
Raw diff: https://github.com/php/php-src/commit/345b88deebf59574023020f5c1feebabfe0767f3.diff

Fix GH-22665: pdo_odbc OOB write on oversized diagnostic length

pdo_odbc_error() writes the NUL terminator at
einfo->last_err_msg[errmsgsize], where errmsgsize is the diagnostic length
SQLGetDiagRec reports. On a truncated message (SQL_SUCCESS_WITH_INFO) the
driver reports the full untruncated length, which can reach or exceed the
512-byte buffer, so the terminator lands out of bounds in the adjacent
last_error field. Clamp the index to sizeof(last_err_msg) - 1; the size_t
cast also folds a negative driver-reported length into the same guard.

Fixes GH-22665
Closes GH-22675

Changed paths:
  A  ext/pdo_odbc/tests/gh22665.phpt
  M  NEWS
  M  ext/pdo_odbc/odbc_driver.c


Diff:

diff --git a/NEWS b/NEWS
index 76be38bcaa70..70dd9bce1c48 100644
--- a/NEWS
+++ b/NEWS
@@ -53,6 +53,8 @@ PHP                                                                        NEWS
     driver-reported display size). (iliaal)
   . Fixed bug GH-22666 (Heap buffer overflow when an output parameter value is
     longer than the declared maxlen). (iliaal)
+  . Fixed bug GH-22665 (Out-of-bounds write when the ODBC driver reports a
+    diagnostic message length beyond the error buffer). (iliaal)
 
 - Phar:
   . Fixed inconsistent handling of the magic ".phar" directory. Paths such as
diff --git a/ext/pdo_odbc/odbc_driver.c b/ext/pdo_odbc/odbc_driver.c
index 2cdac30223c0..3bb028ed6314 100644
--- a/ext/pdo_odbc/odbc_driver.c
+++ b/ext/pdo_odbc/odbc_driver.c
@@ -90,6 +90,8 @@ void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement,
 
 	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
 		errmsgsize = 0;
+	} else if ((size_t) errmsgsize >= sizeof(einfo->last_err_msg)) {
+		errmsgsize = sizeof(einfo->last_err_msg) - 1;
 	}
 
 	einfo->last_err_msg[errmsgsize] = '\0';
diff --git a/ext/pdo_odbc/tests/gh22665.phpt b/ext/pdo_odbc/tests/gh22665.phpt
new file mode 100644
index 000000000000..d6edc62288e8
--- /dev/null
+++ b/ext/pdo_odbc/tests/gh22665.phpt
@@ -0,0 +1,32 @@
+--TEST--
+GH-22665 (OOB write in pdo_odbc_error when the driver reports a long message)
+--EXTENSIONS--
+pdo_odbc
+--SKIPIF--
+<?php
+require __DIR__ . '/config.inc';
+try {
+    $pdo = new PDO(PDO_ODBC_SQLITE_DSN);
+} catch (PDOException $e) {
+    die("skip requires the SQLite3 ODBC driver");
+}
+?>
+--FILE--
+<?php
+require __DIR__ . '/config.inc';
+$pdo = new PDO(PDO_ODBC_SQLITE_DSN, null, null, [
+    PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
+]);
+
+// A failing query with a long identifier makes the driver report a diagnostic
+// message length >= the fixed last_err_msg buffer. The terminator write must
+// stay inside the buffer.
+$pdo->query('SELECT * FROM "' . str_repeat('A', 4096) . '"');
+$info = $pdo->errorInfo();
+
+echo "sqlstate: ", $info[0], "\n";
+echo "done\n";
+?>
+--EXPECT--
+sqlstate: HY000
+done
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.