Edit report at https://bugs.php.net/bug.php?id=63327&edit=1
ID: 63327
User updated by: rainer dot jung at kippdata dot de
Reported by: rainer dot jung at kippdata dot de
Summary: Crash (Bus Error) in mysqlnd due to wrong alignment
Status: Open
Type: Bug
-Package: MSSQL related
+Package: MySQL related
Operating System: Solaris Sparc
PHP Version: 5.3.18
Block user comment: N
Private report: N
New Comment:
Changed Package from MSSQL to MySQL. Original Package was chosen in error.
Previous Comments:
------------------------------------------------------------------------
[2012-10-21 21:26:30] rainer dot jung at kippdata dot de
Description:
------------
All info refers to PHP 5.3.18. The problem goes back to much older 5.3 versions
though. It seems current HEAD contains the same problematic code.
The misalignment can happen as soon as mysqlnd.collect_memory_statistics=On. It
occurs on Solaris Sparc for 32 Bit builds. It is strictly reproducible on my
Solaris 8 system, but not on Solaris 10. This is due to the fact, that a memory
allocation for a size that is only divisibleby 4 but not by 8 can return an
address only divisible by 4 (this happens on my Solaris 8 system) but it can
also return a block of memory starting at an address divisible by 8 (happens by
incident on my Solaris 10 system).
Crash happens in stack:
#0 0x002880f0 in php_mysqlnd_conn_init_pub (conn=0x70dcf4) at
/shared/build/autobuild/workdirs/20121021_163211/bld/php53/ext/mysqlnd/mysqlnd.c:2354
No locals.
#1 0x002880a0 in _mysqlnd_init (persistent=0 '\0') at
/shared/build/autobuild/workdirs/20121021_163211/bld/php53/ext/mysqlnd/mysqlnd.c:2380
ret = (MYSQLND *) 0x70dcf4
#2 0xfee742e0 in php_mysql_do_connect (ht=<value optimized out>,
return_value=0x70dc40, return_value_ptr=0x0, this_ptr=<value optimized out>,
return_value_used=1,
persistent=<value optimized out>) at
/shared/build/autobuild/workdirs/20121021_163211/bld/php53/ext/mysql/php_mysql.c:965
index_ptr = (zend_rsrc_list_entry *) 0x70e758
new_index_ptr = {ptr = 0x69e8f0, type = 2778356, refcount = 7393472}
user = 0x70db98 "myuser"
passwd = 0x70dc00 "mypass"
host_and_port = 0x70e808 "myserv:3306"
socket = 0x0
tmp = <value optimized out>
host = 0x70dc10 "myserv"
user_len = 6
passwd_len = 6
host_len = 11
hashed_details = 0x70dc80 "mysql_myserv:3306_myuser_mypass_131072"
hashed_details_length = 38
port = 3306
client_flags = 131072
mysql = <value optimized out>
free_host = 1 '\001'
new_link = 1 '\001'
connect_timeout = 60
Analysis shows, that actually the crash happens immediately before entering
php_mysqlnd_conn_init_pub(), namely in line 2352 of ext/mysqlnd/mysqlnd.c:
2346 /* {{{ mysqlnd_conn::init */
2347 static enum_func_status
2348 MYSQLND_METHOD(mysqlnd_conn, init)(MYSQLND * conn TSRMLS_DC)
2349 {
2350 DBG_ENTER("mysqlnd_conn::init");
2351 mysqlnd_stats_init(&conn->stats, STAT_LAST);
2352 SET_ERROR_AFF_ROWS(conn);
The macro SET_ERROR_AFF_ROWS is defined in ext/mysqlnd/mysqlnd_priv.h as:
#define SET_ERROR_AFF_ROWS(s) (s)->upsert_status.affected_rows = (uint64_t) ~0
So it is important, that "affected_rows" is aligned correctly for 64 Bits. So
lets check the alignment of conn. On my Solaris Sparc system it has size
sizeof(MYSQLND), which is 776 so divisible by 8 and the structure should be
correctly aligned.
But: this is only true if memory statistics for mysqlnd are turned off. If they
are turned On, the allocation of conn is actually done for 776 +sizeof(size_t)
bytes. This is due to the followinglines in ext/mysqlnd/mysqlnd_debug.c:
814 #define REAL_SIZE(s) (collect_memory_statistics? (s) + sizeof(size_t) :
(s))
...
919 /* {{{ _mysqlnd_pecalloc */
920 void * _mysqlnd_pecalloc(unsigned int nmemb, size_t size, zend_bool
persistent MYSQLND_MEM_D)
921 {
...
932 ret = pecalloc(nmemb, REAL_SIZE(size), persistent);
So here instead of allocating 776 bytes, we allocate 776+sizeof(size_t) = 776+4
= 780 bytes which is no longer divisible by 8! So memory allocation not
necessarily allocates at 8 bytes alignment.
To fix it I expect you need to replace sizeof(size_t) in the following lines by
a size that does not reduce alignment for any allocation done in
mysqlnd_debug.c. Using sizeof(uint64_t) might suffice for the time being.
814 #define REAL_SIZE(s) (collect_memory_statistics? (s) + sizeof(size_t) :
(s))
815 #define REAL_PTR(p) (collect_memory_statistics && (p)? (((char *)(p)) -
sizeof(size_t)) : (p))
816 #define FAKE_PTR(p) (collect_memory_statistics && (p)? (((char *)(p)) +
sizeof(size_t)) : (p))
In HEAD you can find the same problem in file mysqlnd_alloc.c.
Note: this problem is *not* the same as the alignment problem in mysqlnd I
reported 2.5 years ago in https://bugs.php.net/bug.php?id=51583.
Regards,
Rainer
Test script:
---------------
<h2>MySQL Test</h2>
<?php
ini_set ('display_errors', true);
// Enter your database connection info here
$db_server = 'myserv';
$db_port = '3306';
$db_name = 'mysql';
$db_username = 'myuser';
$db_password = 'mypass';
echo 'Connecting ...<br>';
$connection = mysql_connect("$db_server:$db_port", $db_username,
$db_password, $db_name);
if (! $connection) {
echo "<br><b>ERROR - Unable to connect to database server
'$db_server:$db_port': ". mysql_error() . '</b><br>';
} else {
mysql_close($connection);
}
?>
Actual result:
--------------
<h2>MySQL Test</h2>
Connecting ...<br>Bus Error (core dumped)
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=63327&edit=1