Commit 05202568c43 for php.net
commit 05202568c43f77f472709e466097338241e9cbcf
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Tue Aug 4 13:29:02 2026 -0400
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
Closes GH-23045
diff --git a/NEWS b/NEWS
index 195115821e0..b069e00a86b 100644
--- a/NEWS
+++ b/NEWS
@@ -37,6 +37,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 37c76c1df0e..33da3411466 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 00000000000..6b23352094e
--- /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--
+<?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);
+$pdo->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