Skip to content

Commit 13a0a46

Browse files
committed
Merge pull request #1920 from bettio/bigint-fixes
bigint: misc fixes Those bugs were unlikely causing a crash, but still code wasn't correct. As an additional change: add `size_align_up_pow2` and similar utilities to `utils.h` since they are also required in intn.h as well. These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents 9a184e0 + dcf4c27 commit 13a0a46

File tree

5 files changed

+121
-26
lines changed

5 files changed

+121
-26
lines changed

src/libAtomVM/bif.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,7 @@ term bif_erlang_sub_2(Context *ctx, uint32_t fail_label, int live, term arg1, te
802802
}
803803
}
804804

805+
// this function assumes that bigres_len is always <= bigres buffer capacity
805806
static term make_bigint(Context *ctx, uint32_t fail_label, uint32_t live,
806807
const intn_digit_t bigres[], size_t bigres_len, intn_integer_sign_t sign)
807808
{
@@ -1705,6 +1706,10 @@ term bif_erlang_bsl_2(Context *ctx, uint32_t fail_label, int live, term arg1, te
17051706

17061707
intn_digit_t bigres[INTN_MAX_RES_LEN];
17071708
size_t bigres_len = intn_bsl(m, m_len, b, bigres);
1709+
// this check is required in order to avoid out-of-bounds read in make_bigint
1710+
if (UNLIKELY(bigres_len > INTN_MAX_RES_LEN)) {
1711+
RAISE_ERROR_BIF(fail_label, OVERFLOW_ATOM);
1712+
}
17081713

17091714
return make_bigint(ctx, fail_label, live, bigres, bigres_len, m_sign);
17101715

src/libAtomVM/externalterm.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1011,9 +1011,10 @@ static int calculate_heap_usage(const uint8_t *external_term_buf, size_t remaini
10111011
}
10121012

10131013
// num_bytes > 8 bytes || uint64_does_overflow_int64
1014+
size_t required_digits = intn_required_digits_for_unsigned_integer(num_bytes);
10141015
size_t data_size;
10151016
size_t unused_rounded_len;
1016-
term_intn_to_term_size(num_bytes, &data_size, &unused_rounded_len);
1017+
term_intn_to_term_size(required_digits, &data_size, &unused_rounded_len);
10171018
return BOXED_INTN_SIZE(data_size);
10181019
}
10191020

src/libAtomVM/intn.c

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,6 @@ static inline size_t pad_uint16_to_digits(uint16_t n16[], size_t n16_len)
6161
return n16_len;
6262
}
6363

64-
static inline size_t size_round_to(size_t n, size_t round_to)
65-
{
66-
return (n + (round_to - 1)) & ~(round_to - 1);
67-
}
68-
6964
/*
7065
* Multiplication
7166
*/
@@ -439,18 +434,19 @@ size_t intn_divmnu(const intn_digit_t m[], size_t m_len, const intn_digit_t n[],
439434
return padded_q_len / UINT16_IN_A_DIGIT;
440435
}
441436

