php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #73082 string length overflow in mb_encode_* function
Submitted: 2016-09-14 07:48 UTC Modified: 2017-02-13 01:19 UTC
From: secresearch at fortinet dot com Assigned: stas (profile)
Status: Closed Package: mbstring related
PHP Version: 5.6.26RC1 OS: ALL
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: secresearch at fortinet dot com
New email:
PHP Version: OS:

 

 [2016-09-14 07:48 UTC] secresearch at fortinet dot com
Description:
------------
Multi byte encode function did not check the length of encode data, that cause output length will be overflow the integer and cause heap corruption:

mbfl_string * mbfl_html_numeric_entity(
    mbfl_string *string,
    mbfl_string *result,
    int *convmap,
    int mapsize,
    int type)
{
...
	/* feed data */
	p = string->val;
	n = string->len;
	if (p != NULL) {
		while (n > 0) { //this loop will cause the corruption
			if ((*encoder->filter_function)(*p++, encoder) < 0) {
				break;
			}
			n--;
		}
	}

mbfl_memory_device_output(int c, void *data)
{
	mbfl_memory_device *device = (mbfl_memory_device *)data;

	if (device->pos >= device->length) {
		/* reallocate buffer */
		int newlen;
		unsigned char *tmp;

		newlen = device->length + device->allocsz;
		tmp = (unsigned char *)mbfl_realloc((void *)device->buffer, newlen*sizeof(unsigned char));
		if (tmp == NULL) {
			return -1;
		}
		device->length = newlen;
		device->buffer = tmp;
	}

	device->buffer[device->pos++] = (unsigned char)c; //device->pos = -2147483647 -> crash
	return c;
}


Patch:
------------
*check device->pos value in mbfl_memory_device_output
*or just check the output length must accepted (<int_max) in function mbfl_html_numeric_entity

Test script:
---------------
<?php

ini_set('memory_limit', -1);

$str = str_repeat("a", 0xffffffff/9); //a -> &#97; output_len = input_len*5 -> overflow integer

var_dump(strlen($str));

$str1 = mb_encode_numericentity ($str, array (0x0, 0xffff, 0, 0xffff), 'UTF-8');
?>

Expected result:
----------------
No Crash



Actual result:
--------------
Starting program: /home/test/PHP-5.6.26/sapi/cli/php ~/phptestcase/testmb_encode_numericentity_negative.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
int(2147483646)

Program received signal SIGSEGV, Segmentation fault.
0x000000000070a97a in mbfl_memory_device_output (c=38, data=0x7fffffffa670) at /home/test/PHP-5.6.26/ext/mbstring/libmbfl/mbfl/mbfl_memory_device.c:157
157		device->buffer[device->pos++] = (unsigned char)c;
(gdb) bt
#0  0x000000000070a97a in mbfl_memory_device_output (c=38, data=0x7fffffffa670) at /home/test/PHP-5.6.26/ext/mbstring/libmbfl/mbfl/mbfl_memory_device.c:157
#1  0x0000000000701f74 in mbfl_filt_conv_wchar_utf8 (c=38, filter=0x7ffff7fa12c8) at /home/test/PHP-5.6.26/ext/mbstring/libmbfl/filters/mbfilter_utf8.c:219
#2  0x00000000007083c6 in collector_encode_htmlnumericentity (c=26085, data=0x7fffffffa690) at /home/test/PHP-5.6.26/ext/mbstring/libmbfl/mbfl/mbfilter.c:2700
#3  0x0000000000701cf6 in mbfl_filt_conv_utf8_wchar (c=165, filter=0x7ffff7fa1348) at /home/test/PHP-5.6.26/ext/mbstring/libmbfl/filters/mbfilter_utf8.c:139
#4  0x00000000007093c8 in mbfl_html_numeric_entity (string=0x7fffffffa730, result=0x7fffffffa710, convmap=0x7ffff7fa1b48, mapsize=1, type=0) at /home/test/PHP-5.6.26/ext/mbstring/libmbfl/mbfl/mbfilter.c:3092
#5  0x0000000000714548 in php_mb_numericentity_exec (ht=3, return_value=0x7ffff7fa1298, return_value_ptr=0x7ffff7f6c150, this_ptr=0x0, return_value_used=1, type=0) at /home/test/PHP-5.6.26/ext/mbstring/mbstring.c:3804
#6  0x00000000007145ff in zif_mb_encode_numericentity (ht=3, return_value=0x7ffff7fa1298, return_value_ptr=0x7ffff7f6c150, this_ptr=0x0, return_value_used=1) at /home/test/PHP-5.6.26/ext/mbstring/mbstring.c:3818
#7  0x00000000009ccf9e in zend_do_fcall_common_helper_SPEC (execute_data=0x7ffff7f6c248) at /home/test/PHP-5.6.26/Zend/zend_vm_execute.h:558
#8  0x00000000009d4bce in ZEND_DO_FCALL_SPEC_CONST_HANDLER (execute_data=0x7ffff7f6c248) at /home/test/PHP-5.6.26/Zend/zend_vm_execute.h:2602
#9  0x00000000009cb491 in execute_ex (execute_data=0x7ffff7f6c248) at /home/test/PHP-5.6.26/Zend/zend_vm_execute.h:363
#10 0x00000000009cbe7d in zend_execute (op_array=0x7ffff7fa09e0) at /home/test/PHP-5.6.26/Zend/zend_vm_execute.h:388
#11 0x00000000009873bc in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /home/test/PHP-5.6.26/Zend/zend.c:1341
#12 0x00000000008f826e in php_execute_script (primary_file=0x7fffffffe280) at /home/test/PHP-5.6.26/main/main.c:2613
#13 0x0000000000aaa1b2 in do_cli (argc=2, argv=0x1382960) at /home/test/PHP-5.6.26/sapi/cli/php_cli.c:994
#14 0x0000000000aab200 in main (argc=2, argv=0x1382960) at /home/test/PHP-5.6.26/sapi/cli/php_cli.c:1378
(gdb)

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-09-25 23:08 UTC] stas@php.net
-Summary: memory corruption in mb_encode_* function +Summary: string length overflow in mb_encode_* function -Assigned To: +Assigned To: stas
 [2016-09-25 23:08 UTC] stas@php.net
