From 89e383d042c1e76e6feae542e1f64748bacf79ef Mon Sep 17 00:00:00 2001 From: Volker Dusch Date: Fri, 29 May 2026 15:28:38 +0200 Subject: [PATCH 1/3] ext/bcmath: bounds-check $precision in bcround() and Number::round() 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. --- ext/bcmath/bcmath.c | 17 ++++++++++ .../tests/bcround_precision_bounds.phpt | 32 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 ext/bcmath/tests/bcround_precision_bounds.phpt diff --git a/ext/bcmath/bcmath.c b/ext/bcmath/bcmath.c index 2e2d80f76f95..41ceb550f90e 100644 --- a/ext/bcmath/bcmath.c +++ b/ext/bcmath/bcmath.c @@ -155,6 +155,15 @@ static zend_always_inline zend_result bcmath_check_scale(zend_long scale, uint32 return SUCCESS; } +static zend_always_inline zend_result bcmath_check_precision(zend_long precision, uint32_t arg_num) +{ + if (UNEXPECTED(precision < -(zend_long) INT_MAX || precision > INT_MAX)) { + zend_argument_value_error(arg_num, "must be between %d and %d", -INT_MAX, INT_MAX); + return FAILURE; + } + return SUCCESS; +} + static void php_long2num(bc_num *num, zend_long lval) { *num = bc_long2num(lval); @@ -795,6 +804,10 @@ PHP_FUNCTION(bcround) Z_PARAM_ENUM(rounding_mode, rounding_mode_ce) ZEND_PARSE_PARAMETERS_END(); + if (bcmath_check_precision(precision, 2) == FAILURE) { + RETURN_THROWS(); + } + switch (rounding_mode) { case ZEND_ENUM_RoundingMode_HalfAwayFromZero: case ZEND_ENUM_RoundingMode_HalfTowardsZero: @@ -1794,6 +1807,10 @@ PHP_METHOD(BcMath_Number, round) Z_PARAM_ENUM(rounding_mode, rounding_mode_ce); ZEND_PARSE_PARAMETERS_END(); + if (bcmath_check_precision(precision, 1) == FAILURE) { + RETURN_THROWS(); + } + switch (rounding_mode) { case ZEND_ENUM_RoundingMode_HalfAwayFromZero: case ZEND_ENUM_RoundingMode_HalfTowardsZero: diff --git a/ext/bcmath/tests/bcround_precision_bounds.phpt b/ext/bcmath/tests/bcround_precision_bounds.phpt new file mode 100644 index 000000000000..9d01bef98c45 --- /dev/null +++ b/ext/bcmath/tests/bcround_precision_bounds.phpt @@ -0,0 +1,32 @@ +--TEST-- +bcround() and BcMath\Number::round() reject out-of-range $precision +--EXTENSIONS-- +bcmath +--FILE-- +getMessage() . \PHP_EOL; +} +try { + bcround('12345', -PHP_INT_MAX, RoundingMode::AwayFromZero); +} catch (\ValueError $e) { + echo $e->getMessage() . \PHP_EOL; +} +try { + bcround('12345', PHP_INT_MIN, RoundingMode::AwayFromZero); +} 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; +} +?> +--EXPECT-- +bcround(): Argument #2 ($precision) must be between -2147483647 and 2147483647 +bcround(): Argument #2 ($precision) must be between -2147483647 and 2147483647 +bcround(): Argument #2 ($precision) must be between -2147483647 and 2147483647 +BcMath\Number::round(): Argument #1 ($precision) must be between -2147483647 and 2147483647 From 5b86f5dddab959185f1df04af58926dbf55042e0 Mon Sep 17 00:00:00 2001 From: Volker Dusch Date: Fri, 29 May 2026 15:37:31 +0200 Subject: [PATCH 2/3] Inital review feedback - remove the zend_always_inline - use INT_MIN instead of -INT_MAX - use % matching in the tests so they work on 32bit --- ext/bcmath/bcmath.c | 6 +++--- ext/bcmath/tests/bcround_precision_bounds.phpt | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/bcmath/bcmath.c b/ext/bcmath/bcmath.c index 41ceb550f90e..9d1eb4e8bf1d 100644 --- a/ext/bcmath/bcmath.c +++ b/ext/bcmath/bcmath.c @@ -155,10 +155,10 @@ static zend_always_inline zend_result bcmath_check_scale(zend_long scale, uint32 return SUCCESS; } -static zend_always_inline zend_result bcmath_check_precision(zend_long precision, uint32_t arg_num) +static zend_result bcmath_check_precision(zend_long precision, uint32_t arg_num) { - if (UNEXPECTED(precision < -(zend_long) INT_MAX || precision > INT_MAX)) { - zend_argument_value_error(arg_num, "must be between %d and %d", -INT_MAX, INT_MAX); + if (UNEXPECTED(precision < INT_MIN || precision > INT_MAX)) { + zend_argument_value_error(arg_num, "must be between %d and %d", INT_MIN, INT_MAX); return FAILURE; } return SUCCESS; diff --git a/ext/bcmath/tests/bcround_precision_bounds.phpt b/ext/bcmath/tests/bcround_precision_bounds.phpt index 9d01bef98c45..9769760a79cd 100644 --- a/ext/bcmath/tests/bcround_precision_bounds.phpt +++ b/ext/bcmath/tests/bcround_precision_bounds.phpt @@ -25,8 +25,8 @@ try { echo $e->getMessage() . \PHP_EOL; } ?> ---EXPECT-- -bcround(): Argument #2 ($precision) must be between -2147483647 and 2147483647 -bcround(): Argument #2 ($precision) must be between -2147483647 and 2147483647 -bcround(): Argument #2 ($precision) must be between -2147483647 and 2147483647 -BcMath\Number::round(): Argument #1 ($precision) must be between -2147483647 and 2147483647 +--EXPECTF-- +bcround(): Argument #2 ($precision) must be between %i and %i +bcround(): Argument #2 ($precision) must be between %i and %i +bcround(): Argument #2 ($precision) must be between %i and %i +BcMath\Number::round(): Argument #1 ($precision) must be between %i and %i From e508ea640d86a037002c6ec4df0c0d79be421093 Mon Sep 17 00:00:00 2001 From: Volker Dusch Date: Fri, 29 May 2026 16:01:55 +0200 Subject: [PATCH 3/3] Use ZEND_LONG_EXCEEDS_INT over manual bound checking --- ext/bcmath/bcmath.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bcmath/bcmath.c b/ext/bcmath/bcmath.c index 9d1eb4e8bf1d..536fb1a7bf06 100644 --- a/ext/bcmath/bcmath.c +++ b/ext/bcmath/bcmath.c @@ -157,7 +157,7 @@ static zend_always_inline zend_result bcmath_check_scale(zend_long scale, uint32 static zend_result bcmath_check_precision(zend_long precision, uint32_t arg_num) { - if (UNEXPECTED(precision < INT_MIN || precision > INT_MAX)) { + if (ZEND_LONG_EXCEEDS_INT(precision)) { zend_argument_value_error(arg_num, "must be between %d and %d", INT_MIN, INT_MAX); return FAILURE; }