From f8ec5a1d4c002a6bdfcec6b2582f66d58d32003c Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 1 Nov 2021 12:29:48 +0100 Subject: [PATCH] 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. --- ext/opcache/Optimizer/zend_inference.c | 37 ++++++++------------- ext/opcache/tests/range_inference_hang.phpt | 16 +++++++++ 2 files changed, 30 insertions(+), 23 deletions(-) create mode 100644 ext/opcache/tests/range_inference_hang.phpt diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c index 9dd1ba72cc1..b981a1fc1de 100644 --- a/ext/opcache/Optimizer/zend_inference.c +++ b/ext/opcache/Optimizer/zend_inference.c @@ -656,35 +656,26 @@ static int zend_inference_calc_binary_op_range( op2_min = OP2_MIN_RANGE(); op1_max = OP1_MAX_RANGE(); op2_max = OP2_MAX_RANGE(); - if (op2_min <= 0 && op2_max >= 0) { - /* If op2 crosses zero, then floating point values close to zero might be - * possible, which will result in arbitrarily large results. As such, we can't - * do anything useful in that case. */ - break; - } - if (op1_min == ZEND_LONG_MIN && op2_max == -1) { - /* Avoid ill-defined division, which may trigger SIGFPE. */ - break; - } - zend_long t1_, t2_, t3_, t4_; - float_div(op1_min, op2_min, &t1, &t1_); - float_div(op1_min, op2_max, &t2, &t2_); - float_div(op1_max, op2_min, &t3, &t3_); - float_div(op1_max, op2_max, &t4, &t4_); - - /* The only case in which division can "overflow" either a division by an absolute - * value smaller than one, or LONG_MIN / -1 in particular. Both cases have already - * been excluded above. */ - if (OP1_RANGE_UNDERFLOW() || - OP2_RANGE_UNDERFLOW() || - OP1_RANGE_OVERFLOW() || - OP2_RANGE_OVERFLOW()) { + /* If op2 crosses zero, then floating point values close to zero might be + * possible, which will result in arbitrarily large results (overflow). Also + * avoid dividing LONG_MIN by -1, which is UB. */ + if (OP1_RANGE_UNDERFLOW() || OP2_RANGE_UNDERFLOW() || + OP1_RANGE_OVERFLOW() || OP2_RANGE_OVERFLOW() || + (op2_min <= 0 && op2_max >= 0) || + (op1_min == ZEND_LONG_MIN && op2_max == -1) + ) { tmp->underflow = 1; tmp->overflow = 1; tmp->min = ZEND_LONG_MIN; tmp->max = ZEND_LONG_MAX; } else { + zend_long t1_, t2_, t3_, t4_; + float_div(op1_min, op2_min, &t1, &t1_); + float_div(op1_min, op2_max, &t2, &t2_); + float_div(op1_max, op2_min, &t3, &t3_); + float_div(op1_max, op2_max, &t4, &t4_); + tmp->min = MIN(MIN(MIN(t1, t2), MIN(t3, t4)), MIN(MIN(t1_, t2_), MIN(t3_, t4_))); tmp->max = MAX(MAX(MAX(t1, t2), MAX(t3, t4)), MAX(MAX(t1_, t2_), MAX(t3_, t4_))); } diff --git a/ext/opcache/tests/range_inference_hang.phpt b/ext/opcache/tests/range_inference_hang.phpt new file mode 100644 index 00000000000..a5c685fbdee --- /dev/null +++ b/ext/opcache/tests/range_inference_hang.phpt @@ -0,0 +1,16 @@ +--TEST-- +Range inference should not hang +--FILE-- + +===DONE=== +--EXPECT-- +===DONE===