Skip to content

Commit c283029

Browse files
committed
ext/bcmath: Prevent overflow of uint32_t/uint64_t.
If add more than a certain number of times, it will overflow, so need to adjust the digits before adding.
1 parent c265b90 commit c283029

File tree

2 files changed

+56
-6
lines changed

2 files changed

+56
-6
lines changed

ext/bcmath/libbcmath/src/recmul.c

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,32 @@
3333
#include <stddef.h>
3434
#include <assert.h>
3535
#include <stdbool.h>
36+
#include <limits.h>
3637
#include "private.h" /* For _bc_rm_leading_zeros() */
3738
#include "zend_alloc.h"
3839

3940

4041
#if SIZEOF_SIZE_T >= 8
4142
# define BC_MUL_UINT_DIGITS 8
42-
# define BC_MUL_UINT_OVERFLOW 100000000
43+
# define BC_MUL_UINT_OVERFLOW (BC_UINT_T) 100000000
44+
# define BC_MUL_MAX_ADD_COUNT (ULONG_MAX / (BC_MUL_UINT_OVERFLOW * BC_MUL_UINT_OVERFLOW))
4345
#else
4446
# define BC_MUL_UINT_DIGITS 4
45-
# define BC_MUL_UINT_OVERFLOW 10000
47+
# define BC_MUL_UINT_OVERFLOW (BC_UINT_T) 10000
48+
# define BC_MUL_MAX_ADD_COUNT (UINT_MAX / (BC_MUL_UINT_OVERFLOW * BC_MUL_UINT_OVERFLOW))
4649
#endif
4750

4851

4952
/* Multiply utility routines */
5053

54+
static inline void bc_digits_adjustment(BC_UINT_T *prod_uint, size_t prod_arr_size)
55+
{
56+
for (size_t i = 0; i < prod_arr_size - 1; i++) {
57+
prod_uint[i + 1] += prod_uint[i] / BC_MUL_UINT_OVERFLOW;
58+
prod_uint[i] %= BC_MUL_UINT_OVERFLOW;
59+
}
60+
}
61+
5162
/*
5263
* Converts BCD to uint, going backwards from pointer n by the number of
5364
* characters specified by len.
@@ -141,7 +152,18 @@ static void bc_standard_mul(bc_num n1, size_t n1len, bc_num n2, size_t n2len, bc
141152
bc_convert_to_uint(n2_uint, n2end, n2len);
142153

143154
/* Multiplication and addition */
155+
size_t count = 0;
144156
for (i = 0; i < n1_arr_size; i++) {
157+
/*
158+
* This calculation adds the result multiple times to the array entries.
159+
* When multiplying large numbers of digits, there is a possibility of
160+
* overflow, so digit adjustment is performed beforehand.
161+
*/
162+
if (UNEXPECTED(count >= BC_MUL_MAX_ADD_COUNT)) {
163+
bc_digits_adjustment(prod_uint, prod_arr_size);
164+
count = 0;
165+
}
166+
count++;
145167
for (size_t j = 0; j < n2_arr_size; j++) {
146168
prod_uint[i + j] += n1_uint[i] * n2_uint[j];
147169
}
@@ -151,10 +173,7 @@ static void bc_standard_mul(bc_num n1, size_t n1len, bc_num n2, size_t n2len, bc
151173
* Move a value exceeding 4/8 digits by carrying to the next digit.
152174
* However, the last digit does nothing.
153175
*/
154-
for (i = 0; i < prod_arr_size - 1; i++) {
155-
prod_uint[i + 1] += prod_uint[i] / BC_MUL_UINT_OVERFLOW;
156-
prod_uint[i] %= BC_MUL_UINT_OVERFLOW;
157-
}
176+
bc_digits_adjustment(prod_uint, prod_arr_size);
158177

159178
/* Convert to bc_num */
160179
*prod = bc_new_num_nonzeroed(prodlen, 0);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
bcmul() checking overflow
3+
--EXTENSIONS--
4+
bcmath
5+
--INI--
6+
bcmath.scale=0
7+
--FILE--
8+
<?php
9+
for ($i = 1; $i < 15; $i++) {
10+
$repeat = 2 ** $i;
11+
$num1 = str_repeat('99999999', $repeat);
12+
$expected = str_repeat('9', $repeat * 8 - 1) . '8' . str_repeat('0', $repeat * 8 - 1) . '1';
13+
$actual = bcmul($num1, $num1);
14+
echo $repeat . ': ' . ($actual === $expected ? 'OK' : 'NG') . PHP_EOL;
15+
}
16+
?>
17+
--EXPECT--
18+
2: OK
19+
4: OK
20+
8: OK
21+
16: OK
22+
32: OK
23+
64: OK
24+
128: OK
25+
256: OK
26+
512: OK
27+
1024: OK
28+
2048: OK
29+
4096: OK
30+
8192: OK
31+
16384: OK

0 commit comments

Comments
 (0)