[php-src] master: Merge branch 'PHP-8.5'
Ilia Alshanetsky <[email protected]>
| Newsgroups | gmane.comp.php.cvs.general |
|---|---|
| Message-ID | <[email protected]> |
Author: Ilia Alshanetsky (iliaal)
Date: 2026-07-14T15:30:51-04:00
Commit: https://github.com/php/php-src/commit/df2c6bb19e72ed0eb9fb9a3f9d609fcd07f6455f
Raw diff: https://github.com/php/php-src/commit/df2c6bb19e72ed0eb9fb9a3f9d609fcd07f6455f.diff
Merge branch 'PHP-8.5'
* PHP-8.5:
Fix GH-22668: odbc heap over-read on oversized column value
Changed paths:
A ext/odbc/tests/gh22668.phpt
M NEWS
M ext/odbc/php_odbc.c
M ext/odbc/php_odbc_includes.h
M ext/odbc/tests/config.inc
Diff:
diff --git a/NEWS b/NEWS
index c7bee9b7e958..793e0966dfbb 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.6.0alpha3
+- ODBC:
+ . Fixed bug GH-22668 (Heap buffer over-read when a column value exceeds the
+ driver-reported display size). (iliaal)
+
- PDO_ODBC:
. Fixed bug GH-22667 (Heap buffer over-read when a column value exceeds the
driver-reported display size). (iliaal)
diff --git a/ext/odbc/php_odbc.c b/ext/odbc/php_odbc.c
index 17120e88af04..55cfba7e6833 100644
--- a/ext/odbc/php_odbc.c
+++ b/ext/odbc/php_odbc.c
@@ -661,6 +661,7 @@ void odbc_bindcols(odbc_result *result)
for(i = 0; i < result->numcols; i++) {
bool char_extra_alloc = false;
+ result->values[i].value_max_len = 0;
colfieldid = SQL_COLUMN_DISPLAY_SIZE;
rc = SQLColAttribute(result->stmt, (SQLUSMALLINT)(i+1), SQL_DESC_NAME,
@@ -740,6 +741,7 @@ void odbc_bindcols(odbc_result *result)
displaysize *= 4;
}
result->values[i].value = (char *)emalloc(displaysize + 1);
+ result->values[i].value_max_len = displaysize;
rc = SQLBindCol(result->stmt, (SQLUSMALLINT)(i+1), SQL_C_CHAR, result->values[i].value,
displaysize + 1, &result->values[i].vallen);
break;
@@ -1458,7 +1460,11 @@ static void php_odbc_fetch(INTERNAL_FUNCTION_PARAMETERS, bool return_array, php_
ZVAL_FALSE(&tmp);
break;
}
- ZVAL_STRINGL(&tmp, result->values[i].value, result->values[i].vallen);
+ SQLLEN str_len = result->values[i].vallen;
+ if (str_len > result->values[i].value_max_len) {
+ str_len = result->values[i].value_max_len;
+ }
+ ZVAL_STRINGL(&tmp, result->values[i].value, str_len);
break;
}
@@ -1671,7 +1677,11 @@ PHP_FUNCTION(odbc_result)
php_error_docref(NULL, E_WARNING, "Cannot get data of column #%d (driver cannot determine length)", field_ind + 1);
RETURN_FALSE;
} else {
- RETURN_STRINGL(result->values[field_ind].value, result->values[field_ind].vallen);
+ SQLLEN str_len = result->values[field_ind].vallen;
+ if (str_len > result->values[field_ind].value_max_len) {
+ str_len = result->values[field_ind].value_max_len;
+ }
+ RETURN_STRINGL(result->values[field_ind].value, str_len);
}
break;
}
diff --git a/ext/odbc/php_odbc_includes.h b/ext/odbc/php_odbc_includes.h
index 1c53e35bf43a..090dfc3089a9 100644
--- a/ext/odbc/php_odbc_includes.h
+++ b/ext/odbc/php_odbc_includes.h
@@ -90,6 +90,7 @@ typedef struct odbc_result_value {
char name[256];
char *value;
SQLLEN vallen;
+ SQLLEN value_max_len;
SQLLEN coltype;
} odbc_result_value;
diff --git a/ext/odbc/tests/config.inc b/ext/odbc/tests/config.inc
index 8d74feeb6ffb..a0f24f301558 100644
--- a/ext/odbc/tests/config.inc
+++ b/ext/odbc/tests/config.inc
@@ -1,5 +1,7 @@
<?php
+defined('ODBC_SQLITE_DSN') || define('ODBC_SQLITE_DSN', "Driver=SQLite3;Database=:memory:");
+
$dsn = getenv("ODBC_TEST_DSN");
$user = getenv("ODBC_TEST_USER");
$pass = getenv("ODBC_TEST_PASS");
diff --git a/ext/odbc/tests/gh22668.phpt b/ext/odbc/tests/gh22668.phpt
new file mode 100644
index 000000000000..6dbaa9797170
--- /dev/null
+++ b/ext/odbc/tests/gh22668.phpt
@@ -0,0 +1,33 @@
+--TEST--
+GH-22668 (Heap buffer over-read when a column value exceeds the bound buffer)
+--EXTENSIONS--
+odbc
+--SKIPIF--
+<?php
+include __DIR__ . "/config.inc";
+$conn = @odbc_connect(ODBC_SQLITE_DSN, null, null);
+if (!$conn) {
+ die("skip requires the SQLite3 ODBC driver");
+}
+?>
+--FILE--
+<?php
+include __DIR__ . "/config.inc";
+$conn = odbc_connect(ODBC_SQLITE_DSN, null, null);
+
+// The SQLite3 driver reports a 255 byte display size for a computed column, so
+// the bound buffer holds at most 255 bytes while the value is far longer. A
+// conforming driver truncates into the buffer but reports the full length; the
+// returned string must stay within the buffer, not over-read past it.
+$result = odbc_exec($conn, "SELECT printf('%.*c', 4096, 'A') AS data");
+$row = odbc_fetch_array($result);
+$s = $row['data'];
+
+echo "clamped to buffer: "; var_dump(strlen($s) < 4096);
+echo "only value bytes: "; var_dump(strlen($s) === substr_count($s, 'A'));
+
+odbc_close($conn);
+?>
+--EXPECT--
+clamped to buffer: bool(true)
+only value bytes: bool(true)