php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #72402 _php_mb_regex_ereg_replace_exec - double free
Submitted: 2016-06-14 11:47 UTC Modified: 2016-06-23 12:50 UTC
From: shm@php.net Assigned: stas (profile)
Status: Closed Package: mbstring related
PHP Version: 5.5.36 OS: Linux
Private report: No CVE-ID: 2016-5768
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: shm@php.net
New email:
PHP Version: OS:

 

 [2016-06-14 11:47 UTC] shm@php.net
Description:
------------
# short description 

_php_mb_regex_ereg_replace_exec when called by mb_ereg_replace_callback frees the description twice in case when callback execution failed (i.e. threw am exception).

# why I think it's security issue?

callbacks used in mb_ereg_replace_callback are usually provided by the script author, but may fail in many cases on particular input. In that case, PHP interpreter will free memory twice, which may be used to allocate memory area for two distinct purposes. So if remote attacker is able to trigger this bug remotely (which depends heavily on callbacks used with this function), then this bug may be exploited to gain code execution. But feel free to evaluate this bug whatever you want. :)

# code

#10 0x0000000000a9fe3f in _php_mb_regex_ereg_replace_exec (execute_data=0x7ffff23b98e0, return_value=0x7ffff23b98c0, options=0xc, is_callable=0x1)
    at /home/shm/src/php-src/ext/mbstring/php_mbregex.c:1012
1012                    efree(description);
gdb-peda$ list
1007                    }
1008                    onig_region_free(regs, 0);
1009            }
1010    
1011            if (description) {
1012                    efree(description);
1013            }
1014            if (regs != NULL) {
1015                    onig_region_free(regs, 1);
1016            }
gdb-peda$ list -40
972                                     /* null terminate buffer */
973                                     smart_str_0(&eval_buf);
974     
975                                     arg_replace_fci.param_count = 1;
976                                     arg_replace_fci.params = args;
977                                     arg_replace_fci.retval = &retval;
978                                     if (zend_call_function(&arg_replace_fci, &arg_replace_fci_cache) == SUCCESS &&
979                                                     !Z_ISUNDEF(retval)) {
980                                             convert_to_string_ex(&retval);
981                                             smart_str_appendl(&out_buf, Z_STRVAL(retval), Z_STRLEN(retval));
gdb-peda$ 
982                                             smart_str_free(&eval_buf);
983                                             zval_ptr_dtor(&retval);
984                                     } else {
985                                             efree(description);
986                                             if (!EG(exception)) {
987                                                     php_error_docref(NULL, E_WARNING, "Unable to call custom replacement function");
988                                             }
989                                     }
990                                     zval_ptr_dtor(&subpats);
991                             }
gdb-peda$ 
992     
993                             n = regs->end[0];
994                             if ((pos - (OnigUChar *)string) < n) {
995                                     pos = (OnigUChar *)string + n;
996                             } else {
997                                     if (pos < string_lim) {
998                                             smart_str_appendl(&out_buf, (char *)pos, 1);
999                                     }
1000                                    pos++;
1001                            }
gdb-peda$ 
1002                    } else { /* nomatch */
1003                            /* stick that last bit of string on our output */
1004                            if (string_lim - pos > 0) {
1005                                    smart_str_appendl(&out_buf, (char *)pos, string_lim - pos);
1006                            }
1007                    }
1008                    onig_region_free(regs, 0);
1009            }
1010    
1011            if (description) {
gdb-peda$ 
1012                    efree(description);
1013            }

decsription is freed twice in 1012 and 984 lines, as shown here:

Breakpoint 6, _php_mb_regex_ereg_replace_exec (execute_data=0x7ffff2a150e0, return_value=0x7ffff2a150c0, options=0xc, is_callable=0x1)
    at /home/shm/src/php-src/ext/mbstring/php_mbregex.c:985
985                                             efree(description);
gdb-peda$ print description
$5 = 0x7ffff2a80200 "/home/shm/research/svn/notes/0day/php/php7/full

Breakpoint 5, _php_mb_regex_ereg_replace_exec (execute_data=0x7ffff2a150e0, return_value=0x7ffff2a150c0, options=0xc, is_callable=0x1)
    at /home/shm/src/php-src/ext/mbstring/php_mbregex.c:1012
1012                    efree(description);
gdb-peda$ print description
$6 = 0x7ffff2a80200 ""


