Skip to content

Commit f8ec5a1

Browse files
committed
Fix range inference hang
We shouldn't switch from range to no range for ZEND_DIV and instead explicitly return an overflowing range. Otherwise the range will not actually get updated during widening, and we'll perform essentially infinite narrowing. Fixes oss-fuzz #40566.
1 parent 1a2fb90 commit f8ec5a1

File tree

2 files changed

+30
-23
lines changed

2 files changed

+30
-23
lines changed

ext/opcache/Optimizer/zend_inference.c

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -656,35 +656,26 @@ static int zend_inference_calc_binary_op_range(
656656
op2_min = OP2_MIN_RANGE();
657657
op1_max = OP1_MAX_RANGE();
658658
op2_max = OP2_MAX_RANGE();
659-
if (op2_min <= 0 && op2_max >= 0) {
660-
/* If op2 crosses zero, then floating point values close to zero might be
661-
* possible, which will result in arbitrarily large results. As such, we can't
662-
* do anything useful in that case. */
663-
break;
664-
}
665-
if (op1_min == ZEND_LONG_MIN && op2_max == -1) {
666-
/* Avoid ill-defined division, which may trigger SIGFPE. */
667-
break;
668-
}
669-
670-
zend_long t1_, t2_, t3_, t4_;
671-
float_div(op1_min, op2_min, &t1, &t1_);
672-
float_div(op1_min, op2_max, &t2, &t2_);
673-
float_div(op1_max, op2_min, &t3, &t3_);
674-
float_div(op1_max, op2_max, &t4, &t4_);
675659

676-
/* The only case in which division can "overflow" either a division by an absolute
677-
* value smaller than one, or LONG_MIN / -1 in particular. Both cases have already
678-
* been excluded above. */
679-
if (OP1_RANGE_UNDERFLOW() ||
680-
OP2_RANGE_UNDERFLOW() ||
681-
OP1_RANGE_OVERFLOW() ||
682-
OP2_RANGE_OVERFLOW()) {
660+
/* If op2 crosses zero, then floating point values close to zero might be
661+
* possible, which will result in arbitrarily large results (overflow). Also
662+
* avoid dividing LONG_MIN by -1, which is UB. */
663+
if (OP1_RANGE_UNDERFLOW() || OP2_RANGE_UNDERFLOW() ||
664+
OP1_RANGE_OVERFLOW() || OP2_RANGE_OVERFLOW() ||
665+
(op2_min <= 0 && op2_max >= 0) ||
666+
(op1_min == ZEND_LONG_MIN && op2_max == -1)
667+
) {
683668
tmp->underflow = 1;
684669
tmp->overflow = 1;
685670
tmp->min = ZEND_LONG_MIN;
686671
tmp->max = ZEND_LONG_MAX;
687672
} else {
673+
zend_long t1_, t2_, t3_, t4_;
674+
float_div(op1_min, op2_min, &t1, &t1_);
675+
float_div(op1_min, op2_max, &t2, &t2_);
676+
float_div(op1_max, op2_min, &t3, &t3_);
677+
float_div(op1_max, op2_max, &t4, &t4_);
678+
688679
tmp->min = MIN(MIN(MIN(t1, t2), MIN(t3, t4)), MIN(MIN(t1_, t2_), MIN(t3_, t4_)));
689680
tmp->max = MAX(MAX(MAX(t1, t2), MAX(t3, t4)), MAX(MAX(t1_, t2_), MAX(t3_, t4_)));
690681
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Range inference should not hang
3+
--FILE--
4+
<?php
5+
6+
function test() {
7+
$a = 0;
8+
while (true) {
9+
$a = $a+!$a=$a/!!~$a;
10+
}
11+
}
12+
13+
?>
14+
===DONE===
15+
--EXPECT--
16+
===DONE===

0 commit comments

Comments
 (0)