Debian – 64bit UnixODBC and FreeTDS: a bug in libtdsodbc.so

debianodbcphp5valgrind

We have a legacy Windows web application which uses Microsoft SQL Server 2005. Two years ago we rewrote a part of this application using PHP and ODBC on a 32 bit virtualized Debian system. This application works okay (about one SQL request of thousand produces bogus data, but this is handled by the application). Debian packages used are: php5-odbc, odbcinst1debian1, tdsodbc, unixodbc, freetds-common.

Now we want to unvirtualize this and install the application as an Apache virtual host on a 64 bit Debian Lenny system. But something bad happens in the PHP function odbc_fetch_object(). I have

echo "Before odbc_fetch_object(); $query\n"; flush();
if ($query) $row = odbc_fetch_object($query);
echo "After odbc_fetch_object();\n"; flush();
echo "Edition number $row->Id\n";

but the text "After odbc_fetch_object()" and text following it are never shown.

I debugged the PHP file by calling it by php5 directly (package php5-cli). This time it really gets data from the database (the current edition number which changes every week). But after the output I get the error message string

ALERT - canary mismatch on efree() - heap overflow detected (attacker 'REMOTE_ADDR not set', file 'unknown')

You should know that Debian PHP5 is integrated with the Suhosin patch. It seems that it discovers memory corruption in odbc_fetch_object().

We tried to debug with valgrind with suppressed Zend memory allocation:

USE_ZEND_ALLOC=0 valgrind --leak-check=full ./current.php

and got following output:

==3831== Memcheck, a memory error detector.
==3831== Copyright (C) 2002-2007, and GNU GPL'd, by Julian Seward et al.
==3831== Using LibVEX rev 1854, a library for dynamic binary translation.
==3831== Copyright (C) 2004-2007, and GNU GPL'd, by OpenWorks LLP.
==3831== Using valgrind-3.3.1-Debian, a dynamic binary instrumentation framework.
==3831== Copyright (C) 2000-2007, and GNU GPL'd, by Julian Seward et al.
==3831== For more details, rerun with: -v
==3831== 
==3831== Invalid write of size 8
==3831==    at 0xD64420C: (within /usr/lib/odbc/libtdsodbc.so)
==3831==    by 0xB55E859: SQLColAttributes (in /usr/lib/libodbc.so.1.0.0)
==3831==    by 0xB34AA37: odbc_bindcols (in /usr/lib/php5/20060613/odbc.so)
==3831==    by 0xB350B86: zif_odbc_exec (in /usr/lib/php5/20060613/odbc.so)
==3831==    by 0xBDEDC9C: (within /usr/lib/php5/20060613/suhosin.so)
==3831==    by 0x6A5798: (within /usr/bin/php5)
==3831==    by 0x691003: execute (in /usr/bin/php5)
==3831==    by 0xBDEE125: (within /usr/lib/php5/20060613/suhosin.so)
==3831==    by 0x66CDF7: zend_execute_scripts (in /usr/bin/php5)
==3831==    by 0x627667: php_execute_script (in /usr/bin/php5)
==3831==    by 0x6EBFF6: main (in /usr/bin/php5)
==3831==  Address 0xd2b564c is 44 bytes inside a block of size 48 alloc'd
==3831==    at 0x4C2260E: malloc (vg_replace_malloc.c:207)
==3831==    by 0xB34A911: odbc_bindcols (in /usr/lib/php5/20060613/odbc.so)
==3831==    by 0xB350B86: zif_odbc_exec (in /usr/lib/php5/20060613/odbc.so)
==3831==    by 0xBDEDC9C: (within /usr/lib/php5/20060613/suhosin.so)
==3831==    by 0x6A5798: (within /usr/bin/php5)
==3831==    by 0x691003: execute (in /usr/bin/php5)
==3831==    by 0xBDEE125: (within /usr/lib/php5/20060613/suhosin.so)
==3831==    by 0x66CDF7: zend_execute_scripts (in /usr/bin/php5)
==3831==    by 0x627667: php_execute_script (in /usr/bin/php5)
==3831==    by 0x6EBFF6: main (in /usr/bin/php5)
Before odbc_fetch_object(): Resource id #6 
After odbc_fetch_object()
Edition number 547
Some static text
==3831== 
==3831== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 531 from 4)
==3831== malloc/free: in use at exit: 58,755 bytes in 1,558 blocks.
==3831== malloc/free: 22,559 allocs, 21,001 frees, 3,867,219 bytes allocated.
==3831== For counts of detected errors, rerun with: -v
==3831== searching for pointers to 1,558 not-freed blocks.
==3831== checked 1,223,080 bytes.
==3831== 
==3831== 
==3831== 2 bytes in 1 blocks are definitely lost in loss record 1 of 24
==3831==    at 0x4C2260E: malloc (vg_replace_malloc.c:207)
==3831==    by 0x7609D91: strdup (in /lib/libc-2.7.so)
==3831==    by 0xBDDF74B: ???
==3831==    by 0x68199D: zend_register_ini_entries (in /usr/bin/php5)
==3831==    by 0xBDDFBCF: ???
==3831==    by 0x6732DA: zend_startup_module_ex (in /usr/bin/php5)
==3831==    by 0x67828A: zend_hash_apply (in /usr/bin/php5)
==3831==    by 0x671B59: zend_startup_modules (in /usr/bin/php5)
==3831==    by 0x628E22: php_module_startup (in /usr/bin/php5)
==3831==    by 0x6EA71C: (within /usr/bin/php5)
==3831==    by 0x6EAF31: main (in /usr/bin/php5)
==3831== 
==3831== 
==3831== 292 (52 direct, 240 indirect) bytes in 1 blocks are definitely lost in loss record 11 of 24
==3831==    at 0x4C2260E: malloc (vg_replace_malloc.c:207)
==3831==    by 0x766D52F: (within /lib/libc-2.7.so)
==3831==    by 0x766DD06: __nss_database_lookup (in /lib/libc-2.7.so)
==3831==    by 0xCC2631F: ???
==3831==    by 0xCC2702C: ???
==3831==    by 0x762C101: getpwuid_r (in /lib/libc-2.7.so)
==3831==    by 0x762B9CE: getpwuid (in /lib/libc-2.7.so)
==3831==    by 0xB59C2EF: ???
==3831==    by 0xB599B2B: ???
==3831==    by 0xB58A013: ???
==3831==    by 0xB56307F: ???
==3831==    by 0xB34896D: ???
==3831== 
==3831== 
==3831== 512 bytes in 1 blocks are definitely lost in loss record 17 of 24
==3831==    at 0x4C22741: realloc (vg_replace_malloc.c:429)
==3831==    by 0x678AC8: (within /usr/bin/php5)
==3831==    by 0x678B44: (within /usr/bin/php5)
==3831==    by 0x67AEF7: _zend_hash_add_or_update (in /usr/bin/php5)
==3831==    by 0xBDED02C: ???
==3831==    by 0xBDDE995: ???
==3831==    by 0x677690: (within /usr/bin/php5)
==3831==    by 0x6634B1: zend_llist_apply_with_del (in /usr/bin/php5)
==3831==    by 0x677676: zend_startup_extensions (in /usr/bin/php5)
==3831==    by 0x628E5B: php_module_startup (in /usr/bin/php5)
==3831==    by 0x6EA71C: (within /usr/bin/php5)
==3831==    by 0x6EAF31: main (in /usr/bin/php5)
==3831== 
==3831== LEAK SUMMARY:
==3831==    definitely lost: 566 bytes in 3 blocks.
==3831==    indirectly lost: 240 bytes in 10 blocks.
==3831==      possibly lost: 0 bytes in 0 blocks.
==3831==    still reachable: 57,949 bytes in 1,545 blocks.
==3831==         suppressed: 0 bytes in 0 blocks.
==3831== Reachable blocks (those to which a pointer was found) are not shown.
==3831== To see them, rerun with: --leak-check=full --show-reachable=yes

Can you advise a workaround for something that seems to be a memory allocation bug in the library libtdsodbc.so? Or do you have an idea what could we do except getting the source code and fixing the bug ourselves?

Best Answer

Hmm, seems there's more 64-bit bugs in the PHP odbc driver. I reported this bug to redhat a few years ago; that was for PHP 4.3.x on RHEL4, and back then it was already fixed upstream so presumably Debian Lenny already has the fixed version.

Beyond fixing it yourself, or at least filing bug reports upstream and in the mean time not using php-odbc on 64-bit platforms, I don't have any better advice. Sorry.

EDIT: One thing you can try, is to not use ODBC at all, but rather the TDS interface directly. Though I'm not sure if there exists a PHP level TDS interface, but you can do this at least if you use the PHP PDO database interface which is available in PHP 5.x. See the PDO_DBLIB module.