Bug #81688 [NEW]: PDO_ODBC doesn't handle fixed-length character columns with character conversio

From: Date: Thu, 02 Dec 2021 18:30:32 +0000
Subject: Bug #81688 [NEW]: PDO_ODBC doesn't handle fixed-length character columns with character conversio
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-238119@lists.php.net to get a copy of this message
From:             calvin at cmpct dot info
Operating system: IBM i 7.2
PHP version:      8.0.13
Package:          ODBC related
Bug Type:         Bug
Bug description:PDO_ODBC doesn't handle fixed-length character columns with character conversio

Description:
------------
(was kinda wanting to wait until the transition to GH issues, but
deciding to post this now... let me know if i should)

It seems PDO_ODBC doesn't handle situations where if characters are
converted as part of a data bind. For example, if the CHAR(30) column on
the database stores as one encoding and it gets converted to something
requiring more bytes to represent, there's seemingly a possibility of a
buffer overflow (what looks like to me, anyways). The example script has
one such example; an é takes one byte to represent in some encodings,
and two in UTF-8.

Db2 is a pathological case because most columns are stored as some kind
of EBCDIC encoding and gets converted to usually UTF-8 on the client,
but it's quite possible for other databases too (i.e. ISO-8859-1 columns
to UTF-8). The example I posted would probably trigger the same issue if
the driver is converting.

Looking at the unixODBC trace a user provided, I can see PHP just
allocates what the driver claims is the CHAR(n) size plus one.

PHP PDO_ODBC:

```
[ODBC][24294][1638369671.619132][SQLDescribeCol.c][504]
		Exit:[SQL_SUCCESS]                
			Column Name = [VARTE1]                
			Data Type = 70000000009b318 -> 1                
			Column Size = fffffffffffce78 -> 30 (64 bits)                
			Decimal Digits = NULLPTR                
			Nullable = NULLPTR
[ODBC][24294][1638369671.619175][SQLColAttribute.c][294]
		Entry:
			Statement = 180847ad0
			Column Number = 1
			Field Identifier = SQL_DESC_DISPLAY_SIZE
			Character Attr = 0
			Buffer Length = 0
			String Length = 0
			Numeric Attribute = fffffffffffce70
[ODBC][24294][1638369671.619219][SQLColAttribute.c][709]
		Exit:[SQL_SUCCESS]
[ODBC][24294][1638369671.619261][SQLBindCol.c][245]
		Entry:
			Statement = 180847ad0
			Column Number = 1
			Target Type = 1 SQL_CHAR
			Target Value = 700000000056540
			Buffer Length = 31
			StrLen Or Ind = 70000000009b310
[ODBC][24294][1638369671.619304][SQLBindCol.c][353]
		Exit:[SQL_SUCCESS]
```

For comparison, Node ODBC library:

```
[ODBC][24271][1638369403.771225][SQLDescribeCol.c][504]
		Exit:[SQL_SUCCESS]                
			Column Name = [VARTE1]                
			Data Type = 1805796a4 -> 1                
			Column Size = 1805796a8 -> 30 (64 bits)                
			Decimal Digits = 1805796b0 -> 0                
			Nullable = 1805796c0 -> 0
[ODBC][24271][1638369403.771312][SQLBindCol.c][245]
		Entry:
			Statement = 180552e70
			Column Number = 1
			Target Type = 1 SQL_CHAR
			Target Value = 180583130
			Buffer Length = 121
			StrLen Or Ind = 1805437d0
[ODBC][24271][1638369403.771392][SQLBindCol.c][353]
		Exit:[SQL_SUCCESS]
```

It looks to be overallocating, per:
https://github.com/markdirish/node-odbc/blob/master/src/odbc_connection.cpp#L1659

ibm_db2 actually has a flag (i5_dbcs_alloc) in case of these situations.
It is a bit crude, but seeing other ODBC consumers do it is kind of
suggesting to me it may be a valid solution.

The other option might be to reallocate more aggressively if the buffer
is inadequate. I'm unsure how tricky that'd be to implement. The
annoying option for users but simplest to prevent crashes in PHP is to
emit a warning if the driver is trying to return more that what PHP
allocated.

Test script:
---------------
<?php

// SQL to repro
create table calvin.bigchars(
    x char(30)
);

// character that must be representable as a single char in DB but
expanded on client (i.e. accented e in ISO-8859 or EBCDIC is multiple
bytes in UTF-8)
insert into calvin.bigchars
values('éééééééééééééééééééééééééééééé');
// 30
insert into calvin.bigchars
values('ééééééééééééééé'); //
15
insert into calvin.bigchars values('ééééé'); // 5
// End SQL


//Connect to IBM i
$user = 'user';
$pw = 'password';
// CCSID 1208 for UTF-8, 819 for ISO-8859-1
$dsn = 'Driver={IBM i Access ODBC
Driver};System=127.0.0.1;AlwaysCalculateResultLength=1;CCSID=1208';
$persistence = false;
// create pdo odbc connection
try {
    $pdo_odbc = new PDO("odbc:$dsn", $user, $pw, array(
        PDO::ATTR_PERSISTENT => $persistence,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING,
    ));
} catch (PDOException $e) {
        die($e->getMessage());
}
$sql = "select * from calvin.bigchars";
$pdoStmt = $pdo_odbc->prepare($sql);
echo "before PDO\n";
var_dump($pdoStmt->execute());
echo "after PDO\n";
while($record = $pdoStmt->fetch(PDO::FETCH_ASSOC)){
        echo "Record\n";
        var_dump($record["X"]);
}


Actual result:
--------------
before PDO
bool(true)
after PDO
Record
string(60) "éééééééééééééééT`�
                              ��sql"
Record
string(45) "éééééééééééééééT`�
                              "
Record
string(35) "ééééé                    "


-- 
Edit bug report at https://bugs.php.net/bug.php?id=81688&edit=1
-- 
Fix committed:                    https://bugs.php.net/fix.php?id=81688&r=fixed
Fixed in release:                 https://bugs.php.net/fix.php?id=81688&r=alreadyfixed
Need backtrace:                   https://bugs.php.net/fix.php?id=81688&r=needtrace
Need Reproduce Script:            https://bugs.php.net/fix.php?id=81688&r=needscript
Try newer version:                https://bugs.php.net/fix.php?id=81688&r=oldversion
Not developer issue:              https://bugs.php.net/fix.php?id=81688&r=support
Expected behavior:                https://bugs.php.net/fix.php?id=81688&r=notwrong
Not enough info:                  https://bugs.php.net/fix.php?id=81688&r=notenoughinfo
Submitted twice:                  https://bugs.php.net/fix.php?id=81688&r=submittedtwice
register_globals:                 https://bugs.php.net/fix.php?id=81688&r=globals
PHP version support discontinued: https://bugs.php.net/fix.php?id=81688&r=phptooold
Daylight Savings:                 https://bugs.php.net/fix.php?id=81688&r=dst
IIS Stability:                    https://bugs.php.net/fix.php?id=81688&r=isapi
Install GNU Sed:                  https://bugs.php.net/fix.php?id=81688&r=gnused
Floating point limitations:       https://bugs.php.net/fix.php?id=81688&r=float
No Zend Extensions:               https://bugs.php.net/fix.php?id=81688&r=nozend
MySQL Configuration Error:        https://bugs.php.net/fix.php?id=81688&r=mysqlcfg


Thread (37 messages)

« previous php.bugs (#238119) next »