Req #70351 [NEW]: zend_parse_parameters returns invalid address for string parameter on 64bit OS

From: Date: Tue, 25 Aug 2015 05:21:13 +0000
Subject: Req #70351 [NEW]: zend_parse_parameters returns invalid address for string parameter on 64bit OS
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-195490@lists.php.net to get a copy of this message
From: chenxy at gmail dot com Operating system: CentOS 7.1.1513 x86_64 PHP version: 7.0.0RC1 Package: Reproducible crash Bug Type: Feature/Change Request Bug description:zend_parse_parameters returns invalid address for string parameter on 64bit OS Description: ------------ I am modifying an old extension of PHP 5.x, trying to let it to work on PHP 7.0.0. But I always get 'Segmentation fault'. zend_parse_parameters always returns an invalid address for the char *key. My OS is CentOS 7.1.1503, x86_64. PHP version is 7.0.0beta[123] and 7.0.0RC1, compiled with default options. The code is as following: PHP_METHOD(TestMod, get) { char *key = NULL; // Strictly, I should use 'size_t key_len', but 'int key_len' works properly for PHP 5.x int key_len = 0; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) == FAILURE) { return; } printf("key: %s\n", key); RETURN_TRUE; } After a long time of debugging, I find that it's due to the declaration of 'key_len', if I use 'size_t key_len', the extension runs normally, but 'int key_len' always causes an invalid pointer returned. PHP 7.0.0 uses zend_string internally in processing string parameters. It returns ZSTR_LEN(str) as the string length: #define ZSTR_LEN(zstr) (zstr)->len ... *dest = ZSTR_VAL(str); *dest_len = ZSTR_LEN(str); (zstr)->len is of type 'size_t', which is 8 bytes in an OS of x86_64. But in PHP 5.x, zval is used internally in processing string parameters. it returns (zval).value.str.len as the string length: #define Z_STRLEN_PP(zval_pp) Z_STRLEN(**zval_pp) #define Z_STRLEN(zval) (zval).value.str.len ... *p = Z_STRVAL_PP(arg); *pl = Z_STRLEN_PP(arg); (zval).value.str.len is of type 'int', which is 4 bytes in an OS of x86_64. So if I use 'int key_len' as input, in PHP 5.x: *dest_len = (zval).value.str.len; But in PHP 7.0.0: *dest_len = (zstr)->len; So in PHP 7.0.0, *dest_len overwrites the content of *dest, causes it to point to an invalid address, which leads to 'Segmentation Fault'. Test script: --------------- PHP_METHOD(TestMod, get) { char *key = NULL; // Strictly, I should use 'size_t key_len', but 'int key_len' works properly for PHP 5.x int key_len = 0; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) == FAILURE) { return; } printf("key: %s\n", key); RETURN_TRUE; } Expected result: ---------------- A valid address of char *key should be returned. Although 'size_t key_len' works normally, but the old fashion of 'int key_len' causes a crash, which is hard to debug. Checking of this type of improper declaration in compilation time may be difficult. Perhaps we may use sizeof() to check the size of the input variable. If it is less than 8, we may throw an exception. Anyway, programmers hate 'segmentation fault'. Actual result: -------------- See the debugging process to clarify it: (gdb) list 1128 } 1129 if (check_null && UNEXPECTED(!str)) { 1130 *dest = NULL; 1131 *dest_len = 0; 1132 } else { 1133 *dest = ZSTR_VAL(str); 1134 *dest_len = ZSTR_LEN(str); 1135 } 1136 return 1; 1137 } (gdb) print dest $1 = (char **) 0x7fffffffa658 (gdb) print *dest $2 = 0x0 (gdb) print dest_len $3 = (size_t *) 0x7fffffffa654 (gdb) s 1134 *dest_len = ZSTR_LEN(str); (gdb) print dest $4 = (char **) 0x7fffffffa658 (gdb) print *dest $5 = 0x7ffff5e555d8 "abc" (gdb) s 1136 return 1; (gdb) print dest_len $6 = (size_t *) 0x7fffffffa654 (gdb) print *dest_len $7 = 3 (gdb) print dest $8 = (char **) 0x7fffffffa658 (gdb) print *dest $9 = 0x7fff00000000 <Address 0x7fff00000000 out of bounds> (gdb) -- Edit bug report at https://bugs.php.net/bug.php?id=70351&edit=1 -- Try a snapshot (PHP 5.4): https://bugs.php.net/fix.php?id=70351&r=trysnapshot54 Try a snapshot (PHP 5.5): https://bugs.php.net/fix.php?id=70351&r=trysnapshot55 Try a snapshot (trunk): https://bugs.php.net/fix.php?id=70351&r=trysnapshottrunk Fixed in SVN: https://bugs.php.net/fix.php?id=70351&r=fixed Fixed in release: https://bugs.php.net/fix.php?id=70351&r=alreadyfixed Need backtrace: https://bugs.php.net/fix.php?id=70351&r=needtrace Need Reproduce Script: https://bugs.php.net/fix.php?id=70351&r=needscript Try newer version: https://bugs.php.net/fix.php?id=70351&r=oldversion Not developer issue: https://bugs.php.net/fix.php?id=70351&r=support Expected behavior: https://bugs.php.net/fix.php?id=70351&r=notwrong Not enough info: https://bugs.php.net/fix.php?id=70351&r=notenoughinfo Submitted twice: https://bugs.php.net/fix.php?id=70351&r=submittedtwice register_globals: https://bugs.php.net/fix.php?id=70351&r=globals PHP 4 support discontinued: https://bugs.php.net/fix.php?id=70351&r=php4 Daylight Savings: https://bugs.php.net/fix.php?id=70351&r=dst IIS Stability: https://bugs.php.net/fix.php?id=70351&r=isapi Install GNU Sed: https://bugs.php.net/fix.php?id=70351&r=gnused Floating point limitations: https://bugs.php.net/fix.php?id=70351&r=float No Zend Extensions: https://bugs.php.net/fix.php?id=70351&r=nozend MySQL Configuration Error: https://bugs.php.net/fix.php?id=70351&r=mysqlcfg

« previous php.bugs (#195490) next »