You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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.
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:
Crash report
What happened?
Allocation-failure fuzzing of CPython
mainturned up 35 distinct ways the interpretercrashes — segfault, failed
assert(...), orPy_FatalError— when a memory allocationfails 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
main(3.16.0a0) at commit15d7406. These are allocation-path bugs, so theyare not tied to that exact revision.
OOM-ID-repro.py(standard library only) that drives the failing pathwhile
_testcapi.set_nomemory(...)forces allocations to fail. Just runpython OOM-ID-repro.py— where the exact failing-allocation index is sensitive to the build'sallocation 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).Py_FatalErrorfailures — the
assert(...)is compiled out under-DNDEBUG, so a release build doesn'tabort. 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 onrelease 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:
PyUnicode_AsUTF8/PyUnicode_EncodeFSDefaultreturning
NULLis dereferenced (≈ one-line NULL checks).Py_DECREF(NULL)on an unchecked-allocationerror path.
_interpreters.capture_exceptioncalls_PyXI_FreeExcInfo(NULL)with noNULL guard.
sys.pathentry.Findings
Status: blank = not yet filed ·
#N= a CPython issue is open.Segfaults (7)
Py_DECREFof a NULLfilenameindo_warn(_warnings.c:1139)Py_DECREF(NULL)inPyContextVar_Set(context.c:367)template_iter(templateobject.c:232)os__path_normpath_impl(posixmodule.c:6149)infoderef in_excinfo_clear_type(crossinterp.c:1319)sys.pathentry inPyType_IsSubtype(typeobject.c:2931)PyUnicode_AsUTF8NULL deref inpegen.c:33Assertion failures / aborts (23)
_co_unique_idassert incode_dealloc(codeobject.c:2440)clear_freelist(object.c:909)_PyFrame_ClearLocals(frame.c:101)_PyObject_GC_UNTRACKassert on untracked iterator indictiter_dealloc(dictobject.c:5532)assert(!PyErr_Occurred())in_PyType_LookupStackRefAndVersion(typeobject.c:6343)release1flag trips an ownership assert inreplace(unicodeobject.c:10783)assert(_PyErr_Occurred(tstate))in_PyEval_EvalFrameDefault(generated_cases.c.h:13817)assert(!PyErr_Occurred())inspecialize(specialize.c:364)get_tools_for_instruction(instrumentation.c:1106)_Py_BuiltinCallFastWithKeywords_StackRef(ceval.c:843)channelsmod__channel_id(_interpchannelsmodule.c:3487)cfunction_check_kwargs(methodobject.c:409)assert(!queue->alive)in_queue_clear(_interpqueuesmodule.c:559)gc_refs("refcount too small") invalidate_gc_objects(gc_free_threading.c:1116)set_keys(dictobject.c:205)_PyPegen_raise_error_known_location(pegen_errors.c:363)assert(!PyErr_Occurred())inunspecialize(specialize.c:378)PyErrdesync inhandle_channel_error(_interpchannelsmodule.c:398/:443)assert(PyStackRef_BoolCheck(cond))inPOP_JUMP_IF_FALSE(generated_cases.c.h:11120)MemoryError(tuple_dealloc,tupleobject.c:277)Py_DECREFof NULL-data unicode inunicode_subtype_new(unicodeobject.c:13986)warn_explicitnormalization (_warnings.c:799/806)maxcharin_PyUnicode_FromUCS4(unicodeobject.c:2228)Fatal Python error (5)
context_tp_deallocclears the pending exception (context.c:535)_PyMem_DebugRawFree: bad IDinfree_threadstate(pystate.c:1527)_Py_CheckFunctionResult(call.c:43)MemoryErrortrips_Py_CheckSlotResultinreload_singlephase_extension(import.c:2011)subtype_dealloc(typeobject.c:2719)Related groups (one fix may cover several)
_Py_Deallocinvariant): OOM-0007(
context_tp_dealloc) and OOM-0023 (the genericsubtype_dealloc, covering a family ofpure-Python types) free an object while a
MemoryErroris pending and don't save/restore it.call layers): OOM-0008, 0010, 0011, 0015, 0025, 0032. Several share the theme that the
adaptive specializer / call machinery is entered with a
MemoryErroralready 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).
debug-build detector): OOM-0005, 0019, 0029, 0033.
_co_unique_id), OOM-0017 (cyclic GC),OOM-0018 (managed dict), OOM-0020 (thread-state reservation).
Disclosure & caveats
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.
but the exact offending line wasn't pinned (noted in those reports): OOM-0010, 0027, 0029,
0033, 0035.
#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
os._path_normpath()#151779