From b1fdc6ee7c38f867e6e87dab81d2dfa9dea7ab0a Mon Sep 17 00:00:00 2001 From: mkrishna793 Date: Wed, 28 Oct 2026 10:32:37 +0530 Subject: [PATCH 1/2] Fix zend_inference: fix unsound shift_left_overflows range when shift wraps shift_left_overflows() previously checked 'safe_shift_left(n, s) > n' for negative numbers, which incorrectly returned false when (n << s) wrapped around to a value <= n (such as ZEND_LONG_MIN or negative wrapped values). Positive shifts also missed overflow when wrapping. Use a round-trip arithmetic shift check ((shifted >> s) != n) to soundly detect overflow across all inputs. --- Zend/Optimizer/zend_inference.c | 12 +++--- Zend/tests/shift_left_overflow_range.phpt | 51 +++++++++++++++++++++++ 2 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 Zend/tests/shift_left_overflow_range.phpt diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index dff89b16f9d4..5a8bfed10cfb 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -762,12 +762,14 @@ static inline zend_long safe_shift_left(zend_long n, zend_long s) { } static inline bool shift_left_overflows(zend_long n, zend_long s) { - /* This considers shifts that shift in the sign bit to be overflowing as well */ - if (n >= 0) { - return s >= SIZEOF_ZEND_LONG * 8 - 1 || safe_shift_left(n, s) < n; - } else { - return s >= SIZEOF_ZEND_LONG * 8 || safe_shift_left(n, s) > n; + if (UNEXPECTED((zend_ulong)s >= SIZEOF_ZEND_LONG * 8)) { + return true; + } + if (UNEXPECTED(s == SIZEOF_ZEND_LONG * 8 - 1)) { + return n != 0 && n != -1; } + zend_long shifted = (zend_long)((zend_ulong)n << (zend_ulong)s); + return (shifted >> s) != n; } /* If b does not divide a exactly, return the two adjacent values between which the real result diff --git a/Zend/tests/shift_left_overflow_range.phpt b/Zend/tests/shift_left_overflow_range.phpt new file mode 100644 index 000000000000..fb016393a834 --- /dev/null +++ b/Zend/tests/shift_left_overflow_range.phpt @@ -0,0 +1,51 @@ +--TEST-- +Too narrow range inferred for SL when the shift wraps +--SKIPIF-- + +--FILE-- += 2, + * so the result range may not be derived from the two endpoints: both of them + * happen to yield 1 << 62, while the values in between do not. */ +function shift_positive(): array { + $out = []; + for ($i = 1; $i <= 5; $i++) { + $out[] = $i << 62; + } + return $out; +} + +/* Likewise for RANGE[-3..-1] shifted by 63: both endpoints yield LONG_MIN, + * but -2 << 63 is 0. */ +function shift_negative(): array { + $out = []; + for ($i = -1; $i >= -3; $i--) { + $out[] = $i << 63; + } + return $out; +} + +var_dump(shift_positive()); +var_dump(shift_negative()); +?> +--EXPECT-- +array(5) { + [0]=> + int(4611686018427387904) + [1]=> + int(-9223372036854775808) + [2]=> + int(-4611686018427387904) + [3]=> + int(0) + [4]=> + int(4611686018427387904) +} +array(3) { + [0]=> + int(-9223372036854775808) + [1]=> + int(0) + [2]=> + int(-9223372036854775808) +} From d246600b8d99490be489def6f80e1bbdca369b1e Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 28 Oct 2026 10:50:08 +0100 Subject: [PATCH 2/2] [ci skip] NEWS --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 79fdd4b7ef67..515b58fa9ece 100644 --- a/NEWS +++ b/NEWS @@ -164,6 +164,7 @@ PHP NEWS . Fixed JIT segfault on switch over a string reference holding a non-string value. (jorgsowa) . Fixed AArch64 JIT encoding of invalid logical immediates. (jorgsowa) + . Fixed unsound shift_left_overflows range when shift wraps. (mkrishna793) - PDO: . Fixed invalid ATTR_ORACLE_NULLS values being accepted and truncated.