Commit 4e26dd0f795 for php.net

commit 4e26dd0f795f8467196424573b8ce0985930bee8
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Mon Aug 24 18:32:11 2026 -0400

    ext/pdo: Bound-check column index in getColumnMeta()

    PDOStatement::getColumnMeta() indexed stmt->columns as soon as the driver
    hook reported success, without checking that the columns had been
    described or that the index was in range, so pdo_odbc, whose hook always
    reports success, read out of bounds and crashed. Raise SQLSTATE 07009 and
    return false instead, matching what the drivers that bound-check the
    index in their own hook already return.

    Closes GH-23654

diff --git a/NEWS b/NEWS
index 5f598989c66..9a862b7e0e1 100644
--- a/NEWS
+++ b/NEWS
@@ -23,6 +23,10 @@ PHP                                                                        NEWS
   . Fixed bug GH-23106 (mb_strpos() reads past the end of a haystack ending in
     a truncated UTF-8 sequence). (Lazizbek Ergashev)

+- PDO:
+  . Fixed PDOStatement::getColumnMeta() reading out of bounds for an invalid
+    column index. (Ilia Alshanetsky)
+
 - Zip:
   . Fixed ZipArchive::extractTo() ignoring files given in a non-list array.
     (David Carlier)
diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c
index 51c2f58c6c2..8612a01e044 100644
--- a/ext/pdo/pdo_stmt.c
+++ b/ext/pdo/pdo_stmt.c
@@ -1700,6 +1700,13 @@ PHP_METHOD(PDOStatement, getColumnMeta)
 		RETURN_FALSE;
 	}

+	if (stmt->columns == NULL || colno >= stmt->column_count) {
+		zval_ptr_dtor(return_value);
+		ZVAL_UNDEF(return_value);
+		pdo_raise_impl_error(stmt->dbh, stmt, "07009", "invalid column index");
+		RETURN_FALSE;
+	}
+
 	/* add stock items */
 	col = &stmt->columns[colno];
 	add_assoc_str(return_value, "name", zend_string_copy(col->name));
diff --git a/ext/pdo_odbc/tests/getcolumnmeta_bounds.phpt b/ext/pdo_odbc/tests/getcolumnmeta_bounds.phpt
new file mode 100644
index 00000000000..192da0903ce
--- /dev/null
+++ b/ext/pdo_odbc/tests/getcolumnmeta_bounds.phpt
@@ -0,0 +1,49 @@
+--TEST--
+PDO_odbc getColumnMeta() bounds checking on unexecuted statements and invalid indexes
+--EXTENSIONS--
+pdo_odbc
+--SKIPIF--
+<?php
+try {
+    new Pdo\Odbc('odbc:Driver={SQLite3};Database=:memory:');
+} catch (Throwable $e) {
+    die('skip SQLite3 ODBC driver not available');
+}
+?>
+--FILE--
+<?php
+$pdo = new Pdo\Odbc('odbc:Driver={SQLite3};Database=:memory:',
+    null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
+$stmt = $pdo->prepare('SELECT 1 AS one');
+
+try {
+    var_dump($stmt->getColumnMeta(0));
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+$stmt->execute();
+var_dump($stmt->getColumnMeta(0));
+
+try {
+    var_dump($stmt->getColumnMeta(5));
+} catch (Throwable $e) {
+    echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+echo "done\n";
+?>
+--EXPECTF--
+PDOException: SQLSTATE[07009]: Invalid descriptor index: invalid column index
+array(4) {
+  ["pdo_type"]=>
+  int(2)
+  ["name"]=>
+  string(3) "one"
+  ["len"]=>
+  int(%d)
+  ["precision"]=>
+  int(%d)
+}
+PDOException: SQLSTATE[07009]: Invalid descriptor index: invalid column index
+done