diff --git a/NEWS b/NEWS index 6b1e857a8708..c0c76d078bcb 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-- +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