Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ PHP NEWS
. Fixed IntlChar methods leaving stale global error state after successful
calls. (Xuyang Zhang)

- ODBC:
. Fixed bug GH-22668 (Heap buffer over-read when a column value exceeds the
driver-reported display size). (iliaal)

- OpenSSL:
. Fixed timeout for supplemental read at end of a blocking stream in SSL
stream wrapper. (ilutov)
Expand Down
21 changes: 18 additions & 3 deletions ext/odbc/php_odbc.c
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ void odbc_bindcols(odbc_result *result)

for(i = 0; i < result->numcols; i++) {
charextraalloc = 0;
result->values[i].value_max_len = 0;
colfieldid = SQL_COLUMN_DISPLAY_SIZE;

rc = PHP_ODBC_SQLCOLATTRIBUTE(result->stmt, (SQLUSMALLINT)(i+1), PHP_ODBC_SQL_DESC_NAME,
Expand All @@ -706,6 +707,7 @@ void odbc_bindcols(odbc_result *result)
#ifdef HAVE_ADABAS
case SQL_TIMESTAMP:
result->values[i].value = (char *)emalloc(27);
result->values[i].value_max_len = 26;
SQLBindCol(result->stmt, (SQLUSMALLINT)(i+1), SQL_C_CHAR, result->values[i].value,
27, &result->values[i].vallen);
break;
Expand Down Expand Up @@ -775,6 +777,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;
Expand Down Expand Up @@ -1492,7 +1495,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;
}

Expand Down Expand Up @@ -1660,7 +1667,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);
Expand Down Expand Up @@ -1910,7 +1921,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;
}
Expand Down
1 change: 1 addition & 0 deletions ext/odbc/php_odbc_includes.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ typedef struct odbc_result_value {
char name[256];
char *value;
SQLLEN vallen;
SQLLEN value_max_len;
SQLLEN coltype;
} odbc_result_value;

Expand Down
31 changes: 31 additions & 0 deletions ext/odbc/tests/gh22668.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
GH-22668 (Heap buffer over-read when a column value exceeds the bound buffer)
--EXTENSIONS--
odbc
--SKIPIF--
<?php
$conn = @odbc_connect("Driver=SQLite3;Database=:memory:", null, null);
if (!$conn) {
die("skip requires the SQLite3 ODBC driver");
}
?>
--FILE--
<?php
$conn = odbc_connect("Driver=SQLite3;Database=:memory:", 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)
Loading