|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[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
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sun Dec 07 03:00:01 2025 UTC |
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.