Commit 617e4c38e0d for php.net

commit 617e4c38e0df927a6500f037970a62ca2951e703
Author: David Carlier <devnexen@gmail.com>
Date:   Sun Apr 19 08:44:55 2026 +0100

    ext/gmp: reject values larger than unsigned long in gmp_pow/binomial/root/rootrem and shift/pow operators.

    Applies the gmp_fact() pattern (0236667bd94) to the remaining
    zend_long -> gmp_ulong casts that could silently overflow on
    LLP64 platforms.

    gmp_powm() switches to zend_argument_error() with the correct
    argument index ($modulus) for modulo-by-zero.

    close GH-21812

diff --git a/NEWS b/NEWS
index 481c4ea2c38..73a2b3d6637 100644
--- a/NEWS
+++ b/NEWS
@@ -43,6 +43,8 @@ PHP                                                                        NEWS

 - GMP:
   . gmp_fact() reject values larger than unsigned long. (David Carlier)
+  . gmp_pow/binomial/root/rootrem and shift/pow operators reject values
+    larger than unsigned long. (David Carlier)

 - Hash:
   . Upgrade xxHash to 0.8.2. (timwolla)
diff --git a/UPGRADING b/UPGRADING
index 0e66a96909f..9c3d5a2b29a 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -155,6 +155,13 @@ PHP 8.6 UPGRADE NOTES
 - GMP:
   . gmp_fact() now throws a ValueError() if $num does not fit into
     a unsigned long.
+  . gmp_pow(), gmp_binomial(), gmp_root() and gmp_rootrem() now throw a
+    ValueError if their second argument does not fit into an unsigned long.
+  . The shift (<<, >>) and exponentiation (**) operators on GMP objects
+    now throw a ValueError if the right operand does not fit into an
+    unsigned long.
+  . gmp_powm() modulo-by-zero now raises a DivisionByZeroError whose
+    message includes the function name and argument index ($modulus).

 - mysqli:
   . The return structure of mysqli_get_charset() no longer contains
diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c
index a2097458090..b04fc9f5c1b 100644
--- a/ext/gmp/gmp.c
+++ b/ext/gmp/gmp.c
@@ -356,10 +356,10 @@ static zend_result shift_operator_helper(gmp_binary_ui_op_t op, zval *return_val
 		shift = Z_LVAL_P(op2);
 	}

