ext/bcmath: Prevent overflow of uint32_t/uint64_t (#14297)

If add more than a certain number of times, it will overflow, so need to adjust
the digits before adding.
This commit is contained in:
Saki Takamachi 2024-05-23 06:13:11 +09:00 committed by GitHub
parent fe7f699c0a
commit 8734a9a4af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 59 additions and 6 deletions

View File

@ -39,15 +39,25 @@
#if SIZEOF_SIZE_T >= 8
# define BC_MUL_UINT_DIGITS 8
# define BC_MUL_UINT_OVERFLOW 100000000
# define BC_MUL_UINT_OVERFLOW (BC_UINT_T) 100000000
#else
# define BC_MUL_UINT_DIGITS 4
# define BC_MUL_UINT_OVERFLOW 10000
# define BC_MUL_UINT_OVERFLOW (BC_UINT_T) 10000
#endif
#define BC_MUL_MAX_ADD_COUNT (~((BC_UINT_T) 0) / (BC_MUL_UINT_OVERFLOW * BC_MUL_UINT_OVERFLOW))
/* Multiply utility routines */
static inline void bc_digits_adjustment(BC_UINT_T *prod_uint, size_t prod_arr_size)
{
for (size_t i = 0; i < prod_arr_size - 1; i++) {
prod_uint[i + 1] += prod_uint[i] / BC_MUL_UINT_OVERFLOW;
prod_uint[i] %= BC_MUL_UINT_OVERFLOW;
}
}
/*
* Converts BCD to uint, going backwards from pointer n by the number of
* 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
bc_convert_to_uint(n2_uint, n2end, n2len);
/* Multiplication and addition */
size_t count = 0;
for (i = 0; i < n1_arr_size; i++) {
/*
* This calculation adds the result multiple times to the array entries.
* When multiplying large numbers of digits, there is a possibility of
* overflow, so digit adjustment is performed beforehand.
*/
if (UNEXPECTED(count >= BC_MUL_MAX_ADD_COUNT)) {
bc_digits_adjustment(prod_uint, prod_arr_size);
count = 0;
}
count++;
for (size_t j = 0; j < n2_arr_size; j++) {
prod_uint[i + j] += n1_uint[i] * n2_uint[j];
}
@ -151,10 +172,7 @@ static void bc_standard_mul(bc_num n1, size_t n1len, bc_num n2, size_t n2len, bc
* Move a value exceeding 4/8 digits by carrying to the next digit.
* However, the last digit does nothing.
*/
for (i = 0; i < prod_arr_size - 1; i++) {
prod_uint[i + 1] += prod_uint[i] / BC_MUL_UINT_OVERFLOW;
prod_uint[i] %= BC_MUL_UINT_OVERFLOW;
}
bc_digits_adjustment(prod_uint, prod_arr_size);
/* Convert to bc_num */
*prod = bc_new_num_nonzeroed(prodlen, 0);

View File

@ -0,0 +1,35 @@
--TEST--
bcmul() checking overflow
--EXTENSIONS--
bcmath
--INI--
bcmath.scale=0
--FILE--
<?php
for ($i = 1; $i < 15; $i++) {
$repeat = 2 ** $i;
$num1 = str_repeat('99999999', $repeat);
/*
* 9999 * 9999 = 99980001
* 99999999 * 99999999 = 9999999800000001
*/
$expected = str_repeat('9', $repeat * 8 - 1) . '8' . str_repeat('0', $repeat * 8 - 1) . '1';
$actual = bcmul($num1, $num1);
echo $repeat . ': ' . ($actual === $expected ? 'OK' : 'NG') . PHP_EOL;
}
?>
--EXPECT--
2: OK
4: OK
8: OK
16: OK
32: OK
64: OK
128: OK
256: OK
512: OK
1024: OK
2048: OK
4096: OK
8192: OK
16384: OK