Skip to content

use after free found at micropython/py/objarray.c:509 [micropython@a5bdd39127]  #13283

@zeroone-kr

Description

@zeroone-kr

Hi all, I'm Wonil Jang, from the research group S2Lab in UNIST.
We found a use after free bug from micropython by our custom tool. The detailed information is as follows.

Environment

  • OS: Ubuntu 22.04
  • Version: micropython at commit a5bdd39
  • Build: unix ports
  • Bug Type: use-after-free
  • Bug Location: micropython/py/objarray.c:509
  • Credits: Junwha Hong and Wonil Jang, from S2Lab in UNIST

Proof-of-Concept (PoC)

b = bytearray(b'1234567')
for i in range(3):
     b[-1:] = b

Problem Statement

By PoC,

STATIC mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t **value**) {
...
size_t src_len;
void *src_items;
size_t item_sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL);
if (mp_obj_is_obj(value) && MP_OBJ_TYPE_GET_SLOT_OR_NULL(((mp_obj_base_t *)MP_OBJ_TO_PTR(value))->type, subscr) == array_subscr) {
    // value is array, bytearray or memoryview
    **mp_obj_array_t *src_slice = MP_OBJ_TO_PTR(value);**
    if (item_sz != mp_binary_get_size('@', src_slice->typecode & TYPECODE_MASK, NULL)) {
    compat_error:
        mp_raise_ValueError(MP_ERROR_TEXT("lhs and rhs should be compatible"));
    }
    **src_len = src_slice->len;**
    **src_items = src_slice->items;**
    #if MICROPY_PY_BUILTINS_MEMORYVIEW
    if (mp_obj_is_type(value, &mp_type_memoryview)) {
        src_items = (uint8_t *)src_items + (src_slice->memview_offset * item_sz);
    }
    #endif
}
...
if ((size_t)len_adj > o->free) {
    // TODO: alloc policy; at the moment we go conservative
    **o->items = m_renew(byte, o->items, (o->len + o->free) * item_sz, (o->len + len_adj) * item_sz);**
    o->free = len_adj;
    dest_items = o->items;
}
mp_seq_replace_slice_grow_inplace(dest_items, o->len,
    slice.start, slice.stop, **src_items**, src_len, len_adj, item_sz);

If you run the PoC, self_in and value can be same in array_subscr function. This is because when subscr() function is called, It just uses reference without copying. and then, if the address is rebased after calling realloc, only self_in changed and value(src) becomes a dangling pointer. So when copying it, the use after free bug occurs.

Patch

It should keep source before calling fixed realloc().

Log

    #1 0x5555557340a4 in array_subscr /home/qbit/testing-2023/micropython/ports/unix/../../py/objarray.c:509:21
    #2 0x555555731de6 in mp_obj_subscr /home/qbit/testing-2023/micropython/ports/unix/../../py/obj.c:536:24
    #3 0x555555781d05 in mp_execute_bytecode /home/qbit/testing-2023/micropython/ports/unix/../../py/vm.c:487:21
    #4 0x55555574261b in fun_bc_call /home/qbit/testing-2023/micropython/ports/unix/../../py/objfun.c:273:42
    #5 0x555555903f3d in execute_from_lexer /home/qbit/testing-2023/micropython/ports/unix/main.c:161:13
    #6 0x555555902ad5 in do_file /home/qbit/testing-2023/micropython/ports/unix/main.c:310:12
    #7 0x555555902ad5 in main_ /home/qbit/testing-2023/micropython/ports/unix/main.c:722:19
    #8 0x7ffff7c29d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #9 0x7ffff7c29e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #10 0x555555593a34 in _start (/home/qbit/testing-2023/micropython/ports/unix/build-standard/micropython+0x3fa34)

Thank you for reading my report.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions