php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #47928 Crash in mysqli_stmt_fetch() with longtext column
Submitted: 2009-04-08 20:37 UTC Modified: 2009-04-20 08:57 UTC
From: jjuergens at web dot de Assigned: mysql
Status: Not a bug Package: MySQLi related
PHP Version: 5.*, 6CVS (2009-04-19) OS: *
Private report: No CVE-ID:
 [2009-04-08 20:37 UTC] jjuergens at web dot de
Description:
------------
When trying to retrieve data from a MySQL-Database using a mysqli-statement, PHP just crashes. I excerpted the code below from a larger web-application and invoked it via the PHP-Cli and it still fails with a memory-error.
Interestingly enough though, if you just change a single value within $arg1 and $arg2 (e.g. replace the last 8 from $arg1 with a 7), the bug doesn't occur anymore.
I've included a Valgrind-output which shows the error.

MySQL-Version is 5.0.67.

Reproduce code:
---------------
<?php
/*
This is the database-table used:

CREATE TABLE `sessionData` (
 `sessionId` varchar(60) collate utf8_unicode_ci NOT NULL,
 `pathHash` varchar(32) collate utf8_unicode_ci NOT NULL,
 `path` varchar(100) collate utf8_unicode_ci NOT NULL,
 `data` longtext collate utf8_unicode_ci NOT NULL,
 PRIMARY KEY  (`sessionId`,`pathHash`),
 CONSTRAINT `sessionData_ibfk_1` FOREIGN KEY (`sessionId`) REFERENCES `sessionIndex` (`sessionId`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

*/

//create db-link
$dbLink=new mysqli("host","user","pass","db",3306);

//create the statement
$stmt=$dbLink->prepare("SELECT * FROM `sessionData` WHERE `sessionId`=?  AND `pathHash`=? LIMIT 1");

//bind params
$arg1="e75c7781166e3a361b7cff546563d5e8";
$arg2="9ddec3abec5c92628022210892e76afb";
$stmt->bind_param("ss",$arg1,$arg2);

//execute
$stmt->execute();

//create set of result-fields (see http://php.net/manual/de/mysqli-stmt.bind-result.php#85470)
$resData=$stmt->result_metadata();
$resFields=array();
$bindArray=array();

while($field=mysqli_fetch_field($resData)){
	$resFields[]=&$bindArray[$field->name];
}

//bind result-fields
call_user_func_array(array($stmt,'bind_result'),$resFields);
		
//fetch result
$res=0;
while($stmt->fetch()){
	$tmpRes=array();
	foreach($bindArray as $key=>$value){
		$tmpRes[$key]=$value;
	}
	//add this row	(not needed for bug reproduction)		
// 	array_push($result,$tmpRes);
	$res++;
}

//close statement
$stmt->close();

Expected result:
----------------
In this case, the script should just exit normally without a result.

Actual result:
--------------
Running it in a shell, I get a memory-error.
Using Valgrind, I get the following:

==13749== Memcheck, a memory error detector.
==13749== Copyright (C) 2002-2007, and GNU GPL'd, by Julian Seward et al.
==13749== Using LibVEX rev 1854, a library for dynamic binary translation.
==13749== Copyright (C) 2004-2007, and GNU GPL'd, by OpenWorks LLP.
==13749== Using valgrind-3.3.1, a dynamic binary instrumentation framework.
==13749== Copyright (C) 2000-2007, and GNU GPL'd, by Julian Seward et al.
==13749== For more details, rerun with: -v
==13749==
==13749== Invalid read of size 4
==13749==    at 0x51AA261: mysql_stmt_fetch (in /usr/lib/libmysqlclient.so.15.0.0)
==13749==    by 0x5187D5C: zif_mysqli_stmt_fetch (in /usr/lib/php5/extensions/mysqli.so)
==13749==    by 0x81DE342: (within /usr/bin/php5)
==13749==    by 0x81C94BA: execute (in /usr/bin/php5)
==13749==    by 0x81A3D4F: zend_execute_scripts (in /usr/bin/php5)
==13749==    by 0x81589F9: php_execute_script (in /usr/bin/php5)
==13749==    by 0x821C780: main (in /usr/bin/php5)
==13749==  Address 0x84 is not stack'd, malloc'd or (recently) free'd
==13749==
==13749== Process terminating with default action of signal 11 (SIGSEGV)
==13749==  Access not within mapped region at address 0x84
==13749==    at 0x51AA261: mysql_stmt_fetch (in /usr/lib/libmysqlclient.so.15.0.0)
==13749==    by 0x5187D5C: zif_mysqli_stmt_fetch (in /usr/lib/php5/extensions/mysqli.so)
==13749==    by 0x81DE342: (within /usr/bin/php5)
==13749==    by 0x81C94BA: execute (in /usr/bin/php5)
==13749==    by 0x81A3D4F: zend_execute_scripts (in /usr/bin/php5)
==13749==    by 0x81589F9: php_execute_script (in /usr/bin/php5)
==13749==    by 0x821C780: main (in /usr/bin/php5)
==13749==
==13749== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 118 from 2)
==13749== malloc/free: in use at exit: 1,296,363 bytes in 13,676 blocks.
==13749== malloc/free: 14,687 allocs, 1,011 frees, 2,096,685 bytes allocated.
==13749== For counts of detected errors, rerun with: -v
==13749== searching for pointers to 13,676 not-freed blocks.
==13749== checked 1,736,688 bytes.
==13749==
==13749== LEAK SUMMARY:
==13749==    definitely lost: 30,599 bytes in 11 blocks.
==13749==      possibly lost: 10,263 bytes in 2 blocks.
==13749==    still reachable: 1,255,501 bytes in 13,663 blocks.
==13749==         suppressed: 0 bytes in 0 blocks.
==13749== Rerun with --leak-check=full to see details of leaked memory.


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2009-04-18 09:57 UTC] jjuergens at web dot de
<?php
//IMPORTANT: Database-Name (here: tst) needs to have exactly 3 characters!
$dbLink=new mysqli("localhost","user","pass","tst",3306);

$dbLink->query("CREATE TABLE IF NOT EXISTS `sessionData` (
  `sessionId` varchar(60) collate utf8_unicode_ci NOT NULL,
  `pathHash` varchar(32) collate utf8_unicode_ci NOT NULL,
  `path` varchar(100) collate utf8_unicode_ci NOT NULL,
  `data` longtext collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`sessionId`,`pathHash`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci");
$dbLink->query("INSERT INTO `sessionData` (`sessionId`, `pathHash`, `path`, `data`) VALUES
('e75c7781166e3a361b7cff546563d5e8', '633fed500f479acaaaf54be8ec9ac657', '/bla', '0018a901234001222425678901235678345612341315789012345678901234567890123423456789012223456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678')");

$stmt=$dbLink->prepare("SELECT * FROM `sessionData` WHERE `sessionId`=?  AND `pathHash`=? LIMIT 1");
$arg1="e75c7781166e3a361b7cff546563d5e8";
$arg2="633fed500f479acaaaf54be8ec9ac657";
$stmt->bind_param("ss",$arg1,$arg2);
$stmt->execute();
$resData=$stmt->result_metadata();

while($field=mysqli_fetch_field($resData)){
 	$resFields[$field->name]=null;
}

call_user_func_array(array($stmt,'bind_result'),$resFields);
$result=array();		

while($stmt->fetch()){
	$tmpRes=array();
	foreach($resFields as $key=>$value){
		$tmpRes[$key]=$value;
	}
 	array_push($result,$tmpRes);
}

$stmt->close();
print_r($result);
?>
 [2009-04-19 10:59 UTC] jani@php.net
Above example causes crash also on my test server. (I removed other 
irrelevant comments)
 [2009-04-19 14:07 UTC] jani@php.net
Here is the shortest possible test I could come up with:

<?php

/* Test database and table with data:
drop database crashtest; create database crashtest; use crashtest;
create table crash ( test longtext );
insert into crash set test='123456789';
grant select on crashtest.* to 'test'@'localhost';
*/

$dbLink=new mysqli("localhost","test","","crashtest",3306);
$stmt=$dbLink->prepare("SELECT test FROM crash");
$stmt->execute();
$stmt->bind_result($foo);
while($stmt->fetch());
$stmt->close();
?>

The problem seems to be with the longtext column. If that is changed 
to text column, everything works just fine.

 [2009-04-19 14:11 UTC] jani@php.net
See also bug #46808

 [2009-04-19 14:44 UTC] jjuergens at web dot de
Yeah, you're right: Soon as I change the column-type from longtext to text, PHP doesn't crash anymore. The example you provided also crashes on my debug-enabled PHP-Version, while the Opensuse-Version (with Suoshin-Patch) throws efree()-errors until there are more than 396 characters in the textfield.
I actually tried to debug the PHP-code some (with very limited knowledge) and I think that the problem is somewhere within the binding of the resultset since thats where the script stops.
 [2009-04-19 15:14 UTC] jani@php.net
Here's better reproduce data (the longtext column has to have enough 
data to cause crash):

drop database crashtest; create database crashtest; use crashtest;
create table crash ( test longtext );
insert into crash set test='
12345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890
';
grant select on crashtest.* to 'test'@'localhost';

 [2009-04-19 16:19 UTC] jani@php.net
In PHP_5_3 / HEAD the crash happens with any BLOB/TEXT types.
(due to mysqli_api.c:398)

This might be also a MySQL bug since it seems to set MYSQL_TYPE_BLOB 
always for any blob column.
 [2009-04-20 08:57 UTC] johannes@php.net
Sorry, but your problem does not imply a bug in PHP itself.  For a
list of more appropriate places to ask for help using PHP, please
visit http://www.php.net/support.php as this bug system is not the
appropriate forum for asking support questions.  Due to the volume
of reports we can not explain in detail here why your report is not
a bug.  The support channels will be able to provide an explanation
for you.

Thank you for your interest in PHP.

This is indeed an issue with the MySQL cLient library. It works properly when using mysqlnd with 5.3 (--with-mysqli=mysqlnd) or when using MySQL's Connector/C, which is a standalone version of libmysql distributed witohut the server.

I didn't try using a current vcersion of libmysql bundled with the server.
 [2010-03-02 02:44 UTC] rob at tdd dot org dot uk
I experienced this problem regardless of BLOB, LONGBLOB, TEXT, LONGTEXT etc...

However to fix, I found that if I called mysqli_stmt_store_result after executing the statement then the problem went away. The documentation says it increases performance but at a cost of memory, but I would argue that for BLOB's you should only be selecting them for a single row anyway.
 
PHP Copyright © 2001-2014 The PHP Group
All rights reserved.
Last updated: Sun Apr 20 15:01:54 2014 UTC