The fix is in security repo as e1709b7e588cbda71c577f6e5b701713d0c70a23 and in https://gist.github.com/5677c59cc852e4935cad3571a0daf761
 [2016-09-26 04:10 UTC] secresearch at fortinet dot com
How to access this fix e1709b7e588cbda71c577f6e5b701713d0c70a23
 [2016-09-26 06:34 UTC] secresearch at fortinet dot com
The patch must looks like this:

--- PHP-5.6.26_old/ext/mbstring/libmbfl/mbfl/mbfilter.c	2016-09-26 11:54:21.369998637 +0800
+++ PHP-5.6.26/ext/mbstring/libmbfl/mbfl/mbfilter.c	2016-09-26 14:32:27.536978132 +0800
@@ -111,6 +111,7 @@
 	0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x41,0x42,0x43,0x44,0x45,0x46
 };
 
+#define INT_MAX_LIMIT           ((1UL << (SIZEOF_INT * 8 - 1)) - 1)
 
 
 /*
@@ -3042,7 +3043,7 @@
 	int n;
 	unsigned char *p;
 
-	if (string == NULL || result == NULL) {
+	if (string == NULL || result == NULL || (string->len > INT_MAX_LIMIT/5)) {
 		return NULL;
 	}
 	mbfl_string_init(result);


output_len <= input_len*5 (5 times is the maximum output length, this depend on convmap, type of encode. So I just leave 5 here for quick fix)
 [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=e1709b7e588cbda71c577f6e5b701713d0c70a23
Log: Fix bug #73082
 [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=e1709b7e588cbda71c577f6e5b701713d0c70a23
Log: Fix bug #73082
 [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=e1709b7e588cbda71c577f6e5b701713d0c70a23
Log: Fix bug #73082
 [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=e1709b7e588cbda71c577f6e5b701713d0c70a23
Log: Fix bug #73082
 [2017-02-13 01:19 UTC] stas@php.net
-Type: Security +Type: Bug
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Nov 21 15:01:30 2024 UTC