php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #76390 A Division by Zero can be triggered using range()
Submitted: 2018-05-29 12:57 UTC Modified: 2018-06-05 06:45 UTC
From: daniel dot teuchert at rub dot de Assigned:
Status: Closed Package: Reproducible crash
PHP Version: 7.2.6 OS: Linux 4.6.2
Private report: No CVE-ID: None
 [2018-05-29 12:57 UTC] daniel dot teuchert at rub dot de
Description:
------------
Root Cause
==========
The variable lstep in the function range() is not tested for 0 before being used as a divisor:
In line 2897 the variable called "step" is tested if zero or less. Afterwards step is stored in a variable called lstep and lstep is later used as a divisor. The problem is, that lstep is a variable of type zend_ulong but the variable step can be a float number.
So for example if step is a number like 1.4096248204881922e+20 it will be tested as greater than 0 in line 2897 (so it will not produce an error) but lstep will then contain the value 0 because of the type conversion.
Later when the macro RANGE_CHECK_LONG_INIT_ARRAY is called lstep is used as the divisor in line 2746 which causes a division by zero.

Below is the relevant code from array.c (commit 44be0fa67b46506353c3dd27d97801bd0eddbb00):

2921: if (step <= 0) {
2922:    err = 1;
2923:    goto err;
2924: }
2925: 
2926: lstep = (zend_ulong)step;
2927:
2928: Z_TYPE_INFO(tmp) = IS_LONG;
2929: if (low > high) { 		/* Negative steps */
2930:	if ((zend_ulong)(low - high) < lstep) {
2931:	  err = 1;
2932:	  goto err;
2933:	}
2934:
2935:	RANGE_CHECK_LONG_INIT_ARRAY(low, high);
...


2769: #define RANGE_CHECK_LONG_INIT_ARRAY(start, end) do { \
2770: 	zend_ulong __calc_size = (start - end) / lstep; \
2771: 	if (__calc_size >= HT_MAX_SIZE - 1) { \
2772: 	  php_error_docref(NULL, E_WARNING, "The supplied range exceeds the maximum array size: start=" ZEND_LONG_FMT " end=" ZEND_LONG_FMT, end, start); \
2773: 			RETURN_FALSE; \
2774: 	  } \
2775: 	  size = (uint32_t)(__calc_size + 1); \
2776: 	  array_init_size(return_value, size); \
2777: 	  zend_hash_real_init(Z_ARRVAL_P(return_value), 1); \
2778: 	} while (0)


Impact
======
If an attacker can control the step variable of the range function he can cause a denial of service condition by causing php to crash because of a division by zero.


Test script:
---------------
<?php
$a = "140962482048819216326.24.";
range(0,1,$a);
?>

Expected result:
----------------
Check if lstep is zero and abort if so.

Actual result:
--------------
Stack backtrace:

#0  0x000000000078e41f in zif_range (execute_data=0x7ffff441d0b0, return_value=0x7fffffffa510) at /.../php-7.2.6/ext/standard/array.c:2925
#1  0x000000000097cdb3 in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER () at /.../php-7.2.6/Zend/zend_vm_execute.h:573
#2  0x0000000000a03c9d in execute_ex (ex=0x7ffff441d030) at /.../php-7.2.6/Zend/zend_vm_execute.h:59731
#3  0x0000000000a090a9 in zend_execute (op_array=0x7ffff4483300, return_value=0x0) at /.../php-7.2.6/Zend/zend_vm_execute.h:63760
#4  0x0000000000918cc4 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /.../php-7.2.6/Zend/zend.c:1496
#5  0x000000000087cab5 in php_execute_script (primary_file=0x7fffffffcbd0) at /.../php-7.2.6/main/main.c:2590
#6  0x0000000000a0bc8a in do_cli (argc=2, argv=0x13fc090) at /.../php-7.2.6/sapi/cli/php_cli.c:1011
#7  0x0000000000a0ce47 in main (argc=2, argv=0x13fc090) at /.../php-7.2.6/sapi/cli/php_cli.c:1404

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2018-05-30 12:18 UTC] cmb@php.net
-Status: Open +Status: Verified
 [2018-05-30 12:18 UTC] cmb@php.net
I can confirm that the provided test script crashes the
interpreter. However, I get a Floating point exception with
current master, and I wonder why

  <?php
  $a = "140962482048819216326.24";
  range(0,1,$a);

works as expected.
 [2018-05-30 13:13 UTC] daniel dot teuchert at rub dot de
In the case of
<?php
  $a = "140962482048819216326.24";
  range(0,1,$a);

In line 2795 to 2800 the variable is_step_double is set to 1. If $a = "140962482048819216326.24." it isn't so is_step_double stays 0.

	if (zstep) {
		if (Z_TYPE_P(zstep) == IS_DOUBLE ||
			(Z_TYPE_P(zstep) == IS_STRING && is_numeric_string(Z_STRVAL_P(zstep), Z_STRLEN_P(zstep), NULL, NULL, 0) == IS_DOUBLE)
		) {
			is_step_double = 1;
		}

later in line 2866 the variable is_step_double is tested:
2866: } else if (Z_TYPE_P(zlow) == IS_DOUBLE || Z_TYPE_P(zhigh) == IS_DOUBLE || is_step_double) {

So from here the program flow is different. The rest should be clear from the source code.
If the mentioned "else if" path is taken step is not converted into another variable and the macro RANGE_CHECK_LONG_INIT_ARRAY is not used, therefore now division by zero.
 [2018-05-30 13:31 UTC] cmb@php.net
> If $a = "140962482048819216326.24." it isn't so
> is_step_double stays 0.

It seems to me that this is the actual problem.
 [2018-06-01 06:40 UTC] daniel dot teuchert at rub dot de
I guess you are right, as long as changing this also makes sure that lstep can not become zero because of a type conversion.
 [2018-06-05 06:45 UTC] stas@php.net
-Type: Security +Type: Bug
 [2018-06-05 06:45 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=73bf238507e41cc87107055e39a57e1ebb5619df
Log: Fix bug #76390 - do not allow invalid strings in range()
 [2018-06-05 06:45 UTC] stas@php.net
-Status: Verified +Status: Closed
 [2018-06-05 06:45 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=c12fc77bdfbaa545aaa57b31633bc54a64784842
Log: Fix bug #76390 - do not allow invalid strings in range()
 [2018-06-05 09:20 UTC] laruence@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=73bf238507e41cc87107055e39a57e1ebb5619df
Log: Fix bug #76390 - do not allow invalid strings in range()
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Wed Apr 24 23:01:34 2024 UTC