[php-src] master: Fix GH-23016: pdo_odbc returns garbage for NULL long columns

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-08-04T15:20:14-04:00

Commit: https://github.com/php/php-src/commit/05202568c43f77f472709e466097338241e9cbcf
Raw diff: https://github.com/php/php-src/commit/05202568c43f77f472709e466097338241e9cbcf.diff

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

Changed paths:
  A  ext/pdo_odbc/tests/gh23016.phpt
  M  NEWS
  M  ext/pdo_odbc/odbc_stmt.c


Diff:

diff --git a/NEWS b/NEWS
index 195115821e06..b069e00a86b4 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 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--
+<?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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.