Skip to content

Commit 0c11f48

Browse files
committed
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4: Fix phpGH-22668: odbc heap over-read on oversized column value
2 parents 3583a7b + 8e4daef commit 0c11f48

5 files changed

Lines changed: 57 additions & 3 deletions

File tree

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ PHP NEWS
4040
. Fixed bug GH-18173 (ext/hash relies on implementation-defined malloc
4141
alignment). (iliaal)
4242

43+
- ODBC:
44+
. Fixed bug GH-22668 (Heap buffer over-read when a column value exceeds the
45+
driver-reported display size). (iliaal)
46+
4347
- Opcache:
4448
. Fixed bug GH-22158 (Tracing JIT dispatches the observer begin handler
4549
through the wrong run_time_cache slot on megamorphic calls). (ptondereau,

ext/odbc/php_odbc.c

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,7 @@ void odbc_bindcols(odbc_result *result)
670670

671671
for(i = 0; i < result->numcols; i++) {
672672
bool char_extra_alloc = false;
673+
result->values[i].value_max_len = 0;
673674
colfieldid = SQL_COLUMN_DISPLAY_SIZE;
674675

675676
rc = SQLColAttribute(result->stmt, (SQLUSMALLINT)(i+1), SQL_DESC_NAME,
@@ -749,6 +750,7 @@ void odbc_bindcols(odbc_result *result)
749750
displaysize *= 4;
750751
}
751752
result->values[i].value = (char *)emalloc(displaysize + 1);
753+
result->values[i].value_max_len = displaysize;
752754
rc = SQLBindCol(result->stmt, (SQLUSMALLINT)(i+1), SQL_C_CHAR, result->values[i].value,
753755
displaysize + 1, &result->values[i].vallen);
754756
break;
@@ -1420,7 +1422,11 @@ static void php_odbc_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int result_type)
14201422
ZVAL_FALSE(&tmp);
14211423
break;
14221424
}
1423-
ZVAL_STRINGL(&tmp, result->values[i].value, result->values[i].vallen);
1425+
SQLLEN str_len = result->values[i].vallen;
1426+
if (str_len > result->values[i].value_max_len) {
1427+
str_len = result->values[i].value_max_len;
1428+
}
1429+
ZVAL_STRINGL(&tmp, result->values[i].value, str_len);
14241430
break;
14251431
}
14261432

@@ -1570,7 +1576,11 @@ PHP_FUNCTION(odbc_fetch_into)
15701576
ZVAL_FALSE(&tmp);
15711577
break;
15721578
}
1573-
ZVAL_STRINGL(&tmp, result->values[i].value, result->values[i].vallen);
1579+
SQLLEN str_len = result->values[i].vallen;
1580+
if (str_len > result->values[i].value_max_len) {
1581+
str_len = result->values[i].value_max_len;
1582+
}
1583+
ZVAL_STRINGL(&tmp, result->values[i].value, str_len);
15741584
break;
15751585
}
15761586
zend_hash_index_update(Z_ARRVAL_P(pv_res_arr), i, &tmp);
@@ -1793,7 +1803,11 @@ PHP_FUNCTION(odbc_result)
17931803
php_error_docref(NULL, E_WARNING, "Cannot get data of column #%d (driver cannot determine length)", field_ind + 1);
17941804
RETURN_FALSE;
17951805
} else {
1796-
RETURN_STRINGL(result->values[field_ind].value, result->values[field_ind].vallen);
1806+
SQLLEN str_len = result->values[field_ind].vallen;
1807+
if (str_len > result->values[field_ind].value_max_len) {
1808+
str_len = result->values[field_ind].value_max_len;
1809+
}
1810+
RETURN_STRINGL(result->values[field_ind].value, str_len);
17971811
}
17981812
break;
17991813
}

ext/odbc/php_odbc_includes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ typedef struct odbc_result_value {
9292
char name[256];
9393
char *value;
9494
SQLLEN vallen;
95+
SQLLEN value_max_len;
9596
SQLLEN coltype;
9697
} odbc_result_value;
9798

ext/odbc/tests/config.inc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
defined('ODBC_SQLITE_DSN') || define('ODBC_SQLITE_DSN', "Driver=SQLite3;Database=:memory:");
4+
35
$dsn = getenv("ODBC_TEST_DSN");
46
$user = getenv("ODBC_TEST_USER");
57
$pass = getenv("ODBC_TEST_PASS");

ext/odbc/tests/gh22668.phpt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
GH-22668 (Heap buffer over-read when a column value exceeds the bound buffer)
3+
--EXTENSIONS--
4+
odbc
5+
--SKIPIF--
6+
<?php
7+
include __DIR__ . "/config.inc";
8+
$conn = @odbc_connect(ODBC_SQLITE_DSN, null, null);
9+
if (!$conn) {
10+
die("skip requires the SQLite3 ODBC driver");
11+
}
12+
?>
13+
--FILE--
14+
<?php
15+
include __DIR__ . "/config.inc";
16+
$conn = odbc_connect(ODBC_SQLITE_DSN, null, null);
17+
18+
// The SQLite3 driver reports a 255 byte display size for a computed column, so
19+
// the bound buffer holds at most 255 bytes while the value is far longer. A
20+
// conforming driver truncates into the buffer but reports the full length; the
21+
// returned string must stay within the buffer, not over-read past it.
22+
$result = odbc_exec($conn, "SELECT printf('%.*c', 4096, 'A') AS data");
23+
$row = odbc_fetch_array($result);
24+
$s = $row['data'];
25+
26+
echo "clamped to buffer: "; var_dump(strlen($s) < 4096);
27+
echo "only value bytes: "; var_dump(strlen($s) === substr_count($s, 'A'));
28+
29+
odbc_close($conn);
30+
?>
31+
--EXPECT--
32+
clamped to buffer: bool(true)
33+
only value bytes: bool(true)

0 commit comments

Comments
 (0)