Skip to content

Reentrant equality can invalidate list storage in count and comparison #19753

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

List operations retain element storage and bounds across calls to user-defined __eq__. If the callback clears or shrinks a participating list, the operation later reads invalid elements.

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

Reproduction

list.count continues after __eq__ clears the searched list

class Boom:
    def __eq__(self, other):
        # Re-entrant mutation: free the list backing store while count() still uses it.
        lst.clear()
        return False


lst = [Boom(), 1]
lst.count(0)

Observed: SUMMARY: AddressSanitizer: SEGV ../../py/obj.c:233 in mp_obj_equal_not_equal

Complete ASan output
AddressSanitizer:DEADLYSIGNAL
=================================================================
==37==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0xaaaab110fbc4 bp 0xffffccce3020 sp 0xffffccce3020 T0)
==37==The signal is caused by a READ memory access.
==37==Hint: address points to the zero page.
    #0 0xaaaab110fbc4 in mp_obj_equal_not_equal ../../py/obj.c:233
    #1 0xaaaab111057c in mp_obj_equal ../../py/obj.c:303
    #2 0xaaaab11504b0 in mp_seq_count_obj ../../py/sequence.c:209
    #3 0xaaaab1127e40 in list_count ../../py/objlist.c:369
    #4 0xaaaab11204cc in fun_builtin_2_call ../../py/objfun.c:82
    #5 0xaaaab1100618 in mp_call_function_n_kw ../../py/runtime.c:719
    #6 0xaaaab1100fbc in mp_call_method_n_kw ../../py/runtime.c:735
    #7 0xaaaab11678f4 in mp_execute_bytecode ../../py/vm.c:1069
    #8 0xaaaab1120c64 in fun_bc_call ../../py/objfun.c:294
    #9 0xaaaab1100618 in mp_call_function_n_kw ../../py/runtime.c:719
    #10 0xaaaab1104720 in mp_call_function_0 ../../py/runtime.c:693
    #11 0xaaaab128a60c in parse_compile_execute ../../shared/runtime/pyexec.c:137
    #12 0xaaaab128b774 in pyexec_file ../../shared/runtime/pyexec.c:739
    #13 0xaaaab12803f0 in do_file /src/ports/unix/main.c:269
    #14 0xaaaab1281a18 in main_ /src/ports/unix/main.c:692
    #15 0xaaaab1282000 in main /src/ports/unix/main.c:452
    #16 0xffffb1a62258  (/lib/aarch64-linux-gnu/libc.so.6+0x22258) (BuildId: 4c1eca4527d1163b2dde55860b69f270158febb4)
    #17 0xffffb1a62338 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x22338) (BuildId: 4c1eca4527d1163b2dde55860b69f270158febb4)
    #18 0xaaaab10bccac 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:233 in mp_obj_equal_not_equal
==37==ABORTING

List comparison continues after __eq__ shrinks the right-hand list

class Boom:
    def __eq__(self, other):
        rhs.pop()
        return True

rhs = [Boom(), 1]
lhs = [Boom(), 1]
lhs == rhs

Observed: SUMMARY: AddressSanitizer: SEGV ../../py/obj.c:247 in mp_obj_equal_not_equal

Complete ASan output
AddressSanitizer:DEADLYSIGNAL
=================================================================
==70==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0xaaaadda4fc28 bp 0xffffec615a50 sp 0xffffec615a50 T0)
==70==The signal is caused by a READ memory access.
==70==Hint: address points to the zero page.
    #0 0xaaaadda4fc28 in mp_obj_equal_not_equal ../../py/obj.c:247
    #1 0xaaaadda5057c in mp_obj_equal ../../py/obj.c:303
    #2 0xaaaadda90068 in mp_seq_cmp_objs ../../py/sequence.c:153
    #3 0xaaaadda69648 in list_binary_op ../../py/objlist.c:152
    #4 0xaaaadda5030c in mp_obj_equal_not_equal ../../py/obj.c:280
    #5 0xaaaadda45594 in mp_binary_op ../../py/runtime.c:403
    #6 0xaaaaddaa562c in mp_execute_bytecode ../../py/vm.c:1302
    #7 0xaaaadda60c64 in fun_bc_call ../../py/objfun.c:294
    #8 0xaaaadda40618 in mp_call_function_n_kw ../../py/runtime.c:719
    #9 0xaaaadda44720 in mp_call_function_0 ../../py/runtime.c:693
    #10 0xaaaaddbca60c in parse_compile_execute ../../shared/runtime/pyexec.c:137
    #11 0xaaaaddbcb774 in pyexec_file ../../shared/runtime/pyexec.c:739
    #12 0xaaaaddbc03f0 in do_file /src/ports/unix/main.c:269
    #13 0xaaaaddbc1a18 in main_ /src/ports/unix/main.c:692
    #14 0xaaaaddbc2000 in main /src/ports/unix/main.c:452
    #15 0xffff7f062258  (/lib/aarch64-linux-gnu/libc.so.6+0x22258) (BuildId: 4c1eca4527d1163b2dde55860b69f270158febb4)
    #16 0xffff7f062338 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x22338) (BuildId: 4c1eca4527d1163b2dde55860b69f270158febb4)
    #17 0xaaaadd9fccac 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:247 in mp_obj_equal_not_equal
==70==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-11, MicroPython-22.

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