Skip to content

Commit 8734a9a

Browse files
ext/bcmath: Prevent overflow of uint32_t/uint64_t (php#14297)
If add more than a certain number of times, it will overflow, so need to adjust the digits before adding.
1 parent fe7f699 commit 8734a9a

File tree

2 files changed

+59
-6
lines changed

2 files changed

+59
-6
lines changed

ext/bcmath/libbcmath/src/recmul.c

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,25 @@
3939

4040
#if SIZEOF_SIZE_T >= 8
4141
# define BC_MUL_UINT_DIGITS 8
42-
# define BC_MUL_UINT_OVERFLOW 100000000
42+
# define BC_MUL_UINT_OVERFLOW (BC_UINT_T) 100000000
4343
#else
4444
# define BC_MUL_UINT_DIGITS 4
45-
# define BC_MUL_UINT_OVERFLOW 10000
45+
# define BC_MUL_UINT_OVERFLOW (BC_UINT_T) 10000
4646
#endif
4747

48+
#define BC_MUL_MAX_ADD_COUNT (~((BC_UINT_T) 0) / (BC_MUL_UINT_OVERFLOW * BC_MUL_UINT_OVERFLOW))
49+
4850

4951
/* Multiply utility routines */
5052

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

143153
/* Multiplication and addition */
154+
size_t count = 0;
144155
for (i = 0; i < n1_arr_size; i++) {
156+
/*
157+
* This calculation adds the result multiple times to the array entries.
158+
* When multiplying large numbers of digits, there is a possibility of
159+
* overflow, so digit adjustment is performed beforehand.
160+
*/
161+
if (UNEXPECTED(count >= BC_MUL_MAX_ADD_COUNT)) {
162+
bc_digits_adjustment(prod_uint, prod_arr_size);
163+
count = 0;
164+
}
165+
count++;
145166
for (size_t j = 0; j < n2_arr_size; j++) {
146167
prod_uint[i + j] += n1_uint[i] * n2_uint[j];
147168
}
@@ -151,10 +172,7 @@ static void bc_standard_mul(bc_num n1, size_t n1len, bc_num n2, size_t n2len, bc
151172
* Move a value exceeding 4/8 digits by carrying to the next digit.
152173
* However, the last digit does nothing.
153174
*/
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-
}
175+
bc_digits_adjustment(prod_uint, prod_arr_size);
158176

159177
/* Convert to bc_num */
160178
*prod = bc_new_num_nonzeroed(prodlen, 0);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
/*
13+
* 9999 * 9999 = 99980001
14+
* 99999999 * 99999999 = 9999999800000001
15+
*/
16+
$expected = str_repeat('9', $repeat * 8 - 1) . '8' . str_repeat('0', $repeat * 8 - 1) . '1';
17+
$actual = bcmul($num1, $num1);
18+
echo $repeat . ': ' . ($actual === $expected ? 'OK' : 'NG') . PHP_EOL;
19+
}
20+
?>
21+
--EXPECT--
22+
2: OK
23+
4: OK
24+
8: OK
25+
16: OK
26+
32: OK
27+
64: OK
28+
128: OK
29+
256: OK
30+
512: OK
31+
1024: OK
32+
2048: OK
33+
4096: OK
34+
8192: OK
35+
16384: OK

0 commit comments

Comments
 (0)