[php-src] master: ext/gmp: Fix GMP operator RHS overflow for GMP values (#22656)

Weilin Du via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Weilin Du (LamentXU123)
Committer: GitHub (web-flow)
Pusher: LamentXU123
Date: 2026-07-15T20:40:19+08:00

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

ext/gmp: Fix GMP operator RHS overflow for GMP values (#22656)

```php
<?php
$too_large = gmp_init("18446744073709551616");

var_dump(gmp_init(2) ** $too_large);
var_dump(gmp_init(2) << $too_large);
```
```
object(GMP)#3 (1) {
  ["num"]=>
  string(1) "1"
}
object(GMP)#2 (1) {
  ["num"]=>
  string(1) "2"
}
```
This PR fix the overflow by rejecting large gmp objects in these
calculation (throws ValueError)

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


Diff:

diff --git a/NEWS b/NEWS
index 5bb78b98c4e7..cb10b3be0b6d 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ PHP                                                                        NEWS
 |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
 ?? ??? ????, PHP 8.6.0alpha3
 
+- GMP:
+  . Fixed GMP power and shift operators to reject GMP right operands outside
+    the unsigned long range instead of silently truncating them. (Weilin Du)
+
 - ODBC:
   . Fixed bug GH-22668 (Heap buffer over-read when a column value exceeds the
     driver-reported display size). (iliaal)
diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c
index 59209c4e1be3..b05706e48297 100644
--- a/ext/gmp/gmp.c
+++ b/ext/gmp/gmp.c
@@ -327,8 +327,17 @@ static zend_result binop_operator_helper(gmp_binary_op_t gmp_op, zval *return_va
 
 typedef void (*gmp_binary_ui_op_t)(mpz_ptr, mpz_srcptr, gmp_ulong);
 
+static void gmp_shift_operator_range_error(uint8_t opcode) {
+	zend_throw_error(
+		zend_ce_value_error, "%s must be between 0 and %lu",
+		opcode == ZEND_POW ? "Exponent" : "Shift", ULONG_MAX
+	);
+}
+
 static zend_result shift_operator_helper(gmp_binary_ui_op_t op, zval *return_value, zval *op1, zval *op2, uint8_t opcode) {
 	zend_long shift = 0;
+	gmp_ulong shift_ui = 0;
+	bool have_shift_ui = false;
 
 	if (UNEXPECTED(Z_TYPE_P(op2) != IS_LONG)) {
 		if (UNEXPECTED(!IS_GMP(op2))) {
@@ -349,31 +358,36 @@ static zend_result shift_operator_helper(gmp_binary_ui_op_t op, zval *return_val
 					goto typeof_op_failure;
 			}
 		} else {
-			// TODO We shouldn't cast the GMP object to int here
-			shift = zval_get_long(op2);
+			mpz_ptr gmpnum_shift = GET_GMP_FROM_ZVAL(op2);
+			if (!mpz_fits_ulong_p(gmpnum_shift)) {
+				gmp_shift_operator_range_error(opcode);
+				return FAILURE;
+			}
+			shift_ui = (gmp_ulong) mpz_get_ui(gmpnum_shift);
+			have_shift_ui = true;
 		}
 	} else {
 		shift = Z_LVAL_P(op2);
 	}
 
-	if (shift < 0 || shift > ULONG_MAX) {
-		zend_throw_error(
-			zend_ce_value_error, "%s must be between 0 and %lu",
-			opcode == ZEND_POW ? "Exponent" : "Shift", ULONG_MAX
-		);
-		return FAILURE;
-	} else {
-		mpz_ptr gmpnum_op, gmpnum_result;
-
-		if (!gmp_zend_parse_arg_into_mpz_ex(op1, &gmpnum_op, 1, true)) {
-			goto typeof_op_failure;
+	if (!have_shift_ui) {
+		if (shift < 0 || shift > ULONG_MAX) {
+			gmp_shift_operator_range_error(opcode);
+			return FAILURE;
 		}
+		shift_ui = (gmp_ulong) shift;
+	}
 
-		INIT_GMP_RETVAL(gmpnum_result);
-		op(gmpnum_result, gmpnum_op, (gmp_ulong) shift);
-		return SUCCESS;
+	mpz_ptr gmpnum_op, gmpnum_result;
+
+	if (!gmp_zend_parse_arg_into_mpz_ex(op1, &gmpnum_op, 1, true)) {
+		goto typeof_op_failure;
 	}
 
+	INIT_GMP_RETVAL(gmpnum_result);
+	op(gmpnum_result, gmpnum_op, shift_ui);
+	return SUCCESS;
+
 typeof_op_failure: ;
 	/* Returning FAILURE without throwing an exception would emit the
 	 * Unsupported operand types: GMP OP TypeOfOp2
diff --git a/ext/gmp/tests/gmp_operator_rhs_gmp_overflow.phpt b/ext/gmp/tests/gmp_operator_rhs_gmp_overflow.phpt
new file mode 100644
index 000000000000..e8b6139ace0f
--- /dev/null
+++ b/ext/gmp/tests/gmp_operator_rhs_gmp_overflow.phpt
@@ -0,0 +1,33 @@
+--TEST--
+GMP operator right operand rejects values outside the unsigned long range
+--EXTENSIONS--
+gmp
+--FILE--
+<?php
+$too_large = gmp_init("18446744073709551616");
+
+try {
+    var_dump(gmp_init(2) ** $too_large);
+} catch (ValueError $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+try {
+    var_dump(gmp_init(2) << $too_large);
+} catch (ValueError $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+try {
+    var_dump(gmp_init(2) >> $too_large);
+} catch (ValueError $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+echo "Done\n";
+?>
+--EXPECTF--
+Exponent must be between 0 and %d
+Shift must be between 0 and %d
+Shift must be between 0 and %d
+Done
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.