-	if (shift < 0) {
+	if (shift < 0 || shift > ULONG_MAX) {
 		zend_throw_error(
-			zend_ce_value_error, "%s must be greater than or equal to 0",
-			opcode == ZEND_POW ? "Exponent" : "Shift"
+			zend_ce_value_error, "%s must be between 0 and %lu",
+			opcode == ZEND_POW ? "Exponent" : "Shift", ULONG_MAX
 		);
 		ZVAL_UNDEF(return_value);
 		return FAILURE;
@@ -1087,11 +1087,6 @@ ZEND_FUNCTION(gmp_fact)
 		GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum)
 	ZEND_PARSE_PARAMETERS_END();

-	if (mpz_sgn(gmpnum) < 0) {
-		zend_argument_value_error(1, "must be greater than or equal to 0");
-		RETURN_THROWS();
-	}
-
 	if (!mpz_fits_ulong_p(gmpnum)) {
 		zend_argument_value_error(1, "must be between 0 and %lu", ULONG_MAX);
 		RETURN_THROWS();
@@ -1114,8 +1109,8 @@ ZEND_FUNCTION(gmp_binomial)
 		Z_PARAM_LONG(k)
 	ZEND_PARSE_PARAMETERS_END();

-	if (k < 0) {
-		zend_argument_value_error(2, "must be greater than or equal to 0");
+	if (k < 0 || k > ULONG_MAX) {
+		zend_argument_value_error(2, "must be between 0 and %lu", ULONG_MAX);
 		RETURN_THROWS();
 	}

@@ -1136,8 +1131,8 @@ ZEND_FUNCTION(gmp_pow)
 		Z_PARAM_LONG(exp)
 	ZEND_PARSE_PARAMETERS_END();

-	if (exp < 0) {
-		zend_argument_value_error(2, "must be greater than or equal to 0");
+	if (exp < 0 || exp > ULONG_MAX) {
+		zend_argument_value_error(2, "must be between 0 and %lu", ULONG_MAX);
 		RETURN_THROWS();
 	}

@@ -1163,7 +1158,7 @@ ZEND_FUNCTION(gmp_powm)
 	}

 	if (!mpz_cmp_ui(gmpnum_mod, 0)) {
-		zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Modulo by zero");
+		zend_argument_error(zend_ce_division_by_zero_error, 3, "Modulo by zero");
 		RETURN_THROWS();
 	}

@@ -1226,8 +1221,8 @@ ZEND_FUNCTION(gmp_root)
 		Z_PARAM_LONG(nth)
 	ZEND_PARSE_PARAMETERS_END();

-	if (nth <= 0) {
-		zend_argument_value_error(2, "must be greater than 0");
+	if (nth <= 0 || nth > ULONG_MAX) {
+		zend_argument_value_error(2, "must be between 1 and %lu", ULONG_MAX);
 		RETURN_THROWS();
 	}

@@ -1253,8 +1248,8 @@ ZEND_FUNCTION(gmp_rootrem)
 		Z_PARAM_LONG(nth)
 	ZEND_PARSE_PARAMETERS_END();

-	if (nth <= 0) {
-		zend_argument_value_error(2, "must be greater than or equal to 1");
+	if (nth <= 0 || nth > ULONG_MAX) {
+		zend_argument_value_error(2, "must be between 1 and %lu", ULONG_MAX);
 		RETURN_THROWS();
 	}

diff --git a/ext/gmp/tests/gmp_binomial.phpt b/ext/gmp/tests/gmp_binomial.phpt
index 4598a4c47ee..1bf371e68a1 100644
--- a/ext/gmp/tests/gmp_binomial.phpt
+++ b/ext/gmp/tests/gmp_binomial.phpt
@@ -26,7 +26,7 @@
     echo $e->getMessage() . \PHP_EOL;
 }
 ?>
---EXPECT--
+--EXPECTF--
 object(GMP)#1 (1) {
   ["num"]=>
   string(3) "252"
@@ -67,4 +67,4 @@
   ["num"]=>
   string(1) "7"
 }
-gmp_binomial(): Argument #2 ($k) must be greater than or equal to 0
+gmp_binomial(): Argument #2 ($k) must be between 0 and %d
diff --git a/ext/gmp/tests/gmp_fact.phpt b/ext/gmp/tests/gmp_fact.phpt
index e0393145494..b28f572c697 100644
--- a/ext/gmp/tests/gmp_fact.phpt
+++ b/ext/gmp/tests/gmp_fact.phpt
@@ -45,17 +45,17 @@

 echo "Done\n";
 ?>
---EXPECT--
+--EXPECTF--
 string(1) "1"
 gmp_fact(): Argument #1 ($num) is not an integer string
 string(1) "1"
-gmp_fact(): Argument #1 ($num) must be greater than or equal to 0
-gmp_fact(): Argument #1 ($num) must be greater than or equal to 0
+gmp_fact(): Argument #1 ($num) must be between 0 and %d
+gmp_fact(): Argument #1 ($num) must be between 0 and %d
 string(19) "2432902008176640000"
 string(65) "30414093201713378043612608166064768844377641568960512000000000000"
 string(7) "3628800"
 string(1) "1"
 string(9) "479001600"
-gmp_fact(): Argument #1 ($num) must be greater than or equal to 0
+gmp_fact(): Argument #1 ($num) must be between 0 and %d
 gmp_fact(): Argument #1 ($num) must be of type GMP|string|int, array given
 Done
diff --git a/ext/gmp/tests/gmp_overflow_llp64.phpt b/ext/gmp/tests/gmp_overflow_llp64.phpt
new file mode 100644
index 00000000000..ea242bc5215
--- /dev/null
+++ b/ext/gmp/tests/gmp_overflow_llp64.phpt
@@ -0,0 +1,58 @@
+--TEST--
+GMP functions reject values larger than unsigned long on LLP64
+--EXTENSIONS--
+gmp
+--SKIPIF--
+<?php
+if (PHP_OS_FAMILY !== "Windows" || PHP_INT_SIZE !== 8) die("skip LLP64 (Windows 64-bit) only");
+?>
+--FILE--
+<?php
+
+try {
+    gmp_pow(2, PHP_INT_MAX);
+} catch (ValueError $e) {
+    echo $e->getMessage() . PHP_EOL;
+}
+
+try {
+    gmp_binomial(10, PHP_INT_MAX);
+} catch (ValueError $e) {
+    echo $e->getMessage() . PHP_EOL;
+}
+
+try {
+    gmp_root(10, PHP_INT_MAX);
+} catch (ValueError $e) {
+    echo $e->getMessage() . PHP_EOL;
+}
+
+try {
+    gmp_rootrem(10, PHP_INT_MAX);
+} catch (ValueError $e) {
+    echo $e->getMessage() . PHP_EOL;
+}
+
+$n = gmp_init(2);
+try {
+    $n << PHP_INT_MAX;
+} catch (ValueError $e) {
+    echo $e->getMessage() . PHP_EOL;
+}
+
+try {
+    $n ** PHP_INT_MAX;
+} catch (ValueError $e) {
+    echo $e->getMessage() . PHP_EOL;
+}
+
+echo "Done\n";
+?>
+--EXPECTF--
+gmp_pow(): Argument #2 ($exponent) must be between 0 and %d
+gmp_binomial(): Argument #2 ($k) must be between 0 and %d
+gmp_root(): Argument #2 ($nth) must be between 1 and %d
+gmp_rootrem(): Argument #2 ($nth) must be between 1 and %d
+Shift must be between 0 and %d
+Exponent must be between 0 and %d
+Done
diff --git a/ext/gmp/tests/gmp_pow.phpt b/ext/gmp/tests/gmp_pow.phpt
index f42e44e31ab..36d0d16d8cc 100644
--- a/ext/gmp/tests/gmp_pow.phpt
+++ b/ext/gmp/tests/gmp_pow.phpt
@@ -43,17 +43,17 @@

 echo "Done\n";
 ?>
---EXPECT--
+--EXPECTF--
 string(4) "1024"
 string(4) "1024"
 string(5) "-2048"
 string(4) "1024"
 string(1) "1"
-gmp_pow(): Argument #2 ($exponent) must be greater than or equal to 0
+gmp_pow(): Argument #2 ($exponent) must be between 0 and %d
 string(4) "1024"
 string(14) "10240000000000"
 string(17) "97656250000000000"
-gmp_pow(): Argument #2 ($exponent) must be greater than or equal to 0
+gmp_pow(): Argument #2 ($exponent) must be between 0 and %d
 string(14) "10240000000000"
 string(14) "10240000000000"
 gmp_pow(): Argument #2 ($exponent) must be of type int, array given
diff --git a/ext/gmp/tests/gmp_pow2.phpt b/ext/gmp/tests/gmp_pow2.phpt
index 43bf9bb5aaa..96f33e8c25c 100644
--- a/ext/gmp/tests/gmp_pow2.phpt
+++ b/ext/gmp/tests/gmp_pow2.phpt
@@ -31,5 +31,5 @@
   ["num"]=>
   string(4) "1024"
 }
-Exponent must be greater than or equal to 0
-Exponent must be greater than or equal to 0
+Exponent must be between 0 and %d
+Exponent must be between 0 and %d
diff --git a/ext/gmp/tests/gmp_pown.phpt b/ext/gmp/tests/gmp_pown.phpt
index 0e7b3bd1fa5..79709f27271 100644
--- a/ext/gmp/tests/gmp_pown.phpt
+++ b/ext/gmp/tests/gmp_pown.phpt
@@ -63,7 +63,7 @@

 echo "Done\n";
 ?>
---EXPECT--
+--EXPECTF--
 string(1) "0"
 string(1) "5"
 string(1) "5"
@@ -73,8 +73,8 @@
 string(3) "331"
 string(3) "171"
 string(3) "371"
-Modulo by zero
-Modulo by zero
+gmp_powm(): Argument #3 ($modulus) Modulo by zero
+gmp_powm(): Argument #3 ($modulus) Modulo by zero
 gmp_powm(): Argument #1 ($num) must be of type GMP|string|int, array given
 gmp_powm(): Argument #2 ($exponent) must be of type GMP|string|int, array given
 gmp_powm(): Argument #2 ($exponent) must be of type GMP|string|int, TypeError given
diff --git a/ext/gmp/tests/gmp_remroot.phpt b/ext/gmp/tests/gmp_remroot.phpt
index 8e53858c604..de54d0e91c3 100644
--- a/ext/gmp/tests/gmp_remroot.phpt
+++ b/ext/gmp/tests/gmp_remroot.phpt
@@ -105,5 +105,5 @@
     string(1) "0"
   }
 }
