Commit 0c2fc141fcf for php.net

commit 0c2fc141fcf9edb13b57b34c3843ed75e24ddcf5
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Sun Sep 6 11:01:32 2026 -0400

    odbc: check SQLColAttribute return codes in the field info functions

    odbc_column_lengths() and odbc_field_type() ignored the SQLColAttribute
    return code and returned their output buffer regardless, so a driver that
    fails the call left odbc_field_len(), odbc_field_scale() and
    odbc_field_type() reporting uninitialized stack. Check the code, warn with
    the driver's own diagnostic, and return 0 or false instead.

    Closes GH-23601

diff --git a/NEWS b/NEWS
index 6a6d6110970..935583e8733 100644
--- a/NEWS
+++ b/NEWS
@@ -66,6 +66,11 @@ PHP                                                                        NEWS
     replacement when a \k<name> backref has no closing delimiter.
     (Ilia Alshanetsky)

+- ODBC:
+  . Fixed odbc_field_len(), odbc_field_scale() and odbc_field_type()
+    returning uninitialized memory when SQLColAttribute fails.
+    (Ilia Alshanetsky)
+
 - Opcache:
   . Fixed a crash when the huge page SHM remap discarded mappings outside the
     reserved address range. (Piotr Hałas)
diff --git a/ext/odbc/php_odbc.c b/ext/odbc/php_odbc.c
index 8b78308f942..798245c4ca9 100644
--- a/ext/odbc/php_odbc.c
+++ b/ext/odbc/php_odbc.c
@@ -683,6 +683,7 @@ void odbc_bindcols(odbc_result *result)
 		result->values[i].value_max_len = 0;
 		colfieldid = SQL_COLUMN_DISPLAY_SIZE;

+		result->values[i].name[0] = '\0';
 		rc = PHP_ODBC_SQLCOLATTRIBUTE(result->stmt, (SQLUSMALLINT)(i+1), PHP_ODBC_SQL_DESC_NAME,
 				result->values[i].name, sizeof(result->values[i].name), &colnamelen, 0);
 		result->values[i].coltype = 0;
@@ -809,10 +810,30 @@ void odbc_transact(INTERNAL_FUNCTION_PARAMETERS, int type)
 }
 /* }}} */

+static void odbc_colattribute_failed(odbc_result *result, zend_long pv_num)
+{
+#if defined(ODBCVER) && (ODBCVER >= 0x0300)
+	SQLINTEGER diag_error;
+	SQLCHAR diag_state[6];
+	SQLCHAR diag_text[128];
+
+	memset(diag_state, '\0', sizeof(diag_state));
+	memset(diag_text, '\0', sizeof(diag_text));
+	if (SQL_SUCCESS == SQLGetDiagRec(SQL_HANDLE_STMT, result->stmt, 1, diag_state, &diag_error, diag_text, sizeof(diag_text), NULL)) {
+		diag_state[sizeof(diag_state) - 1] = '\0';
+		diag_text[sizeof(diag_text) - 1] = '\0';
+		php_error_docref(NULL, E_WARNING, "SQLColAttribute failed for field #%d: [%s] %s", (int)pv_num, diag_state, diag_text);
+		return;
+	}
+#endif
+	php_error_docref(NULL, E_WARNING, "SQLColAttribute failed for field #%d", (int)pv_num);
+}
+
 /* {{{ odbc_column_lengths */
 void odbc_column_lengths(INTERNAL_FUNCTION_PARAMETERS, int type)
 {
 	odbc_result *result;
+	RETCODE rc;
 #if defined(HAVE_SOLID) || defined(HAVE_SOLID_30)
 	/* this seems to be necessary for Solid2.3 ( tested by
 	 * tammy@synchronis.com) and Solid 3.0 (tested by eric@terra.telemediair.nl)
@@ -849,7 +870,11 @@ void odbc_column_lengths(INTERNAL_FUNCTION_PARAMETERS, int type)
 		RETURN_FALSE;
 	}

-	PHP_ODBC_SQLCOLATTRIBUTE(result->stmt, (SQLUSMALLINT)pv_num, (SQLUSMALLINT) (type?SQL_COLUMN_SCALE:SQL_COLUMN_PRECISION), NULL, 0, NULL, &len);
+	rc = PHP_ODBC_SQLCOLATTRIBUTE(result->stmt, (SQLUSMALLINT)pv_num, (SQLUSMALLINT)(type ? SQL_COLUMN_SCALE : SQL_COLUMN_PRECISION), NULL, 0, NULL, &len);
+	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
+		odbc_colattribute_failed(result, pv_num);
+		len = 0;
+	}

 	RETURN_LONG(len);
 }
@@ -2597,6 +2622,7 @@ PHP_FUNCTION(odbc_field_type)
 	odbc_result	*result;
 	char    	tmp[32];
 	SQLSMALLINT	tmplen;
+	RETCODE		rc;
 	zval		*pv_res;
 	zend_long		pv_num;

@@ -2622,7 +2648,13 @@ PHP_FUNCTION(odbc_field_type)
 		RETURN_FALSE;
 	}

-	PHP_ODBC_SQLCOLATTRIBUTE(result->stmt, (SQLUSMALLINT)pv_num, SQL_COLUMN_TYPE_NAME, tmp, 31, &tmplen, NULL);
+	tmp[0] = '\0';
+	rc = PHP_ODBC_SQLCOLATTRIBUTE(result->stmt, (SQLUSMALLINT)pv_num, SQL_COLUMN_TYPE_NAME, tmp, sizeof(tmp) - 1, &tmplen, NULL);
+	if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
+		odbc_colattribute_failed(result, pv_num);
+		RETURN_FALSE;
+	}
+
 	RETURN_STRING(tmp);
 }
 /* }}} */