Skip to content

Segfault when cleaning up after a MemoryError with deeply nested objects #149146

@stestagg

Description

@stestagg

Crash report

What happened?

When in memory-constrained situations, the following code generates a segfault on main:

b = 9
while True:
    b = (b, None)

Clearly, b grows to be an unbounded nested tuple, which eventually exhausts the memory.
A MemoryError is correctly thrown, but during unwind, python tries to clean up the object, and this triggers a stack overflow.

Example stack
Process 369 stopped

Thread 1 "fuzz_python" stop reason = signal SIGSEGV
  frame #0: frame #0: 0x0000aaaaaea31648 fuzz_python`tuple_dealloc(self=0x0000ffff9fa045e0) at tupleobject.c:256
  frame #1: frame #1: 0x0000aaaaae9ccd54 fuzz_python`_Py_Dealloc(op=0x0000ffff9fa045e0) at object.c:3303:5
  frame #2: frame #2: 0x0000aaaaaea3182c fuzz_python`Py_DECREF(op=<unavailable>) at refcount.h:427:9 [inlined]
  frame #3: frame #3: 0x0000aaaaaea3180c fuzz_python`Py_XDECREF(op=<unavailable>) at refcount.h:520:9 [inlined]
  frame #4: frame #4: 0x0000aaaaaea31804 fuzz_python`tuple_dealloc(self=0x0000ffff9fa04630) at tupleobject.c:277:9
  frame #5: frame #5: 0x0000aaaaae9ccd54 fuzz_python`_Py_Dealloc(op=0x0000ffff9fa04630) at object.c:3303:5
  frame #6: frame #6: 0x0000aaaaaea3182c fuzz_python`Py_DECREF(op=<unavailable>) at refcount.h:427:9 [inlined]
  frame #7: frame #7: 0x0000aaaaaea3180c fuzz_python`Py_XDECREF(op=<unavailable>) at refcount.h:520:9 [inlined]
...
  frame #2709: frame #2709: 0x0000aaaaae9ccd54 fuzz_python`_Py_Dealloc(op=0x0000ffff9fa11cd0) at object.c:3303:5
  frame #2710: frame #2710: 0x0000aaaaaea3182c fuzz_python`Py_DECREF(op=<unavailable>) at refcount.h:427:9 [inlined]
  frame #2711: frame #2711: 0x0000aaaaaea3180c fuzz_python`Py_XDECREF(op=<unavailable>) at refcount.h:520:9 [inlined]
  frame #2712: frame #2712: 0x0000aaaaaea31804 fuzz_python`tuple_dealloc(self=0x0000ffff9fa11d20) at tupleobject.c:277:9
  frame #2713: frame #2713: 0x0000aaaaae9ccd54 fuzz_python`_Py_Dealloc(op=0x0000ffff9fa11d20) at object.c:3303:5
  frame #2714: frame #2714: 0x0000aaaaaea3182c fuzz_python`Py_DECREF(op=<unavailable>) at refcount.h:427:9 [inlined]
  frame #2715: frame #2715: 0x0000aaaaaea3180c fuzz_python`Py_XDECREF(op=<unavailable>) at refcount.h:520:9 [inlined]
  frame #2716: frame #2716: 0x0000aaaaaea31804 fuzz_python`tuple_dealloc(self=0x0000ffff9fa11d70) at tupleobject.c:277:9
  frame #2717: frame #2717: 0x0000aaaaae9ccd54 fuzz_python`_Py_Dealloc(op=0x0000ffff9fa11d70) at object.c:3303:5
  frame #2718: frame #2718: 0x0000aaaaaea3182c fuzz_python`Py_DECREF(op=<unavailable>) at refcount.h:427:9 [inlined]
  frame #2719: frame #2719: 0x0000aaaaaea3180c fuzz_python`Py_XDECREF(op=<unavailable>) at refcount.h:520:9 [inlined]
  frame #2720: frame #2720: 0x0000aaaaaea31804 fuzz_python`tuple_dealloc(self=0x0000ffff9fa11dc0) at tupleobject.c:277:9
  frame #2721: frame #2721: 0x0000aaaaae9ccd54 fuzz_python`_Py_Dealloc(op=0x0000ffff9fa11dc0) at object.c:3303:5
  frame #2722: frame #2722: 0x0000aaaaae98f290 fuzz_python`Py_DECREF(op=<unavailable>) at refcount.h:427:9 [inlined]
  frame #2723: frame #2723: 0x0000aaaaae98f26c fuzz_python`Py_XDECREF(op=<unavailable>) at refcount.h:520:9 [inlined]
  frame #2724: frame #2724: 0x0000aaaaae98f26c fuzz_python`dictkeys_decref(dk=0x0000ffffbe045210, use_qsbr=false) at dictobject.c:495:17
  frame #2725: frame #2725: 0x0000aaaaae98630c fuzz_python`dict_dealloc(self=0x0000ffffbe055f40) at dictobject.c:0
  frame #2726: frame #2726: 0x0000aaaaae9ccd54 fuzz_python`_Py_Dealloc(op=0x0000ffffbe055f40) at object.c:3303:5
  frame #2727: frame #2727: 0x0000aaaaae7ecb8c fuzz_python`Py_DECREF(op=0x0000ffffbe055f40) at refcount.h:427:9 [inlined]
  frame #2728: frame #2728: 0x0000aaaaae7ecb58 fuzz_python`main(argc=1, argv=0x0000fffff67f4238) at fuzz_python.c:226:17
  frame #2729: frame #2729: 0x0000ffffbe1e1f9c ld-musl-aarch64.so.1`libc_start_main_stage2(main=(fuzz_python`main at fuzz_python.c:71), argc=1, argv=0x0000fffff67f4238) at __libc_start_main.c:95:2

The following dockerfile reliably reproduces it for me:

Dockerfile
FROM alpine:latest

RUN apk add --no-cache \
    git build-base linux-headers openssl-dev zlib-dev bzip2-dev \
    xz-dev sqlite-dev readline-dev libffi-dev ncurses-dev gdbm-dev \
    tcl-dev tk-dev

WORKDIR /src
RUN git clone --depth=1 https://github.com/python/cpython.git
WORKDIR /src/cpython
RUN ./configure && make -j2

RUN cat > /tc.py <<'PY'
b = 9
while True:
    b = (b, None)
PY

RUN cat > /entrypoint.sh <<'SH'
#!/bin/sh

echo "Running test"
# 512 MiB virtual memory limit
ulimit -v 524288

/src/cpython/python /tc.py
echo "Python process exited with code $?"
SH

RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Gives me:

>docker run --rm -it 29apr
Running test
Traceback (most recent call last):
  File "/tc.py", line 3, in <module>
object address  : 0xffffae809fc0
object refcount : 3
object type     : 0xaaaad32175a8
object type name: MemoryError
object repr     : MemoryError()
lost sys.stderr
Segmentation fault
Python process exited with code 139

ISTR that stack overflows from recursive objects were at least considered low priority, if not 'not worth fixing' before, but I can't recall where I read that, it was a while ago, so apologies if this isn't a useful report!

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Output from running 'python -VV' on the command line:

Python 3.15.0a8+ (heads/main:d4eee16, Apr 29 2026, 11:03:26) [GCC 15.2.0]

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dump
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions