Skip to content

CPython crashes on memory-allocation failure (OOM): 35 findings #151763

@devdanzin

Description

@devdanzin

Crash report

What happened?

Allocation-failure fuzzing of CPython main turned up 35 distinct ways the interpreter
crashes
— segfault, failed assert(...), or Py_FatalError — when a memory allocation
fails part-way through an operation. Each is a separate underlying bug with a minimal,
stdlib-only reproducer, a backtrace, a root-cause analysis, and a suggested fix, published
as a self-contained gist (linked below).

I'm filing them under one umbrella so they can be picked off individually without flooding
the tracker with 35 issues at once. To take one: open a normal CPython issue (or PR) for
it and drop a comment here with the link — I'll mark it in the table. If any turn out to be
duplicates or non-bugs, say so and I'll annotate them.

These were found with fusil's OOM-injection mode
(fusil originally by @vstinner). The reports and reduced reproducers were drafted with
AI assistance (Claude Code) and then reviewed and re-verified by hand — see Disclosure.

Reproducing

  • Found on main (3.16.0a0) at commit 15d7406. These are allocation-path bugs, so they
    are not tied to that exact revision.
  • Every gist ships a minimal OOM-ID-repro.py (standard library only) that drives the failing path
    while _testcapi.set_nomemory(...) forces allocations to fail. Just run python OOM-ID-repro.py — where the exact failing-allocation index is sensitive to the build's
    allocation count, the repro self-sweeps (a fresh subprocess per index) and stops at the
    first crash, so no manual tuning is needed.
  • _testcapi.set_nomemory requires a test/debug interpreter (--with-pydebug exposes it).
  • Build matrix (in each report): many of these are debug-only assertion / Py_FatalError
    failures — the assert(...) is compiled out under -DNDEBUG, so a release build doesn't
    abort. They are still real bugs: on release the same defect is latent undefined behaviour
    (a use-after-free, a Py_DECREF(NULL), or a silently-lost exception). A subset segfault on
    release builds directly; those are called out below.

Highest-confidence starting points

These crash a release build (not just a debug assertion) and have a clean minimal
reproducer — the lowest-effort to confirm and fix:

  • OOM-0034, OOM-0028 — unchecked PyUnicode_AsUTF8 / PyUnicode_EncodeFSDefault
    returning NULL is dereferenced (≈ one-line NULL checks).
  • OOM-0001, OOM-0002, OOM-0014Py_DECREF(NULL) on an unchecked-allocation
    error path.
  • OOM-0031_interpreters.capture_exception calls _PyXI_FreeExcInfo(NULL) with no
    NULL guard.
  • OOM-0033 — a module import under OOM over-decrefs a sys.path entry.
  • OOM-0012, OOM-0020 — instrumentation / thread-state corruption under OOM.

Findings

Status: blank = not yet filed · #N = a CPython issue is open.

Segfaults (7)

Report Title Status
OOM-0001 Segfault: Py_DECREF of a NULL filename in do_warn (_warnings.c:1139) #151673
OOM-0002 Segfault: Py_DECREF(NULL) in PyContextVar_Set (context.c:367)
OOM-0024 Segfault: dealloc of uninitialized iterator in template_iter (templateobject.c:232) #151815
OOM-0028 Segfault: NULL deref in os__path_normpath_impl (posixmodule.c:6149)
OOM-0031 Segfault: NULL info deref in _excinfo_clear_type (crossinterp.c:1319)
OOM-0033 Segfault / negative-refcount: over-decreffed sys.path entry in PyType_IsSubtype (typeobject.c:2931)
OOM-0034 Segfault: unchecked PyUnicode_AsUTF8 NULL deref in pegen.c:33

Assertion failures / aborts (23)

