Skip to content

Commit bd42692

Browse files
committed
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
1 parent 103c84f commit bd42692

3 files changed

Lines changed: 36 additions & 0 deletions

File tree

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ PHP NEWS
4747
carries no credentials). (iliaal)
4848
. Fixed bug GH-22667 (Heap buffer over-read when a column value exceeds the
4949
driver-reported display size). (iliaal)
50+
. Fixed bug GH-22665 (Out-of-bounds write when the ODBC driver reports a
51+
diagnostic message length beyond the error buffer). (iliaal)
5052

5153
- Phar:
5254
. Fixed inconsistent handling of the magic ".phar" directory. Paths such as

ext/pdo_odbc/odbc_driver.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement,
9090

9191
if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
9292
errmsgsize = 0;
93+
} else if ((size_t) errmsgsize >= sizeof(einfo->last_err_msg)) {
94+
errmsgsize = sizeof(einfo->last_err_msg) - 1;
9395
}
9496

9597
einfo->last_err_msg[errmsgsize] = '\0';

ext/pdo_odbc/tests/gh22665.phpt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
GH-22665 (OOB write in pdo_odbc_error when the driver reports a long message)
3+
--EXTENSIONS--
4+
pdo_odbc
5+
--SKIPIF--
6+
<?php
7+
require __DIR__ . '/config.inc';
8+
try {
9+
$pdo = new PDO(PDO_ODBC_SQLITE_DSN);
10+
} catch (PDOException $e) {
11+
die("skip requires the SQLite3 ODBC driver");
12+
}
13+
?>
14+
--FILE--
15+
<?php
16+
require __DIR__ . '/config.inc';
17+
$pdo = new PDO(PDO_ODBC_SQLITE_DSN, null, null, [
18+
PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
19+
]);
20+
21+
// A failing query with a long identifier makes the driver report a diagnostic
22+
// message length >= the fixed last_err_msg buffer. The terminator write must
23+
// stay inside the buffer.
24+
$pdo->query('SELECT * FROM "' . str_repeat('A', 4096) . '"');
25+
$info = $pdo->errorInfo();
26+
27+
echo "sqlstate: ", $info[0], "\n";
28+
echo "done\n";
29+
?>
30+
--EXPECT--
31+
sqlstate: HY000
32+
done

0 commit comments

Comments
 (0)