[php-src] master: Fix GH-22549: GMP compound pow/shift assign asserts on negative operand (#22582)

Ilia Alshanetsky via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Committer: GitHub (web-flow)
Pusher: iliaal
Date: 2026-07-06T15:26:00-04:00

Commit: https://github.com/php/php-src/commit/f04eaec41ac88ea75e51592e91aa3f7c65939d21
Raw diff: https://github.com/php/php-src/commit/f04eaec41ac88ea75e51592e91aa3f7c65939d21.diff

Fix GH-22549: GMP compound pow/shift assign asserts on negative operand (#22582)

shift_operator_helper() wrote ZVAL_UNDEF(return_value) on the out-of-range
exponent/shift path, but for a compound assignment return_value aliases op1,
so pow_function's fallback then reached the scalar conversion with op1 set to
IS_UNDEF and hit ZEND_UNREACHABLE(). Drop the stray undef; the helper's other
error paths already return FAILURE without touching return_value, leaving the
operand intact for the thrown ValueError to propagate.

Fixes GH-22549

Changed paths:
  A  ext/gmp/tests/gh22549.phpt
  M  NEWS
  M  ext/gmp/gmp.c


Diff:

diff --git a/NEWS b/NEWS
index 11b470654384..2a266243a747 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,10 @@ PHP                                                                        NEWS
   . Fixed bug GH-11020 (exif_read_data() emits a spurious "Illegal IFD size"
     warning when an IFD is not followed by a next-IFD offset). (Eyüp Can Akman)
 
+- GMP:
+  . Fixed bug GH-22549 (Assertion failure / UB on a compound GMP power or shift
+    assignment with a negative exponent). (iliaal)
+
 - Intl:
   . Fixed NumberFormatter::parse() and NumberFormatter::parseCurrency() to
     reject offset values outside the 32-bit range instead of silently
diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c
index ef06d0fda30c..59209c4e1be3 100644
--- a/ext/gmp/gmp.c
+++ b/ext/gmp/gmp.c
@@ -361,7 +361,6 @@ static zend_result shift_operator_helper(gmp_binary_ui_op_t op, zval *return_val
 			zend_ce_value_error, "%s must be between 0 and %lu",
 			opcode == ZEND_POW ? "Exponent" : "Shift", ULONG_MAX
 		);
-		ZVAL_UNDEF(return_value);
 		return FAILURE;
 	} else {
 		mpz_ptr gmpnum_op, gmpnum_result;
diff --git a/ext/gmp/tests/gh22549.phpt b/ext/gmp/tests/gh22549.phpt
new file mode 100644
index 000000000000..82a66901bd09
--- /dev/null
+++ b/ext/gmp/tests/gh22549.phpt
@@ -0,0 +1,55 @@
+--TEST--
+GH-22549 (Assertion failure in _zendi_try_convert_scalar_to_number with compound GMP shift/pow)
+--EXTENSIONS--
+gmp
+--FILE--
+<?php
+// Compound assignment aliases result and op1. A negative GMP shift/exponent
+// must throw a ValueError, not clobber op1 into IS_UNDEF and abort.
+$cases = [
+    'int ** gmp'   => [2,            fn(&$x) => $x **= gmp_init(-5)],
+    'gmp ** gmp'   => [gmp_init(2),  fn(&$x) => $x **= gmp_init(-5)],
+    'array ** gmp' => [[0],          fn(&$x) => $x **= gmp_init(-5)],
+    'int << gmp'   => [8,            fn(&$x) => $x <<= gmp_init(-2)],
+    'int >> gmp'   => [8,            fn(&$x) => $x >>= gmp_init(-2)],
+    'gmp << gmp'   => [gmp_init(8),  fn(&$x) => $x <<= gmp_init(-2)],
+];
+
+foreach ($cases as $label => [$x, $op]) {
+    try {
+        $op($x);
+        echo "$label: no exception\n";
+    } catch (\Throwable $e) {
+        echo "$label: ", $e::class, ": ", $e->getMessage(), "\n";
+    }
+    var_dump($x);
+}
+
+// Valid operations must still succeed (guard against an over-broad regression).
+$p = 2;   $p **= gmp_init(3);  echo "valid pow: ", gmp_strval($p), "\n";
+$s = 1;   $s <<= gmp_init(4);  echo "valid shift: ", gmp_strval($s), "\n";
+?>
+--EXPECTF--
+int ** gmp: ValueError: Exponent must be between 0 and %d
+int(2)
+gmp ** gmp: ValueError: Exponent must be between 0 and %d
+object(GMP)#%d (1) {
+  ["num"]=>
+  string(1) "2"
+}
+array ** gmp: ValueError: Exponent must be between 0 and %d
+array(1) {
+  [0]=>
+  int(0)
+}
+int << gmp: ValueError: Shift must be between 0 and %d
+int(8)
+int >> gmp: ValueError: Shift must be between 0 and %d
+int(8)
+gmp << gmp: ValueError: Shift must be between 0 and %d
+object(GMP)#%d (1) {
+  ["num"]=>
+  string(1) "8"
+}
+valid pow: 8
+valid shift: 16
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.