Commit c0fa54f796f for php.net
commit c0fa54f796fa2687019c1efd0d1c59aca2252b1e
Author: Volker Dusch <volker@tideways-gmbh.com>
Date: Fri May 29 15:28:38 2026 +0200
ext/bcmath: bounds-check $precision in bcround() and Number::round() (#22182)
Fix ASAN issue withh entry points passed $precision to bc_round()
unchecked, allowing PHP_INT_MAX / PHP_INT_MIN to trigger oversized
allocations and signed overflow in libbcmath/src/round.c.
closes #22182
diff --git a/NEWS b/NEWS
index 49b61d23696..ff552103ebc 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.4.23
+- BCMath:
+ . Fixed issues with oversized allocations and signed overflow in bcround()
+ and BcMath\Number::round(). (edorian)
+
- GD:
. Fixed bug GH-22121 (Double free in gdImageSetStyle() after
overflow-triggered early return). (iliaal)
diff --git a/UPGRADING b/UPGRADING
index 50c58169528..821fa4a723f 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -625,6 +625,10 @@ PHP 8.4 UPGRADE NOTES
5. Changed Functions
========================================
+- BCMath:
+ . bcround() and BcMath\Number::round() now throw a ValueError when the precision
+ argument is out of range.
+
- Core:
. trigger_error() and user_error() now have a return type of true instead of
bool.
diff --git a/ext/bcmath/bcmath.c b/ext/bcmath/bcmath.c
index 3628b95a78e..e54e5bfac77 100644
--- a/ext/bcmath/bcmath.c
+++ b/ext/bcmath/bcmath.c
@@ -157,6 +157,16 @@ static zend_always_inline zend_result bcmath_check_scale(zend_long scale, uint32
return SUCCESS;
}
+static zend_result bcmath_check_precision(zend_long precision, uint32_t arg_num)
+{
+ if (ZEND_LONG_INT_OVFL(precision)) {
+ zend_argument_value_error(arg_num, "must be between " ZEND_LONG_FMT " and %d",
+ (zend_long) ZEND_LONG_MIN, INT_MAX);
+ return FAILURE;
+ }
+ return SUCCESS;
+}
+
static void php_long2num(bc_num *num, zend_long lval)
{
*num = bc_long2num(lval);
@@ -778,6 +788,10 @@ PHP_FUNCTION(bcround)
Z_PARAM_OBJ_OF_CLASS(mode_object, rounding_mode_ce)
ZEND_PARSE_PARAMETERS_END();
+ if (bcmath_check_precision(precision, 2) == FAILURE) {
+ RETURN_THROWS();
+ }
+
if (mode_object != NULL) {
mode = php_math_round_mode_from_enum(mode_object);
}
@@ -1784,6 +1798,10 @@ PHP_METHOD(BcMath_Number, round)
Z_PARAM_OBJ_OF_CLASS(mode_object, rounding_mode_ce);
ZEND_PARSE_PARAMETERS_END();
+ if (bcmath_check_precision(precision, 1) == FAILURE) {
+ RETURN_THROWS();
+ }
+
if (mode_object != NULL) {
rounding_mode = php_math_round_mode_from_enum(mode_object);
}
diff --git a/ext/bcmath/libbcmath/src/round.c b/ext/bcmath/libbcmath/src/round.c
index ac3c7c41a31..a495221ff00 100644
--- a/ext/bcmath/libbcmath/src/round.c
+++ b/ext/bcmath/libbcmath/src/round.c
@@ -70,12 +70,10 @@ void bc_round(bc_num num, zend_long precision, zend_long mode, bc_num *result)
return;
}
- /* If precision is -3, it becomes 1000. */
- if (UNEXPECTED(precision == ZEND_LONG_MIN)) {
- *result = bc_new_num((size_t) ZEND_LONG_MAX + 2, 0);
- } else {
- *result = bc_new_num(-precision + 1, 0);
- }
+ /* If precision is -3, it becomes 1000. Negate in unsigned space so
+ * precision == ZEND_LONG_MIN doesn't overflow signed long. */
+ zend_ulong magnitude = -(zend_ulong) precision;
+ *result = bc_new_num((size_t) magnitude + 1, 0);
(*result)->n_value[0] = 1;
(*result)->n_sign = num->n_sign;
return;
diff --git a/ext/bcmath/tests/bcround_precision_bounds.phpt b/ext/bcmath/tests/bcround_precision_bounds.phpt
new file mode 100644
index 00000000000..4c4f910476a
--- /dev/null
+++ b/ext/bcmath/tests/bcround_precision_bounds.phpt
@@ -0,0 +1,28 @@
+--TEST--
+bcround() and BcMath\Number::round() reject $precision above INT_MAX
+--EXTENSIONS--
+bcmath
+--SKIPIF--
+<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
+--FILE--
+<?php
+try {
+ bcround('1', PHP_INT_MAX);
+} catch (\ValueError $e) {
+ echo $e->getMessage() . \PHP_EOL;
+}
+try {
+ (new BcMath\Number('1'))->round(PHP_INT_MAX);
+} catch (\ValueError $e) {
+ echo $e->getMessage() . \PHP_EOL;
+}
+try {
+ (new BcMath\Number('1'))->round(2147483648); // INT_MAX + 1
+} catch (\ValueError $e) {
+ echo $e->getMessage() . \PHP_EOL;
+}
+?>
+--EXPECTF--
+bcround(): Argument #2 ($precision) must be between %i and %d
+BcMath\Number::round(): Argument #1 ($precision) must be between %i and %d
+BcMath\Number::round(): Argument #1 ($precision) must be between %i and %d