[php-src] PHP-8.5: Merge branch 'PHP-8.4' into 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:29:36-04:00
Commit: https://github.com/php/php-src/commit/0c11f486d7e423967f29a2a9e56968e9a95cb92e
Raw diff: https://github.com/php/php-src/commit/0c11f486d7e423967f29a2a9e56968e9a95cb92e.diff
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4:
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 de82bc685dae..2c88dee0b23c 100644
--- a/NEWS
+++ b/NEWS
@@ -40,6 +40,10 @@ PHP NEWS
. Fixed bug GH-18173 (ext/hash relies on implementation-defined malloc
alignment). (iliaal)
+- ODBC:
+ . Fixed bug GH-22668 (Heap buffer over-read when a column value exceeds the
+ driver-reported display size). (iliaal)
+
- Opcache:
. Fixed bug GH-22158 (Tracing JIT dispatches the observer begin handler
through the wrong run_time_cache slot on megamorphic calls). (ptondereau,
diff --git a/ext/odbc/php_odbc.c b/ext/odbc/php_odbc.c
index 7a6bc2871825..e75d11b554b8 100644
--- a/ext/odbc/php_odbc.c
+++ b/ext/odbc/php_odbc.c
@@ -670,6 +670,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,
@@ -749,6 +750,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;
@@ -1420,7 +1422,11 @@ static void php_odbc_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int result_type)
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;
}
@@ -1570,7 +1576,11 @@ PHP_FUNCTION(odbc_fetch_into)
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;
}
zend_hash_index_update(Z_ARRVAL_P(pv_res_arr), i, &tmp);
@@ -1793,7 +1803,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 058684c2ed03..0a99f1a756e3 100644
--- a/ext/odbc/php_odbc_includes.h
+++ b/ext/odbc/php_odbc_includes.h
@@ -92,6 +92,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)