ASAN report bt:
bt
#0  0x00007ffff3596cc9 in __GI_raise (sig=sig@entry=0x6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#1  0x00007ffff359a0d8 in __GI_abort () at abort.c:89
#2  0x00007ffff4e667f9 in ?? () from /usr/lib/x86_64-linux-gnu/libasan.so.0
#3  0x00007ffff4e5d3ec in ?? () from /usr/lib/x86_64-linux-gnu/libasan.so.0
#4  0x00007ffff4e63fe2 in ?? () from /usr/lib/x86_64-linux-gnu/libasan.so.0
#5  0x00007ffff4e636e1 in ?? () from /usr/lib/x86_64-linux-gnu/libasan.so.0
#6  0x00007ffff4e6265f in ?? () from /usr/lib/x86_64-linux-gnu/libasan.so.0
#7  0x00007ffff4e53a18 in ?? () from /usr/lib/x86_64-linux-gnu/libasan.so.0
#8  0x00007ffff4e60368 in free () from /usr/lib/x86_64-linux-gnu/libasan.so.0
#9  0x0000000000f87b2f in _efree (ptr=0x60220001fa80, __zend_filename=0x18d7dc0 "/home/shm/src/php-src/ext/mbstring/php_mbregex.c", __zend_lineno=0x3f4,
    __zend_orig_filename=0x0, __zend_orig_lineno=0x0) at /home/shm/src/php-src/Zend/zend_alloc.c:2461
#10 0x0000000000a9fe3f in _php_mb_regex_ereg_replace_exec (execute_data=0x7ffff23b98e0, return_value=0x7ffff23b98c0, options=0xc, is_callable=0x1)
    at /home/shm/src/php-src/ext/mbstring/php_mbregex.c:1012
#11 0x0000000000aa0257 in zif_mb_ereg_replace_callback (execute_data=0x7ffff23b98e0, return_value=0x7ffff23b98c0) at /home/shm/src/php-src/ext/mbstring/php_mbregex.c:1051
#12 0x00000000010fe846 in ZEND_DO_ICALL_SPEC_HANDLER () at /home/shm/src/php-src/Zend/zend_vm_execute.h:586
#13 0x00000000010fd8bf in execute_ex (ex=0x7ffff23b9830) at /home/shm/src/php-src/Zend/zend_vm_execute.h:414
#14 0x00000000010fdb1a in zend_execute (op_array=0x60220001fba0, return_value=0x0) at /home/shm/src/php-src/Zend/zend_vm_execute.h:458
#15 0x0000000001012aef in zend_execute_scripts (type=0x8, retval=0x0, file_count=0x3) at /home/shm/src/php-src/Zend/zend.c:1427
#16 0x0000000000ea2159 in php_execute_script (primary_file=0x7fffffffcb30) at /home/shm/src/php-src/main/main.c:2494
#17 0x000000000122931c in do_cli (argc=0x2, argv=0x60060000ed40) at /home/shm/src/php-src/sapi/cli/php_cli.c:974
#18 0x000000000122b8f8 in main (argc=0x2, argv=0x60060000ed40) at /home/shm/src/php-src/sapi/cli/php_cli.c:1344
#19 0x00007ffff3581ec5 in __libc_start_main (main=0x122a3d0 <main>, argc=0x2, argv=0x7fffffffe038, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
    stack_end=0x7fffffffe028) at libc-start.c:287
#20 0x000000000042dc49 in _start ()


Test script:
---------------
<?php
function exception_handler($exception) {
  echo "Uncaught exception: " , $exception->getMessage(), "\n";
}
$var10 = "exception_handler";
$var14 = mb_ereg_replace_callback("", $var10, "");


Expected result:
----------------
description is freed once

Actual result:
--------------
description is freed twice

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-06-19 04:50 UTC] stas@php.net
-Assigned To: +Assigned To: stas
 [2016-06-19 04:50 UTC] stas@php.net
Fixed in security repo as 5b597a2e5b28e2d5a52fc1be13f425f08f47cb62 and in https://gist.github.com/9ecb704363f2df6b5e256a9ab3557257
 [2016-06-20 18:16 UTC] shm@php.net
LGTM, thanks.
 [2016-06-21 06:46 UTC] stas@php.net
-PHP Version: 7.0Git-2016-06-14 (Git) +PHP Version: 5.5.36
 [2016-06-21 06:48 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=5b597a2e5b28e2d5a52fc1be13f425f08f47cb62
Log: Fix bug #72402: _php_mb_regex_ereg_replace_exec - double free
 [2016-06-21 06:48 UTC] stas@php.net
-Status: Assigned +Status: Closed
 [2016-06-21 07:03 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=5b597a2e5b28e2d5a52fc1be13f425f08f47cb62
Log: Fix bug #72402: _php_mb_regex_ereg_replace_exec - double free
 [2016-06-21 07:26 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=5b597a2e5b28e2d5a52fc1be13f425f08f47cb62
Log: Fix bug #72402: _php_mb_regex_ereg_replace_exec - double free
 [2016-06-21 07:27 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=5b597a2e5b28e2d5a52fc1be13f425f08f47cb62
Log: Fix bug #72402: _php_mb_regex_ereg_replace_exec - double free
 [2016-06-22 05:58 UTC] krakjoe@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=5b597a2e5b28e2d5a52fc1be13f425f08f47cb62
Log: Fix bug #72402: _php_mb_regex_ereg_replace_exec - double free
 [2016-06-23 12:50 UTC] kaplan@php.net
-CVE-ID: +CVE-ID: 2016-5768
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Sun Nov 19 01:31:42 2017 UTC