Skip to content

Commit c0fa54f

Browse files
edorianSakiTakamachi
authored andcommitted
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
1 parent 3064540 commit c0fa54f

5 files changed

Lines changed: 58 additions & 6 deletions

File tree

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
?? ??? ????, PHP 8.4.23
44

5+
- BCMath:
6+
. Fixed issues with oversized allocations and signed overflow in bcround()
7+
and BcMath\Number::round(). (edorian)
8+
59
- GD:
610
. Fixed bug GH-22121 (Double free in gdImageSetStyle() after
711
overflow-triggered early return). (iliaal)

UPGRADING

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,10 @@ PHP 8.4 UPGRADE NOTES
625625
5. Changed Functions
626626
========================================
627627

628+
- BCMath:
629+
. bcround() and BcMath\Number::round() now throw a ValueError when the precision
630+
argument is out of range.
631+
628632
- Core:
629633
. trigger_error() and user_error() now have a return type of true instead of
630634
bool.

ext/bcmath/bcmath.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,16 @@ static zend_always_inline zend_result bcmath_check_scale(zend_long scale, uint32
157157
return SUCCESS;
158158
}
159159

160+
static zend_result bcmath_check_precision(zend_long precision, uint32_t arg_num)
161+
{
162+
if (ZEND_LONG_INT_OVFL(precision)) {
163+
zend_argument_value_error(arg_num, "must be between " ZEND_LONG_FMT " and %d",
164+
(zend_long) ZEND_LONG_MIN, INT_MAX);
165+
return FAILURE;
166+
}
167+
return SUCCESS;
168+
}
169+
160170
static void php_long2num(bc_num *num, zend_long lval)
161171
{
162172
*num = bc_long2num(lval);
@@ -778,6 +788,10 @@ PHP_FUNCTION(bcround)
778788
Z_PARAM_OBJ_OF_CLASS(mode_object, rounding_mode_ce)
779789
ZEND_PARSE_PARAMETERS_END();
780790

791+
if (bcmath_check_precision(precision, 2) == FAILURE) {
792+
RETURN_THROWS();
793+
}
794+
781795
if (mode_object != NULL) {
782796
mode = php_math_round_mode_from_enum(mode_object);
783797
}
@@ -1784,6 +1798,10 @@ PHP_METHOD(BcMath_Number, round)
17841798
Z_PARAM_OBJ_OF_CLASS(mode_object, rounding_mode_ce);
17851799
ZEND_PARSE_PARAMETERS_END();
17861800

1801+
if (bcmath_check_precision(precision, 1) == FAILURE) {
1802+
RETURN_THROWS();
1803+
}
1804+
17871805
if (mode_object != NULL) {
17881806
rounding_mode = php_math_round_mode_from_enum(mode_object);
17891807
}

ext/bcmath/libbcmath/src/round.c

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,10 @@ void bc_round(bc_num num, zend_long precision, zend_long mode, bc_num *result)
7070
return;
7171
}
7272

73-
/* If precision is -3, it becomes 1000. */
74-
if (UNEXPECTED(precision == ZEND_LONG_MIN)) {
75-
*result = bc_new_num((size_t) ZEND_LONG_MAX + 2, 0);
76-
} else {
77-
*result = bc_new_num(-precision + 1, 0);
78-
}
73+
/* If precision is -3, it becomes 1000. Negate in unsigned space so
74+
* precision == ZEND_LONG_MIN doesn't overflow signed long. */
75+
zend_ulong magnitude = -(zend_ulong) precision;
76+
*result = bc_new_num((size_t) magnitude + 1, 0);
7977
(*result)->n_value[0] = 1;
8078
(*result)->n_sign = num->n_sign;
8179
return;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
bcround() and BcMath\Number::round() reject $precision above INT_MAX
3+
--EXTENSIONS--
4+
bcmath
5+
--SKIPIF--
6+
<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
7+
--FILE--
8+
<?php
9+
try {
10+
bcround('1', PHP_INT_MAX);
11+
} catch (\ValueError $e) {
12+
echo $e->getMessage() . \PHP_EOL;
13+
}
14+
try {
15+
(new BcMath\Number('1'))->round(PHP_INT_MAX);
16+
} catch (\ValueError $e) {
17+
echo $e->getMessage() . \PHP_EOL;
18+
}
19+
try {
20+
(new BcMath\Number('1'))->round(2147483648); // INT_MAX + 1
21+
} catch (\ValueError $e) {
22+
echo $e->getMessage() . \PHP_EOL;
23+
}
24+
?>
25+
--EXPECTF--
26+
bcround(): Argument #2 ($precision) must be between %i and %d
27+
BcMath\Number::round(): Argument #1 ($precision) must be between %i and %d
28+
BcMath\Number::round(): Argument #1 ($precision) must be between %i and %d

0 commit comments

Comments
 (0)