442-
// This function assumes no leading zeros (lenght is used in comparison)
443-
// Caller must ensure this precondition
444437
int intn_cmp(const intn_digit_t a[], size_t a_len, const intn_digit_t b[], size_t b_len)
445438
{
446-
if (a_len > b_len) {
439+
size_t normal_a_len = intn_count_digits(a, a_len);
440+
size_t normal_b_len = intn_count_digits(b, b_len);
441+
442+
if (normal_a_len > normal_b_len) {
447443
return 1;
448444
}
449-
if (a_len < b_len) {
445+
if (normal_a_len < normal_b_len) {
450446
return -1;
451447
}
452448

453-
for (size_t i = a_len; i > 0; i--) {
449+
for (size_t i = normal_a_len; i > 0; i--) {
454450
if (a[i - 1] > b[i - 1]) {
455451
return 1;
456452
}
@@ -791,23 +787,21 @@ size_t intn_bnot(const intn_digit_t m[], size_t m_len, intn_integer_sign_t m_sig
791787

792788
size_t intn_bsl(const intn_digit_t num[], size_t len, size_t n, intn_digit_t *out)
793789
{
794-
size_t digit_bit_size = sizeof(uint32_t) * 8;
795-
796790
size_t digit_left_bit_shift = n % 32;
797791
size_t right_shift_n = (32 - digit_left_bit_shift);
798792

799793
size_t counted_digits = intn_count_digits(num, len);
800794
size_t ms_digit_bits = 32 - uint32_nlz(num[counted_digits - 1]);
801-
size_t effective_bits_len = (counted_digits - 1) * digit_bit_size + ms_digit_bits;
802-
size_t new_bits_len = size_round_to(effective_bits_len + n, digit_bit_size);
795+
size_t effective_bits_len = (counted_digits - 1) * INTN_DIGIT_BITS + ms_digit_bits;
796+
size_t new_bits_len = size_align_up_pow2(effective_bits_len + n, INTN_DIGIT_BITS);
803797

804-
size_t new_digits_count = new_bits_len / digit_bit_size;
798+
size_t new_digits_count = new_bits_len / INTN_DIGIT_BITS;
805799

806800
if (new_digits_count > INTN_BSL_MAX_RES_LEN) {
807801
return new_digits_count;
808802
}
809803

810-
size_t initial_zeros = MIN(n / digit_bit_size, INTN_BSL_MAX_RES_LEN);
804+
size_t initial_zeros = MIN(n / INTN_DIGIT_BITS, INTN_BSL_MAX_RES_LEN);
811805
memset(out, 0, initial_zeros * sizeof(uint32_t));
812806

813807
if (right_shift_n == 32) {
@@ -837,15 +831,14 @@ size_t intn_bsl(const intn_digit_t num[], size_t len, size_t n, intn_digit_t *ou
837831
void bsru(
838832
const uint32_t num[], size_t effective_bits_len, size_t n, uint32_t last_digit, uint32_t *out)
839833
{
840-
size_t digit_bit_size = sizeof(uint32_t) * 8; // 32
841-
842-
size_t digit_right_bit_shift = n % digit_bit_size;
843-
size_t left_shift_n = (digit_bit_size - digit_right_bit_shift);
834+
size_t digit_right_bit_shift = n % INTN_DIGIT_BITS;
835+
size_t left_shift_n = (INTN_DIGIT_BITS - digit_right_bit_shift);
844836

845-
size_t len_in_digits = size_round_to(effective_bits_len, digit_bit_size) / digit_bit_size;
837+
size_t len_in_digits
838+
= size_align_up_pow2(effective_bits_len, INTN_DIGIT_BITS) / INTN_DIGIT_BITS;
846839

847840
// caller makes sure that discarded < len_in_digits
848-
size_t discarded = n / digit_bit_size;
841+
size_t discarded = n / INTN_DIGIT_BITS;
849842

850843
if (left_shift_n == 32) {
851844
memcpy(out, num + discarded, (len_in_digits - discarded) * sizeof(uint32_t));
@@ -868,17 +861,17 @@ void bsru(
868861
size_t intn_bsr(
869862
const intn_digit_t num[], size_t len, intn_integer_sign_t num_sign, size_t n, intn_digit_t *out)
870863
{
871-
size_t digit_bit_size = sizeof(uint32_t) * 8;
872864
size_t counted_digits = intn_count_digits(num, len);
873865
size_t ms_digit_bits = 32 - uint32_nlz(num[counted_digits - 1]);
874-
size_t effective_bits_len = (counted_digits - 1) * digit_bit_size + ms_digit_bits;
866+
size_t effective_bits_len = (counted_digits - 1) * INTN_DIGIT_BITS + ms_digit_bits;
875867

876868
if (n >= effective_bits_len) {
877869
out[0] = (num_sign == IntNPositiveInteger) ? 0 : 1;
878870
return 1;
879871
}
880872

881-
size_t shifted_len = size_round_to(effective_bits_len - n, digit_bit_size) / digit_bit_size;
873+
size_t shifted_len
874+
= size_align_up_pow2(effective_bits_len - n, INTN_DIGIT_BITS) / INTN_DIGIT_BITS;
882875

883876
if (num_sign == IntNPositiveInteger) {
884877
bsru(num, effective_bits_len, n, 0, out);

src/libAtomVM/intn.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#define INTN_DIV_OUT_LEN(m, n) ((m) - (n) + 1 + 1)
5353
#define INTN_ABS_OUT_LEN(m) ((m) + 1)
5454

55+
#define INTN_DIGIT_BITS 32
5556
#define INTN_MAX_UNSIGNED_BYTES_SIZE 32
5657
#define INTN_MAX_UNSIGNED_BITS_SIZE 256
5758

@@ -160,6 +161,11 @@ int intn_to_integer_bytes(const intn_digit_t in[], size_t in_len, intn_integer_s
160161

161162
size_t intn_required_unsigned_integer_bytes(const intn_digit_t in[], size_t in_len);
162163

164+
static inline size_t intn_required_digits_for_unsigned_integer(size_t size_in_bytes)
165+
{
166+
return size_align_up_pow2(size_in_bytes, sizeof(intn_digit_t)) / sizeof(intn_digit_t);
167+
}
168+
163169
static inline intn_integer_sign_t intn_negate_sign(intn_integer_sign_t sign)
164170
{
165171
return (sign == IntNPositiveInteger) ? IntNNegativeInteger : IntNPositiveInteger;

src/libAtomVM/utils.h

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,96 @@ static inline __attribute__((always_inline)) func_ptr_t cast_void_to_func_ptr(vo
359359
#define MAXI(A, B) ((A > B) ? (A) : (B))
360360
#define MINI(A, B) ((A > B) ? (B) : (A))
361361

362+
/**
363+
* @brief Align size up to power-of-2 boundary
364+
*
365+
* Rounds up a size value to the next multiple of a power-of-2 alignment.
366+
* This function uses bit manipulation for efficient alignment calculation
367+
* and is faster than the general-purpose \c size_align_up().
368+
*
369+
* @param n Size value to align
370+
* @param align Power-of-2 alignment boundary
371+
* @return Size rounded up to next multiple of align
372+
*
373+
* @pre align must be a power of 2 (e.g., 2, 4, 8, 16, 32, ...)
374+
* @warning Undefined behavior if align is not a power of 2
375+
* @warning Undefined behavior if align is 0
376+
*
377+
* @note Result is always >= n
378+
*
379+
* @code
380+
* size_t aligned = size_align_up_pow2(17, 8); // Returns 24
381+
* size_t aligned = size_align_up_pow2(16, 8); // Returns 16 (already aligned)
382+
* @endcode
383+
*
384+
* @see size_align_up() for arbitrary alignment values
385+
*/
386+
static inline size_t size_align_up_pow2(size_t n, size_t align)
387+
{
388+
return (n + (align - 1)) & ~(align - 1);
389+
}
390+
391+
/**
392+
* @brief Align size up to arbitrary boundary
393+
*
394+
* Rounds up a size value to the next multiple of an alignment boundary.
395+
* Works with any alignment value, not just powers of 2.
396+
*
397+
* @param n Size value to align
398+
* @param align Alignment boundary (any positive value, or 0)
399+
* @return Size rounded up to next multiple of align, or n if align is 0
400+
*
401+
* @note Returns n unchanged if align is 0 (no alignment)
402+
* @note Result is always >= n
403+
* @note For power-of-2 alignments, \c size_align_up_pow2() is more efficient
404+
*
405+
* @code
406+
* size_t aligned = size_align_up(17, 10); // Returns 20
407+
* size_t aligned = size_align_up(20, 10); // Returns 20 (already aligned)
408+
* size_t aligned = size_align_up(17, 0); // Returns 17 (no alignment)
409+
* @endcode
410+
*
411+
* @see size_align_up_pow2() for optimized power-of-2 alignment
412+
* @see size_align_down() for rounding down instead of up
413+
*/
414+
static inline size_t size_align_up(size_t n, size_t align)
415+
{
416+
if (align == 0) {
417+
return n;
418+
}
419+
return ((n + align - 1) / align) * align;
420+
}
421+
422+
/**
423+
* @brief Align size down to arbitrary boundary
424+
*
425+
* Rounds down a size value to the previous multiple of an alignment boundary.
426+
* Works with any alignment value, not just powers of 2.
427+
*
428+
* @param n Size value to align
429+
* @param align Alignment boundary (any positive value, or 0)
430+
* @return Size rounded down to previous multiple of align, or n if align is 0
431+
*
432+
* @note Returns n unchanged if align is 0 (no alignment)
433+
* @note Result is always <= n
434+
* @note Commonly used for finding aligned base addresses within buffers
435+
*
436+
* @code
437+
* size_t aligned = size_align_down(17, 10); // Returns 10
438+
* size_t aligned = size_align_down(20, 10); // Returns 20 (already aligned)
439+
* size_t aligned = size_align_down(7, 10); // Returns 0
440+
* @endcode
441+
*
442+
* @see size_align_up() for rounding up instead of down
443+
*/
444+
static inline size_t size_align_down(size_t n, size_t align)
445+
{
446+
if (align == 0) {
447+
return n;
448+
}
449+
return (n / align) * align;
450+
}
451+
362452
/**
363453
* @brief Negate unsigned 32-bit value (\c uint32_t) to signed integer (\c int32_t)
364454
*

0 commit comments

Comments
 (0)