Skip to content

Commit 0ee0bae

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 ebed41a commit 0ee0bae

3 files changed

Lines changed: 34 additions & 0 deletions

File tree

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ PHP NEWS
4343
- PDO_ODBC:
4444
. Fixed bug GH-20726 (Crash with ODBC connection pooling when the DSN
4545
carries no credentials). (iliaal)
46+
. Fixed bug GH-22665 (Out-of-bounds write when the ODBC driver reports a
47+
diagnostic message length beyond the error buffer). (iliaal)
4648

4749
- Phar:
4850
. 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: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
try {
8+
$pdo = new PDO("odbc:Driver=SQLite3;Database=:memory:");
9+
} catch (PDOException $e) {
10+
die("skip requires the SQLite3 ODBC driver");
11+
}
12+
?>
13+
--FILE--
14+
<?php
15+
$pdo = new PDO("odbc:Driver=SQLite3;Database=:memory:", null, null, [
16+
PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
17+
]);
18+
19+
// A failing query with a long identifier makes the driver report a diagnostic
20+
// message length >= the fixed last_err_msg buffer. The terminator write must
21+
// stay inside the buffer.
22+
$pdo->query('SELECT * FROM "' . str_repeat('A', 4096) . '"');
23+
$info = $pdo->errorInfo();
24+
25+
echo "sqlstate: ", $info[0], "\n";
26+
echo "done\n";
27+
?>
28+
--EXPECT--
29+
sqlstate: HY000
30+
done

0 commit comments

Comments
 (0)