Report Title Status
OOM-0003 Abort: uninitialized _co_unique_id assert in code_dealloc (codeobject.c:2440)
OOM-0004 Abort/Segfault: corrupted object freelist in clear_freelist (object.c:909)
OOM-0005 Abort: negative-refcount over-decref in _PyFrame_ClearLocals (frame.c:101)
OOM-0006 Abort/Segfault: _PyObject_GC_UNTRACK assert on untracked iterator in dictiter_dealloc (dictobject.c:5532)
OOM-0008 Abort: assert(!PyErr_Occurred()) in _PyType_LookupStackRefAndVersion (typeobject.c:6343)
OOM-0009 Abort: stale release1 flag trips an ownership assert in replace (unicodeobject.c:10783)
OOM-0010 Abort: assert(_PyErr_Occurred(tstate)) in _PyEval_EvalFrameDefault (generated_cases.c.h:13817)
OOM-0011 Abort: assert(!PyErr_Occurred()) in specialize (specialize.c:364)
OOM-0012 Abort/Segfault: stale instrumentation in get_tools_for_instruction (instrumentation.c:1106)
OOM-0013 Abort: builtin breaks result/error contract in _Py_BuiltinCallFastWithKeywords_StackRef (ceval.c:843)
OOM-0014 Abort/Segfault: unchecked NULL in channelsmod__channel_id (_interpchannelsmodule.c:3487)
OOM-0015 Abort: stale exception in cfunction_check_kwargs (methodobject.c:409)
OOM-0016 Abort: assert(!queue->alive) in _queue_clear (_interpqueuesmodule.c:559)
OOM-0017 Abort: negative gc_refs ("refcount too small") in validate_gc_objects (gc_free_threading.c:1116)
OOM-0018 Abort: ownership assert in set_keys (dictobject.c:205)
OOM-0019 Abort: double-free in _PyPegen_raise_error_known_location (pegen_errors.c:363)
OOM-0025 Abort: assert(!PyErr_Occurred()) in unspecialize (specialize.c:378)
OOM-0026 Abort: err-code vs PyErr desync in handle_channel_error (_interpchannelsmodule.c:398 / :443)
OOM-0027 Abort: assert(PyStackRef_BoolCheck(cond)) in POP_JUMP_IF_FALSE (generated_cases.c.h:11120)
OOM-0029 Abort: negative refcount on a MemoryError (tuple_dealloc, tupleobject.c:277)
OOM-0030 Abort: Py_DECREF of NULL-data unicode in unicode_subtype_new (unicodeobject.c:13986)
OOM-0032 Abort: pending-exception assert from warn_explicit normalization (_warnings.c:799/806)
OOM-0035 Abort / malformed str: invalid maxchar in _PyUnicode_FromUCS4 (unicodeobject.c:2228)

Fatal Python error (5)

Report Title Status
OOM-0007 Fatal: context_tp_dealloc clears the pending exception (context.c:535)
OOM-0020 Fatal: _PyMem_DebugRawFree: bad ID in free_threadstate (pystate.c:1527)
OOM-0021 Fatal: NULL returned without an exception set in _Py_CheckFunctionResult (call.c:43)
OOM-0022 Fatal: stale MemoryError trips _Py_CheckSlotResult in reload_singlephase_extension (import.c:2011)
OOM-0023 Fatal: dealloc clears the in-flight exception in subtype_dealloc (typeobject.c:2719)

Related groups (one fix may cover several)

  • Dealloc clears the in-flight exception (the tp_dealloc docs should mention error indicator may be set #89373 _Py_Dealloc invariant): OOM-0007
    (context_tp_dealloc) and OOM-0023 (the generic subtype_dealloc, covering a family of
    pure-Python types) free an object while a MemoryError is pending and don't save/restore it.
  • A pending exception survives into code that asserts there is none (specializer / eval /
    call layers): OOM-0008, 0010, 0011, 0015, 0025, 0032. Several share the theme that the
    adaptive specializer / call machinery is entered with a MemoryError already pending.
  • Py_DECREF/Py_CLEAR of a NULL or partially-initialized object on the OOM error path:
    OOM-0001, 0002, 0006, 0014, 0024, 0030, 0031 (an allocation fails after a slot is taken
    but before the object is valid, and the error path frees it anyway).
  • Over-decref → negative refcount under OOM (a real memory-safety bug; the assert is the
    debug-build detector): OOM-0005, 0019, 0029, 0033.
  • Free-threading-specific: OOM-0003 (_co_unique_id), OOM-0017 (cyclic GC),
    OOM-0018 (managed dict), OOM-0020 (thread-state reservation).

Disclosure & caveats

  • The reports and reduced reproducers were drafted with AI assistance (Claude Code); each
    gist carries an explicit disclaimer. The reproducers were re-run on the build matrix and the
    root causes audited against the CPython source before publishing.
  • A few root causes are explicitly marked partial — the trigger is minimal and verified,
    but the exact offending line wasn't pinned (noted in those reports): OOM-0010, 0027, 0029,
    0033, 0035.
  • OOM-0001 is already filed as
    #151673; the other 34 had no matching
    python/cpython issue when checked and appear novel.

Found with fusil (OOM-injection mode; fusil originally
by Victor Stinner). Drafted with Claude Code; reproducers machine-generated and human-verified.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

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

Python 3.16.0a0 free-threading build (heads/main:15d74068f3a, Jun 18 2026, 16:44:30) [Clang 22.1.2 (1ubuntu1)]

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirinterpreter-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