Edit report at https://bugs.php.net/bug.php?id=80460&edit=1
ID: 80460
Comment by: ml at menten dot com
Reported by: mirish at ibm dot com
Summary: ODBC doesn't account for SQL_NO_TOTAL indicator,
causing segmentation fault
Status: No Feedback
Type: Bug
Package: ODBC related
Operating System: RHEL 7.9
PHP Version: Irrelevant
Assigned To: cmb
Block user comment: N
Private report: N
New Comment:
Hello,
according to IBM support we have a related problem reading special characters from VARGRAPHIC fields
in PHP 7.3.26 with the following driver:
IBM i Access Client Solutions - PASE Application Package for IBM i.
Connectivity to Db2® for i using ODBC with unixODBC
ibm-iaccess.ppc64 1.1.0.14-0 @/ibm-iaccess-1.1.0.14-0.ibmi7.2.ppc64
If we write to a VARGRAPHIC field of length 70, for example, we can write the full 70 characters,
but we can only read them if the 70 characters do not consist of special characters or if there are
at most 35 special characters (e.g. ä).
For example, if we have i special characters and 69 non-special characters in the field, it cannot
be read out either. Analogously, this problem also occurs with CHAR and VARCHAR fields.
Test Script:
<?php
ini_set('display_errors', true);
header('Content-Type: text/html; charset=utf-8');
ini_set("default_mimetype", "text/html");
ini_set("default_charset", "UTF-8");
// setup
$user = "test";
$pw = "test";
// full 70 length / no special characters -> OK
# $test = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
// 35 length / all special characters -> OK
# $test =
"äääääääääääääääääääääääääääääääääää";
// 36 length / 35 special characters, 1 normal character -> no result
$test =
"äääääääääääääääääääääääääääääääääää1";
// 52 length / 35 normal characters, 17 special character -> OK
# $test =
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaäääääääääääääääää";
// 53 length / 35 normal characters, 18 special character -> no result
# $test =
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaääääääääääääääääää";
// full 70 length / 70 special characters -> no result
# $test =
"ääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääää";
try
{
$connection = new PDO("odbc:DRIVER={IBM i Access ODBC
Driver};SYSTEM=localhost;DATABASE=*LOCAL;NAM=1;DBQ=IEFFECTDB,IEFFECT28;CCSID=1208;DEBUG=524288",
$user, $pw, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT));
}
catch (Exception $e)
{
echo($e->getMessage());
echo "<br>";
echo($e->getCode());
die();
}
// create table
$query = "CREATE TABLE IEFFECTDB/TEST (TEST1 VARGRAPHIC(70) ccsid 1200 NOT null DEFAULT
'')";
$params = array();
$stmt = $connection->prepare($query);
$result = $stmt->execute($params);
// insert
$query = "INSERT INTO IEFFECTDB/TEST (TEST1) VALUES (?)";
$params = array($test);
$stmt = $connection->prepare($query);
$result = $stmt->execute($params);
// select
$query = "SELECT TEST1 FROM IEFFECTDB/TEST";
$params = array();
$stmt = $connection->prepare($query);
$result = $stmt->execute($params);
echo "<pre>";
var_dump($stmt->fetch(PDO::FETCH_ASSOC));
echo "<pre>";
// drop table
$query = "DROP TABLE IEFFECTDB/TEST";
$params = array();
$stmt = $connection->prepare($query);
$result = $stmt->execute($params);
$connection = null;
echo
"------------------------------------------------------------------------------------------------------------";
echo "<br><br>";
try
{
$connection = new PDO("odbc:DRIVER={IBM i Access ODBC
Driver};SYSTEM=localhost;DATABASE=*LOCAL;NAM=1;DBQ=IEFFECTDB,IEFFECT28;CCSID=1208", $user, $pw,
array(PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT));
}
catch (Exception $e)
{
echo($e->getMessage());
echo "<br>";
echo($e->getCode());
die();
}
// create table
$query = "CREATE TABLE IEFFECTDB/TEST (TEST1 VARGRAPHIC(70) ccsid 1200 NOT null DEFAULT
'')";
$params = array();
$stmt = $connection->prepare($query);
$result = $stmt->execute($params);
// insert
$query = "INSERT INTO IEFFECTDB/TEST (TEST1) VALUES (?)";
$params = array($test);
$stmt = $connection->prepare($query);
$result = $stmt->execute($params);
// select
$query = "SELECT TEST1 FROM IEFFECTDB/TEST";
$params = array();
$stmt = $connection->prepare($query);
$result = $stmt->execute($params);
echo "<pre>";
var_dump($stmt->fetch(PDO::FETCH_ASSOC));
echo "<pre>";
// drop table
$query = "DROP TABLE IEFFECTDB/TEST";
$params = array();
$stmt = $connection->prepare($query);
$result = $stmt->execute($params);
Previous Comments:
------------------------------------------------------------------------
[2020-12-13 04:22:09] php-bugs at lists dot php dot net
No feedback was provided. The bug is being suspended because
we assume that you are no longer experiencing the problem.
If this is not the case and you are able to provide the
information that was requested earlier, please do so and
change the status of the bug back to "Re-Opened". Thank you.
------------------------------------------------------------------------
[2020-12-04 15:57:28] cmb@php.net
While the other bug report was indeed about SQL_NO_DATA, the fix
is supposed to cater to any result other than SQL_SUCCESS or
SQL_SUCCESS_WITH_INFO, and to return early in those cases.
Anyway, could you please generate a stack backtrace[1] and post it
here (or provide it somewhere else if it is very large)?
[1] <https://bugs.php.net/bugs-generating-backtrace.php>
------------------------------------------------------------------------
[2020-12-02 17:07:42] mirish at ibm dot com
It doesn't look like they are related. Bug #44618 seems to note that it should handle
SQL_NO_DATA, but that is a cursor indicator different from SQL_NO_TOTAL, which indicates that there
is data left to retrieve, but the driver doesn't know (or doesn't want to calculate) how
much data is left. As written, SQL_NO_TOTAL indicator (-4) is passed as a length to string
functions.
I can confirm that this segmentation fault occurs on 7.4.13.
------------------------------------------------------------------------
[2020-12-02 10:55:01] cmb@php.net
Isn't the second part basically a duplicate of bug #44618, which
is supposed to be fixed as of PHP 7.3.25 and 7.4.13?
------------------------------------------------------------------------
[2020-12-02 02:46:36] mirish at ibm dot com
Description:
------------
The ODBC extension (ext/odbc/php_odbc.c) causes a segmentation fault when SQL_NO_TOTAL indicator is
returned to a bound column from a call to SQLFetch and similar functions.
There are two major issues:
1. There is an assumption that the number of bytes returned from a call to SQLColAttribute with a
type of SQL_DESC_OCTET_LENGTH will return the number of bytes required to store the value. There are
cases when data is stored in an encoding which stores character data in a single byte, which then
needs to be converted to variable multi-byte when SQLFetch is actually called (eg. EBCDIC 278 to
UTF8 conversion). This might be a broken driver implementation on our part, but the ODBC docs are
rather thin and at times seem contradictory on this point.
This will cause a buffer to be allocated of size X, where up to X * 4 may be needed to store a
string. There is a variable in the code (charextraalloc) to allocate buffers of this size, but none
of the checks work for ODBC v 3.0+ drivers. When the buffer is too small, the driver may return
SQL_NO_TOTAL when in the StrLen_or_IndPtr, as it didn't have enough space for the column, and
might not know how many bytes are left.
2. The bigger issue is that calls to SQLFetch can return SQL_NO_TOTAL to the StrLen_or_IndPtr stored
on SQLBindCol when the buffer bound isn't large enough for the data, but the code isn't
checking for this. Instead, the code checks if the value of StrLen_or_IndPtr is SQL_NULL_DATA and
assigns a value to null. If it isn't SQL_NULL_DATA, it assumes that it holds the length of the
string, then calls functions like ZVAL_STRINGL and RETURN_STRINGL with SQL_NO_TOTAL (which evaluates
to -4) as the length. Trying to create a string with length of -4 causes a segmentation fault, which
causes the program to crash.
As mentioned, the first issue might be a problem in driver implementation, but the second definitely
needs to be fixed. SQL_NO_TOTAL is definitely a valid StrLen_or_IndPtr value, and there are no
checks for it in the code, causing a segfault.
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=80460&edit=1