-gmp_rootrem(): Argument #2 ($nth) must be greater than or equal to 1
-gmp_rootrem(): Argument #2 ($nth) must be greater than or equal to 1
+gmp_rootrem(): Argument #2 ($nth) must be between 1 and %d
+gmp_rootrem(): Argument #2 ($nth) must be between 1 and %d
diff --git a/ext/gmp/tests/gmp_root.phpt b/ext/gmp/tests/gmp_root.phpt
index 2313b207cc8..36793e24972 100644
--- a/ext/gmp/tests/gmp_root.phpt
+++ b/ext/gmp/tests/gmp_root.phpt
@@ -58,5 +58,5 @@
   ["num"]=>
   string(1) "0"
 }
-gmp_root(): Argument #2 ($nth) must be greater than 0
-gmp_root(): Argument #2 ($nth) must be greater than 0
+gmp_root(): Argument #2 ($nth) must be between 1 and %d
+gmp_root(): Argument #2 ($nth) must be between 1 and %d
diff --git a/ext/gmp/tests/overloading.phpt b/ext/gmp/tests/overloading.phpt
index 14c35ea8470..f55a83f0bd5 100644
--- a/ext/gmp/tests/overloading.phpt
+++ b/ext/gmp/tests/overloading.phpt
@@ -123,7 +123,7 @@
 var_dump($a);

 ?>
---EXPECT--
+--EXPECTF--
 object(GMP)#3 (1) {
   ["num"]=>
   string(2) "59"
@@ -254,8 +254,8 @@
   ["num"]=>
   string(3) "-11"
 }
-Shift must be greater than or equal to 0
-Shift must be greater than or equal to 0
+Shift must be between 0 and %d
+Shift must be between 0 and %d
 object(GMP)#5 (1) {
   ["num"]=>
   string(3) "-43"