From eded7f709f5d35ab6abb5577d0507a1f96cef0f4 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Tue, 4 Aug 2026 13:29:02 -0400 Subject: [PATCH] Fix GH-23016: pdo_odbc returns garbage for NULL long columns LONG_COLUMN_BUFFER_SIZE derives from ZSTR_MAX_OVERHEAD, which is a size_t, so every comparison of the signed SQLLEN indicator against it was evaluated unsigned and SQL_NULL_DATA compared as SIZE_MAX. The early exit to in_data was skipped for NULL columns, and seed_len then clamped to LONG_COLUMN_BUFFER_SIZE - 1, seeding the result with uninitialized bytes from C->data. Cast the macro to SQLLEN and send negative indicators other than SQL_NO_TOTAL to in_data, which already maps them to NULL. The colsize and datalen comparisons keep their existing behaviour; both are unsigned quantities. Fixes GH-23016 --- NEWS | 4 ++++ ext/pdo_odbc/odbc_stmt.c | 6 +++++- ext/pdo_odbc/tests/gh23016.phpt | 27 +++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 ext/pdo_odbc/tests/gh23016.phpt diff --git a/NEWS b/NEWS index f1b69e3e8ca6..70172e036dca 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,10 @@ PHP NEWS . Fixed bug GH-21134 (Crash with \C + UTF-8). Using \C in UTF-8 patterns is now forbidden. (Arnaud) +- PDO_ODBC: + . Fixed bug GH-23016 (NULL values in long columns come back as garbage + binary strings). (Calvin Buckley, iliaal) + - Reflection: . Fixed bug GH-22905 (Reflection exception messages truncate on null bytes). (DanielEScherzer) diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c index 37c76c1df0e6..33da34114664 100644 --- a/ext/pdo_odbc/odbc_stmt.c +++ b/ext/pdo_odbc/odbc_stmt.c @@ -27,7 +27,7 @@ #include "php_pdo_odbc_int.h" /* Buffer size; bigger columns than this become a "long column" */ -#define LONG_COLUMN_BUFFER_SIZE (ZEND_MM_PAGE_SIZE- ZSTR_MAX_OVERHEAD) +#define LONG_COLUMN_BUFFER_SIZE ((SQLLEN)(ZEND_MM_PAGE_SIZE - ZSTR_MAX_OVERHEAD)) enum pdo_odbc_conv_result { PDO_ODBC_CONV_NOT_REQUIRED, @@ -736,6 +736,10 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo goto in_data; } + if (C->fetched_len < 0 && C->fetched_len != SQL_NO_TOTAL) { + goto in_data; + } + if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_SUCCESS) { /* * This is a long column. diff --git a/ext/pdo_odbc/tests/gh23016.phpt b/ext/pdo_odbc/tests/gh23016.phpt new file mode 100644 index 000000000000..6b23352094eb --- /dev/null +++ b/ext/pdo_odbc/tests/gh23016.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-23016 (NULL in a long column is fetched as a garbage binary string) +--EXTENSIONS-- +pdo_odbc +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +$pdo->exec('CREATE TABLE test_gh23016 (data text)'); +$pdo->exec('INSERT INTO test_gh23016 VALUES (NULL)'); + +$row = $pdo->query('SELECT data FROM test_gh23016')->fetch(PDO::FETCH_NUM); +var_dump($row[0]); +?> +--EXPECT-- +NULL