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 (4 have since been confirmed as duplicates via rr → 31 distinct; see the dedup comment)
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 commit15d7406. 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 runpython 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_nomemoryrequires a test/debug interpreter (--with-pydebugexposes it).- Build matrix (in each report): many of these are debug-only assertion /
Py_FatalError
failures — theassert(...)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, aPy_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
returningNULLis dereferenced (≈ one-line NULL checks). - OOM-0001, OOM-0002, OOM-0014 —
Py_DECREF(NULL)on an unchecked-allocation
error path. - OOM-0031 —
_interpreters.capture_exceptioncalls_PyXI_FreeExcInfo(NULL)with no
NULL guard. - OOM-0012, OOM-0020 — instrumentation / thread-state corruption under OOM.
Status: blank = not yet filed · #N = open issue · #N (PR) = open pull request · #N FIXED = filed and fixed (issue closed-completed or PR merged) · 🔁 = confirmed duplicate, folded into another finding — please don't pursue.
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) |
#151773 FIXED |
| 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) |
#152058 FIXED |
| OOM-0031 | Segfault: NULL info deref in _excinfo_clear_type (crossinterp.c:1319) |
#151842 FIXED |
| OOM-0033 | [SUPERSEDED — duplicate of OOM-0036 / #151818] Segfault / negative-refcount: over-decreffed sys.path entry in PyType_IsSubtype (typeobject.c:2931) |
🔁 dup of OOM-0036 → #151818 |
| OOM-0034 | Segfault: unchecked PyUnicode_AsUTF8 NULL deref in pegen.c:33 |
#151798 (PR) |
Assertion failures / aborts (23)
| Report | Title | Status |
|---|---|---|
| OOM-0003 | Abort: uninitialized _co_unique_id assert in code_dealloc (codeobject.c:2440) |
#152034 FIXED (PR) |
| OOM-0004 | Abort/Segfault: corrupted object freelist in clear_freelist (object.c:909) |
#152125 |
| OOM-0005 | [SUPERSEDED — duplicate of OOM-0036 / #151818] Abort: negative-refcount over-decref in _PyFrame_ClearLocals (frame.c:101) |
🔁 dup of OOM-0036 → #151818 (reported as #152147, closed as dup) |
| OOM-0006 | Abort/Segfault: _PyObject_GC_UNTRACK assert on untracked iterator in dictiter_dealloc (dictobject.c:5532) |
#152107 |
| OOM-0008 | Abort: assert(!PyErr_Occurred()) in _PyType_LookupStackRefAndVersion (typeobject.c:6343) |
#151905 FIXED |
| OOM-0009 | Abort: stale release1 flag trips an ownership assert in replace (unicodeobject.c:10783) |
#152228 |
| OOM-0010 | Abort: assert(_PyErr_Occurred(tstate)) in _PyEval_EvalFrameDefault (generated_cases.c.h:13817) |
|
| OOM-0011 | [SUPERSEDED — duplicate of OOM-0008] Abort: assert(!PyErr_Occurred()) in specialize (specialize.c:364) |
🔁 dup of OOM-0008 (above) |
| 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) |
#151968 FIXED (PR) |
| OOM-0014 | Abort/Segfault: unchecked NULL in channelsmod__channel_id (_interpchannelsmodule.c:3487) |
#151902 (PR) |
| OOM-0015 | Abort: stale exception in cfunction_check_kwargs (methodobject.c:409) |
|
| OOM-0016 | Abort: assert(!queue->alive) in _queue_clear (_interpqueuesmodule.c:559) |
#152130 |
| 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) |
#152254 |
| OOM-0019 | Abort: double-free in _PyPegen_raise_error_known_location (pegen_errors.c:363) |
#151931 (PR) |
| 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) |
#152156 FIXED |
| OOM-0027 | Abort: assert(PyStackRef_BoolCheck(cond)) in POP_JUMP_IF_FALSE (generated_cases.c.h:11120) |
|
| OOM-0029 | [SUPERSEDED — duplicate of OOM-0036 / #151818] Abort: negative refcount on a MemoryError (tuple_dealloc, tupleobject.c:277) |
🔁 dup of OOM-0036 → #151818 |
| 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) |
#152083 |
| 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_Deallocinvariant): OOM-0007
(context_tp_dealloc) and OOM-0023 (the genericsubtype_dealloc, covering a family of
pure-Python types) free an object while aMemoryErroris 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, 0015, 0025, 0032 (OOM-0011 was a second detector face of
OOM-0008 — sameframe.f_backswallow — and is folded). Several share the theme that the
adaptive specializer / call machinery is entered with aMemoryErroralready pending. Py_DECREF/Py_CLEARof 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-0019. (rr later showed OOM-0005, OOM-0029 and OOM-0033 are
all the same_CALL_LIST_APPENDlist.append()-under-MemoryErrordouble-free —
OOM-0036, filed #151818 — and are
folded; OOM-0019 is a distinct pegen double-free.) - 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, 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
- gh-151763: Fix NULL deref in
os._path_normpath()#151779 - gh-151763: Fix OOM-0034 tokenizer offset error handling #151798
- gh-151763: Fix OOM-0014 crash in _interpchannels._channel_id #151902
- gh-151763: Fix double decref in _PyPegen_raise_error_known_location() #151931
- gh-151763: Fix OOM-0013 crash when the parser or compiler fails to allocate #151968
- gh-151763: Fix
_interpreters.capture_exceptioncrash on memory errors #152013 - gh-151763: Fix possible crash on
CodeTypedeallocation #152034 - [3.15] gh-151763: Fix possible crash on
CodeTypedeallocation (GH-152034) #152069 - [3.14] gh-151763: Fix possible crash on
CodeTypedeallocation (GH-152034) #152070 - [3.13] gh-151763: Fix NULL dereference in
os._path_normpath()under OOM (GH-151779) #152094 - [3.14] gh-151763: Fix NULL dereference in
os._path_normpath()under OOM (GH-151779) #152095 - [3.15] gh-151763: Fix NULL dereference in
os._path_normpath()under OOM (GH-151779) #152096 - gh-151763: Fix crash in
_interpqueues.createonMemoryError#152131 - gh-151763: Fix OOM cleanup of initial thread state #152155
- [3.15] gh-151763: Fix crash in
_interpqueues.createonMemoryError(GH-152131) #152158 - [3.14] gh-151763: Fix crash in
_interpqueues.createonMemoryError(GH-152131) #152159 - [3.13] gh-151763: Fix crash in
_interpqueues.createonMemoryError(GH-152131) #152160 - gh-151763: Fix debug assertion in unicode subtype cleanup under OOM #152164
- gh-151763: Fix OOM cleanup of initial thread state #152165
- [3.15] gh-151763: Fix OOM-0013 crash when the parser or compiler fails to allocate (GH-151968) #152836
- [3.14] gh-151763: Fix OOM-0013 crash when the parser or compiler fails to allocate (GH-151968) #152837
- [3.13] gh-151763: Fix OOM-0013 crash when the parser or compiler fails to allocate (GH-151968) #152840
- gh-151763: Fix dealloc clears the in-flight exception in subtype_dealloc #153548
- gh-151763: Fix crash in PyList_New() on allocation failure under low memory #154019
- gh-151763: Restore exception if parser recovery path leaves none set (OOM-0021) #154717