Skip to content

Commit df2c6bb

Browse files
committed
Merge branch 'PHP-8.5'
* PHP-8.5: Fix GH-22668: odbc heap over-read on oversized column value
2 parents 84f90c5 + 0c11f48 commit df2c6bb

5 files changed

Lines changed: 52 additions & 2 deletions

File tree

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
?? ??? ????, PHP 8.6.0alpha3
44

5+
- ODBC:
6+
. Fixed bug GH-22668 (Heap buffer over-read when a column value exceeds the
7+
driver-reported display size). (iliaal)
8+
59
- PDO_ODBC:
610
. Fixed bug GH-22667 (Heap buffer over-read when a column value exceeds the
711
driver-reported display size). (iliaal)

ext/odbc/php_odbc.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,7 @@ void odbc_bindcols(odbc_result *result)
661661

662662
for(i = 0; i < result->numcols; i++) {
663663
bool char_extra_alloc = false;
664+
result->values[i].value_max_len = 0;
664665
colfieldid = SQL_COLUMN_DISPLAY_SIZE;
665666

666667
rc = SQLColAttribute(result->stmt, (SQLUSMALLINT)(i+1), SQL_DESC_NAME,
@@ -740,6 +741,7 @@ void odbc_bindcols(odbc_result *result)
740741
displaysize *= 4;
741742
}
742743
result->values[i].value = (char *)emalloc(displaysize + 1);
744+
result->values[i].value_max_len = displaysize;
743745
rc = SQLBindCol(result->stmt, (SQLUSMALLINT)(i+1), SQL_C_CHAR, result->values[i].value,
744746
displaysize + 1, &result->values[i].vallen);
745747
break;
@@ -1458,7 +1460,11 @@ static void php_odbc_fetch(INTERNAL_FUNCTION_PARAMETERS, bool return_array, php_
14581460
ZVAL_FALSE(&tmp);
14591461
break;
14601462
}
1461-
ZVAL_STRINGL(&tmp, result->values[i].value, result->values[i].vallen);
1463+
SQLLEN str_len = result->values[i].vallen;
1464+
if (str_len > result->values[i].value_max_len) {
1465+
str_len = result->values[i].value_max_len;
1466+
}
1467+
ZVAL_STRINGL(&tmp, result->values[i].value, str_len);
14621468
break;
14631469
}
14641470

@@ -1671,7 +1677,11 @@ PHP_FUNCTION(odbc_result)
16711677
php_error_docref(NULL, E_WARNING, "Cannot get data of column #%d (driver cannot determine length)", field_ind + 1);
16721678
RETURN_FALSE;
16731679
} else {
1674-
RETURN_STRINGL(result->values[field_ind].value, result->values[field_ind].vallen);
1680+
SQLLEN str_len = result->values[field_ind].vallen;
1681+
if (str_len > result->values[field_ind].value_max_len) {
1682+
str_len = result->values[field_ind].value_max_len;
1683+
}
1684+
RETURN_STRINGL(result->values[field_ind].value, str_len);
16751685
}
16761686
break;
16771687
}

ext/odbc/php_odbc_includes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ typedef struct odbc_result_value {
9090
char name[256];
9191
char *value;
9292
SQLLEN vallen;
93+
SQLLEN value_max_len;
9394
SQLLEN coltype;
9495
} odbc_result_value;
9596

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)